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.
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. Checkruntime/queries/<lang>/ for indents.scm.
Installation
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 --openfor 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 movinghead 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_selectionis 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, documentHistory, 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
aViewId.
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 aSurface. 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 aVec<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 theevents! 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!, orerror!. (log::info!("helix!")) - Pass the appropriate verbosity level option for the desired log level. (
hx -v <file>for info, morevs for higher verbosity) - Want to display the logs in a separate file instead of using the
:log-opencommand in your compiled Helix editor? Start your debug version withcargo run -- --log foo.logand in a new terminal usetail -f foo.log
- Print using
- Instead of running a release version of Helix, while developing you may want to run in debug mode with
cargo runwhich 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 becauserust-toolchain.tomlselects 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
cargo xtask docgeninside the project. We use xtask as an ad-hoc task runner.
To preview the book itself, install mdbook. Then, run
mdbook serve bookand 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-versionkey inCargo.tomlin the repository root - the
env.MSRVkey at the top of.github/workflows/build.yml - the
toolchain.channelkey inrust-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.versionkey inCargo.toml. Cargo only accepts
SemVer versions so a CalVer version of22.07for example must be formatted
as22.7.0. Patch/bugfix releases should increment the SemVer patch number. A
patch release for 22.07 would be22.7.1. - Run
cargo checkand commit the resulting change toCargo.lock
- Update the
- Add changelog notes to
CHANGELOG.md - Add new
<release>entry incontrib/Helix.appdata.xmlwith release information according to the AppStream spec
- Update the version:
- Tag and push
- Switch to master and pull
git tag -s -m "<tag>" -a <tag> && git push origin <tag>(note the-swhich 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
- Use
- Merge the release notes PR
- Download the macos and linux binaries and update the
sha256s in the homebrew formula- Use
sha256sumon the downloaded.tar.xzfiles to determine the hash
- Use
- 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
https://github.com/helix-editor/helix/compare/22.05...masterEither 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:
https://github.com/helix-editor/helix/compare/7706a4a0d8b67b943c31d0c5f7b00d357b5d838d...masterA 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:
# Author : NameIf 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:
# 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.