combobulate icon indicating copy to clipboard operation
combobulate copied to clipboard

New architecture

Open DamienCassou opened this issue 3 years ago • 1 comments

Problem

I see several problems with the way combobulate's code is split into files:

  1. There is no library of functions another elisp hacker could build on without adding a dependency on combobulate's user interface.
  2. Combobulate decides which language is used in a given buffer by looking at the current major-mode. This is not good enough for Javascript vs. Typescript.
  3. combobulate.el requires language-specific combobulate's file, making it awkward for an external package (or an end-user) to specify another language or tweak an existing one.

Possible solution

I suggest a different architecture:

  • libcombobulate.el defines movement commands (e.g., libcombobulate-navigate-up), utility functions (e.g., libcombobulate-get-nearest-navigable-node) and variables (e.g., libcombobulate-navigation-node-types). This file shouldn't say anything about how the user will use combobulate (e.g., no key-bindings, no user interface).
  • libcombobulate-javascript.el, libcombobulate-python.el, ... define language-specific functions and values (for the variables of libcombobulate.el). These files shouldn't say anything about how the user will use combobulate (e.g., no key-bindings, no user interface).
  • avy-combobulate.el, hydra-combobulate.el, multiple-cursors-combobulate.el, ... build on top of libcombobulate.el and an external package.

This architecture doesn't say where to define key bindings. There are several solutions:

  1. The user is responsible for doing that (might make sense early in the project's life) and the README gives an example setup.
  2. A combobulate.el file (depending only on libcombobulate.el) defines all general-purpose key bindings and the user must setup key bindings for the language-specific commands.
  3. Each language-specific file defines its own minor-mode (e.g., combobulate-javascript-mode) with its own key bindings; general-purpose key bindings can be defined in a combobulate-core.el file that all language-specific files import.
  4. There are other solutions.

This is an architectural pattern I've followed for several projects and it always helped me. For example:

I usually decompose my project into several git repositories but that's not mandatory (I just like the project to have clear boundaries).

DamienCassou avatar Apr 24 '22 10:04 DamienCassou

These are all valid points, Damien. Thank you taking the time to writing this out.

Right now, my main interest is experimenting with the ergonomics of tree-sitter-aware semantic tools. I will indeed split out (and remove) dependencies to third-party libs as I never wanted to thrust those on people; they're there right now for no other reason than to test what is possible --- combulate's just an alpha prototype, and it's got more important issues than those right now to tackle than MVC.

Namely:

  1. Having to build and maintain sparse trees of 'interesting' nodes. This is slow.
  2. Dealing with the complexities around whether point is intersecting the right node. If point is to the right of <foo/>-!- (for example) it's effectively outside it, and detecting adjacency without interval trees (which I've also experimented with, but are hard to keep in sync with TS without reinventing the whole thing) or another datastructure that is quick to check for adjacency due to the sluggish performance of having to walk the tree to check every time. This is a serious issue, actually, because node spans are just the length of their syntactic unit, notwithstanding generalised ones that might span a whole semantic class of things, like a whole function or a class. Consider:
def foo():
    print("foo")
    -!-

Point is at module and not inside a function. Technically correct.... but unhelpful. You could nav backward until you encounter a non-whitespace char and then back once more --- I did this with an experimental indentation engine I wrote for Python using TS -- but that brings a whole host of awkward corner cases along with it.

  1. How can I extend Emacs's builtin navigation and editing facilities in a way that is mostly transparent and builds on what's already there with additional semantic and syntactic awareness

  2. Handling undo, and dealing with the tree sitter parse tree going out of sync with the buffer text during buffer changes made in elisp.

mickeynp avatar Apr 28 '22 08:04 mickeynp