Giter Club home page Giter Club logo

tree-edit's Introduction

MELPA

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. As a result, editing can oftentimes devolve into a tedious exercise in character manipulation.

Tree-edit provides language-agnostic 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 the languages under edit to power it’s editing capabilities.

Table of Contents

Getting started

Tree-edit is consists of two packages: tree-edit, a library for providing structural editing, and evil-tree-edit, a package which exposes this functionality as evil state with preconfigured bindings and visualization (as seen above). To get an overview of tree-edit’s full capabilities, check out the EmacsConf talk!

The following sections of the README describe usage with evil-tree-edit. See here for a short guide on using the library.

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, as tree-edit does not always produce text consistent in formatting with the surrounding nodes. If your language provides a performant formatter, you can even run it after every editing operation:

(add-hook 'evil-tree-edit-after-change-hook #'my-format-buffer-command)

Usage

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.

Navigation

The navigation primitives follow the tree structure of the language.

OperationKeybindDescription
NextjMove cursor to the next sibling.
PreviouskMove cursor to the previous sibling.
InwardsfMove cursor to the first child.
OutwardshMove cursor to the parent.
Jump tosAvy jump to a node of node-type for a node inside the current.
Outwards SignificantAMove outwards until a significant node (e.g. function or class declaration) is hit.
Goto PlaceholdernJump 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.

Editing operations

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.

OperationKeybindDescription
RaiserReplace the current node’s parent with the current node.
DeletedDelete the current node.
MovemCopy then delete the current node.
ChangecDelete the current node and drop into insert state. Tree state will be re-entered on ESC.
WrapwCreate a new node of node-type and insert the current one in it.
ExchangeeExchange the current node with a new node of node-type.
InsertiInsert a new node of node-type to the right of the current.
AppendaInsert a new node of node-type to the left of the current.
Insert ChildIInsert a new node of node-type as a child of the current. Useful for nodes with no named children, i.e. {}
Goto Placeholder and ChangeNJump to the first placeholder node within the current and edit it.
Append Placeholder and ChangexAdd 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.
CopyyCopy the text of the current node.
UndouUndo the last operation.
Preview?Preview the possible variations of the current node.
Tree viewvEnable tree-edit-view or display if already enabled.

Pasting

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.

A note on raise vs. delete, wrap vs. insert

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.

Supported languages

StatusLanguage
Python
🔨C
🔨Java
Supported
🔶Requires custom grammar
🔨Under development

Tree-edit is designed to be as language-agnostic as possible. Currently the list of supported languages is not very impressive, but in theory it should be as simple as running a script to preprocess a grammar and adding a configuration file for the language.

See here to learn the process for adding a new language.

Customization

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.

Limitations

A non-comprehensive list of some of the larger limitations that tree-edit currently has:

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.

Implementation

To learn more about how tree-edit works under the hood, see this high-level overview or check out this org doc with executable code examples demonstrating how the syntax generation works.

Contributing

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.

The project is fairly complex and the documentation is still in progress, so feel free to open a discussion if you’re interested in helping out but you’re not sure where to start!

Tests can be run using ./run-tests.sh script.

Related projects

combobulate
Structural navigation and limited structural editing
grammatical-edit
Smartparens-like using tree-sitter (?)
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.

tree-edit's People

Contributors

ethan-leba avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.