| ID | 6141dc5c-2232-4bc0-9464-410c21135c86 |
|---|---|
| DeertopiaVisibility | public |
A flexible system for lookup strategies in Emacs
Inbox
syd-define-repl????
Problem statement
In Emacs, many tasks, while identical in the abstract, involve different systems with different behaviours. For Sydnix's fancy new Emacs config, I want a single command to intelligently select the relevant systems to consult, and report back to me with a single sensible response — i.e., a strategy pattern. One could imagine trying lsp-describe-thing-at-point iff lsp-mode is active, before attempting to consult the repl's :doc command, and if that doesn't work, jumping to the definition and parsing the docstring by hand.
I've read through Doom's implementation, and it's a bit more complicated than I like. I turned to face the Dijkstra cutout on my wall and prayed for advice. Through the crude portrait, Dijkstra looked down and said "Rewrite that inelegant trash."
What kinds of tasks are we talking about?
Here's what I want:
Lookup documentation
Find references
Find definition
Find implementations (e.g. instance decls. in Haskell)
Additionally, the following would be neat to include, but I must note that Doom gives them their own systems for reasons I'm currently ignorant of:
Evaluate region/string
Open repl
Format region/buffer
List errors, jump to next/previous error
A proposed interface
Which strategies are run?
My current system that only supports repls maintains a global alist pairing repl commands to major-modes. Once a repl is opened, the repl buffer is stored in another global hash table mapping
(major-mode . project-root)pairs to open repl buffers; i.e., there is at most one active repl for each language in each project.A global alist can be more convenient than a buffer-local variable, since you don't have to create hooks to set it.
Perhaps a buffer-local variable could default to the value corresponding to the major-mode key of a global alist?
Strategies associated with minor-modes should take precedence over those associated with major-modes.
(defvar syd-default-strategies
`((emacs-lisp-mode . ((:documentation . ,#'syd-emacs-lisp-lookup-documentation)
(:repl . ,#'syd/open-emacs-lisp-repl)
(:eval . ,#'syd-emacs-lisp-eval)))
(lsp-mode . ((:documentation . ,#'lsp-describe-thing-at-point)))))
Calling strategies
Let's imagine possible signatures for the planned 'tasks.'
- Lookup documentation
Identifier → Docs- Find references
Identifier → Set SourcePosition- Find definition
Identifier → SourcePosition- Find implementations
Identifier → Set SourcePosition- Evaluate buffer/region/string
String -> IO ()- Open repl
Maybe ReplIdentifier- Format region/buffer
String -> String
Look at all the assumptions we're making }:\.
For anything that returns a single value, not wrapped in a collection: are we sure? Why can't there be multiple valid (or at least candidate) definitions for a name?
We can offer great flexibility with a simple trick: define a single, sane "default" signature and use that, unless the strategy is an interactive command. Interactive commands can fetch their own arguments however they like.
Open problems
Share a repl between project buffers
Possible solutions
Delegate to individual repl handlers
Simply have the repl handler find the buffer to share, and return that. At the very least, we can provide helpers to make this easier.
Pros
Simple; dumb.
Cons
Inelegant. Every repl handler must be trusted to fulfil a somewhat complex invariant.
Protocol: get-repl handler and open-repl handler
The handler
get-replwill identify the appropriate repl andThe handler dispatcher will run the
get-replhandlers.Expect repl-handlers to return nil or a unique identifier for the 'type' of repl opened. The handler's function symbol could serve as a unique identifier.
The 'dispatch' command that calls the repl-handlers will look up
(«project root» . «unique identifier»)in some state.
Just have one repl handler per major-mode
Very dumb, but it's all we need for now