Files
sydnix/modules/home/users/crumb/emacs/lib/syd-handle-lookup.el
2025-07-15 11:40:54 -06:00

133 lines
5.3 KiB
EmacsLisp

;;; syd-handle-lookup.el -*- lexical-binding: t; -*-
(require 'syd-text)
(use-package better-jumper)
(require 'better-jumper)
(defvar syd-lookup-online-documentation-backends
`(("Kagi" . "https://kagi.com/search?q=%s")
("DuckDuckGo" . "https://duckduckgo.com/?q=%s")
("Nixpkgs" . "https://search.nixos.org/packages?query=%s")
("Hackage" . "https://hackage.haskell.org/packages/search?terms=%s")
("The Free Dictionary" . "https://www.thefreedictionary.com/%s")
("The Free Thesaurus" . "https://www.freethesaurus.com/%s"))
"A list of pairs (NAME . BACKEND) describing the various backends
`syd-lookup-online-documentation' may delegate to.
NAME is a string used when speaking to the user about BACKEND.
If BACKEND is an interactive command, it will be called interactively.
If BACKEND is a procedure, it will be called with the search string as the lone
argument.
If BACKEND is a string, the user's browser will be opened to the URL returned by
(format BACKEND QUERY), where QUERY is the appropriately-encoded search
string.")
(defvar syd-lookup-documentation-handlers '(syd-lookup-online-documentation)
"A list of lookup handlers used to find documentation. A lookup handler
receives an identifier, and is expected to return nil on failure, and non-nil on
success. When a handler returns a marker, the marker will be jumped to.")
(defun syd-lookup--prompt-for-online-backend ()
(assoc-string
(completing-read "Search with: "
(mapcar #'car syd-lookup-online-documentation-backends)
nil
t)
syd-lookup-online-documentation-backends))
(cl-defun syd-lookup--call-online-backend (backend &key query-string)
(pcase-let ((`(,name . ,backend-fn) backend))
(cond ((functionp backend-fn) (if (commandp backend-fn)
(call-interactively backend-fn)
(funcall backend-fn query-string)))
((stringp backend-fn)
(browse-url (format backend-fn
(url-encode-url
(read-string (format "Search %s for: "
name)
query-string)))))
(t (signal 'wrong-type-argument `("backend" ,backend-fn))))))
;;;###autoload
(cl-defun syd-lookup-online-documentation (backend &key query-string)
(interactive (list (syd-lookup--prompt-for-online-backend)
:query-string (when (use-region-p)
(syd-thing-at-point-or-region))))
(syd-lookup--call-online-backend backend
:query-string query-string))
;;;###autoload
(defun syd-lookup-documentation (identifier)
"Try to find documentation on IDENTIFIER, and "
(interactive (list (syd-thing-at-point-or-region)))
(or (syd-lookup--jump-to 'documentation identifier
:display-fn #'display-buffer)
(user-error "Couldn't find documentation on %S"
(substring-no-properties identifier))))
(defvar syd-lookup--handlers-by-category
'((documentation . syd-lookup-documentation-handlers)))
(cl-defun syd-lookup--jump-to
(category identifier &key (display-fn #'switch-to-buffer))
(let* ((handlers (alist-get category syd-lookup--handlers-by-category))
(origin (point-marker))
;; TODO: If called with a prefix argument, prompt the user to select a
;; handler.
(result (run-hook-wrapped handlers #'syd-lookup--run-handler
identifier origin)))
(unwind-protect
(when (cond ((null result)
(message "No lookup handler could find %S" identifier)
nil)
((markerp result)
(let ((b (marker-buffer result)))
(funcall display-fn b)
(when (eq (current-buffer) b)
(goto-char result)))
result)
(result))
(with-current-buffer (marker-buffer origin)
(better-jumper-set-jump (marker-position origin)))
result))
(set-marker origin nil)))
(defun syd-lookup--run-handler (handler identifier origin)
(condition-case-unless-debug e
(let ((wconf (current-window-configuration))
(result (condition-case-unless-debug e
(if (commandp handler)
(call-interactively handler)
(funcall handler identifier))
(error
(message "Lookup handler %S threw an error: %s" handler e)
'fail))))
(cond ((eq result 'fail)
(set-window-configuration wconf)
nil)
((or (get handler 'syd-lookup-async)
(eq result 'deferred)))
((bufferp result)
(with-current-buffer result
(point-marker)))
((or result
(null origin)
(/= (point-marker) origin))
(prog1 (point-marker)
(set-window-configuration wconf)))))
((error user-error)
(message "Lookup handler %S: %s" handler e)
nil)))
(general-def
:states 'normal
"K" #'syd-lookup-documentation)
(provide 'syd-handle-lookup)
;;; syd-handle-lookup.el ends here