⚠ Tree-edit is very much a work-in-progress. Expect to run into bugs and breaking changes!
Every programming language has a formally defined structure, but most text editors are completely ignorant to it when editing code. As a result, editing can oftentimes devolve into a tedious exercise in character manipulation.
Tree-edit provides editing operations that map directly to the structure of the language, abstracting away the process of manually entering syntax. Leveraging the tree-sitter parser, tree-edit always has access to the precise state of the syntax tree – and directly wields the grammars of supported languages in order to power it’s editing capabilities.
- Table of Contents
- Getting started
- Usage
- Supported languages
- Customization
- Limitations
- Contributing
- Related projects
Tree-edit is consists of two packages: tree-edit, a library for providing structural editing, and evil-tree-edit, a new evil tree state which exposes this functionality. The following sections of the README describe usage with evil-tree-edit.
After installation, add hooks for any language you’d like tree-edit to automatically enable in.
(add-hook 'java-mode-hook #'evil-tree-edit-mode)
It’s also recommended to use tree-edit with an autoformatter in it’s current state (see limitations).
(add-hook 'tree-edit-after-change-hook #'my-format-buffer-command)
The concept of the cursor, a position in the 2D plane of text, is replaced by the current node, which is a position in the syntax tree in tree-edit. All operations unless otherwise specified are performed on the current node. To help visualize the syntax tree, tree-edit provides M-x tree-edit-view-mode as seen in the demo GIF.
Tree-edit adopts a vim-style approach to editing, where certain operators also require a noun. In vim’s case, the nouns are text objects; In tree-edit’s case, the nouns are node types. For example, iv would insert a variable declaration. Due to the fact that most languages contain a large number of node types, and vary across languages, using which-key with tree-edit is highly recommended.
To activate tree-edit from normal state, press Q, and to return to normal state press ESC.
The navigation primitives follow the tree structure of the language.
Operation | Keybind | Description |
---|---|---|
Next | j | Move cursor to the next sibling. |
Previous | k | Move cursor to the previous sibling. |
Inwards | f | Move cursor to the first child. |
Outwards | h | Move cursor to the parent. |
Jump to | s | Jump to a node of node-type for a node inside the current. |
Outwards Significant | A | Move outwards until a significant node (e.g. function or class declaration) is hit. |
The most important feature of tree-edit: editing the syntax tree. For any editing operation, the syntax will be added or deleted at will based on the needs of the operation. Any transformations will be rejected if a syntactically valid result cannot be generated.
Operation | Keybind | Description |
---|---|---|
Raise | r | Replace the current node’s parent with the current node. |
Delete | d | Delete the current node. |
Change | c | Delete the current node and drop into insert state. Tree state will be re-entered on ESC. |
Wrap | w | Create a new node of node-type and insert the current one in it. |
Exchange | e | Exchange the current node with a new node of node-type. |
Insert | i | Insert a new node of node-type to the right of the current. |
Append | a | Insert a new node of node-type to the left of the current. |
Insert Child | I | Insert a new node of node-type as a child of the current. Useful for nodes with no named children, i.e. {} |
Slurp | > | Grow the current node to contain the nearest right-most element. |
Barf | < | Shrink the current node to place it’s left-most element into the parent node. |
Copy | y | Copy the text of the current node. |
Along with the standard node-types of the given language, tree-edit has a special node-type p that will attempt to parse the type of the most recently copied text. If a type can be identified and the operation is valid, the copied text will be used.
Status | Language |
---|---|
🔨 | Java |
Currently the list of supported languages is not very impressive, but in theory it should be as simple as adding a new grammar and language file for a given language. In practice I expect this to unearth some language-specific assumptions in the tree-edit code base!
Currently adding customization ontop of the preset language files requires a fair bit of boilerplate, but here’s some code to get started.
(with-eval-after-load 'tree-edit-java
(with-mode-local java-mode
(setq-mode-local
java-mode
tree-edit-syntax-snippets
(append
;; Put your snippets here
'((identifier . ("FOOBAR")))
tree-edit-syntax-snippets)
tree-edit-nodes
(append
;; Put your nodes here
'((:type if_statement
:key "z"
:name "if-else statement"
:node-override '((if_statement . ("if" parenthesized_expression block "else" block)))))
tree-edit-nodes)))
(evil-tree-edit--set-state-bindings 'java-mode))
See tree-edit-java.el and the docstrings of the accompanying variables for more information.
A non-comprehensive list of some of the larger limitations that tree-edit currently has:
- Formatting
- tree-edit does not currently make any attempts to produce code in a well-formatted manner, and may change the formatting of adjacent nodes during editing operations.
- Impedance mismatch
- Most tree-sitter grammars were not designed with tree-edit’s usecase in mind, so some grammars may be structured inconveniently for tree-edit’s purposes.
- Tree-sitter-langs
- Tree-edit currently depends on tree-sitter-langs to power the tree-sitter parsers, however tree-sitter-langs does not always have the most up-to-date grammars and is missing some languages. If this continues to be an issue a fork may be needed.
Contributions are very much welcome! In particular, adding language files would be a great place to help. Otherwise, the issues are a good place to propose features or find ones to implement.
Tests can be run using ./run-tests.sh
script.
- evil-textobj-tree-sitter
- Evil mode text objects using tree-sitter queries.
- lispy
- Lisp structural editing package – big inspiration for tree-edit!
- smartparens
- Multilingual package with structural editing limited to matching delimiters.