Majjit LSP

An out-there suggestion for the nascent jj ecosystem!

I was skimming Martins 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 dont 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 dont actually like VS Code VCS UI. I use @kaholes 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. Whats 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 doesnt 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, theres 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 emulators 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 thats 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 canclick on a particular word in the text.

Lets 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, theres 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 terminals 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-linksfor 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 dont 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 shouldnt 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 wouldnt get out of LSP is Magits 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

Its useful to take a birds 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!