The problem

I have a few makeshift, monolithic functions for consolidating documentation and code navigation commands under a single function.

They have become unwieldly as they grow to accommodate more languages and modes.

Example of an unwieldly function

(defun my/doc-thing-at-point (arg &optional immediate winfunc)
  "Show doc for thing under pointl. winfunc = 'spv or 'sph elisp function"
  (interactive "P")

  (if (not winfunc)
      (setq winfunc 'sph))

  (cond
   ((string-equal (preceding-sexp-or-element) "#lang")
    (progn
      ;; (racket--repl-command "doc %s" (concat "H:" (str (sexp-at-point))))
      (str (racket--cmd/async `(doc ,(concat "H:" (str (sexp-at-point))))))
      (sleep 1)
      (let ((url (cl/xc nil :notify t)))
        (if immediate
            (if (string-match-p "racket/search/index.html\\?q=" url)
                ;; (eval `(,winfunc (concat "browsh " (e/q url))))
                (eval `(,winfunc (concat "ff " (e/q url))))
              (eval `(,winfunc (concat "eww " (e/q url))))))
        (message "%s" url))))
   ((derived-mode-p 'clojure-mode 'cider-repl-mode 'inf-clojure)
    ;; (condition-case nil (cider-doc-thing-at-point) (error (message "%s" "You may need to enable the default cider jack-in repl")))
    (cider-doc-thing-at-point))
   ((derived-mode-p 'lisp-mode)
    (call-interactively 'slime-documentation)
    ;; (slime-documentation (symbol-name (intern (format "%s" (thing-at-point 'symbol)))))
    ;; This is docs, not goto definition
    ;; ((derived-mode-p 'go-mode)
    ;;  (progn (spacemacs/jump-to-definition)))
    ((derived-mode-p 'go-mode) (progn (godoc-at-point (point)))))
   ((derived-mode-p 'c-mode) (man (concat "3 " (thing-at-point 'symbol))))
   ((derived-mode-p 'python-mode) (if arg (call-interactively 'pydoc-at-point) (anaconda-mode-show-doc)))
   ;; (spacemacs/jump-to-definition)
   ;; ((derived-mode-p 'haskell-mode) (progn (hoogle (my/thing-at-point) t)))
   ((derived-mode-p 'haskell-mode) (progn (shut-up (my/nil (ns "implement hs-doc")))))
   ((derived-mode-p 'racket-mode) (progn (my-racket-doc immediate winfunc) (deselect)))

   ((derived-mode-p 'emacs-lisp-mode) (describe-thing-at-point))
   ;; ((derived-mode-p 'emacs-lisp-mode) (helpful-symbol (symbol-at-point)))
   ((derived-mode-p 'hy-mode) (hy-describe-thing-at-point))
   ;; This is also used by evil while navigating, so only allow it to do things for specific modes, such as python
   ;; ((derived-mode-p 'text-mode) (progn (message "%s" "text mode. probably minibuffer") (describe-thing-at-point)))
   ;; (t (search-google-for-doc))
   ;; (t (message "%s" "unknown mode"))
   ))

The solution: handle.el

handle.el
https://gitlab.com/jjzmajic/handle

Add to handle-keywords

my fork of handle.el
https://gitlab.com/mullikine/handle

I modified the original package because modifying this list externally had no effect.

(add-to-list 'handle-keywords :playground)
(add-to-list 'handle-keywords :godef)
(add-to-list 'handle-keywords :docsearch)
(add-to-list 'handle-keywords :nextdef)
(add-to-list 'handle-keywords :prevdef)
(add-to-list 'handle-keywords :nexterr)
(add-to-list 'handle-keywords :preverr)
(add-to-list 'handle-keywords :assignments)
(add-to-list 'handle-keywords :references)
;; This may use GPT-2 at some stage
(add-to-list 'handle-keywords :spellcorrect)

$EMACSD/config/my-handle.el

;; This must come before require to generate the functions

;; This may not have been good enough. I have added to the package source instead
;; (add-to-list 'handle-keywords :godef)
;; (add-to-list 'handle-keywords :docsearch)
;; (add-to-list 'handle-keywords :nextdef)
;; (add-to-list 'handle-keywords :prevdef)
(require 'handle)

;; I don't think we need to actually have the definitions to create handlers
;; (require 'haskell-mode)

;; docsearch allows you to enter the symbol to search for

;; I need a default mode handler for handle
;; If non exists then I should not actually call handle-docs by default but call my own thing which tests
;; Perhaps I can put a prog-mode handler

(handle '(python-mode inferior-python-mode)
        :repls '(run-python)
        :formatters '(lsp-format-buffer)
        :docs '(
                anaconda-mode-show-doc
                lsp-describe-thing-at-point
                python-help-for-region-or-symbol
                python-eldoc-for-region-or-symbol
                python-shell-print-region-or-symbol)
        :godef '(anaconda-mode-find-definitions
                 lsp-find-definition
                 xref-find-definitions
                 helm-gtags-dwim)
        :docsearch '(my/doc)
        :complete '(anaconda-mode-complete)
        :assignments '(anaconda-mode-find-assignments)
        :references '(anaconda-mode-find-references)
        )

(handle '(clojure-mode cider-repl-mode inf-clojure)
        :repls '(run-python)
        :formatters '(lsp-format-buffer)
        :docs '(lsp-describe-thing-at-point
                cider-doc-thing-at-point)
        :godef '(lsp-find-definition
                 xref-find-definitions
                 helm-gtags-dwim)
        :docsearch '(my/doc)
        :nextdef '(lispy-flow))

(handle '(haskell-mode)
        :repls '(haskell-intero/pop-to-repl)
        :formatters '(lsp-format-buffer)
        :docs '(hoogle-thing-at-point)
        :godef '(haskell-mode-jump-to-def
                 haskell-mode-jump-to-def-or-tag
                 lsp-find-definition
                 xref-find-definitions
                 helm-gtags-dwim)
        :nextdef '(haskell-cabal-next-section)
        :prevdef '(haskell-cabal-previous-section)
        :docsearch '(my/doc)
        :spellcorrect '()
        )

(handle '(org-mode)
        :repls '()
        :formatters '()
        :docs '()
        :godef '()
        :nextdef '(next-defun)
        :prevdef '(previous-defun)
        :nexterr '(flycheck-next-error
                   my-flyspell-next)
        :preverr '(flycheck-previous-error
                   my-flyspell-prev)
        :docsearch '(my/doc)
        :spellcorrect '(my-flyspell-correct))

(handle '(emacs-lisp-mode)
        :repls '(ielm)
        :formatters '(lsp-format-buffer)
        :docs '(describe-thing-at-point
                helpful-symbol-at-point)
        :godef '(lispy-goto-symbol
                 elisp-slime-nav-find-elisp-thing-at-point
                 lsp-find-definition
                 xref-find-definitions
                 helm-gtags-dwim)
        :docsearch '(my/doc)
        :nextdef '(my-prog-next-def
                   lispy-flow)
        :prevdef '(my-prog-prev-def))

(handle '(go-mode)
        :repls '(go-playground)
        :playground '(go-playground)
        :formatters '(gofmt
                      lsp-format-buffer)
        :docs '(godoc-at-point
                )
        :godef '(go-guru-definition
                 lsp-find-definition
                 xref-find-definitions
                 helm-gtags-dwim)
        :docsearch '(my/doc)
        :nextdef '(my-prog-next-def)
        :prevdef '(my-prog-prev-def)
        )

(handle '(racket-mode)
        :repls '(ielm)
        :formatters '(lsp-format-buffer)
        :docs '(describe-thing-at-point
                helpful-symbol-at-point)
        :godef '(racket-visit-definition
                 lsp-find-definition
                 xref-find-definitions
                 helm-gtags-dwim)
        :docsearch '(my/doc)
        :nextdef '(lispy-flow))

(handle '(hy-mode)
        :repls '(run-hy)
        :formatters '(lsp-format-buffer)
        :docs '(hy-describe-thing-at-point)
        :godef '(helm-gtags-dwim)
        :docsearch '(my/doc)
        :nextdef '(lispy-flow))

(handle '(biblio-selection-mode)
        :repls '()
        :formatters '()
        :docs '()
        :godef '()
        :docsearch '()
        :nextdef '(biblio--selection-next)
        :prevdef '(biblio--selection-previous))

;; This works for prog-derived modes which do not have a handle defined
(handle '(prog-mode)
        :repls '()
        :formatters '(lsp-format-buffer)
        :docs '(my/doc-thing-at-point)
        :godef '(lsp-find-definition
                 xref-find-definitions
                 helm-gtags-dwim)
        :docsearch '(my/doc)
        :nextdef '(my-prog-next-def)
        :prevdef '(my-prog-prev-def)
        :nexterr '(flycheck-next-error)
        :preverr '(flycheck-previous-error))

;; Runs the interpreter
;; (handle-repls)

;; Runs the docs for thing at point
;; (handle-docs)


(defun handle-setup-keybindings ()
  )

(provide 'my-handle)

Example generic navigation functions

(defun my-prog-prev-def ()
  (interactive)
  (backward-to-indentation 0)
  (search-backward-regexp "^[^#;/ \t]+ ")
  (backward-to-indentation 0))

(defun my-prog-next-def ()
  (interactive)
  (backward-to-indentation 0)
  (if (looking-at-p "^[^#;/ \t]+ ")
      (progn
        (forward-char)))
  (try (search-forward-regexp "^[^#;/ \t]+ "))
  (backward-to-indentation 0))

Use these with the default prog-mode handle

(handle '(prog-mode)
          :repls '()
          :formatters '(lsp-format-buffer)
          :docs '(my/doc-thing-at-point)
          :godef '(lsp-find-definition
                   xref-find-definitions
                   helm-gtags-dwim)
          :docsearch '(my/doc)
          :nextdef '(my-prog-next-def)
          :prevdef '(my-prog-prev-def)
          :nexterr '(flycheck-next-error)
          :preverr '(flycheck-previous-error))

Unbind keys from major and minor modes occupying the chords you want

The process of integrating new bindings now becomes subtractive as opposed to additive.

Additive/Subtractive | David D Quillin Architecture

Petra - Wikipedia

(require 'elisp-slime-nav)
(define-key elisp-slime-nav-mode-map (kbd "M-.") nil)
(define-key lispy-mode-map (kbd "M-.") nil)

(require 'helm-gtags)
(define-key helm-gtags-mode-map (kbd "M-.") nil)
(define-key ggtags-mode-map (kbd "M-.") nil)

Bind handle functions to keys in prog-mode

$EMACSD/config/my-prog.el

(require 'my-handle)

(define-key prog-mode-map (kbd "M-9") 'handle-docs)
(define-key prog-mode-map (kbd "M-.") 'handle-godef)

(provide 'my-prog)

Restart emacs so major modes inherit from prog-mode

Caveats

Keymap inheritance

Unfortunately, there is no guarantee that a keymap will inherit from prog-mode-map.

(defvar emacs-lisp-mode-map
  (let ((map (make-sparse-keymap "Emacs-Lisp"))
        (menu-map (make-sparse-keymap "Emacs-Lisp"))
        (lint-map (make-sparse-keymap))
        (prof-map (make-sparse-keymap))
        (tracing-map (make-sparse-keymap)))
    (set-keymap-parent map lisp-mode-shared-map)
    …
    map))

Therefore, it may be necessary to manually bind for each major mode the call to handle- docs, etc.

  • Automate this for each mode defined in handle
```emacs
(define-key emacs-lisp-mode-map (kbd "M-9") 'handle-docs)
(define-key emacs-lisp-mode-map (kbd "M-.") 'handle-godef)
```

```emacs
(defun handle-setup-keybindings ()
  ;; ...
  )
```