;; -*- lexical-binding: t; -*- (defvar hmm-web-search-engines nil "hmm web search engines.") (defvar hmm-web-browsers nil "hmm web browers.") (defvar hmm-handlers '(:query nil :url nil :file nil)) (defmacro hmm-define-web-search-engine (engine browser) (let* ((engine-name (plist-get engine :name)) (browser-name (plist-get browser :name)) (function-name (intern (format "hmm-search-%s-%s" engine-name browser-name)))) `(progn (defun ,function-name (query) ,(format "Search %s in %s." engine-name browser-name) (interactive ,(format "sSearch %s in %s for: " engine-name browser-name)) (,(plist-get browser :command) (format ,(plist-get engine :format) query))) (hmm-add-handler '(:command ,function-name) :query)))) (defmacro hmm-define-external-handler (handler) (let* ((description (format "%s - %s" (plist-get handler :display-name) (plist-get handler :description))) (function-name (intern (format "hmm-%s" (plist-get handler :name)))) (display-name (plist-get handler :display-name)) (external-command (mapcar (lambda (token) (if (string-match "^%[fFuU]$" token) `arg token)) (split-string (plist-get handler :external-command)))) (handler (plist-put handler :command function-name))) `(progn (defun ,function-name (arg) ,description (interactive ,(format "sRun %s with: " display-name)) (apply 'start-process (append (list (format ,(format "%s-%%s" function-name) arg) (format ,(format "*%s-%%s*" function-name) arg)) ,(cons 'list external-command)))) (hmm-add-handler ',handler :file)))) (defun hmm-add-handler (handler handling) (let ((handlers (plist-get hmm-handlers handling))) (add-to-list 'handlers handler) (plist-put hmm-handlers handling handlers))) (defun hmm-thing-in-region (from to) (interactive "r") (unless (region-active-p) (error "Region not active.")) (let* ((query (buffer-substring from to))) (hmm-query query))) (defun hmm-thing-at-point () (interactive) "Prompt for what to do with thing at point. If it is a file, display a list of file handlers. If it is a url, display a list of url handlers." (let ((thing)) (cond ((setq thing (thing-at-point-url-at-point)) (hmm-url thing)) ((setq thing (thing-at-point-file-at-point)) (hmm-file thing)) ((and (derived-mode-p 'dired-mode) (setq thing (dired-get-filename nil t))) (hmm-file thing)) ((derived-mode-p 'dired-mode) (hmm-file (expand-file-name default-directory))) ((and (derived-mode-p 'org-mode) (setq thing (my-org-link-at-point))) (hmm-url thing)) ((setq thing (get-text-property (point) 'shr-url)) (hmm-url thing)) ((setq thing (thing-at-point 'symbol)) (hmm-query thing)) ((setq thing (buffer-file-name)) (hmm-file thing)) ((setq thing (expand-file-name default-directory)) (hmm-file thing)) (t (error "Cannot guess what to do with what."))))) (defun hmm () (interactive) (cond ((region-active-p) (call-interactively 'hmm-thing-in-region)) (t (call-interactively 'hmm-thing-at-point)))) (define-key global-map (kbd "C-M-") 'hmm) (defun hmm-query (query) (interactive "sQuery: ") (hmm-open-internal query :query "Search %s using: ")) (defun hmm-url (url) (interactive (list (read-string "URL: " (thing-at-point-url-at-point)))) (hmm-handle-internal url :url "Handle url %s using: ")) (defun hmm-file (file) (interactive (list (read-file-name "File: " default-directory (thing-at-point-file-at-point)))) (hmm-handle-internal file :file "Handle file %s using: ")) (defun hmm-handle-internal (thing type prompt) (let ((selected (completing-read (format prompt thing) (mapcar (lambda (command) (format "%s (%s)" command (if (documentation command) (car (split-string (documentation command) "\n")) "Undocumented"))) (hmm-filter-commands thing type))))) (funcall-interactively (intern (car (split-string selected " ("))) thing))) (defun hmm-filter-commands (thing type) (let ((mimetype (hmm-file-mime-type thing))) (mapcar (lambda (handler) (plist-get handler :command)) (seq-filter (lambda (handler) (and (hmm-thing-match thing type handler) (or (null (plist-get handler :mimetypes)) (member mimetype (plist-get handler :mimetypes))))) (plist-get hmm-handlers type))))) (defun hmm-thing-match (thing type handler) (let ((regex (plist-get handler :regex))) (cond ((eq type :url) (let ((schemes (plist-get handler :schemes)) (url (url-generic-parse-url thing)) (url-no-scheme)) (and (or (null schemes) (member (url-type url) schemes)) (or (not regex) (string-match regex (progn (setf (url-type url) nil (url-fullness url) nil) (setq url-no-scheme (url-recreate-url url)))))))) (t (or (not regex) (string-match regex thing)))))) (defun hmm-update-handlers () (interactive) (dolist (engine hmm-web-search-engines) (dolist (browser hmm-web-browsers) (eval `(hmm-define-web-search-engine ,engine ,browser)))) (dolist (xdg-handler (hmm-get-xdg-handlers)) (eval `(hmm-define-external-handler ,xdg-handler))) (dolist (browser hmm-web-browsers) (hmm-add-handler (list :command (plist-get browser :command) :schemes (list "http" "https")) :url))) (defun hmm-get-xdg-handlers () (seq-filter (lambda (handler) (and handler (plist-get handler :mimetypes))) (mapcar (lambda (desktop-file) (condition-case nil (let ((parsed (xdg-desktop-read-file desktop-file))) (list :name (intern (concat "xdg-desktop-" (file-name-base desktop-file))) :display-name (gethash "Name" parsed) :description (gethash "Comment" parsed) :external-command (gethash "Exec" parsed) :mimetypes (seq-filter (lambda (s) (not (string-empty-p s))) (split-string (gethash "MimeType" parsed) ";")))) (error nil))) (hmm-get-all-desktop-files)))) (defun hmm-get-all-desktop-files () (mapcan (lambda (dir) (let ((app-dir (expand-file-name "applications" dir))) (when (file-exists-p app-dir) (directory-files app-dir t "\\.desktop$")))) (cons (xdg-data-home) (xdg-data-dirs)))) (defun hmm-file-mime-type (file) "Returns (or print if called interactively) mimetype of FILE." (interactive) (let ((out)) (when (file-exists-p file) (replace-regexp-in-string "^.*: \\(.*\\); .*$" "\\1" (with-temp-buffer (call-process "file" nil '(t t) nil "-Li" file) (setq out (string-trim (buffer-string))) (if (called-interactively-p) (message out) out)))))) (provide 'hmm)