Closed DamienCassou closed 1 year ago
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:
Having to build and maintain sparse trees of 'interesting' nodes. This is slow.
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.
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
Handling undo, and dealing with the tree sitter parse tree going out of sync with the buffer text during buffer changes made in elisp.
Problem
I see several problems with the way combobulate's code is split into files:
major-mode
. This is not good enough for Javascript vs. Typescript.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 oflibcombobulate.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 oflibcombobulate.el
and an external package.This architecture doesn't say where to define key bindings. There are several solutions:
combobulate.el
file (depending only onlibcombobulate.el
) defines all general-purpose key bindings and the user must setup key bindings for the language-specific commands.combobulate-javascript-mode
) with its own key bindings; general-purpose key bindings can be defined in acombobulate-core.el
file that all language-specific files import.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).