evil-tree-edit
can be installed on MELPA.
Many, if not all languages will require custom grammars to be installed. See main README for instructions.
After installation, add hooks for any language you’d like evil-tree-edit to automatically enable in.
(add-hook 'java-mode-hook #'evil-tree-edit-mode)
It’s also recommended to use tree-edit alongside an autoformatter in it’s current state, as tree-edit does not always produce text consistent in formatting with the surrounding nodes.
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 | Avy 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. |
Goto Placeholder | n | Jump to the first placeholder node within the current. |
The definition of a placeholder node is configurable, but generally it’s the
TREE
identifiers as seen in the GIF demo.
The most important feature of tree-edit: editing the syntax tree.
For any editing operation, the syntax will be added or deleted based on the needs of the operation. For example, when adding an additional argument to a function, tree-edit can infer that a comma is needed based on the grammar of the language.
tree-edit-syntax-snippets
defines how node types will actually be represented
upon insertion: see example here.
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. |
Move | m | Copy then 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. {} |
Goto Placeholder and Change | N | Jump to the first placeholder node within the current and edit it. |
Append Placeholder and Change | x | Add a placeholder node and then immediately edit it. |
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. |
Undo | u | Undo the last operation. |
Preview | ? | Preview the possible variations of the current node. |
Tree view | V | Enable tree-edit-view or display if already enabled. |
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.
Both of the following definition for argument list produce the same result on a textual level:
argument_list = expression | seq[expression "," argument_list] argument_list = seq[expression, repeat["," expression]]
However, at the tree level, these two constructions result in different ways to modify the node.
For the first construction, you’d need to use raise/wrap to add and remove expressions:
(foo, [bar]) ==raise==> (foo) ([foo]) ===wrap==> (foo, bar)
While for the second, you can use insert/delete.
(foo, [bar]) ==delete=> (foo) ([foo]) ==insert=> (foo, bar)
This is something you may need to be aware of if you’re running trying to
perform an operation that you think should work, but doesn’t! In doubt, check
the grammar.js
of the language.
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.