Parinfer-rust is a tool that perserves an invariant in Lisp code between indentation and brackets. It has modes for fixing brackets to match indentation, fixing indenation to match brackets, and a "smart" mode which is a combination of the two based on cursor position in the buffer. Parinfer-rust both changes the buffer and adjusts selections synchronously as the user types or moves the cursor.
Parinfer-rust internally deals with display columns, not byte indices or codepoints. It punts on tabs, replacing them with two spaces, regardless of where the tab is encountered — this is okay because the use of tabs in Clojure is non-idiomatic. It correctly handles combining characters and double-wide characters.
There is one case where parinfer-rust modifies the buffer when no change has been made, called "cursor hold".
In the following case, parinfer defers reformatting the line (| is the cursor):
+
(defn some-function [arg1 arg2 |])
+ When the cursor leaves the line, it removes the space before the closing brackets.
In theory, it should adjust all selections; however, since the algorithm was designed for single-selection editors, it currently only adjusts the main selection. Making it work for multiple selections is complicated, but on my long-term to-do list.
Because of the above, parinfer-rust runs on every keypress, not just after modifications, and it always returns a new main selection.
The Kakoune wrapper script gets all selections then replaces them all after updating the main selection.
Since the select command operates in bytes, but parinfer-rust responds in display columns, this causes "wide" characters in a line to make the cursor move strangely, generally by jumping backward on every update.
Parinfer-rust is used in Vim, Emacs, and Kakoune. I have not played with the Emacs version, but the Vim version does not exhibit this issue.
nREPL is the REPL protocol used by most Clojure development. The nREPL server — which is loaded into your running project — allows loading of middleware to add features available to clients, including editors, such as code completion and online documentation. select-nrepl is middleware which adds support for selecting Clojure text objects in an editor-agnostic way.
This is based on a rewrite-clj, a zipper library for navigating and manipulating Clojure code, preserving its indentation and comments. This library is the basis for the majority of Clojure online refactoring tools, and takes its coordinates in codepoints.
The middleware registers a handler for a message that takes the current input buffer, selection coordinates, and a text object type, and returns a new selection indicating the extent of that text object. It runs synchronously from mappings in the object menu.
It currently uses select but does not translate codepoint coordinates to byte coordinates, so it doesn’t work when a line contains wide characters before the anchor or cursor, or inside the resulting selection.
Note that using a suffix instead of a -codepoints or -display switch allows mixing of coordinates.
This would be used in parinfer-rust, since it takes all the selections, modifies only the main one, and puts them back.
If a switch is used instead, the parinfer-rust case could be satisfied with a new selection_desc_in_display_columns.
Pros:
-
Is reusable by other plugins
-
Doesn’t require adaptation of existing editor-agnostic tooling
-
Doesn’t have another dependency for other plugins
Cons:
-
Timestamp handling in Kakoune gets complicated
-
Coordinates aren’t interpretable without a Buffer reference and a timestamp
In parinfer-rust, strings are byte strings and a UTF8 library is used to segment strings, making this not too hard.
In select-nrepl, code is parsed from a Java-native character stream using the zipper library, which is backed by either a Java UTF16 String or an input port. Further, code is sent in UTF8 byte arrays in bencode format using the REPL protocol. The nREPL server automatically decodes the string into Java-native UTF16 strings before the middleware handler is activated. The recommended way to send binary data is to base64 encode it in UTF8 string. Tracking byte columns requires either:
+ 1. another fork for base64, decoding in the server, heavily adapting rewrite-clj to parse byte streams, including teaching the parsing about UTF8 segmentation 2. modifying rewrite-clj to track a "theoretical" utf8 byte offset when parsing
+ This "theoretical offset" idea might completely work — the only possible problem I can see is if, when decoding the UTF8, Java prefers precomposed characters or not precomposed characters or something.
NOTE. The original parinfer, written in JavaScript, also parses code from a UTF16 JavaScript string sent in JSON. I expect it would have all the same issues as the JVM.
Pros:
-
No need to take a second pass.
-
No changes in Kakoune.
Cons:
-
The cursor position will need to be sent in both display columns and bytes.
-
For select-nrepl, this could be a lot of work in the rewrite-clj library which is apparently only needed for Kakoune.
-
The mechanism is not useable by other Kakoune plugins.
Take a separate pass afterward, given the output text, to locate the line and translate the display column to the byte column.
Pros:
-
Doesn’t require adaptation of existing editor-agnostic tooling
-
Nice separation of concerns.
Cons:
-
The mechanism is not useable by other Kakoune plugins.
A command-line tool takes buffer contents as input and a list of display or char coordinates and prints their translations into byte columns.
Pros:
-
Is reusable by other plugins
-
Nice separation of concerns
-
Fits the POSIX model
-
Doesn’t require adaptation of existing editor-agnostic tooling
-
Timestamp handling goes away, since the filter is always invoked with the "right" version of the buffer
Cons:
-
Adds a dependency for installing plugins that use it
-
Adds at least one fork per keystroke, assuming it can filter multiple coordinates per invocation