Majjit LSP
An out-there suggestion for the nascent jj ecosystem!
I was skimming Martin’s post, and the VS Code section of it kicked off this particular train of thought. Summarizing, VSCode has some built-in integrations with git, and they don’t necessary directly applicable to jj. Direct solution is, of course, to add first class support of jj to VSCode.
But I want better. Specifically, my problem is that I don’t actually like VS Code VCS UI. I use @kahole’s awesome implementation of Magit for VSCode, edamagit. Magit is objectively correct way to implement a VCS user interface (more on this in just a moment).
So I need to wait for someone for coding Magit/jj/VSCode triple. What’s worse, I actually want to switch to Zed at some point, and absence of Magit is one of the bigger things that prevent this. But Zed doesn’t at the moment have a rich-enough plugin API to express that.
At the same time, although two separate code-bases, Magit in VSCode and Magit in Emacs are basically the same thing from the perspective of the user. We are clearly missing a narrow waist here which would allow us to implement Magit only once.
Or rather, there’s a “barely adequate enough” narrow waist — shell/terminal combo. You could implement magit-tui which would be re-usable between, say, Vim and Helix. But this particular thin waist sucks. It suffers from Byzantine complexity (why the hell the kernel needs to be directly involved), unfixable design bugs (terminfo database, necessity to set both your terminal emulator’s and your shell color themes separately), and missing features (window management, for example). I think an absolutely massive improvement is possible here. I have a separate set of notes about that, but that’s a huge reinvent the wheel by starting from atoms kind of thing.
Luckily, for the present problem of getting first-class UI for jj into any editor, I think a shortcut exists! We can implement “magit for jj” once, and have it working for almost free in any editor, GUI or terminal. To not spoil the answer right away, let me describe, first,
What is Magit
The core idea is text file is the user interface. That’ it! This the paradigm of Emacs (and Acme), with Magit being the most successful application. Text being the interface means two things:
- When we need to present information to the user, we present it in textual format.
- When we need input from the user, it is also text driven. In the simplest form, the user can “click” on a particular word in the text.
Let’s apply this to various UI tasks of a VCS. The basic one is of course informing the user about
changes. Here, we can generate a textual diff — a file witch each line marked at +
or -
. On
top of this, there’s a number of progressive enhancements:
-
Of course
+
lines are colored green and-
red. - Consecutive changes are grouped into hunks, and each hung is individually foldable.
- A diff can span multiple files, and files form another level in the folding hierarchy. By folding the entire diff recursively, you get a list of changed files, from which you can drill down an individual file.
This is the presentation side of the interface. And now, interaction:
Clicking on any line in the diff opens the corresponding file in the working copy. This composes well with split view (a feature missing from the terminal’s narrow waist): on the right you can have your hierarchical diff, on the left, your code. Moving in the diff file automatically moves the working copy view.
This is a very productive interface to make sense of larger changes. You open the change as this hierarchical diff. First, everything is folded, so you only see the list of changed files, which gives you the context about a change. You then unfold the most interesting changed file and start perusing the diff. If, at some point, you realize that you need more context to understand a particular hunk, you click on it, and view, at the other half of the screen, the entire function surrounding the changed line.
The rest of VCS builds naturally on top. Above hunks and files you add commits as another level of hierarchy. So, “everything folded” is now a list of commits. The list of commits view suggest a natural way to model history manipulation. If you want to reorder two commits, you can reorder two lines in the “list of commits” text document.
If you want to split an individual commit, you can open a text document which contains two diffs. One is the commit, the other is initially empty. Clicking on a hunk moves is from one diff to another.
And of course, you can have a “dashboard” text document, which shows the diff for the latest commit (jj) or staging area (git), a list of recent commits, a list of “branches”, and has “hyper-links” for all VCS operations.
Magit For All Editors
Finally, the idea I wrote this article for:
You can materialize all the above files on disk
That is, for the dashboard, we don’t write a custom editor extension that materializes a virtual
text buffer, but rather directly create an .jj/status.jj
file on disk and open it in the editor.
The editor watches the file for changes, so we can update the on-disk buffer to update the user.
And I think most of the interactions you need to implement Magit-like experience are expressible in LSP:
- Semantic Tokens give syntax highlighting for diffs&dashboard.
- Folding Ranges give folding for diffs.
-
Goto Definition
give “click hunk to jump to working copy” behavior for diffs. It can also be used by dashboard for
materializing additional files. The dashboard probably shouldn’t show diffs for any commits but
last. Rather, “goto to definition” on a commit
zxf
should materialize.jj/zxf.diff
file and than jump there. - And of course, Code Actions can provide interface for arbitrary commands. For example, if you want to reword a commit, you can place your cursor on it in the dashboard, and pick “reword” from code-actions context menu.
What you wouldn’t get out of LSP is Magit’s sleek one-letter mnemonics for action. In Magit, the
status buffer is read-only, so rewording a commit is a simple as placing a cursor on it and typing
r
. This level of integration I think will require some tighter interaction with the editor, but
this should be a relatively thin layer.
Bigger Picture
It’s useful to take a bird’s eye view here. You can think of a VCS as a system for storing your source code files. But that is the illusion, the reality is the opposite. What VCS actually does is it stores is a complex graph of objects linked through content hashes. You use VCS to mutate this graph. Now, mutating the graph manually is a pain, so VCS resorts to using your working copy as a convenient way to author graph changes! To view a node in the graph, it is “checked out” into the working copy. To insert a new node, you save working copy as this node. See how bidirectional human-computer interaction is mediated through mutable file system!
In other words, Git, and jj, already use “file system as the user interface” paradigm at the macro
level (authoring new commits) and at the micro level (authoring commit messages). The middle-end is
missing though! git status
is a CLI utility, but it could be just a file on disk!