A flexible system for lookup strategies in Emacs

This section is empty…

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-repl will identify the appropriate repl and

  • The handler dispatcher will run the get-repl handlers.

  • 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