helix Documentation

A post-modern modal text editor.

45,808 stars Rust Rust 1 file ยท ~5,395 tokens #kakoune#rust#text-editor#vim
RAW Doc

File: README.md

A Kakoune / Neovim inspired editor, written in Rust.

The editing model is very heavily based on Kakoune; during development I found
myself agreeing with most of Kakoune's design decisions.

For more information, see the website or
documentation.

All shortcuts/keymaps can be found in the documentation on the website.

Troubleshooting

Features

  • Vim-like modal editing
  • Multiple selections
  • Built-in language server support
  • Smart, incremental syntax highlighting and code editing via tree-sitter

Although it's primarily a terminal-based editor, I am interested in exploring
a custom renderer (similar to Emacs) using wgpu.

Note: Only certain languages have indentation definitions at the moment. Check
runtime/queries/<lang>/ for indents.scm.

Installation

Installation documentation.

Contributing

Contributing guidelines can be found here.

Getting help

Your question might already be answered on the FAQ.

Discuss the project on the community Matrix Space (make sure to join #helix-editor:matrix.org if you're on a client that doesn't support Matrix Spaces yet).

Credits

Thanks to @jakenvac for designing the logo!


File: book/src/guides/README.md

Guides

This section contains guides for adding new languages to Helix: language and
grammar configuration, and the tree-sitter query files that drive highlighting,
power indentation, textobjects, symbol tags, and other features.


File: docs/architecture.md

Crate Description
helix-stdx Extensions to the standard library (similar to rust-analyzer's)
helix-core Core editing primitives, functional.
helix-lsp Language server client
helix-lsp-types Language Server Protocol type definitions
helix-dap Debug Adapter Protocol (DAP) client
helix-event Primitives for defining and handling events within the editor
helix-loader Functions for building, fetching, and loading external resources
helix-view UI abstractions for use in backends, imperative shell.
helix-term Terminal UI
helix-tui TUI primitives, forked from tui-rs, inspired by Cursive

This document contains a high-level overview of Helix internals.

NOTE: Use cargo doc --open for API documentation as well as dependency
documentation.

Core

The core contains basic building blocks used to construct the editor. It is
heavily based on CodeMirror 6. The primitives
are functional: most operations won't modify data in place but instead return
a new copy.

The main data structure used for representing buffers is a Rope. We re-export
the excellent ropey library. Ropes are cheap
to clone, and allow us to easily make snapshots of a text state.

Multiple selections are a core editing primitive. Document selections are
represented by a Selection. Each Range in the selection consists of a moving
head and an immovable anchor. A single cursor in the editor is simply
a selection with a single range, with the head and the anchor in the same
position.

Ropes are modified by constructing an OT-like Transaction. It represents
a single coherent change to the document and can be applied to the rope.
A transaction can be inverted to produce an undo. Selections and marks can be
mapped over a transaction to translate to a position in the new text state after
applying the transaction.

NOTE: Transaction::change/Transaction::change_by_selection is the main
interface used to generate text edits.

Syntax is the interface used to interact with tree-sitter ASTs for syntax
highlighting and other features.

View

The view layer was supposed to be a frontend-agnostic imperative library that
would build on top of core to provide the common editor logic. Currently it's
tied to the terminal UI.

A Document ties together the Rope, Selection(s), Syntax, document
History, language server (etc.) into a comprehensive representation of an open
file.

A View represents an open split in the UI. It holds the currently open
document ID and other related state. Views encapsulate the gutter, status line,
diagnostics, and the inner area where the code is displayed.

NOTE: Multiple views are able to display the same document, so the document
contains selections for each view. To retrieve, document.selection() takes
a ViewId.

Info is the autoinfo box that shows hints when awaiting another key with bindings
like g and m. It is attached to the viewport as a whole.

Surface is like a buffer to which widgets draw themselves to, and the
surface is then rendered on the screen on each cycle.

Rects are areas (simply an x and y coordinate with the origin at the
screen top left and then a height and width) which are part of a
Surface. They can be used to limit the area to which a Component can
render. For example if we wrap a Markdown component in a Popup
(think the documentation popup with space+k), Markdown's render method
will get a Rect that is the exact size of the popup.

Widgets are called Components internally, and you can see most of them
in helix-term/src/ui. Some components like Popup and Overlay can take
other components as children.

Layers are how multiple components are displayed, and is simply a
Vec<Component>. Layers are managed by the Compositor. On each top
level render call, the compositor renders each component in the order
they were pushed into the stack. This makes multiple components "layer"
on top of one another. Hence we get a file picker displayed over the
editor, etc.

The Editor holds the global state: all the open documents, a tree
representation of all the view splits, the configuration, and a registry of
language servers. To open or close files, interact with the editor.

LSP

A language server protocol client.

Term

The terminal frontend.

The main function sets up a new Application that runs the event loop.

commands.rs is probably the most interesting file. It contains all commands
(actions tied to keybindings).

keymap.rs links commands to key combinations.

TUI / Term

TODO: document Component and rendering related stuff

Event

The helix-event crate defines primitives for defining and acting on events
within the editor. "Events" cover things like opening, changing and closing of
documents, starting and stopping of language servers and more.

helix-event has tools for defining events and registering hooks which run
any time an event is emitted. helix-event also provides AsyncHook - a tool
for running cancellable tasks which run after events with debouncing.

See the AsyncHook type for more information. Events can be created within the
events! macro. Synchronous hooks can be created with register_hook!. And
editor-wide events can be sent to hooks with helix_event::dispatch.


File: docs/CONTRIBUTING.md

Contributing

Contributors are very welcome! No contribution is too small and all contributions are valued.

Some suggestions to get started:

  • You can look at the good first issue label on the issue tracker.
  • Help with packaging on various distributions needed!
  • To use print debugging to the Helix log file, you must:
    • Print using log::info!, warn!, or error!. (log::info!("helix!"))
    • Pass the appropriate verbosity level option for the desired log level. (hx -v <file> for info, more vs for higher verbosity)
    • Want to display the logs in a separate file instead of using the :log-open command in your compiled Helix editor? Start your debug version with cargo run -- --log foo.log and in a new terminal use tail -f foo.log
  • Instead of running a release version of Helix, while developing you may want to run in debug mode with cargo run which is way faster to compile
  • Looking for even faster compile times? Give mold a try
  • If your preferred language is missing, integrating a tree-sitter grammar for
    it and defining syntax highlight queries for it is straightforward and
    doesn't require much knowledge of the internals.
  • If you don't use the Nix development shell and are getting your rust-analyzer binary from rustup, you may need to run rustup component add rust-analyzer.
    This is because rust-toolchain.toml selects our MSRV for the development toolchain but doesn't download the matching rust-analyzer automatically.

We provide an architecture.md that should give you
a good overview of the internals.

Auto generated documentation

Some parts of the book are autogenerated from the code itself,
like the list of :commands and supported languages. To generate these
files, run

shell
cargo xtask docgen

inside the project. We use xtask as an ad-hoc task runner.

To preview the book itself, install mdbook. Then, run

shell
mdbook serve book

and visit http://localhost:3000.

Testing

Unit tests/Documentation tests

Run cargo test --workspace to run unit tests and documentation tests in all packages.

Integration tests

Integration tests for helix-term can be run with cargo integration-test. Code
contributors are strongly encouraged to write integration tests for their code.
Existing tests can be used as examples. Helpers can be found in
helpers.rs. The log level can be set with the HELIX_LOG_LEVEL
environment variable, e.g. HELIX_LOG_LEVEL=debug cargo integration-test.

Contributors using MacOS might encounter Too many open files (os error 24)
failures while running integration tests. This can be resolved by increasing
the default value (e.g. to 10240 from 256) by running ulimit -n 10240.

Minimum Stable Rust Version (MSRV) Policy

Helix keeps an intentionally low MSRV for the sake of easy building and packaging
downstream. We follow Firefox's MSRV policy. Once Firefox's MSRV increases we
may bump ours as well, but be sure to check that popular distributions like Ubuntu
package the new MSRV version. When increasing the MSRV, update these three places:

  • the workspace.package.rust-version key in Cargo.toml in the repository root
  • the env.MSRV key at the top of .github/workflows/build.yml
  • the toolchain.channel key in rust-toolchain.toml

File: docs/releases.md

Checklist

Helix releases are versioned in the Calendar Versioning scheme:
YY.0M(.MICRO), for example, 22.05 for May of 2022, or in a patch release,
22.05.1. In these instructions we'll use <tag> as a placeholder for the tag
being published.

  • Merge the PR with the release updates. That branch should:
    • Update the version:
      • Update the workspace.package.version key in Cargo.toml. Cargo only accepts
        SemVer versions so a CalVer version of 22.07 for example must be formatted
        as 22.7.0. Patch/bugfix releases should increment the SemVer patch number. A
        patch release for 22.07 would be 22.7.1.
      • Run cargo check and commit the resulting change to Cargo.lock
    • Add changelog notes to CHANGELOG.md
    • Add new <release> entry in contrib/Helix.appdata.xml with release information according to the AppStream spec
  • Tag and push
    • Switch to master and pull
    • git tag -s -m "<tag>" -a <tag> && git push origin <tag> (note the -s which signs the tag)
  • Wait for the Release CI to finish
    • It will automatically turn the git tag into a GitHub release when it uploads artifacts
  • Edit the new release
    • Use <tag> as the title
    • Link to the changelog and release notes
  • Merge the release notes PR
  • Download the macos and linux binaries and update the sha256s in the homebrew formula
    • Use sha256sum on the downloaded .tar.xz files to determine the hash
  • Link to the release notes in this-week-in-rust
  • Post to reddit

Changelog Curation

The changelog is currently created manually by reading through commits in the
log since the last release. GitHub's compare view is a nice way to approach
this. For example, when creating the 22.07 release notes, this compare link
may be used

text
https://github.com/helix-editor/helix/compare/22.05...master

Either side of the triple-dot may be replaced with an exact revision, so if
you wish to incrementally compile the changelog, you can tackle a weeks worth
or so, record the revision where you stopped, and use that as a starting point
next week:

text
https://github.com/helix-editor/helix/compare/7706a4a0d8b67b943c31d0c5f7b00d357b5d838d...master

A work-in-progress commit for a changelog might look like
this example.

Not every PR or commit needs a blurb in the changelog. Each release section
tends to have a blurb that links to a GitHub comparison between release
versions for convenience:

As usual, the following is a summary of each of the changes since the last
release. For the full log, check out the git log.

Typically, small changes like dependencies or documentation updates, refactors,
or meta changes like GitHub Actions work are left out.


File: docs/vision.md

The Helix project still has a ways to go before reaching its goals. This document outlines some of those goals and the overall vision for the project.

Vision

An efficient, batteries-included editor you can take anywhere and be productive... if it's your kind of thing.

  • Cross-platform. Whether on Linux, Windows, or OSX, you should be able to take your editor with you.
  • Terminal first. Not all environments have a windowing system, and you shouldn't have to abandon your preferred editor in those cases.
  • Native. No Electron or HTML DOM here. We want an efficient, native-compiled editor that can run with minimal resources when needed. If you're working on a Raspberry Pi, your editor shouldn't consume half of your RAM.
  • Batteries included. Both the default configuration and bundled features should be enough to have a good editing experience and be productive. You shouldn't need a massive custom config or external executables and plugins for basic features and functionality.
  • Don't try to be everything for everyone. There are many great editors out there to choose from. Let's make Helix one of those great options, with its own take on things.

Goals

Vision statements are all well and good, but are also vague and subjective. Here is a (non-exhaustive) list of some of Helix's more concrete goals, to help give a clearer idea of the project's direction:

  • Modal. Vim is a great idea.
  • Selection -> Action, not Verb -> Object. Interaction models aren't linguistics, and "selection first" lets you see what you're doing (among other benefits).
  • We aren't playing code golf. It's more important for the keymap to be consistent and easy to memorize than it is to save a key stroke or two when editing.
  • Built-in tools for working with code bases efficiently. Most projects aren't a single file, and an editor should handle that as a first-class use case. In Helix's case, this means (among other things) a fuzzy-search file navigator and LSP support.
  • Edit anything that comes up when coding, within reason. Whether it's a 200 MB XML file, a megabyte of minified javascript on a single line, or Japanese text encoded in ShiftJIS, you should be able to open it and edit it without problems. (Note: this doesn't mean handle every esoteric use case. Sometimes you do just need a specialized tool, and Helix isn't that.)
  • Configurable, within reason. Although the defaults should be good, not everyone will agree on what "good" is. Within the bounds of Helix's core interaction models, it should be reasonably configurable so that it can be "good" for more people. This means, for example, custom key maps among other things.
  • Extensible, within reason. Although we want Helix to be productive out-of-the-box, it's not practical or desirable to cram every useful feature and use case into the core editor. The basics should be built-in, but you should be able to extend it with additional functionality as needed.
  • Clean code base. Sometimes other factors (e.g. significant performance gains, important features, correctness, etc.) will trump strict readability, but we nevertheless want to keep the code base straightforward and easy to understand to the extent we can.

File: helix-lsp-types/README.md

Helix's `lsp-types`

This is a fork of the lsp-types crate (gluon-lang/lsp-types) taken at version v0.95.1 (commit 3e6daee). This fork focuses usability improvements that make the types easier to work with for the Helix codebase.

The URL type has been replaced with a newtype wrapper of a String. The lsp-types crate at the forked version used url::Url which provides conveniences for using URLs according to the WHATWG URL spec. Helix supports a subset of valid URLs, namely the file:// scheme, so a wrapper around a normal String is sufficient. Plus the LSP spec requires URLs to be in RFC3986 format instead.


File: helix-tui/README.md

helix-tui

This library is a fork of the great library
tui-rs. We've mainly relied on the double
buffer implementation and render diffing, side-stepping its widget and
layouting.


File: runtime/themes/README.md

User submitted themes

If you submit a theme, please include a comment at the top with your name and email address:

toml
# Author : Name

If you are submitting a theme that is already published somewhere, then please
add the corresponding license file for it under licenses/ with the following
name theme_name.license and add a comment at the top of the theme file:

toml
# License: <License specification>

We have a preview page for themes on our wiki!


File: runtime/queries/ecma/README.md

Inheritance model for ecma-based languages

Ecma-based languages share many traits. Because of this we want to share as many queries as possible while avoiding nested inheritance that can make query behaviour unpredictable due to unexpected precedence.

To achieve that, there are "public" and "private" versions for javascript, jsx, and typescript query files, that share the same name, but the "private" version name starts with an underscore (with the exception of ecma, that already exists as a sort of base "private" language). This allows the "private" versions to host the specific queries of the language excluding any ; inherits statement, in order to make them safe to be inherited by the "public" version of the same language and other languages as well. The tsx language doesn't have a "private" version given that currently it doesn't need to be inherited by other languages.

Language Inherits from
javascript _javascript, ecma
jsx _jsx, _javascript, ecma
typescript _typescript, ecma
tsx _jsx, _typescript, ecma

If you intend to add queries to any of the ecma-based languages above, make sure you add them to the correct private language they belong to, so that other languages down the line can benefit from them.