Merge branch 'rewrite-and-refactor-all-documentation' of github.com:David-Else/helix into rewrite-and-refactor-all-documentation

pull/5534/head
David-Else 1 year ago
commit 7c8404248f
No known key found for this signature in database
GPG Key ID: B221A2F7DC869E40

77
Cargo.lock generated

@ -728,7 +728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c52b625ad8cc360a0b7f426266f21fb07bd49b8f4ccf1b3ca7bc89424db1dec4" checksum = "c52b625ad8cc360a0b7f426266f21fb07bd49b8f4ccf1b3ca7bc89424db1dec4"
dependencies = [ dependencies = [
"git-hash", "git-hash",
"hashbrown 0.13.1", "hashbrown 0.13.2",
] ]
[[package]] [[package]]
@ -1104,9 +1104,9 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.13.1" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [ dependencies = [
"ahash 0.8.2", "ahash 0.8.2",
] ]
@ -1121,7 +1121,7 @@ dependencies = [
"chrono", "chrono",
"encoding_rs", "encoding_rs",
"etcetera", "etcetera",
"hashbrown 0.13.1", "hashbrown 0.13.2",
"helix-loader", "helix-loader",
"imara-diff", "imara-diff",
"log", "log",
@ -1379,6 +1379,16 @@ dependencies = [
"hashbrown 0.12.3", "hashbrown 0.12.3",
] ]
[[package]]
name = "indexmap"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]] [[package]]
name = "indoc" name = "indoc"
version = "1.0.8" version = "1.0.8"
@ -1427,9 +1437,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.137" version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -1546,6 +1556,15 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.45" version = "0.1.45"
@ -1867,6 +1886,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c68e921cef53841b8925c2abadd27c9b891d9613bdc43d6b823062866df38e8"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "sha1_smol" name = "sha1_smol"
version = "1.0.0" version = "1.0.0"
@ -2122,9 +2150,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.24.1" version = "1.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes", "bytes",
@ -2164,11 +2192,36 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.5.10" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb9d890e4dc9298b70f740f615f2e05b9db37dce531f6b24fb77ac993f9f217"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" checksum = "729bfd096e40da9c001f778f5cdecbd2957929a24e10e5883d9392220a751581"
dependencies = [ dependencies = [
"indexmap",
"nom8",
"serde", "serde",
"serde_spanned",
"toml_datetime",
] ]
[[package]] [[package]]
@ -2336,9 +2389,9 @@ checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]] [[package]]
name = "which" name = "which"
version = "4.3.0" version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
dependencies = [ dependencies = [
"either", "either",
"libc", "libc",

@ -1,6 +1,5 @@
# Commands # Commands
Command mode, similar to Vim, can be activated by pressing `:`. The built-in Command mode, similar to Vim, can be activated by pressing `:`. The built-in commands are:
commands are:
{{#include ./generated/typable-cmd.md}} {{#include ./generated/typable-cmd.md}}

@ -6,8 +6,7 @@ in your config directory:
- Linux and Mac: `~/.config/helix/config.toml` - Linux and Mac: `~/.config/helix/config.toml`
- Windows: `%AppData%\helix\config.toml` - Windows: `%AppData%\helix\config.toml`
> 💡 You can easily open the config file by typing `:config-open` within Helix > 💡 You can easily open the config file by typing `:config-open` within Helix normal mode.
> normal mode.
Example config: Example config:
@ -28,17 +27,16 @@ hidden = false
``` ```
You can use a custom configuration file by specifying it with the `-c` or You can use a custom configuration file by specifying it with the `-c` or
`--config` command line argument, for example `--config` command line argument, for example `hx -c path/to/custom-config.toml`.
`hx -c path/to/custom-config.toml`. Additionally, you can reload the Additionally, you can reload the configuration file by sending the USR1
configuration file by sending the USR1 signal to the Helix process on Unix signal to the Helix process on Unix operating systems, such as by using the command `pkill -USR1 hx`.
operating systems, such as by using the command `pkill -USR1 hx`.
## Editor ## Editor
### `[editor]` Section ### `[editor]` Section
| Key | Description | Default | | Key | Description | Default |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | |--|--|---------|
| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling. | `5` | | `scrolloff` | Number of lines of padding around the edge of the screen when scrolling. | `5` |
| `mouse` | Enable mouse mode. | `true` | | `mouse` | Enable mouse mode. | `true` |
| `middle-click-paste` | Middle click paste support. | `true` | | `middle-click-paste` | Middle click paste support. | `true` |
@ -127,10 +125,10 @@ The following statusline elements can be configured:
### `[editor.cursor-shape]` Section ### `[editor.cursor-shape]` Section
Defines the shape of cursor in each mode. Valid values for these options are Defines the shape of cursor in each mode.
`block`, `bar`, `underline`,or `hidden`. Valid values for these options are `block`, `bar`, `underline`, or `hidden`.
> 💡Due to limitations of the terminal environment, only the primary cursor can > 💡 Due to limitations of the terminal environment, only the primary cursor can
> change shape. > change shape.
| Key | Description | Default | | Key | Description | Default |
@ -145,20 +143,20 @@ Defines the shape of cursor in each mode. Valid values for these options are
### `[editor.file-picker]` Section ### `[editor.file-picker]` Section
Set options for file picker and global search. Ignoring a file means it is not Sets options for file picker and global search. Ignoring a file means it is
visible in the Helix file picker and global search. not visible in the Helix file picker and global search.
All git related options are only enabled in a git repository. All git related options are only enabled in a git repository.
| Key | Description | Default | | Key | Description | Default |
| ------------- | -------------------------------------------------------------------------------------------------------- | ------------------- | |--|--|---------|
| `hidden` | Enables ignoring hidden files. | true | |`hidden` | Enables ignoring hidden files. | true
| `parents` | Enables reading ignore files from parent directories. | true | |`parents` | Enables reading ignore files from parent directories. | true
| `ignore` | Enables reading `.ignore` files. | true | |`ignore` | Enables reading `.ignore` files. | true
| `git-ignore` | Enables reading `.gitignore` files. | true | |`git-ignore` | Enables reading `.gitignore` files. | true
| `git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludefile` option. | true | |`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludefile` option. | true
| `git-exclude` | Enables reading `.git/info/exclude` files. | true | |`git-exclude` | Enables reading `.git/info/exclude` files. | true
| `max-depth` | Set with an integer value for maximum depth to recurse. | Defaults to `None`. | |`max-depth` | Set with an integer value for maximum depth to recurse. | Defaults to `None`.
### `[editor.auto-pairs]` Section ### `[editor.auto-pairs]` Section
@ -210,9 +208,9 @@ name = "rust"
Search specific options. Search specific options.
| Key | Description | Default | | Key | Description | Default |
| ------------- | -------------------------------------------------------------------------------------------------- | ------- | |--|--|---------|
| `smart-case` | Enable smart case regex searching (case-insensitive unless pattern contains upper case characters) | `true` | | `smart-case` | Enable smart case regex searching (case-insensitive unless pattern contains upper case characters) | `true` |
| `wrap-around` | Whether the search should wrap after depleting the matches | `true` | | `wrap-around`| Whether the search should wrap after depleting the matches | `true` |
### `[editor.whitespace]` Section ### `[editor.whitespace]` Section
@ -262,3 +260,54 @@ render = true
character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽" character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽"
skip-levels = 1 skip-levels = 1
``` ```
### `[editor.gutters]` Section
For simplicity, `editor.gutters` accepts an array of gutter types, which will
use default settings for all gutter components.
```toml
[editor]
gutters = ["diff", "diagnostics", "line-numbers", "spacer"]
```
To customize the behavior of gutters, the `[editor.gutters]` section must
be used. This section contains top level settings, as well as settings for
specific gutter components as sub-sections.
| Key | Description | Default |
| --- | --- | --- |
| `layout` | A vector of gutters to display | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
Example:
```toml
[editor.gutters]
layout = ["diff", "diagnostics", "line-numbers", "spacer"]
```
#### `[editor.gutters.line-numbers]` Section
Options for the line number gutter
| Key | Description | Default |
| --- | --- | --- |
| `min-width` | The minimum number of characters to use | `3` |
Example:
```toml
[editor.gutters.line-numbers]
min-width = 1
```
#### `[editor.gutters.diagnotics]` Section
Currently unused
#### `[editor.gutters.diff]` Section
Currently unused
#### `[editor.gutters.spacer]` Section

@ -109,6 +109,7 @@
| ron | ✓ | | ✓ | | | ron | ✓ | | ✓ | |
| ruby | ✓ | ✓ | ✓ | `solargraph` | | ruby | ✓ | ✓ | ✓ | `solargraph` |
| rust | ✓ | ✓ | ✓ | `rust-analyzer` | | rust | ✓ | ✓ | ✓ | `rust-analyzer` |
| sage | ✓ | ✓ | | |
| scala | ✓ | | ✓ | `metals` | | scala | ✓ | | ✓ | `metals` |
| scheme | ✓ | | | | | scheme | ✓ | | | |
| scss | ✓ | | | `vscode-css-language-server` | | scss | ✓ | | | `vscode-css-language-server` |
@ -129,7 +130,7 @@
| twig | ✓ | | | | | twig | ✓ | | | |
| typescript | ✓ | ✓ | ✓ | `typescript-language-server` | | typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
| ungrammar | ✓ | | | | | ungrammar | ✓ | | | |
| v | ✓ | | | `vls` | | v | ✓ | | | `v` |
| vala | ✓ | | | `vala-language-server` | | vala | ✓ | | | `vala-language-server` |
| verilog | ✓ | ✓ | | `svlangserver` | | verilog | ✓ | ✓ | | `svlangserver` |
| vhs | ✓ | | | | | vhs | ✓ | | | |

@ -50,10 +50,3 @@ below.
grammars. grammars.
- If a parser is causing a segfault or you want to remove it, make sure to - If a parser is causing a segfault or you want to remove it, make sure to
remove the compiled parser located at `runtime/grammar/<name>.so`. remove the compiled parser located at `runtime/grammar/<name>.so`.
[language configuration section]: ../languages.md
[neovim-query-precedence]:
https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090
[install-lsp-wiki]:
https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers
[lang-support]: ../lang-support.md

@ -1,15 +1,15 @@
# Adding Textobject Queries # Adding Textobject Queries
Helix supports textobjects that are language specific, such as functions, Helix supports textobjects that are language specific, such as functions, classes, etc.
classes, etc. These textobjects require an accompanying tree-sitter grammar and These textobjects require an accompanying tree-sitter grammar and a `textobjects.scm` query file
a `textobjects.scm` query file to work properly. Tree-sitter allows us to query to work properly. Tree-sitter allows us to query the source code syntax tree
the source code syntax tree and capture specific parts of it. The queries are and capture specific parts of it. The queries are written in a lisp dialect.
written in a lisp dialect. More information on how to write queries can be found More information on how to write queries can be found in the [official tree-sitter
in the [official tree-sitter documentation][tree-sitter-queries]. documentation][tree-sitter-queries].
Query files should be placed in `runtime/queries/{language}/textobjects.scm` Query files should be placed in `runtime/queries/{language}/textobjects.scm`
when contributing to Helix. Note that to test the query files locally you should when contributing to Helix. Note that to test the query files locally you should put
put them under your local runtime directory (`~/.config/helix/runtime` on Linux them under your local runtime directory (`~/.config/helix/runtime` on Linux
for example). for example).
The following [captures][tree-sitter-captures] are recognized: The following [captures][tree-sitter-captures] are recognized:
@ -31,23 +31,18 @@ repository.
## Queries for Textobject Based Navigation ## Queries for Textobject Based Navigation
Tree-sitter based navigation in Helix is done using captures in the following Tree-sitter based navigation in Helix is done using captures in the
order: following order:
- `object.movement` - `object.movement`
- `object.around` - `object.around`
- `object.inside` - `object.inside`
For example if a `function.around` capture has been already defined for a For example if a `function.around` capture has been already defined for a language
language in its `textobjects.scm` file, function navigation should also work in its `textobjects.scm` file, function navigation should also work automatically.
automatically. `function.movement` should be defined only if the node captured `function.movement` should be defined only if the node captured by `function.around`
by `function.around` doesn't make sense in a navigation context. doesn't make sense in a navigation context.
[textobjects]: ../usage.md#textobjects [tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
[textobjects-nav]: ../usage.md#tree-sitter-textobject-based-navigation [tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes
[tree-sitter-queries]: [textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=
https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
[tree-sitter-captures]:
https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes
[textobject-examples]:
https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=

@ -1,7 +1,6 @@
# Installing Helix # Installing Helix
<!--toc:start--> <!--toc:start-->
- [Installing Helix](#installing-helix) - [Installing Helix](#installing-helix)
- [Using the Pre-built Binaries](#using-the-pre-built-binaries) - [Using the Pre-built Binaries](#using-the-pre-built-binaries)
- [Installing Helix on Linux through the Official Package Manager](#installing-helix-on-linux-through-the-official-package-manager) - [Installing Helix on Linux through the Official Package Manager](#installing-helix-on-linux-through-the-official-package-manager)
@ -33,7 +32,7 @@ line.
## Installing Helix on Linux through the Official Package Manager ## Installing Helix on Linux through the Official Package Manager
If your Linux distribution has Helix available through its official package If your Linux distribution has Helix available through its official package
manager, install it through that. The following list shows availability manager, install it through that. The following shows availability
throughout the Linux ecosystem: throughout the Linux ecosystem:
[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
@ -51,8 +50,7 @@ Helix is available for the following versions of Ubuntu:
- 22.04 LTS (Jammy Jellyfish) - 22.04 LTS (Jammy Jellyfish)
- 22.10 (Kinetic Kudu) - 22.10 (Kinetic Kudu)
Via Via [Maveonair's PPA](https://launchpad.net/~maveonair/+archive/ubuntu/helix-editor)
[Maveonair's PPA](https://launchpad.net/~maveonair/+archive/ubuntu/helix-editor):
```sh ```sh
sudo add-apt-repository ppa:maveonair/helix-editor sudo add-apt-repository ppa:maveonair/helix-editor
@ -97,8 +95,8 @@ brew install helix
## Installing Helix on Windows ## Installing Helix on Windows
Install on Windows using [Scoop](https://scoop.sh/), Install on Windows using [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
[Chocolatey](https://chocolatey.org/) or [MSYS2](https://msys2.org/). or [MSYS2](https://msys2.org/).
**Scoop:** **Scoop:**
@ -120,7 +118,7 @@ For 64-bit Windows 8.1 or above:
pacman -S mingw-w64-ucrt-x86_64-helix pacman -S mingw-w64-ucrt-x86_64-helix
``` ```
## Building from Source 2. Compile Helix:
1. Clone the repository: 1. Clone the repository:
@ -139,6 +137,13 @@ This command will create the `hx` executable and construct the tree-sitter
grammars in the `runtime` folder, or in the folder specified in `HELIX_RUNTIME` grammars in the `runtime` folder, or in the folder specified in `HELIX_RUNTIME`
(as described below). (as described below).
> 💡 If you are using the musl-libc instead of glibc the following environment variable must be set during the build
> to ensure tree sitter grammars can be loaded correctly:
>
> ```sh
> RUSTFLAGS="-C target-feature=-crt-static"
> ```
3. Configure Helix's runtime files 3. Configure Helix's runtime files
**IMPORTANT**: The runtime files must be accessible to the newly created binary. **IMPORTANT**: The runtime files must be accessible to the newly created binary.
@ -162,7 +167,7 @@ Either,
Or, Or,
2. Create a symlink in `~/.config/helix/` that links to the source code 2. Create a symlink in `~/.config/helix` that links to the source code
directory. directory.
```sh ```sh
@ -174,7 +179,7 @@ And optionally:
3. Configure the Desktop Shortcut 3. Configure the Desktop Shortcut
If your desktop environment supports the If your desktop environment supports the
[XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html), [XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html)
you can configure Helix to show up in the application menu by copying the you can configure Helix to show up in the application menu by copying the
provided `.desktop` and icon files to their correct folders: provided `.desktop` and icon files to their correct folders:
@ -198,7 +203,7 @@ Either,
1. Set the `HELIX_RUNTIME` environment variable on your system to tell Helix 1. Set the `HELIX_RUNTIME` environment variable on your system to tell Helix
where to find the runtime files. where to find the runtime files.
You can either do this using the Windows settings (search for You can either do this using the Windows setting (search for
`Edit environment variables for your account`) or use the `setx` command in `Edit environment variables for your account`) or use the `setx` command in
Cmd: Cmd:

@ -27,10 +27,10 @@
### Movement ### Movement
> 💡 Unlike Vim, `f`, `F`, `t` and `T` are not confined to the current line. > NOTE: Unlike Vim, `f`, `F`, `t` and `T` are not confined to the current line.
| Key | Description | Command | | Key | Description | Command |
| -------------------- | ------------------------------------------- | --------------------------- | | ----- | ----------- | ------- |
| `h`, `Left` | Move left | `move_char_left` | | `h`, `Left` | Move left | `move_char_left` |
| `j`, `Down` | Move down | `move_line_down` | | `j`, `Down` | Move down | `move_line_down` |
| `k`, `Up` | Move up | `move_line_up` | | `k`, `Up` | Move up | `move_line_up` |
@ -106,7 +106,7 @@
### Selection manipulation ### Selection manipulation
| Key | Description | Command | | Key | Description | Command |
| -------------------- | ------------------------------------------------------------- | ------------------------------------ | | ----- | ----------- | ------- |
| `s` | Select all regex matches inside selections | `select_regex` | | `s` | Select all regex matches inside selections | `select_regex` |
| `S` | Split selection into sub selections on regex matches | `split_selection` | | `S` | Split selection into sub selections on regex matches | `split_selection` |
| `Alt-s` | Split selection on newlines | `split_selection_on_newline` | | `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
@ -140,8 +140,7 @@
### Search ### Search
Search commands operate on the `/` register by default. To use a different Search commands all operate on the `/` register by default. To use a different register, use `"<char>`.
register, use `"<char>.`
| Key | Description | Command | | Key | Description | Command |
| --- | ------------------------------------------- | ------------------ | | --- | ------------------------------------------- | ------------------ |
@ -153,8 +152,7 @@ register, use `"<char>.`
### Minor modes ### Minor modes
Minor modes are accessible from normal mode and typically switch back to normal Minor modes are accessible from normal mode and typically switch back to normal mode after a command.
mode after a command.
| Key | Description | Command | | Key | Description | Command |
| -------- | -------------------------------------------------- | -------------- | | -------- | -------------------------------------------------- | -------------- |
@ -167,13 +165,17 @@ mode after a command.
| `Ctrl-w` | Enter [window mode](#window-mode) | N/A | | `Ctrl-w` | Enter [window mode](#window-mode) | N/A |
| `Space` | Enter [space mode](#space-mode) | N/A | | `Space` | Enter [space mode](#space-mode) | N/A |
These modes (except command mode) can be configured by
[remapping keys](https://docs.helix-editor.com/remapping.html#minor-modes).
#### View mode #### View mode
View mode is accessed by typing `z` in [normal mode](#normal-mode) and is View mode is access by typing `z` in [normal mode](#normal-mode)
intended for scrolling and manipulating the view without changing the selection. and is intended for scrolling and manipulating the view without changing
The "sticky" variant of this mode (accessed by typing `Z` in normal mode) is the selection. The "sticky" variant of this mode (accessed by typing `Z` in
persistent and can be exited using the escape key. This is useful when you're normal mode) is persistent and can be exited using the escape key. This is
simply looking over text and not actively editing it. useful when you're simply looking over text and not actively editing it.
| Key | Description | Command | | Key | Description | Command |
| -------------------- | --------------------------------------------------------- | ------------------- | | -------------------- | --------------------------------------------------------- | ------------------- |
@ -194,7 +196,7 @@ Goto mode is accessed by typing `g` in [normal mode](#normal-mode), it jumps to
various locations. various locations.
| Key | Description | Command | | Key | Description | Command |
| --- | ------------------------------------------------ | -------------------------- | | ----- | ----------- | ------- |
| `g` | Go to line number `<n>` else start of file | `goto_file_start` | | `g` | Go to line number `<n>` else start of file | `goto_file_start` |
| `e` | Go to the end of the file | `goto_last_line` | | `e` | Go to the end of the file | `goto_last_line` |
| `f` | Go to files in the selection | `goto_file` | | `f` | Go to files in the selection | `goto_file` |
@ -234,8 +236,8 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`).
#### Window mode #### Window mode
Window mode is accessed by typing `Ctrl-w` in [normal mode](#normal-mode), this Window mode is accessed by typing `Ctrl-w` in [normal mode](#normal-mode),
layer is similar to Vim keybindings as Kakoune does not support window. this layer is similar to Vim keybindings as Kakoune does not support windows.
| Key | Description | Command | | Key | Description | Command |
| ---------------------- | ---------------------------------------------------- | ----------------- | | ---------------------- | ---------------------------------------------------- | ----------------- |
@ -262,11 +264,11 @@ Space mode is accessed by typing `Space` in [normal mode](#normal-mode).
This layer is a kludge of mappings, mostly pickers. This layer is a kludge of mappings, mostly pickers.
| Key | Description | Command | | Key | Description | Command |
| --- | ----------------------------------------------------------------------- | ----------------------------------- | | ----- | ----------- | ------- |
| `f` | Open file picker | `file_picker` | | `f` | Open file picker | `file_picker` |
| `F` | Open file picker at current working directory | `file_picker_in_current_directory` | | `F` | Open file picker at current working directory | `file_picker_in_current_directory` |
| `b` | Open buffer picker | `buffer_picker` | | `b` | Open buffer picker | `buffer_picker` |
| `j` | Open jump list picker | `jumplist_picker` | | `j` | Open jump list picker | `jump list_picker` |
| `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` | | `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` |
| `s` | Open document symbol picker (**LSP**) | `symbol_picker` | | `s` | Open document symbol picker (**LSP**) | `symbol_picker` |
| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` | | `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` |
@ -284,8 +286,7 @@ This layer is a kludge of mappings, mostly pickers.
| `/` | Global search in workspace folder | `global_search` | | `/` | Global search in workspace folder | `global_search` |
| `?` | Open command palette | `command_palette` | | `?` | Open command palette | `command_palette` |
> 💡 Global search displays results in a fuzzy picker, use `Space + '` to bring > TIP: Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file.
> it back up after opening a file.
##### Popup ##### Popup
@ -299,15 +300,14 @@ content than fits on the screen, you can use scroll:
#### Unimpaired #### Unimpaired
These mappings are in the style of These mappings are in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
[vim-unimpaired](https://github.com/tpope/vim-unimpaired).
| Key | Description | Command | | Key | Description | Command |
| -------- | -------------------------------------------- | --------------------- | | ----- | ----------- | ------- |
| `[d` | Go to previous diagnostic (**LSP**) | `goto_prev_diag` |
| `]d` | Go to next diagnostic (**LSP**) | `goto_next_diag` | | `]d` | Go to next diagnostic (**LSP**) | `goto_next_diag` |
| `[D` | Go to first diagnostic in document (**LSP**) | `goto_first_diag` | | `[d` | Go to previous diagnostic (**LSP**) | `goto_prev_diag` |
| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` | | `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
| `[D` | Go to first diagnostic in document (**LSP**) | `goto_first_diag` |
| `]f` | Go to next function (**TS**) | `goto_next_function` | | `]f` | Go to next function (**TS**) | `goto_next_function` |
| `[f` | Go to previous function (**TS**) | `goto_prev_function` | | `[f` | Go to previous function (**TS**) | `goto_prev_function` |
| `]t` | Go to next type definition (**TS**) | `goto_next_class` | | `]t` | Go to next type definition (**TS**) | `goto_next_class` |
@ -322,23 +322,23 @@ These mappings are in the style of
| `[p` | Go to previous paragraph | `goto_prev_paragraph` | | `[p` | Go to previous paragraph | `goto_prev_paragraph` |
| `]g` | Go to next change | `goto_next_change` | | `]g` | Go to next change | `goto_next_change` |
| `[g` | Go to previous change | `goto_prev_change` | | `[g` | Go to previous change | `goto_prev_change` |
| `[G` | Go to first change | `goto_first_change` |
| `]G` | Go to last change | `goto_last_change` | | `]G` | Go to last change | `goto_last_change` |
| `[Space` | Add newline above | `add_newline_above` | | `[G` | Go to first change | `goto_first_change` |
| `]Space` | Add newline below | `add_newline_below` | | `]Space` | Add newline below | `add_newline_below` |
| `[Space` | Add newline above | `add_newline_above` |
## Insert mode ## Insert mode
Insert mode bindings are minimal by default. Helix is designed to be a modal Insert mode bindings are minimal by default. Helix is designed to
editor, and this is reflected in the user experience and internal mechanics. be a modal editor, and this is reflected in the user experience and internal
Changes to the text are only saved for undos when escaping from insert mode to mechanics. Changes to the text are only saved for undos when
normal mode. escaping from insert mode to normal mode.
> 💡 New users are strongly encouraged to learn the modal editing paradigm to > 💡 New users are strongly encouraged to learn the modal editing paradigm
> get the smoothest experience. > to get the smoothest experience.
| Key | Description | Command | | Key | Description | Command |
| ------------------------- | ------------------------- | ------------------------ | | ----- | ----------- | ------- |
| `Escape` | Switch to normal mode | `normal_mode` | | `Escape` | Switch to normal mode | `normal_mode` |
| `Ctrl-s` | Commit undo checkpoint | `commit_undo_checkpoint` | | `Ctrl-s` | Commit undo checkpoint | `commit_undo_checkpoint` |
| `Ctrl-x` | Autocomplete | `completion` | | `Ctrl-x` | Autocomplete | `completion` |
@ -382,9 +382,10 @@ end = "no_op"
## Select / extend mode ## Select / extend mode
Select mode echoes Normal mode, but changes any movements to extend selections Select mode echoes Normal mode, but changes any movements to extend
rather than replace them. Goto motions are also changed to extend, so that `vgl` selections rather than replace them. Goto motions are also changed to
for example extends the selection to the end of the line. extend, so that `vgl` for example extends the selection to the end of
the line.
Search is also affected. By default, `n` and `N` will remove the current Search is also affected. By default, `n` and `N` will remove the current
selection and select the next instance of the search term. Toggling this mode selection and select the next instance of the search term. Toggling this mode

@ -1,11 +1,10 @@
# Language Support # Language Support
The following languages and Language Servers are supported. To use Language The following languages and Language Servers are supported. To use
Server features, you must first [install][lsp-install-wiki] the appropriate Language Server features, you must first [install][lsp-install-wiki] the
Language Server. appropriate Language Server.
You can check the language support in your installed Helix version with You can check the language support in your installed helix version with `hx --health`.
`hx --health`.
Also see the [Language Configuration][lang-config] docs and the [Adding Also see the [Language Configuration][lang-config] docs and the [Adding
Languages][adding-languages] guide for more language configuration information. Languages][adding-languages] guide for more language configuration information.

@ -8,7 +8,7 @@ Language-specific settings and settings for language servers are configured in
There are three possible locations for a `languages.toml` file: There are three possible locations for a `languages.toml` file:
1. In the Helix source code, this lives in the 1. In the Helix source code, this lives in the
[Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml). [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml)
It provides the default configurations for languages and language servers. It provides the default configurations for languages and language servers.
2. In your [configuration directory](./configuration.md). This overrides values 2. In your [configuration directory](./configuration.md). This overrides values
@ -67,8 +67,8 @@ These configuration keys are available:
### File-type detection and the `file-types` key ### File-type detection and the `file-types` key
Helix determines which language configuration to use based on the `file-types` Helix determines which language configuration to use based on the `file-types` key
key from the above section. `file-types` is a list of strings or tables, for from the above section. `file-types` is a list of strings or tables, for
example: example:
```toml ```toml

@ -1,10 +1,10 @@
# Key Remapping # Key Remapping
Helix currently supports one-way key remapping through a simple TOML Helix currently supports one-way key remapping through a simple TOML configuration
configuration file. (More powerful solutions such as rebinding via commands will file. (More powerful solutions such as rebinding via commands will be
be available in the future). available in the future).
To remap keys, create a `config.toml` file in your `Helix` configuration To remap keys, create a `config.toml` file in your `helix` configuration
directory (default `~/.config/helix` on Linux systems) with a structure like directory (default `~/.config/helix` on Linux systems) with a structure like
this: this:
@ -30,8 +30,31 @@ j = { k = "normal_mode" } # Maps `jk` to exit insert mode
``` ```
> NOTE: Bindings can be nested, to create (or edit) minor modes: ## Minor modes
> `g = { a = "code_action"}` adds a new entry to the `goto` mode.
Minor modes are accessed by pressing a key (usually from normal mode), giving access to dedicated bindings. Bindings
can be modified or added by nesting definitions.
```toml
[keys.insert.j]
k = "normal_mode" # Maps `jk` to exit insert mode
[keys.normal.g]
a = "code_action" # Maps `ga` to show possible code actions
# invert `j` and `k` in view mode
[keys.normal.z]
j = "scroll_up"
k = "scroll_down"
# create a new minor mode bound to `+`
[keys.normal."+"]
m = ":run-shell-command make"
c = ":run-shell-command cargo build"
t = ":run-shell-command cargo test"
```
## Special keys and modifiers
Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes `C-`, Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes `C-`,
`S-` and `A-`. Special keys are encoded as follows: `S-` and `A-`. Special keys are encoded as follows:
@ -58,9 +81,6 @@ Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes `C-`,
Keys can be disabled by binding them to the `no_op` command. Keys can be disabled by binding them to the `no_op` command.
You can find a list of available commands at You can find a list of available commands in the [Keymap](https://docs.helix-editor.com/keymap.html) documentation.
[Keymap](https://docs.helix-editor.com/keymap.html)
> Commands can also be found in the source code at > Commands can also be found in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) at the invocation of `static_commands!` macro and the `TypableCommandList`.
> [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs)
> at the invocation of `static_commands!` macro and the `TypableCommandList`.

@ -1,8 +1,6 @@
# Themes # Themes
To use a theme, add `theme = "<name>"` to the top of your To use a theme add `theme = "<name>"` to the top of your [`config.toml`](./configuration.md) file, or select it during runtime using `:theme <name>`.
[`config.toml`](./configuration.md) file, or select it during runtime using
`:theme <name>`.
## Creating a Theme ## Creating a Theme
@ -11,13 +9,18 @@ To use a theme, add `theme = "<name>"` to the top of your
To create a theme file: To create a theme file:
1. Create a 'themes' folder in your user configuration folder (e.g. 1. Create a 'themes' folder in your user configuration folder (e.g.
`~/.config/helix/themes`) `~/.config/helix/themes`).
2. Create a file with the name of your theme as the file name (e.g. 2. Create a file with the name of your theme as the file name (e.g.
`mytheme.toml`) and place it in your `themes` folder. `mytheme.toml`) and place it in your `themes` folder.
> 💡 The names "default" and "base16_default" are reserved for built-in themes > 💡 The names "default" and "base16_default" are reserved for built-in themes
> and cannot be overridden by user-defined themes. > and cannot be overridden by user-defined themes.
### An overview of the Theme File Format
> 💡 The names "default" and "base16_default" are reserved for built-in themes
> and cannot be overridden by user-defined themes.
### An Overview of the Theme File Format ### An Overview of the Theme File Format
Each line in the theme file is specified as follows: Each line in the theme file is specified as follows:
@ -26,10 +29,7 @@ Each line in the theme file is specified as follows:
key = { fg = "#ffffff", bg = "#000000", underline = { color = "#ff0000", style = "curl"}, modifiers = ["bold", "italic"] } key = { fg = "#ffffff", bg = "#000000", underline = { color = "#ff0000", style = "curl"}, modifiers = ["bold", "italic"] }
``` ```
Where `key` represents what you want to style, `fg` specifies the foreground Where `key` represents what you want to style, `fg` specifies the foreground color, `bg` the background color, `underline` the underline `style`/`color`, and `modifiers` is a list of style modifiers. `bg`, `underline` and `modifiers` can be omitted to defer to the defaults.
color, `bg` the background color, `underline` the underline `style`/`color`, and
`modifiers` is a list of style modifiers. `bg`, `underline` and `modifiers` can
be omitted to defer to the defaults.
To specify only the foreground color: To specify only the foreground color:
@ -37,8 +37,7 @@ To specify only the foreground color:
key = "#ffffff" key = "#ffffff"
``` ```
If the key contains a dot `'.'`, it must be quoted to prevent it being parsed as If the key contains a dot `'.'`, it must be quoted to prevent it being parsed as a [dotted key](https://toml.io/en/v1.0.0#keys).
a [dotted key](https://toml.io/en/v1.0.0#keys).
```toml ```toml
"key.key" = "#ffffff" "key.key" = "#ffffff"
@ -62,9 +61,9 @@ cargo xtask themelint onedark # replace onedark with <name>
### Color palettes ### Color palettes
It's recommended to define a palette of named colors and refer to them in the It's recommended to define a palette of named colors, and refer to them in the
configuration values in your theme. To do this, add a table called `palette` to configuration values in your theme. To do this, add a table called
your theme file: `palette` to your theme file:
```toml ```toml
"ui.background" = "white" "ui.background" = "white"
@ -75,8 +74,8 @@ white = "#ffffff"
black = "#000000" black = "#000000"
``` ```
Keep in mind that the [palette] table includes all keys after its header, so it Keep in mind that the `[palette]` table includes all keys after its header,
should be defined after the normal theme options. so it should be defined after the normal theme options.
The default palette uses the terminal's default 16 colors, and the colors names The default palette uses the terminal's default 16 colors, and the colors names
are listed below. The `[palette]` section in the config file takes precedence are listed below. The `[palette]` section in the config file takes precedence
@ -103,11 +102,11 @@ over it and is merged into the default palette.
### Modifiers ### Modifiers
The following values can be used as modifiers, providing they are supported by The following values may be used as modifier, provided they are supported by
your terminal emulator. your terminal emulator.
| Modifier | | Modifier |
| ------------- | | --- |
| `bold` | | `bold` |
| `dim` | | `dim` |
| `italic` | | `italic` |
@ -118,14 +117,22 @@ your terminal emulator.
| `hidden` | | `hidden` |
| `crossed_out` | | `crossed_out` |
> 💡 The `underlined` modifier is deprecated and only available for backwards > 💡 The `underlined` modifier is deprecated and only available for backwards compatibility.
> compatibility. Its behavior is equivalent to setting `underline.style="line"`. > Its behavior is equivalent to setting `underline.style="line"`.
### Underline Style ### Underline Style
One of the following values can be used for `underline.style`, providing it is One of the following values may be used as a value for `underline.style`, providing it is
supported by your terminal emulator. supported by your terminal emulator.
| Modifier |
| --- |
| `line` |
| `curl` |
| `dashed` |
| `dotted` |
| `double_line` |
| Modifier | | Modifier |
| ------------- | | ------------- |
| `line` | | `line` |
@ -136,7 +143,7 @@ supported by your terminal emulator.
### Inheritance ### Inheritance
Extends other themes by setting the `inherits` property to an existing theme. Extend other themes by setting the `inherits` property to an existing theme.
```toml ```toml
inherits = "boo_berry" inherits = "boo_berry"
@ -158,9 +165,7 @@ The following is a list of scopes available to use for styling:
These keys match These keys match
[tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme). [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme).
When determining styling for a highlight, the longest matching theme key will be When determining styling for a highlight, the longest matching theme key will be used. For example, if the highlight is `function.builtin.static`, the key `function.builtin` will be used instead of `function`.
used. For example, if the highlight is `function.builtin.static,` the key
`function.builtin` will be used instead of function.
We use a similar set of scopes as We use a similar set of scopes as
[Sublime Text](https://www.sublimetext.com/docs/scope_naming.html). See also [Sublime Text](https://www.sublimetext.com/docs/scope_naming.html). See also
@ -174,10 +179,8 @@ We use a similar set of scopes as
- `variant` - `variant`
- `constructor` - `constructor`
- `constant` (TODO: constant.other.placeholder for `%v)` - `constant` (TODO: constant.other.placeholder for `%v`)
- `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
- `builtin` Special constants provided by the language (`true`, `false`, `nil`
etc.)
- `boolean` - `boolean`
- `character` - `character`
- `escape` - `escape`
@ -285,28 +288,33 @@ These scopes are used for theming the editor interface:
- `completion` - for completion doc popup UI - `completion` - for completion doc popup UI
- `hover` - for hover popup UI - `hover` - for hover popup UI
| Key | Notes | | Key | Notes |
| --------------------------- | ------------------------------------------------------------------------------------------------ | | --- | --- |
| `ui.background` | | | `ui.background` | |
| `ui.background.separator` | Picker separator below input line | | `ui.background.separator` | Picker separator below input line |
| `ui.cursor` | | | `ui.cursor` | |
| `ui.cursor.normal` | |
| `ui.cursor.insert` | | | `ui.cursor.insert` | |
| `ui.cursor.select` | | | `ui.cursor.select` | |
| `ui.cursor.match` | Matching bracket etc. | | `ui.cursor.match` | Matching bracket etc. |
| `ui.cursor.primary` | Cursor with primary selection | | `ui.cursor.primary` | Cursor with primary selection |
| `ui.cursor.primary.normal` | |
| `ui.cursor.primary.insert` | |
| `ui.cursor.primary.select` | |
| `ui.gutter` | Gutter | | `ui.gutter` | Gutter |
| `ui.gutter.selected` | Gutter for the line the cursor is on | | `ui.gutter.selected` | Gutter for the line the cursor is on |
| `ui.linenr` | Line numbers | | `ui.linenr` | Line numbers |
| `ui.linenr.selected` | Line number for the line the cursor is on | | `ui.linenr.selected` | Line number for the line the cursor is on |
| `ui.statusline` | `statusline` | | `ui.statusline` | Statusline |
| `ui.statusline.inactive` | `statusline` (unfocused document) | | `ui.statusline.inactive` | Statusline (unfocused document) |
| `ui.statusline.normal` | `statusline` mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.insert` | `statusline` mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.select` | `statusline` mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.separator` | Separator character in `statusline` | | `ui.statusline.separator` | Separator character in statusline |
| `ui.popup` | Documentation popups (e.g. Space + k) | | `ui.popup` | Documentation popups (e.g Space + k) |
| `ui.popup.info` | Prompt for multiple key options | | `ui.popup.info` | Prompt for multiple key options |
| `ui.window` | Borderlines separating splits | | `ui.window` | Border lines separating splits |
| `ui.help` | Description box for commands | | `ui.help` | Description box for commands |
| `ui.text` | Command prompts, popup text, etc. | | `ui.text` | Command prompts, popup text, etc. |
| `ui.text.focus` | | | `ui.text.focus` | |
@ -320,10 +328,10 @@ These scopes are used for theming the editor interface:
| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar | | `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
| `ui.selection` | For selections in the editing area | | `ui.selection` | For selections in the editing area |
| `ui.selection.primary` | | | `ui.selection.primary` | |
| `ui.cursorline.primary` | The line of the primary cursor ([if `cursorline` is enabled][editor-section]) | | `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
| `ui.cursorline.secondary` | The lines of any other cursors ([if `cursorline` is enabled][editor-section]) | | `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
| `ui.cursorcolumn.primary` | The column of the primary cursor ([if `cursorcolumn` is enabled][editor-section]) | | `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if `cursorcolumn` is enabled][editor-section]) | | `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
| `warning` | Diagnostics warning (gutter) | | `warning` | Diagnostics warning (gutter) |
| `error` | Diagnostics error (gutter) | | `error` | Diagnostics error (gutter) |
| `info` | Diagnostics info (gutter) | | `info` | Diagnostics info (gutter) |

@ -1,7 +1,6 @@
# Using Helix # Using Helix
<!--toc:start--> <!--toc:start-->
- [Using Helix](#using-helix) - [Using Helix](#using-helix)
- [Registers](#registers) - [Registers](#registers)
- [User-defined Registers](#user-defined-registers) - [User-defined Registers](#user-defined-registers)
@ -34,8 +33,7 @@ example:
- `"ay` - Yank the current selection to register `a`. - `"ay` - Yank the current selection to register `a`.
- `"op` - Paste the text in register `o` after the selection. - `"op` - Paste the text in register `o` after the selection.
If a register is selected before invoking a change or delete command, the If a register is selected before invoking a change or delete command, the selection will be stored in the register and the action will be carried out:
selection will be stored in the register and the action will be carried out:
- `"hc` - Store the selection in register `h` and then change it (delete and - `"hc` - Store the selection in register `h` and then change it (delete and
enter insert mode). enter insert mode).
@ -50,19 +48,15 @@ selection will be stored in the register and the action will be carried out:
| `"` | Last yanked text | | `"` | Last yanked text |
| `_` | Black hole | | `_` | Black hole |
The system clipboard is not directly supported by a built-in register. Instead, The system clipboard is not directly supported by a built-in register. Instead, special commands and keybindings are provided. Refer to the
special commands and keybindings are provided. Refer to the
[key map](keymap.md#space-mode) for more details. [key map](keymap.md#space-mode) for more details.
The black hole register is a no-op register, meaning that no data will be read The black hole register is a no-op register, meaning that no data will be read or written to it.
or written to it.
## Surround ## Surround
Helix includes built-in functionality similar to Helix includes built-in functionality similar to [vim-surround](https://github.com/tpope/vim-surround).
[vim-surround](https://github.com/tpope/vim-surround). The key mappings for this The keymappings have been inspired from [vim-sandwich](https://github.com/machakann/vim-sandwich):
functionality have been inspired by
[vim-sandwich](https://github.com/machakann/vim-sandwich).
![Surround demo](https://user-images.githubusercontent.com/23398472/122865801-97073180-d344-11eb-8142-8f43809982c6.gif) ![Surround demo](https://user-images.githubusercontent.com/23398472/122865801-97073180-d344-11eb-8142-8f43809982c6.gif)
@ -74,8 +68,7 @@ functionality have been inspired by
You can use counts to act on outer pairs. You can use counts to act on outer pairs.
Surround can also act on multiple selections. For example, to change every Surround can also act on multiple selections. For example, to change every occurrence of `(use)` to `[use]`:
occurrence of `(use)` to `[use]`:
1. `%` to select the whole file 1. `%` to select the whole file
2. `s` to split the selections on a search term 2. `s` to split the selections on a search term
@ -86,9 +79,9 @@ Multiple characters are currently not supported, but planned for future release.
## Moving the Primary Selection with Syntax-tree Motions ## Moving the Primary Selection with Syntax-tree Motions
`Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) allow you to `Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) allow you to move the primary
move the primary selection according to its location in the syntax tree. For selection according to its location in the syntax tree. For example, many languages have the
example, many languages have the following syntax for function calls: following syntax for function calls:
```js ```js
func(arg1, arg2, arg3); func(arg1, arg2, arg3);
@ -129,7 +122,10 @@ If you have a selection that wraps `arg1` (see the tree above), and you use
Alt-n, it will select the next sibling in the syntax tree: `arg2`. Alt-n, it will select the next sibling in the syntax tree: `arg2`.
```js ```js
func([arg1], arg2, arg3) > func(arg1, [arg2], arg3); // before
func([arg1], arg2, arg3)
// after
func(arg1, [arg2], arg3);
``` ```
Similarly, Alt-o will expand the selection to the parent node, in this case, the Similarly, Alt-o will expand the selection to the parent node, in this case, the
@ -173,24 +169,25 @@ function or block of code.
| `t` | Test | | `t` | Test |
| `g` | Change | | `g` | Change |
> 💡`f`, `c`, etc. need a tree-sitter grammar active for the current document > 💡 `f`, `c`, etc need a tree-sitter grammar active for the current
> and a special tree-sitter query file to work properly. [Only some document and a special tree-sitter query file to work properly. [Only
> grammars][lang-support] currently have the query file implemented. some grammars][lang-support] currently have the query file implemented.
> Contributions are welcome! Contributions are welcome!
## Navigating Using Tree-sitter Textobjects ## Navigating Using Tree-sitter Textobject
Navigating between functions, classes, parameters, and other elements is Navigating between functions, classes, parameters, and other elements is
possible using tree-sitter and Textobject queries. For example, to move to the possible using tree-sitter and Textobject queries. For
next function use `]f`, to move to previous class use `[c`, and so on. example to move to the next function use `]f`, to move to previous
class use `[c`, and so on.
![tree-sitter-nav-demo][tree-sitter-nav-demo] ![tree-sitter-nav-demo][tree-sitter-nav-demo]
For the full reference see the [unimpaired][unimpaired-keybinds] section of the For the full reference see the [unimpaired][unimpaired-keybinds] section of the key bind
key bind documentation. documentation.
> 💡 This feature relies on tree-sitter Textobjects and requires the > 💡 This feature relies on tree-sitter Textobjects
> corresponding query file to work properly. > and requires the corresponding query file to work properly.
[lang-support]: ./lang-support.md [lang-support]: ./lang-support.md
[unimpaired-keybinds]: ./keymap.md#unimpaired [unimpaired-keybinds]: ./keymap.md#unimpaired

@ -48,6 +48,18 @@
--searchresults-border-color: #888; --searchresults-border-color: #888;
--searchresults-li-bg: #252932; --searchresults-li-bg: #252932;
--search-mark-bg: #e3b171; --search-mark-bg: #e3b171;
--hljs-background: #191f26;
--hljs-color: #e6e1cf;
--hljs-quote: #5c6773;
--hljs-variable: #ff7733;
--hljs-type: #ffee99;
--hljs-title: #b8cc52;
--hljs-symbol: #ffb454;
--hljs-selector-tag: #ff7733;
--hljs-selector-tag: #36a3d9;
--hljs-selector-tag: #00568d;
--hljs-selector-tag: #91b362;
--hljs-selector-tag: #d96c75;
} }
.coal { .coal {
@ -88,6 +100,18 @@
--searchresults-border-color: #98a3ad; --searchresults-border-color: #98a3ad;
--searchresults-li-bg: #2b2b2f; --searchresults-li-bg: #2b2b2f;
--search-mark-bg: #355c7d; --search-mark-bg: #355c7d;
--hljs-background: #969896;
--hljs-color: #cc6666;
--hljs-quote: #de935f;
--hljs-variable: #f0c674;
--hljs-type: #b5bd68;
--hljs-title: #8abeb7;
--hljs-symbol: #81a2be;
--hljs-selector-tag: #b294bb;
--hljs-selector-tag: #1d1f21;
--hljs-selector-tag: #c5c8c6;
--hljs-selector-tag: #718c00;
--hljs-selector-tag: #c82829;
} }
.light { .light {
@ -128,6 +152,14 @@
--searchresults-border-color: #888; --searchresults-border-color: #888;
--searchresults-li-bg: #e4f2fe; --searchresults-li-bg: #e4f2fe;
--search-mark-bg: #a2cff5; --search-mark-bg: #a2cff5;
--hljs-background: #f6f7f6;
--hljs-color: #000;
--hljs-quote: #575757;
--hljs-variable: #d70025;
--hljs-type: #b21e00;
--hljs-title: #0030f2;
--hljs-symbol: #008200;
--hljs-selector-tag: #9d00ec;
} }
.navy { .navy {
@ -168,6 +200,19 @@
--searchresults-border-color: #5c5c68; --searchresults-border-color: #5c5c68;
--searchresults-li-bg: #242430; --searchresults-li-bg: #242430;
--search-mark-bg: #a2cff5; --search-mark-bg: #a2cff5;
--hljs-background: #969896;
--hljs-color: #cc6666;
--hljs-quote: #de935f;
--hljs-variable: #f0c674;
--hljs-type: #b5bd68;
--hljs-title: #8abeb7;
--hljs-symbol: #81a2be;
--hljs-selector-tag: #b294bb;
--hljs-selector-tag: #1d1f21;
--hljs-selector-tag: #c5c8c6;
--hljs-selector-tag: #718c00;
--hljs-selector-tag: #c82829;
} }
.rust { .rust {
@ -208,6 +253,14 @@
--searchresults-border-color: #888; --searchresults-border-color: #888;
--searchresults-li-bg: #dec2a2; --searchresults-li-bg: #dec2a2;
--search-mark-bg: #e69f67; --search-mark-bg: #e69f67;
--hljs-background: #f6f7f6;
--hljs-color: #000;
--hljs-quote: #575757;
--hljs-variable: #d70025;
--hljs-type: #b21e00;
--hljs-title: #0030f2;
--hljs-symbol: #008200;
--hljs-selector-tag: #9d00ec;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
@ -292,7 +345,15 @@
--searchresults-header-fg: #5f5f71; --searchresults-header-fg: #5f5f71;
--searchresults-border-color: #5c5c68; --searchresults-border-color: #5c5c68;
--searchresults-li-bg: #242430; --searchresults-li-bg: #242430;
--search-mark-bg: #a2cff5; --search-mark-bg: #acff5;
--hljs-background: #2f1e2e;
--hljs-color: #a39e9b;
--hljs-quote: #8d8687;
--hljs-variable: #ef6155;
--hljs-type: #f99b15;
--hljs-title: #fec418;
--hljs-symbol: #48b685;
--hljs-selector-tag: #815ba4;
} }
.colibri { .colibri {
@ -338,5 +399,13 @@
--searchresults-border-color: #5c5c68; --searchresults-border-color: #5c5c68;
--searchresults-li-bg: #242430; --searchresults-li-bg: #242430;
--search-mark-bg: #a2cff5; --search-mark-bg: #a2cff5;
--hljs-background: #TODO;
--hljs-color: #TODO;
--hljs-quote: #TODO;
--hljs-variable: #TODO;
--hljs-type: #TODO;
--hljs-title: #TODO;
--hljs-symbol: #TODO;
--hljs-selector-tag: #TODO;
*/ */
} }

@ -7,12 +7,12 @@ code.hljs {
padding:3px 5px padding:3px 5px
} }
.hljs { .hljs {
background:#2f1e2e; background: var(--hljs-background);
color:#a39e9b color: var(--hljs-color);
} }
.hljs-comment, .hljs-comment,
.hljs-quote { .hljs-quote {
color:#8d8687 color: var(--hljs-quote)
} }
.hljs-link, .hljs-link,
.hljs-meta, .hljs-meta,
@ -23,7 +23,7 @@ code.hljs {
.hljs-tag, .hljs-tag,
.hljs-template-variable, .hljs-template-variable,
.hljs-variable { .hljs-variable {
color:#ef6155 color: var(--hljs-variable)
} }
.hljs-built_in, .hljs-built_in,
.hljs-deletion, .hljs-deletion,
@ -31,22 +31,22 @@ code.hljs {
.hljs-number, .hljs-number,
.hljs-params, .hljs-params,
.hljs-type { .hljs-type {
color:#f99b15 color: var(--hljs-type)
} }
.hljs-attribute, .hljs-attribute,
.hljs-section, .hljs-section,
.hljs-title { .hljs-title {
color:#fec418 color: var(--hljs-title)
} }
.hljs-addition, .hljs-addition,
.hljs-bullet, .hljs-bullet,
.hljs-string, .hljs-string,
.hljs-symbol { .hljs-symbol {
color:#48b685 color: var(--hljs-symbol)
} }
.hljs-keyword, .hljs-keyword,
.hljs-selector-tag { .hljs-selector-tag {
color:#815ba4 color: var(--hljs-selector-tag)
} }
.hljs-emphasis { .hljs-emphasis {
font-style:italic font-style:italic

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.helix_editor.Helix</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MPL-2.0</project_license>
<name>Helix</name>
<summary>A post-modern text editor</summary>
<description>
<p>
Helix is a terminal-based text editor inspired by Kakoune / Neovim and written in Rust.
</p>
<ul>
<li>Vim-like modal editing</li>
<li>Multiple selections</li>
<li>Built-in language server support</li>
<li>Smart, incremental syntax highlighting and code editing via tree-sitter</li>
</ul>
</description>
<launchable type="desktop-id">Helix.desktop</launchable>
<screenshots>
<screenshot type="default">
<caption>Helix with default theme</caption>
<image>https://github.com/helix-editor/helix/raw/d4565b4404cabc522bd60822abd374755581d751/screenshot.png</image>
</screenshot>
</screenshots>
<url type="homepage">https://helix-editor.com/</url>
<url type="donation">https://opencollective.com/helix-editor</url>
<url type="help">https://docs.helix-editor.com/</url>
<url type="vcs-browser">https://github.com/helix-editor/helix</url>
<url type="bugtracker">https://github.com/helix-editor/helix/issues</url>
<content_rating type="oars-1.1" />
<releases>
<release version="22.12" date="2022-12-6">
<url>https://helix-editor.com/news/release-22-12-highlights/</url>
</release>
<release version="22.08" date="2022-8-31">
<url>https://helix-editor.com/news/release-22-08-highlights/</url>
</release>
<release version="22.05" date="2022-5-28">
<url>https://helix-editor.com/news/release-22-05-highlights/</url>
</release>
<release version="22.03" date="2022-3-28">
<url>https://helix-editor.com/news/release-22-03-highlights/</url>
</release>
</releases>
<requires>
<control>keyboard</control>
</requires>
<categories>
<category>Utility</category>
<category>TextEditor</category>
</categories>
<keywords>
<keyword>text</keyword>
<keyword>editor</keyword>
<keyword>development</keyword>
<keyword>programming</keyword>
</keywords>
<provides>
<binary>hx</binary>
<mediatype>text/english</mediatype>
<mediatype>text/plain</mediatype>
<mediatype>text/x-makefile</mediatype>
<mediatype>text/x-c++hdr</mediatype>
<mediatype>text/x-c++src</mediatype>
<mediatype>text/x-chdr</mediatype>
<mediatype>text/x-csrc</mediatype>
<mediatype>text/x-java</mediatype>
<mediatype>text/x-moc</mediatype>
<mediatype>text/x-pascal</mediatype>
<mediatype>text/x-tcl</mediatype>
<mediatype>text/x-tex</mediatype>
<mediatype>application/x-shellscript</mediatype>
<mediatype>text/x-c</mediatype>
<mediatype>text/x-c++</mediatype>
</provides>
</component>

@ -5,6 +5,7 @@ Helix releases are versioned in the Calendar Versioning scheme:
we'll use `<tag>` as a placeholder for the tag being published. we'll use `<tag>` as a placeholder for the tag being published.
* Merge the changelog PR * Merge the changelog PR
* Add new `<release>` entry in `contrib/Helix.appdata.xml` with release information according to the [AppStream spec](https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Releases.html)
* Tag and push * Tag and push
* `git tag -s -m "<tag>" -a <tag> && git push` * `git tag -s -m "<tag>" -a <tag> && git push`
* Make sure to switch to master and pull first * Make sure to switch to master and pull first

@ -31,12 +31,12 @@ arc-swap = "1"
regex = "1" regex = "1"
bitflags = "1.3" bitflags = "1.3"
ahash = "0.8.2" ahash = "0.8.2"
hashbrown = { version = "0.13.1", features = ["raw"] } hashbrown = { version = "0.13.2", features = ["raw"] }
log = "0.4" log = "0.4"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
toml = "0.5" toml = "0.6"
imara-diff = "0.1.0" imara-diff = "0.1.0"

@ -28,8 +28,8 @@ fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
let mut config_file = test_dir; let mut config_file = test_dir;
config_file.push("languages.toml"); config_file.push("languages.toml");
let config = std::fs::read(config_file).unwrap(); let config = std::fs::read_to_string(config_file).unwrap();
let config = toml::from_slice(&config).unwrap(); let config = toml::from_str(&config).unwrap();
let loader = Loader::new(config); let loader = Loader::new(config);
// set runtime path so we can find the queries // set runtime path so we can find the queries

@ -19,7 +19,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
thiserror = "1.0" thiserror = "1.0"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] }
which = "4.2" which = "4.4"
[dev-dependencies] [dev-dependencies]
fern = "0.6" fern = "0.6"

@ -16,7 +16,7 @@ path = "src/main.rs"
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
toml = "0.5" toml = "0.6"
etcetera = "0.4" etcetera = "0.4"
tree-sitter = "0.20" tree-sitter = "0.20"
once_cell = "1.17" once_cell = "1.17"

@ -1,6 +1,9 @@
use std::str::from_utf8;
/// Default built-in languages.toml. /// Default built-in languages.toml.
pub fn default_lang_config() -> toml::Value { pub fn default_lang_config() -> toml::Value {
toml::from_slice(include_bytes!("../../languages.toml")) let default_config = include_bytes!("../../languages.toml");
toml::from_str(from_utf8(default_config).unwrap())
.expect("Could not parse built-in languages.toml to valid toml") .expect("Could not parse built-in languages.toml to valid toml")
} }
@ -11,8 +14,8 @@ pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
.chain([crate::config_dir()].into_iter()) .chain([crate::config_dir()].into_iter())
.map(|path| path.join("languages.toml")) .map(|path| path.join("languages.toml"))
.filter_map(|file| { .filter_map(|file| {
std::fs::read(&file) std::fs::read_to_string(file)
.map(|config| toml::from_slice(&config)) .map(|config| toml::from_str(&config))
.ok() .ok()
}) })
.collect::<Result<Vec<_>, _>>()? .collect::<Result<Vec<_>, _>>()?

@ -515,5 +515,5 @@ pub fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::
.join("queries") .join("queries")
.join(language) .join(language)
.join(filename); .join(filename);
std::fs::read_to_string(&path) std::fs::read_to_string(path)
} }

@ -179,6 +179,8 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi
#[cfg(test)] #[cfg(test)]
mod merge_toml_tests { mod merge_toml_tests {
use std::str;
use super::merge_toml_values; use super::merge_toml_values;
use toml::Value; use toml::Value;
@ -191,8 +193,9 @@ mod merge_toml_tests {
indent = { tab-width = 4, unit = " ", test = "aaa" } indent = { tab-width = 4, unit = " ", test = "aaa" }
"#; "#;
let base: Value = toml::from_slice(include_bytes!("../../languages.toml")) let base = include_bytes!("../../languages.toml");
.expect("Couldn't parse built-in languages config"); let base = str::from_utf8(base).expect("Couldn't parse built-in languages config");
let base: Value = toml::from_str(base).expect("Couldn't parse built-in languages config");
let user: Value = toml::from_str(USER).unwrap(); let user: Value = toml::from_str(USER).unwrap();
let merged = merge_toml_values(base, user, 3); let merged = merge_toml_values(base, user, 3);
@ -224,8 +227,9 @@ mod merge_toml_tests {
language-server = { command = "deno", args = ["lsp"] } language-server = { command = "deno", args = ["lsp"] }
"#; "#;
let base: Value = toml::from_slice(include_bytes!("../../languages.toml")) let base = include_bytes!("../../languages.toml");
.expect("Couldn't parse built-in languages config"); let base = str::from_utf8(base).expect("Couldn't parse built-in languages config");
let base: Value = toml::from_str(base).expect("Couldn't parse built-in languages config");
let user: Value = toml::from_str(USER).unwrap(); let user: Value = toml::from_str(USER).unwrap();
let merged = merge_toml_values(base, user, 3); let merged = merge_toml_values(base, user, 3);

@ -25,4 +25,4 @@ serde_json = "1.0"
thiserror = "1.0" thiserror = "1.0"
tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] } tokio = { version = "1.24", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
tokio-stream = "0.1.11" tokio-stream = "0.1.11"
which = "4.2" which = "4.4"

@ -37,7 +37,7 @@ helix-loader = { version = "0.6", path = "../helix-loader" }
anyhow = "1" anyhow = "1"
once_cell = "1.17" once_cell = "1.17"
which = "4.2" which = "4.4"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
@ -61,7 +61,7 @@ pulldown-cmark = { version = "0.9", default-features = false }
content_inspector = "0.2.4" content_inspector = "0.2.4"
# config # config
toml = "0.5" toml = "0.6"
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

@ -5,7 +5,7 @@ pub(crate) mod typed;
pub use dap::*; pub use dap::*;
use helix_vcs::Hunk; use helix_vcs::Hunk;
pub use lsp::*; pub use lsp::*;
use tui::text::Spans; use tui::widgets::Row;
pub use typed::*; pub use typed::*;
use helix_core::{ use helix_core::{
@ -26,7 +26,6 @@ use helix_core::{
SmallVec, Tendril, Transaction, SmallVec, Tendril, Transaction,
}; };
use helix_view::{ use helix_view::{
apply_transaction,
clipboard::ClipboardType, clipboard::ClipboardType,
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME}, document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
editor::{Action, Motion}, editor::{Action, Motion},
@ -384,6 +383,7 @@ impl MappableCommand {
swap_view_down, "Swap with split below", swap_view_down, "Swap with split below",
transpose_view, "Transpose splits", transpose_view, "Transpose splits",
rotate_view, "Goto next window", rotate_view, "Goto next window",
rotate_view_reverse, "Goto previous window",
hsplit, "Horizontal bottom split", hsplit, "Horizontal bottom split",
hsplit_new, "Horizontal bottom split scratch buffer", hsplit_new, "Horizontal bottom split scratch buffer",
vsplit, "Vertical right split", vsplit, "Vertical right split",
@ -448,9 +448,16 @@ impl MappableCommand {
impl fmt::Debug for MappableCommand { impl fmt::Debug for MappableCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("MappableCommand") match self {
.field(&self.name()) MappableCommand::Static { name, .. } => {
.finish() f.debug_tuple("MappableCommand").field(name).finish()
}
MappableCommand::Typable { name, args, .. } => f
.debug_tuple("MappableCommand")
.field(name)
.field(args)
.finish(),
}
} }
} }
@ -505,12 +512,16 @@ impl PartialEq for MappableCommand {
match (self, other) { match (self, other) {
( (
MappableCommand::Typable { MappableCommand::Typable {
name: first_name, .. name: first_name,
args: first_args,
..
}, },
MappableCommand::Typable { MappableCommand::Typable {
name: second_name, .. name: second_name,
args: second_args,
..
}, },
) => first_name == second_name, ) => first_name == second_name && first_args == second_args,
( (
MappableCommand::Static { MappableCommand::Static {
name: first_name, .. name: first_name, ..
@ -863,7 +874,7 @@ fn align_selections(cx: &mut Context) {
changes.sort_unstable_by_key(|(from, _, _)| *from); changes.sort_unstable_by_key(|(from, _, _)| *from);
let transaction = Transaction::change(doc.text(), changes.into_iter()); let transaction = Transaction::change(doc.text(), changes.into_iter());
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
fn goto_window(cx: &mut Context, align: Align) { fn goto_window(cx: &mut Context, align: Align) {
@ -1315,7 +1326,7 @@ fn replace(cx: &mut Context) {
} }
}); });
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
exit_select_mode(cx); exit_select_mode(cx);
} }
}) })
@ -1333,7 +1344,7 @@ where
(range.from(), range.to(), Some(text)) (range.from(), range.to(), Some(text))
}); });
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
fn switch_case(cx: &mut Context) { fn switch_case(cx: &mut Context) {
@ -1863,7 +1874,7 @@ fn global_search(cx: &mut Context) {
impl ui::menu::Item for FileResult { impl ui::menu::Item for FileResult {
type Data = Option<PathBuf>; type Data = Option<PathBuf>;
fn label(&self, current_path: &Self::Data) -> Spans { fn format(&self, current_path: &Self::Data) -> Row {
let relative_path = helix_core::path::get_relative_path(&self.path) let relative_path = helix_core::path::get_relative_path(&self.path)
.to_string_lossy() .to_string_lossy()
.into_owned(); .into_owned();
@ -2003,6 +2014,10 @@ fn global_search(cx: &mut Context) {
let line_num = *line_num; let line_num = *line_num;
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
let text = doc.text(); let text = doc.text();
if line_num >= text.len_lines() {
cx.editor.set_error("The line you jumped to does not exist anymore because the file has changed.");
return;
}
let start = text.line_to_char(line_num); let start = text.line_to_char(line_num);
let end = text.line_to_char((line_num + 1).min(text.len_lines())); let end = text.line_to_char((line_num + 1).min(text.len_lines()));
@ -2158,7 +2173,7 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
(range.from(), range.to(), None) (range.from(), range.to(), None)
}); });
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
match op { match op {
Operation::Delete => { Operation::Delete => {
@ -2176,7 +2191,7 @@ fn delete_selection_insert_mode(doc: &mut Document, view: &mut View, selection:
let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
(range.from(), range.to(), None) (range.from(), range.to(), None)
}); });
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
fn delete_selection(cx: &mut Context) { fn delete_selection(cx: &mut Context) {
@ -2272,7 +2287,7 @@ fn append_mode(cx: &mut Context) {
doc.text(), doc.text(),
[(end, end, Some(doc.line_ending.as_str().into()))].into_iter(), [(end, end, Some(doc.line_ending.as_str().into()))].into_iter(),
); );
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
let selection = doc.selection(view.id).clone().transform(|range| { let selection = doc.selection(view.id).clone().transform(|range| {
@ -2311,7 +2326,7 @@ fn buffer_picker(cx: &mut Context) {
impl ui::menu::Item for BufferMeta { impl ui::menu::Item for BufferMeta {
type Data = (); type Data = ();
fn label(&self, _data: &Self::Data) -> Spans { fn format(&self, _data: &Self::Data) -> Row {
let path = self let path = self
.path .path
.as_deref() .as_deref()
@ -2380,7 +2395,7 @@ fn jumplist_picker(cx: &mut Context) {
impl ui::menu::Item for JumpMeta { impl ui::menu::Item for JumpMeta {
type Data = (); type Data = ();
fn label(&self, _data: &Self::Data) -> Spans { fn format(&self, _data: &Self::Data) -> Row {
let path = self let path = self
.path .path
.as_deref() .as_deref()
@ -2453,7 +2468,7 @@ fn jumplist_picker(cx: &mut Context) {
impl ui::menu::Item for MappableCommand { impl ui::menu::Item for MappableCommand {
type Data = ReverseKeymap; type Data = ReverseKeymap;
fn label(&self, keymap: &Self::Data) -> Spans { fn format(&self, keymap: &Self::Data) -> Row {
let fmt_binding = |bindings: &Vec<Vec<KeyEvent>>| -> String { let fmt_binding = |bindings: &Vec<Vec<KeyEvent>>| -> String {
bindings.iter().fold(String::new(), |mut acc, bind| { bindings.iter().fold(String::new(), |mut acc, bind| {
if !acc.is_empty() { if !acc.is_empty() {
@ -2582,7 +2597,7 @@ async fn make_format_callback(
if let Ok(format) = format { if let Ok(format) = format {
if doc.version() == doc_version { if doc.version() == doc_version {
apply_transaction(&format, doc, view); doc.apply(&format, view.id);
doc.append_changes_to_history(view); doc.append_changes_to_history(view);
doc.detect_indent_and_line_ending(); doc.detect_indent_and_line_ending();
view.ensure_cursor_in_view(doc, scrolloff); view.ensure_cursor_in_view(doc, scrolloff);
@ -2675,7 +2690,7 @@ fn open(cx: &mut Context, open: Open) {
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index())); transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
// o inserts a new line after each line with a selection // o inserts a new line after each line with a selection
@ -3103,7 +3118,7 @@ pub mod insert {
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
if let Some(t) = transaction { if let Some(t) = transaction {
apply_transaction(&t, doc, view); doc.apply(&t, view.id);
} }
// TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc) // TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc)
@ -3125,7 +3140,7 @@ pub mod insert {
&doc.selection(view.id).clone().cursors(doc.text().slice(..)), &doc.selection(view.id).clone().cursors(doc.text().slice(..)),
indent, indent,
); );
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
pub fn insert_newline(cx: &mut Context) { pub fn insert_newline(cx: &mut Context) {
@ -3230,7 +3245,7 @@ pub mod insert {
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index())); transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
pub fn delete_char_backward(cx: &mut Context) { pub fn delete_char_backward(cx: &mut Context) {
@ -3325,7 +3340,7 @@ pub mod insert {
} }
}); });
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic); lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
} }
@ -3343,7 +3358,7 @@ pub mod insert {
None, None,
) )
}); });
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic); lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
} }
@ -3624,7 +3639,7 @@ fn paste_impl(
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index())); transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
} }
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) { pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
@ -3716,7 +3731,7 @@ fn replace_with_yanked(cx: &mut Context) {
} }
}); });
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
exit_select_mode(cx); exit_select_mode(cx);
} }
} }
@ -3740,7 +3755,7 @@ fn replace_selections_with_clipboard_impl(
) )
}); });
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
doc.append_changes_to_history(view); doc.append_changes_to_history(view);
} }
Err(e) => return Err(e.context("Couldn't get system clipboard contents")), Err(e) => return Err(e.context("Couldn't get system clipboard contents")),
@ -3812,7 +3827,7 @@ fn indent(cx: &mut Context) {
Some((pos, pos, Some(indent.clone()))) Some((pos, pos, Some(indent.clone())))
}), }),
); );
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
fn unindent(cx: &mut Context) { fn unindent(cx: &mut Context) {
@ -3851,7 +3866,7 @@ fn unindent(cx: &mut Context) {
let transaction = Transaction::change(doc.text(), changes.into_iter()); let transaction = Transaction::change(doc.text(), changes.into_iter());
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
fn format_selections(cx: &mut Context) { fn format_selections(cx: &mut Context) {
@ -3906,7 +3921,7 @@ fn format_selections(cx: &mut Context) {
language_server.offset_encoding(), language_server.offset_encoding(),
); );
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
fn join_selections_impl(cx: &mut Context, select_space: bool) { fn join_selections_impl(cx: &mut Context, select_space: bool) {
@ -3938,6 +3953,11 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
} }
} }
// nothing to do, bail out early to avoid crashes later
if changes.is_empty() {
return;
}
changes.sort_unstable_by_key(|(from, _to, _text)| *from); changes.sort_unstable_by_key(|(from, _to, _text)| *from);
changes.dedup(); changes.dedup();
@ -3960,7 +3980,7 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
Transaction::change(doc.text(), changes.into_iter()) Transaction::change(doc.text(), changes.into_iter())
}; };
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) { fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
@ -4103,7 +4123,7 @@ fn toggle_comments(cx: &mut Context) {
.map(|tc| tc.as_ref()); .map(|tc| tc.as_ref());
let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view.id), token); let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view.id), token);
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
exit_select_mode(cx); exit_select_mode(cx);
} }
@ -4159,7 +4179,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
.map(|(range, fragment)| (range.from(), range.to(), Some(fragment))), .map(|(range, fragment)| (range.from(), range.to(), Some(fragment))),
); );
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
fn rotate_selection_contents_forward(cx: &mut Context) { fn rotate_selection_contents_forward(cx: &mut Context) {
@ -4317,6 +4337,10 @@ fn rotate_view(cx: &mut Context) {
cx.editor.focus_next() cx.editor.focus_next()
} }
fn rotate_view_reverse(cx: &mut Context) {
cx.editor.focus_prev()
}
fn jump_view_right(cx: &mut Context) { fn jump_view_right(cx: &mut Context) {
cx.editor.focus_direction(tree::Direction::Right) cx.editor.focus_direction(tree::Direction::Right)
} }
@ -4688,7 +4712,7 @@ fn surround_add(cx: &mut Context) {
let transaction = Transaction::change(doc.text(), changes.into_iter()) let transaction = Transaction::change(doc.text(), changes.into_iter())
.with_selection(Selection::new(ranges, selection.primary_index())); .with_selection(Selection::new(ranges, selection.primary_index()));
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
exit_select_mode(cx); exit_select_mode(cx);
}) })
} }
@ -4728,7 +4752,7 @@ fn surround_replace(cx: &mut Context) {
(pos, pos + 1, Some(t)) (pos, pos + 1, Some(t))
}), }),
); );
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
exit_select_mode(cx); exit_select_mode(cx);
}); });
}) })
@ -4756,7 +4780,7 @@ fn surround_delete(cx: &mut Context) {
let transaction = let transaction =
Transaction::change(doc.text(), change_pos.into_iter().map(|p| (p, p + 1, None))); Transaction::change(doc.text(), change_pos.into_iter().map(|p| (p, p + 1, None)));
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
exit_select_mode(cx); exit_select_mode(cx);
}) })
} }
@ -4971,7 +4995,7 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
if behavior != &ShellBehavior::Ignore { if behavior != &ShellBehavior::Ignore {
let transaction = Transaction::change(doc.text(), changes.into_iter()) let transaction = Transaction::change(doc.text(), changes.into_iter())
.with_selection(Selection::new(ranges, selection.primary_index())); .with_selection(Selection::new(ranges, selection.primary_index()));
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
doc.append_changes_to_history(view); doc.append_changes_to_history(view);
} }
@ -5034,7 +5058,7 @@ fn add_newline_impl(cx: &mut Context, open: Open) {
}); });
let transaction = Transaction::change(text, changes); let transaction = Transaction::change(text, changes);
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
enum IncrementDirection { enum IncrementDirection {
@ -5101,7 +5125,7 @@ fn increment_impl(cx: &mut Context, increment_direction: IncrementDirection) {
let new_selection = Selection::new(new_selection_ranges, selection.primary_index()); let new_selection = Selection::new(new_selection_ranges, selection.primary_index());
let transaction = Transaction::change(doc.text(), changes.into_iter()); let transaction = Transaction::change(doc.text(), changes.into_iter());
let transaction = transaction.with_selection(new_selection); let transaction = transaction.with_selection(new_selection);
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
} }

@ -12,7 +12,7 @@ use helix_view::editor::Breakpoint;
use serde_json::{to_value, Value}; use serde_json::{to_value, Value};
use tokio_stream::wrappers::UnboundedReceiverStream; use tokio_stream::wrappers::UnboundedReceiverStream;
use tui::text::Spans; use tui::{text::Spans, widgets::Row};
use std::collections::HashMap; use std::collections::HashMap;
use std::future::Future; use std::future::Future;
@ -25,7 +25,7 @@ use helix_view::handlers::dap::{breakpoints_changed, jump_to_stack_frame, select
impl ui::menu::Item for StackFrame { impl ui::menu::Item for StackFrame {
type Data = (); type Data = ();
fn label(&self, _data: &Self::Data) -> Spans { fn format(&self, _data: &Self::Data) -> Row {
self.name.as_str().into() // TODO: include thread_states in the label self.name.as_str().into() // TODO: include thread_states in the label
} }
} }
@ -33,7 +33,7 @@ impl ui::menu::Item for StackFrame {
impl ui::menu::Item for DebugTemplate { impl ui::menu::Item for DebugTemplate {
type Data = (); type Data = ();
fn label(&self, _data: &Self::Data) -> Spans { fn format(&self, _data: &Self::Data) -> Row {
self.name.as_str().into() self.name.as_str().into()
} }
} }
@ -41,7 +41,7 @@ impl ui::menu::Item for DebugTemplate {
impl ui::menu::Item for Thread { impl ui::menu::Item for Thread {
type Data = ThreadStates; type Data = ThreadStates;
fn label(&self, thread_states: &Self::Data) -> Spans { fn format(&self, thread_states: &Self::Data) -> Row {
format!( format!(
"{} ({})", "{} ({})",
self.name, self.name,

@ -5,12 +5,15 @@ use helix_lsp::{
util::{diagnostic_to_lsp_diagnostic, lsp_pos_to_pos, lsp_range_to_range, range_to_lsp_range}, util::{diagnostic_to_lsp_diagnostic, lsp_pos_to_pos, lsp_range_to_range, range_to_lsp_range},
OffsetEncoding, OffsetEncoding,
}; };
use tui::text::{Span, Spans}; use tui::{
text::{Span, Spans},
widgets::Row,
};
use super::{align_view, push_jump, Align, Context, Editor, Open}; use super::{align_view, push_jump, Align, Context, Editor, Open};
use helix_core::{path, Selection}; use helix_core::{path, Selection};
use helix_view::{apply_transaction, document::Mode, editor::Action, theme::Style}; use helix_view::{document::Mode, editor::Action, theme::Style};
use crate::{ use crate::{
compositor::{self, Compositor}, compositor::{self, Compositor},
@ -46,7 +49,7 @@ impl ui::menu::Item for lsp::Location {
/// Current working directory. /// Current working directory.
type Data = PathBuf; type Data = PathBuf;
fn label(&self, cwdir: &Self::Data) -> Spans { fn format(&self, cwdir: &Self::Data) -> Row {
// The preallocation here will overallocate a few characters since it will account for the // The preallocation here will overallocate a few characters since it will account for the
// URL's scheme, which is not used most of the time since that scheme will be "file://". // URL's scheme, which is not used most of the time since that scheme will be "file://".
// Those extra chars will be used to avoid allocating when writing the line number (in the // Those extra chars will be used to avoid allocating when writing the line number (in the
@ -80,7 +83,7 @@ impl ui::menu::Item for lsp::SymbolInformation {
/// Path to currently focussed document /// Path to currently focussed document
type Data = Option<lsp::Url>; type Data = Option<lsp::Url>;
fn label(&self, current_doc_path: &Self::Data) -> Spans { fn format(&self, current_doc_path: &Self::Data) -> Row {
if current_doc_path.as_ref() == Some(&self.location.uri) { if current_doc_path.as_ref() == Some(&self.location.uri) {
self.name.as_str().into() self.name.as_str().into()
} else { } else {
@ -110,7 +113,7 @@ struct PickerDiagnostic {
impl ui::menu::Item for PickerDiagnostic { impl ui::menu::Item for PickerDiagnostic {
type Data = (DiagnosticStyles, DiagnosticsFormat); type Data = (DiagnosticStyles, DiagnosticsFormat);
fn label(&self, (styles, format): &Self::Data) -> Spans { fn format(&self, (styles, format): &Self::Data) -> Row {
let mut style = self let mut style = self
.diag .diag
.severity .severity
@ -149,6 +152,7 @@ impl ui::menu::Item for PickerDiagnostic {
Span::styled(&self.diag.message, style), Span::styled(&self.diag.message, style),
Span::styled(code, style), Span::styled(code, style),
]) ])
.into()
} }
} }
@ -467,7 +471,7 @@ pub fn workspace_diagnostics_picker(cx: &mut Context) {
impl ui::menu::Item for lsp::CodeActionOrCommand { impl ui::menu::Item for lsp::CodeActionOrCommand {
type Data = (); type Data = ();
fn label(&self, _data: &Self::Data) -> Spans { fn format(&self, _data: &Self::Data) -> Row {
match self { match self {
lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str().into(), lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str().into(),
lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(), lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
@ -662,7 +666,7 @@ pub fn code_action(cx: &mut Context) {
impl ui::menu::Item for lsp::Command { impl ui::menu::Item for lsp::Command {
type Data = (); type Data = ();
fn label(&self, _data: &Self::Data) -> Spans { fn format(&self, _data: &Self::Data) -> Row {
self.title.as_str().into() self.title.as_str().into()
} }
} }
@ -796,7 +800,7 @@ pub fn apply_workspace_edit(
offset_encoding, offset_encoding,
); );
let view = view_mut!(editor, view_id); let view = view_mut!(editor, view_id);
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
doc.append_changes_to_history(view); doc.append_changes_to_history(view);
}; };

@ -4,10 +4,7 @@ use crate::job::Job;
use super::*; use super::*;
use helix_view::{ use helix_view::editor::{Action, CloseError, ConfigEvent};
apply_transaction,
editor::{Action, CloseError, ConfigEvent},
};
use ui::completers::{self, Completer}; use ui::completers::{self, Completer};
#[derive(Clone)] #[derive(Clone)]
@ -480,7 +477,7 @@ fn set_line_ending(
} }
}), }),
); );
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
doc.append_changes_to_history(view); doc.append_changes_to_history(view);
Ok(()) Ok(())
@ -925,7 +922,7 @@ fn replace_selections_with_clipboard_impl(
(range.from(), range.to(), Some(contents.as_str().into())) (range.from(), range.to(), Some(contents.as_str().into()))
}); });
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
doc.append_changes_to_history(view); doc.append_changes_to_history(view);
Ok(()) Ok(())
} }
@ -1596,7 +1593,7 @@ fn sort_impl(
.map(|(s, fragment)| (s.from(), s.to(), Some(fragment))), .map(|(s, fragment)| (s.from(), s.to(), Some(fragment))),
); );
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
doc.append_changes_to_history(view); doc.append_changes_to_history(view);
Ok(()) Ok(())
@ -1640,7 +1637,7 @@ fn reflow(
(range.from(), range.to(), Some(reflowed_text)) (range.from(), range.to(), Some(reflowed_text))
}); });
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
doc.append_changes_to_history(view); doc.append_changes_to_history(view);
view.ensure_cursor_in_view(doc, scrolloff); view.ensure_cursor_in_view(doc, scrolloff);

@ -184,7 +184,7 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
S: serde::de::SeqAccess<'de>, S: serde::de::SeqAccess<'de>,
{ {
let mut commands = Vec::new(); let mut commands = Vec::new();
while let Some(command) = seq.next_element::<&str>()? { while let Some(command) = seq.next_element::<String>()? {
commands.push( commands.push(
command command
.parse::<MappableCommand>() .parse::<MappableCommand>()
@ -600,4 +600,43 @@ mod tests {
"Mismatch" "Mismatch"
) )
} }
#[test]
fn escaped_keymap() {
use crate::commands::MappableCommand;
use helix_view::input::{KeyCode, KeyEvent, KeyModifiers};
let keys = r#"
"+" = [
"select_all",
":pipe sed -E 's/\\s+$//g'",
]
"#;
let key = KeyEvent {
code: KeyCode::Char('+'),
modifiers: KeyModifiers::NONE,
};
let expectation = Keymap::new(KeyTrie::Node(KeyTrieNode::new(
"",
hashmap! {
key => KeyTrie::Sequence(vec!{
MappableCommand::select_all,
MappableCommand::Typable {
name: "pipe".to_string(),
args: vec!{
"sed".to_string(),
"-E".to_string(),
"'s/\\s+$//g'".to_string()
},
doc: "".to_string(),
},
})
},
vec![key],
)));
assert_eq!(toml::from_str(keys), Ok(expectation));
}
} }

@ -1,7 +1,6 @@
use crate::compositor::{Component, Context, Event, EventResult}; use crate::compositor::{Component, Context, Event, EventResult};
use helix_view::{apply_transaction, editor::CompleteAction, ViewId}; use helix_view::{editor::CompleteAction, ViewId};
use tui::buffer::Buffer as Surface; use tui::buffer::Buffer as Surface;
use tui::text::Spans;
use std::borrow::Cow; use std::borrow::Cow;
@ -33,11 +32,7 @@ impl menu::Item for CompletionItem {
.into() .into()
} }
fn label(&self, _data: &Self::Data) -> Spans { fn format(&self, _data: &Self::Data) -> menu::Row {
self.label.as_str().into()
}
fn row(&self, _data: &Self::Data) -> menu::Row {
menu::Row::new(vec![ menu::Row::new(vec![
menu::Cell::from(self.label.as_str()), menu::Cell::from(self.label.as_str()),
menu::Cell::from(match self.kind { menu::Cell::from(match self.kind {
@ -188,7 +183,7 @@ impl Completion {
// initialize a savepoint // initialize a savepoint
doc.savepoint(); doc.savepoint();
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
editor.last_completion = Some(CompleteAction { editor.last_completion = Some(CompleteAction {
trigger_offset, trigger_offset,
@ -208,7 +203,7 @@ impl Completion {
trigger_offset, trigger_offset,
); );
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
editor.last_completion = Some(CompleteAction { editor.last_completion = Some(CompleteAction {
trigger_offset, trigger_offset,
@ -238,7 +233,7 @@ impl Completion {
additional_edits.clone(), additional_edits.clone(),
offset_encoding, // TODO: should probably transcode in Client offset_encoding, // TODO: should probably transcode in Client
); );
apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
} }
} }

@ -17,7 +17,6 @@ use helix_core::{
visual_coords_at_pos, LineEnding, Position, Range, Selection, Transaction, visual_coords_at_pos, LineEnding, Position, Range, Selection, Transaction,
}; };
use helix_view::{ use helix_view::{
apply_transaction,
document::{Mode, SCRATCH_BUFFER_NAME}, document::{Mode, SCRATCH_BUFFER_NAME},
editor::{CompleteAction, CursorShapeConfig}, editor::{CompleteAction, CursorShapeConfig},
graphics::{Color, CursorKind, Modifier, Rect, Style}, graphics::{Color, CursorKind, Modifier, Rect, Style},
@ -342,23 +341,29 @@ impl EditorView {
let selection_scope = theme let selection_scope = theme
.find_scope_index("ui.selection") .find_scope_index("ui.selection")
.expect("could not find `ui.selection` scope in the theme!"); .expect("could not find `ui.selection` scope in the theme!");
let primary_selection_scope = theme
.find_scope_index("ui.selection.primary")
.unwrap_or(selection_scope);
let base_cursor_scope = theme let base_cursor_scope = theme
.find_scope_index("ui.cursor") .find_scope_index("ui.cursor")
.unwrap_or(selection_scope); .unwrap_or(selection_scope);
let base_primary_cursor_scope = theme
.find_scope_index("ui.cursor.primary")
.unwrap_or(base_cursor_scope);
let cursor_scope = match mode { let cursor_scope = match mode {
Mode::Insert => theme.find_scope_index("ui.cursor.insert"), Mode::Insert => theme.find_scope_index("ui.cursor.insert"),
Mode::Select => theme.find_scope_index("ui.cursor.select"), Mode::Select => theme.find_scope_index("ui.cursor.select"),
Mode::Normal => Some(base_cursor_scope), Mode::Normal => theme.find_scope_index("ui.cursor.normal"),
} }
.unwrap_or(base_cursor_scope); .unwrap_or(base_cursor_scope);
let primary_cursor_scope = theme let primary_cursor_scope = match mode {
.find_scope_index("ui.cursor.primary") Mode::Insert => theme.find_scope_index("ui.cursor.primary.insert"),
.unwrap_or(cursor_scope); Mode::Select => theme.find_scope_index("ui.cursor.primary.select"),
let primary_selection_scope = theme Mode::Normal => theme.find_scope_index("ui.cursor.primary.normal"),
.find_scope_index("ui.selection.primary") }
.unwrap_or(selection_scope); .unwrap_or(base_primary_cursor_scope);
let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new(); let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
for (i, range) in selection.iter().enumerate() { for (i, range) in selection.iter().enumerate() {
@ -386,7 +391,14 @@ impl EditorView {
if range.head > range.anchor { if range.head > range.anchor {
// Standard case. // Standard case.
let cursor_start = prev_grapheme_boundary(text, range.head); let cursor_start = prev_grapheme_boundary(text, range.head);
spans.push((selection_scope, range.anchor..cursor_start)); // non block cursors look like they exclude the cursor
let selection_end =
if selection_is_primary && !cursor_is_block && mode != Mode::Insert {
range.head
} else {
cursor_start
};
spans.push((selection_scope, range.anchor..selection_end));
if !selection_is_primary || cursor_is_block { if !selection_is_primary || cursor_is_block {
spans.push((cursor_scope, cursor_start..range.head)); spans.push((cursor_scope, cursor_start..range.head));
} }
@ -396,7 +408,16 @@ impl EditorView {
if !selection_is_primary || cursor_is_block { if !selection_is_primary || cursor_is_block {
spans.push((cursor_scope, range.head..cursor_end)); spans.push((cursor_scope, range.head..cursor_end));
} }
spans.push((selection_scope, cursor_end..range.anchor)); // non block cursors look like they exclude the cursor
let selection_start = if selection_is_primary
&& !cursor_is_block
&& !(mode == Mode::Insert && cursor_end == range.anchor)
{
range.head
} else {
cursor_end
};
spans.push((selection_scope, selection_start..range.anchor));
} }
} }
@ -1026,7 +1047,7 @@ impl EditorView {
(shift_position(start), shift_position(end), t) (shift_position(start), shift_position(end), t)
}), }),
); );
apply_transaction(&tx, doc, view); doc.apply(&tx, view.id);
} }
InsertEvent::TriggerCompletion => { InsertEvent::TriggerCompletion => {
let (_, doc) = current!(cxt.editor); let (_, doc) = current!(cxt.editor);

@ -4,7 +4,7 @@ use crate::{
compositor::{Callback, Component, Compositor, Context, Event, EventResult}, compositor::{Callback, Component, Compositor, Context, Event, EventResult},
ctrl, key, shift, ctrl, key, shift,
}; };
use tui::{buffer::Buffer as Surface, text::Spans, widgets::Table}; use tui::{buffer::Buffer as Surface, widgets::Table};
pub use tui::widgets::{Cell, Row}; pub use tui::widgets::{Cell, Row};
@ -18,28 +18,24 @@ pub trait Item {
/// Additional editor state that is used for label calculation. /// Additional editor state that is used for label calculation.
type Data; type Data;
fn label(&self, data: &Self::Data) -> Spans; fn format(&self, data: &Self::Data) -> Row;
fn sort_text(&self, data: &Self::Data) -> Cow<str> { fn sort_text(&self, data: &Self::Data) -> Cow<str> {
let label: String = self.label(data).into(); let label: String = self.format(data).cell_text().collect();
label.into() label.into()
} }
fn filter_text(&self, data: &Self::Data) -> Cow<str> { fn filter_text(&self, data: &Self::Data) -> Cow<str> {
let label: String = self.label(data).into(); let label: String = self.format(data).cell_text().collect();
label.into() label.into()
} }
fn row(&self, data: &Self::Data) -> Row {
Row::new(vec![Cell::from(self.label(data))])
}
} }
impl Item for PathBuf { impl Item for PathBuf {
/// Root prefix to strip. /// Root prefix to strip.
type Data = PathBuf; type Data = PathBuf;
fn label(&self, root_path: &Self::Data) -> Spans { fn format(&self, root_path: &Self::Data) -> Row {
self.strip_prefix(root_path) self.strip_prefix(root_path)
.unwrap_or(self) .unwrap_or(self)
.to_string_lossy() .to_string_lossy()
@ -81,7 +77,7 @@ impl<T: Item> Menu<T> {
Self { Self {
options, options,
editor_data, editor_data,
matcher: Box::new(Matcher::default()), matcher: Box::default(),
matches, matches,
cursor: None, cursor: None,
widths: Vec::new(), widths: Vec::new(),
@ -144,10 +140,10 @@ impl<T: Item> Menu<T> {
let n = self let n = self
.options .options
.first() .first()
.map(|option| option.row(&self.editor_data).cells.len()) .map(|option| option.format(&self.editor_data).cells.len())
.unwrap_or_default(); .unwrap_or_default();
let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| { let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| {
let row = option.row(&self.editor_data); let row = option.format(&self.editor_data);
// maintain max for each column // maintain max for each column
for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) { for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) {
let width = cell.content.width(); let width = cell.content.width();
@ -331,7 +327,9 @@ impl<T: Item + 'static> Component for Menu<T> {
(a + b - 1) / b (a + b - 1) / b
} }
let rows = options.iter().map(|option| option.row(&self.editor_data)); let rows = options
.iter()
.map(|option| option.format(&self.editor_data));
let table = Table::new(rows) let table = Table::new(rows)
.style(style) .style(style)
.highlight_style(selected) .highlight_style(selected)

@ -7,23 +7,23 @@ use crate::{
use futures_util::future::BoxFuture; use futures_util::future::BoxFuture;
use tui::{ use tui::{
buffer::Buffer as Surface, buffer::Buffer as Surface,
widgets::{Block, BorderType, Borders}, layout::Constraint,
text::{Span, Spans},
widgets::{Block, BorderType, Borders, Cell, Table},
}; };
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
use tui::widgets::Widget; use tui::widgets::Widget;
use std::{ use std::cmp::{self, Ordering};
cmp::{self, Ordering},
time::Instant,
};
use std::{collections::HashMap, io::Read, path::PathBuf}; use std::{collections::HashMap, io::Read, path::PathBuf};
use crate::ui::{Prompt, PromptEvent}; use crate::ui::{Prompt, PromptEvent};
use helix_core::{movement::Direction, Position}; use helix_core::{movement::Direction, unicode::segmentation::UnicodeSegmentation, Position};
use helix_view::{ use helix_view::{
editor::Action, editor::Action,
graphics::{CursorKind, Margin, Modifier, Rect}, graphics::{CursorKind, Margin, Modifier, Rect},
theme::Style,
Document, DocumentId, Editor, Document, DocumentId, Editor,
}; };
@ -389,6 +389,8 @@ pub struct Picker<T: Item> {
pub truncate_start: bool, pub truncate_start: bool,
/// Whether to show the preview panel (default true) /// Whether to show the preview panel (default true)
show_preview: bool, show_preview: bool,
/// Constraints for tabular formatting
widths: Vec<Constraint>,
callback_fn: Box<dyn Fn(&mut Context, &T, Action)>, callback_fn: Box<dyn Fn(&mut Context, &T, Action)>,
} }
@ -406,10 +408,30 @@ impl<T: Item> Picker<T> {
|_editor: &mut Context, _pattern: &str, _event: PromptEvent| {}, |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {},
); );
let n = options
.first()
.map(|option| option.format(&editor_data).cells.len())
.unwrap_or_default();
let max_lens = options.iter().fold(vec![0; n], |mut acc, option| {
let row = option.format(&editor_data);
// maintain max for each column
for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) {
let width = cell.content.width();
if width > *acc {
*acc = width;
}
}
acc
});
let widths = max_lens
.into_iter()
.map(|len| Constraint::Length(len as u16))
.collect();
let mut picker = Self { let mut picker = Self {
options, options,
editor_data, editor_data,
matcher: Box::new(Matcher::default()), matcher: Box::default(),
matches: Vec::new(), matches: Vec::new(),
cursor: 0, cursor: 0,
prompt, prompt,
@ -418,6 +440,7 @@ impl<T: Item> Picker<T> {
show_preview: true, show_preview: true,
callback_fn: Box::new(callback_fn), callback_fn: Box::new(callback_fn),
completion_height: 0, completion_height: 0,
widths,
}; };
// scoring on empty input: // scoring on empty input:
@ -437,8 +460,6 @@ impl<T: Item> Picker<T> {
} }
pub fn score(&mut self) { pub fn score(&mut self) {
let now = Instant::now();
let pattern = self.prompt.line(); let pattern = self.prompt.line();
if pattern == &self.previous_pattern { if pattern == &self.previous_pattern {
@ -480,8 +501,6 @@ impl<T: Item> Picker<T> {
self.force_score(); self.force_score();
} }
log::debug!("picker score {:?}", Instant::now().duration_since(now));
// reset cursor position // reset cursor position
self.cursor = 0; self.cursor = 0;
let pattern = self.prompt.line(); let pattern = self.prompt.line();
@ -657,7 +676,7 @@ impl<T: Item + 'static> Component for Picker<T> {
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
let text_style = cx.editor.theme.get("ui.text"); let text_style = cx.editor.theme.get("ui.text");
let selected = cx.editor.theme.get("ui.text.focus"); let selected = cx.editor.theme.get("ui.text.focus");
let highlighted = cx.editor.theme.get("special").add_modifier(Modifier::BOLD); let highlight_style = cx.editor.theme.get("special").add_modifier(Modifier::BOLD);
// -- Render the frame: // -- Render the frame:
// clear area // clear area
@ -697,61 +716,123 @@ impl<T: Item + 'static> Component for Picker<T> {
} }
// -- Render the contents: // -- Render the contents:
// subtract area of prompt from top and current item marker " > " from left // subtract area of prompt from top
let inner = inner.clip_top(2).clip_left(3); let inner = inner.clip_top(2);
let rows = inner.height; let rows = inner.height;
let offset = self.cursor - (self.cursor % std::cmp::max(1, rows as usize)); let offset = self.cursor - (self.cursor % std::cmp::max(1, rows as usize));
let cursor = self.cursor.saturating_sub(offset);
let files = self let options = self
.matches .matches
.iter() .iter()
.skip(offset) .skip(offset)
.map(|pmatch| (pmatch.index, self.options.get(pmatch.index).unwrap())); .take(rows as usize)
.map(|pmatch| &self.options[pmatch.index])
for (i, (_index, option)) in files.take(rows as usize).enumerate() { .map(|option| option.format(&self.editor_data))
let is_active = i == (self.cursor - offset); .map(|mut row| {
if is_active { const TEMP_CELL_SEP: &str = " ";
surface.set_string(
inner.x.saturating_sub(3), let line = row.cell_text().fold(String::new(), |mut s, frag| {
inner.y + i as u16, s.push_str(&frag);
" > ", s.push_str(TEMP_CELL_SEP);
selected, s
); });
surface.set_style(
Rect::new(inner.x, inner.y + i as u16, inner.width, 1),
selected,
);
}
let spans = option.label(&self.editor_data); // Items are filtered by using the text returned by menu::Item::filter_text
// but we do highlighting here using the text in Row and therefore there
// might be inconsistencies. This is the best we can do since only the
// text in Row is displayed to the end user.
let (_score, highlights) = FuzzyQuery::new(self.prompt.line()) let (_score, highlights) = FuzzyQuery::new(self.prompt.line())
.fuzzy_indicies(&String::from(&spans), &self.matcher) .fuzzy_indicies(&line, &self.matcher)
.unwrap_or_default(); .unwrap_or_default();
spans.0.into_iter().fold(inner, |pos, span| { let highlight_byte_ranges: Vec<_> = line
let new_x = surface .char_indices()
.set_string_truncated( .enumerate()
pos.x, .filter_map(|(char_idx, (byte_offset, ch))| {
pos.y + i as u16, highlights
&span.content, .contains(&char_idx)
pos.width as usize, .then(|| byte_offset..byte_offset + ch.len_utf8())
|idx| { })
if highlights.contains(&idx) { .collect();
highlighted.patch(span.style)
} else if is_active { // The starting byte index of the current (iterating) cell
selected.patch(span.style) let mut cell_start_byte_offset = 0;
for cell in row.cells.iter_mut() {
let spans = match cell.content.lines.get(0) {
Some(s) => s,
None => continue,
};
let mut cell_len = 0;
let graphemes_with_style: Vec<_> = spans
.0
.iter()
.flat_map(|span| {
span.content
.grapheme_indices(true)
.zip(std::iter::repeat(span.style))
})
.map(|((grapheme_byte_offset, grapheme), style)| {
cell_len += grapheme.len();
let start = cell_start_byte_offset;
let grapheme_byte_range =
grapheme_byte_offset..grapheme_byte_offset + grapheme.len();
if highlight_byte_ranges.iter().any(|hl_rng| {
hl_rng.start >= start + grapheme_byte_range.start
&& hl_rng.end <= start + grapheme_byte_range.end
}) {
(grapheme, style.patch(highlight_style))
} else {
(grapheme, style)
}
})
.collect();
let mut span_list: Vec<(String, Style)> = Vec::new();
for (grapheme, style) in graphemes_with_style {
if span_list.last().map(|(_, sty)| sty) == Some(&style) {
let (string, _) = span_list.last_mut().unwrap();
string.push_str(grapheme);
} else { } else {
text_style.patch(span.style) span_list.push((String::from(grapheme), style))
} }
},
true,
self.truncate_start,
)
.0;
pos.clip_left(new_x - pos.x)
});
} }
let spans: Vec<Span> = span_list
.into_iter()
.map(|(string, style)| Span::styled(string, style))
.collect();
let spans: Spans = spans.into();
*cell = Cell::from(spans);
cell_start_byte_offset += cell_len + TEMP_CELL_SEP.len();
}
row
});
let table = Table::new(options)
.style(text_style)
.highlight_style(selected)
.highlight_symbol(" > ")
.column_spacing(1)
.widths(&self.widths);
use tui::widgets::TableState;
table.render_table(
inner,
surface,
&mut TableState {
offset: 0,
selected: Some(cursor),
},
);
} }
fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) { fn cursor(&self, area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {

@ -130,7 +130,7 @@ pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>(
}) })
.with_selection(test_case.in_selection.clone()); .with_selection(test_case.in_selection.clone());
helix_view::apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
test_key_sequence( test_key_sequence(
&mut app, &mut app,
@ -315,7 +315,7 @@ impl AppBuilder {
.with_selection(selection); .with_selection(selection);
// replace the initial text with the input text // replace the initial text with the input text
helix_view::apply_transaction(&trans, doc, view); doc.apply(&trans, view.id);
} }
Ok(app) Ok(app)

@ -433,7 +433,7 @@ impl Buffer {
(x_offset as u16, y) (x_offset as u16, y)
} }
pub fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) { pub fn set_spans(&mut self, x: u16, y: u16, spans: &Spans, width: u16) -> (u16, u16) {
let mut remaining_width = width; let mut remaining_width = width;
let mut x = x; let mut x = x;
for span in &spans.0 { for span in &spans.0 {
@ -454,7 +454,7 @@ impl Buffer {
(x, y) (x, y)
} }
pub fn set_span<'a>(&mut self, x: u16, y: u16, span: &Span<'a>, width: u16) -> (u16, u16) { pub fn set_span(&mut self, x: u16, y: u16, span: &Span, width: u16) -> (u16, u16) {
self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style) self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style)
} }
@ -521,10 +521,10 @@ impl Buffer {
pub fn merge(&mut self, other: &Buffer) { pub fn merge(&mut self, other: &Buffer) {
let area = self.area.union(other.area); let area = self.area.union(other.area);
let cell: Cell = Default::default(); let cell: Cell = Default::default();
self.content.resize(area.area() as usize, cell.clone()); self.content.resize(area.area(), cell.clone());
// Move original content to the appropriate space // Move original content to the appropriate space
let size = self.area.area() as usize; let size = self.area.area();
for i in (0..size).rev() { for i in (0..size).rev() {
let (x, y) = self.pos_of(i); let (x, y) = self.pos_of(i);
// New index in content // New index in content
@ -537,7 +537,7 @@ impl Buffer {
// Push content of the other buffer into this one (may erase previous // Push content of the other buffer into this one (may erase previous
// data) // data)
let size = other.area.area() as usize; let size = other.area.area();
for i in 0..size { for i in 0..size {
let (x, y) = other.pos_of(i); let (x, y) = other.pos_of(i);
// New index in content // New index in content

@ -436,6 +436,32 @@ impl<'a> From<Vec<Spans<'a>>> for Text<'a> {
} }
} }
impl<'a> From<Text<'a>> for String {
fn from(text: Text<'a>) -> String {
String::from(&text)
}
}
impl<'a> From<&Text<'a>> for String {
fn from(text: &Text<'a>) -> String {
let size = text
.lines
.iter()
.flat_map(|spans| spans.0.iter().map(|span| span.content.len()))
.sum::<usize>()
+ text.lines.len().saturating_sub(1); // for newline after each line
let mut output = String::with_capacity(size);
for spans in &text.lines {
for span in &spans.0 {
output.push_str(&span.content);
}
output.push('\n');
}
output
}
}
impl<'a> IntoIterator for Text<'a> { impl<'a> IntoIterator for Text<'a> {
type Item = Spans<'a>; type Item = Spans<'a>;
type IntoIter = std::vec::IntoIter<Self::Item>; type IntoIter = std::vec::IntoIter<Self::Item>;

@ -4,14 +4,8 @@ use crate::{
text::Text, text::Text,
widgets::{Block, Widget}, widgets::{Block, Widget},
}; };
use cassowary::{
strength::{MEDIUM, REQUIRED, WEAK},
WeightedRelation::*,
{Expression, Solver},
};
use helix_core::unicode::width::UnicodeWidthStr; use helix_core::unicode::width::UnicodeWidthStr;
use helix_view::graphics::{Rect, Style}; use helix_view::graphics::{Rect, Style};
use std::collections::HashMap;
/// A [`Cell`] contains the [`Text`] to be displayed in a [`Row`] of a [`Table`]. /// A [`Cell`] contains the [`Text`] to be displayed in a [`Row`] of a [`Table`].
/// ///
@ -126,6 +120,17 @@ impl<'a> Row<'a> {
fn total_height(&self) -> u16 { fn total_height(&self) -> u16 {
self.height.saturating_add(self.bottom_margin) self.height.saturating_add(self.bottom_margin)
} }
/// Returns the contents of cells as plain text, without styles and colors.
pub fn cell_text(&self) -> impl Iterator<Item = String> + '_ {
self.cells.iter().map(|cell| String::from(&cell.content))
}
}
impl<'a, T: Into<Cell<'a>>> From<T> for Row<'a> {
fn from(cell: T) -> Self {
Row::new(vec![cell.into()])
}
} }
/// A widget to display data in formatted columns. /// A widget to display data in formatted columns.
@ -260,69 +265,32 @@ impl<'a> Table<'a> {
} }
fn get_columns_widths(&self, max_width: u16, has_selection: bool) -> Vec<u16> { fn get_columns_widths(&self, max_width: u16, has_selection: bool) -> Vec<u16> {
let mut solver = Solver::new(); let mut constraints = Vec::with_capacity(self.widths.len() * 2 + 1);
let mut var_indices = HashMap::new();
let mut ccs = Vec::new();
let mut variables = Vec::new();
for i in 0..self.widths.len() {
let var = cassowary::Variable::new();
variables.push(var);
var_indices.insert(var, i);
}
let spacing_width = (variables.len() as u16).saturating_sub(1) * self.column_spacing;
let mut available_width = max_width.saturating_sub(spacing_width);
if has_selection { if has_selection {
let highlight_symbol_width = let highlight_symbol_width =
self.highlight_symbol.map(|s| s.width() as u16).unwrap_or(0); self.highlight_symbol.map(|s| s.width() as u16).unwrap_or(0);
available_width = available_width.saturating_sub(highlight_symbol_width); constraints.push(Constraint::Length(highlight_symbol_width));
} }
for (i, constraint) in self.widths.iter().enumerate() { for constraint in self.widths {
ccs.push(variables[i] | GE(WEAK) | 0.); constraints.push(*constraint);
ccs.push(match *constraint { constraints.push(Constraint::Length(self.column_spacing));
Constraint::Length(v) => variables[i] | EQ(MEDIUM) | f64::from(v), }
Constraint::Percentage(v) => { if !self.widths.is_empty() {
variables[i] | EQ(WEAK) | (f64::from(v * available_width) / 100.0) constraints.pop();
} }
Constraint::Ratio(n, d) => { let mut chunks = crate::layout::Layout::default()
variables[i] .direction(crate::layout::Direction::Horizontal)
| EQ(WEAK) .constraints(constraints)
| (f64::from(available_width) * f64::from(n) / f64::from(d)) .split(Rect {
} x: 0,
Constraint::Min(v) => variables[i] | GE(WEAK) | f64::from(v), y: 0,
Constraint::Max(v) => variables[i] | LE(WEAK) | f64::from(v), width: max_width,
}) height: 1,
} });
solver if has_selection {
.add_constraint( chunks.remove(0);
variables
.iter()
.fold(Expression::from_constant(0.), |acc, v| acc + *v)
| LE(REQUIRED)
| f64::from(available_width),
)
.unwrap();
solver.add_constraints(&ccs).unwrap();
let mut widths = vec![0; variables.len()];
for &(var, value) in solver.fetch_changes() {
let index = var_indices[&var];
let value = if value.is_sign_negative() {
0
} else {
value.round() as u16
};
widths[index] = value;
}
// Cassowary could still return columns widths greater than the max width when there are
// fixed length constraints that cannot be satisfied. Therefore, we clamp the widths from
// left to right.
let mut available_width = max_width;
for w in &mut widths {
*w = available_width.min(*w);
available_width = available_width
.saturating_sub(*w)
.saturating_sub(self.column_spacing);
} }
widths chunks.iter().step_by(2).map(|c| c.width).collect()
} }
fn get_row_bounds( fn get_row_bounds(
@ -477,6 +445,9 @@ impl<'a> Table<'a> {
}; };
let mut col = table_row_start_col; let mut col = table_row_start_col;
for (width, cell) in columns_widths.iter().zip(table_row.cells.iter()) { for (width, cell) in columns_widths.iter().zip(table_row.cells.iter()) {
if is_selected {
buf.set_style(table_row_area, self.highlight_style);
}
render_cell( render_cell(
buf, buf,
cell, cell,
@ -489,9 +460,6 @@ impl<'a> Table<'a> {
); );
col += *width + self.column_spacing; col += *width + self.column_spacing;
} }
if is_selected {
buf.set_style(table_row_area, self.highlight_style);
}
} }
} }
} }

@ -39,10 +39,10 @@ chardetng = "0.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
toml = "0.5" toml = "0.6"
log = "~0.4" log = "~0.4"
which = "4.2" which = "4.4"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]

@ -27,7 +27,7 @@ use helix_core::{
}; };
use crate::editor::RedrawHandle; use crate::editor::RedrawHandle;
use crate::{apply_transaction, DocumentId, Editor, View, ViewId}; use crate::{DocumentId, Editor, View, ViewId};
/// 8kB of buffer space for encoding and decoding `Rope`s. /// 8kB of buffer space for encoding and decoding `Rope`s.
const BUF_SIZE: usize = 8192; const BUF_SIZE: usize = 8192;
@ -650,7 +650,7 @@ impl Document {
// This is not considered a modification of the contents of the file regardless // This is not considered a modification of the contents of the file regardless
// of the encoding. // of the encoding.
let transaction = helix_core::diff::compare_ropes(self.text(), &rope); let transaction = helix_core::diff::compare_ropes(self.text(), &rope);
apply_transaction(&transaction, self, view); self.apply(&transaction, view.id);
self.append_changes_to_history(view); self.append_changes_to_history(view);
self.reset_modified(); self.reset_modified();
@ -852,9 +852,6 @@ impl Document {
} }
/// Apply a [`Transaction`] to the [`Document`] to change its text. /// Apply a [`Transaction`] to the [`Document`] to change its text.
/// Instead of calling this function directly, use [crate::apply_transaction]
/// to ensure that the transaction is applied to the appropriate [`View`] as
/// well.
pub fn apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool { pub fn apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
// store the state just before any changes are made. This allows us to undo to the // store the state just before any changes are made. This allows us to undo to the
// state just before a transaction was applied. // state just before a transaction was applied.
@ -911,7 +908,7 @@ impl Document {
pub fn restore(&mut self, view: &mut View) { pub fn restore(&mut self, view: &mut View) {
if let Some(revert) = self.savepoint.take() { if let Some(revert) = self.savepoint.take() {
apply_transaction(&revert, self, view); self.apply(&revert, view.id);
} }
} }

@ -71,6 +71,96 @@ where
) )
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct GutterConfig {
/// Gutter Layout
pub layout: Vec<GutterType>,
/// Options specific to the "line-numbers" gutter
pub line_numbers: GutterLineNumbersConfig,
}
impl Default for GutterConfig {
fn default() -> Self {
Self {
layout: vec![
GutterType::Diagnostics,
GutterType::Spacer,
GutterType::LineNumbers,
GutterType::Spacer,
GutterType::Diff,
],
line_numbers: GutterLineNumbersConfig::default(),
}
}
}
impl From<Vec<GutterType>> for GutterConfig {
fn from(x: Vec<GutterType>) -> Self {
GutterConfig {
layout: x,
..Default::default()
}
}
}
fn deserialize_gutter_seq_or_struct<'de, D>(deserializer: D) -> Result<GutterConfig, D::Error>
where
D: Deserializer<'de>,
{
struct GutterVisitor;
impl<'de> serde::de::Visitor<'de> for GutterVisitor {
type Value = GutterConfig;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
formatter,
"an array of gutter names or a detailed gutter configuration"
)
}
fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
where
S: serde::de::SeqAccess<'de>,
{
let mut gutters = Vec::new();
while let Some(gutter) = seq.next_element::<String>()? {
gutters.push(
gutter
.parse::<GutterType>()
.map_err(serde::de::Error::custom)?,
)
}
Ok(gutters.into())
}
fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
where
M: serde::de::MapAccess<'de>,
{
let deserializer = serde::de::value::MapAccessDeserializer::new(map);
Deserialize::deserialize(deserializer)
}
}
deserializer.deserialize_any(GutterVisitor)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct GutterLineNumbersConfig {
/// Minimum number of characters to use for line number gutter. Defaults to 3.
pub min_width: usize,
}
impl Default for GutterLineNumbersConfig {
fn default() -> Self {
Self { min_width: 3 }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct FilePickerConfig { pub struct FilePickerConfig {
@ -132,8 +222,8 @@ pub struct Config {
pub cursorline: bool, pub cursorline: bool,
/// Highlight the columns cursors are currently on. Defaults to false. /// Highlight the columns cursors are currently on. Defaults to false.
pub cursorcolumn: bool, pub cursorcolumn: bool,
/// Gutters. Default ["diagnostics", "line-numbers"] #[serde(deserialize_with = "deserialize_gutter_seq_or_struct")]
pub gutters: Vec<GutterType>, pub gutters: GutterConfig,
/// Middle click paste support. Defaults to true. /// Middle click paste support. Defaults to true.
pub middle_click_paste: bool, pub middle_click_paste: bool,
/// Automatic insertion of pairs to parentheses, brackets, /// Automatic insertion of pairs to parentheses, brackets,
@ -206,10 +296,10 @@ pub fn get_terminal_provider() -> Option<TerminalConfig> {
}); });
} }
return Some(TerminalConfig { Some(TerminalConfig {
command: "conhost".to_string(), command: "conhost".to_string(),
args: vec!["cmd".to_string(), "/C".to_string()], args: vec!["cmd".to_string(), "/C".to_string()],
}); })
} }
#[cfg(not(any(windows, target_os = "wasm32")))] #[cfg(not(any(windows, target_os = "wasm32")))]
@ -606,13 +696,7 @@ impl Default for Config {
line_number: LineNumber::Absolute, line_number: LineNumber::Absolute,
cursorline: false, cursorline: false,
cursorcolumn: false, cursorcolumn: false,
gutters: vec![ gutters: GutterConfig::default(),
GutterType::Diagnostics,
GutterType::Spacer,
GutterType::LineNumbers,
GutterType::Spacer,
GutterType::Diff,
],
middle_click_paste: true, middle_click_paste: true,
auto_pairs: AutoPairConfig::default(), auto_pairs: AutoPairConfig::default(),
auto_completion: true, auto_completion: true,
@ -844,6 +928,7 @@ impl Editor {
let config = self.config(); let config = self.config();
self.auto_pairs = (&config.auto_pairs).into(); self.auto_pairs = (&config.auto_pairs).into();
self.reset_idle_timer(); self.reset_idle_timer();
self._refresh();
} }
pub fn clear_idle_timer(&mut self) { pub fn clear_idle_timer(&mut self) {
@ -984,6 +1069,7 @@ impl Editor {
for (view, _) in self.tree.views_mut() { for (view, _) in self.tree.views_mut() {
let doc = doc_mut!(self, &view.doc); let doc = doc_mut!(self, &view.doc);
view.sync_changes(doc); view.sync_changes(doc);
view.gutters = config.gutters.clone();
view.ensure_cursor_in_view(doc, config.scrolloff) view.ensure_cursor_in_view(doc, config.scrolloff)
} }
} }
@ -1285,6 +1371,10 @@ impl Editor {
self.focus(self.tree.next()); self.focus(self.tree.next());
} }
pub fn focus_prev(&mut self) {
self.focus(self.tree.prev());
}
pub fn focus_direction(&mut self, direction: tree::Direction) { pub fn focus_direction(&mut self, direction: tree::Direction) {
let current_view = self.tree.focus; let current_view = self.tree.focus;
if let Some(id) = self.tree.find_split_in_direction(current_view, direction) { if let Some(id) = self.tree.find_split_in_direction(current_view, direction) {
@ -1498,6 +1588,6 @@ fn try_restore_indent(doc: &mut Document, view: &mut View) {
let line_start_pos = text.line_to_char(range.cursor_line(text)); let line_start_pos = text.line_to_char(range.cursor_line(text));
(line_start_pos, pos, None) (line_start_pos, pos, None)
}); });
crate::apply_transaction(&transaction, doc, view); doc.apply(&transaction, view.id);
} }
} }

@ -251,7 +251,6 @@ impl Rect {
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Color { pub enum Color {
Reset, Reset,
Black, Black,
@ -353,7 +352,6 @@ bitflags! {
/// ///
/// let m = Modifier::BOLD | Modifier::ITALIC; /// let m = Modifier::BOLD | Modifier::ITALIC;
/// ``` /// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Modifier: u16 { pub struct Modifier: u16 {
const BOLD = 0b0000_0000_0001; const BOLD = 0b0000_0000_0001;
const DIM = 0b0000_0000_0010; const DIM = 0b0000_0000_0010;
@ -450,7 +448,6 @@ impl FromStr for Modifier {
/// ); /// );
/// ``` /// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Style { pub struct Style {
pub fg: Option<Color>, pub fg: Option<Color>,
pub bg: Option<Color>, pub bg: Option<Color>,

@ -35,10 +35,10 @@ impl GutterType {
} }
} }
pub fn width(self, _view: &View, doc: &Document) -> usize { pub fn width(self, view: &View, doc: &Document) -> usize {
match self { match self {
GutterType::Diagnostics => 1, GutterType::Diagnostics => 1,
GutterType::LineNumbers => line_numbers_width(_view, doc), GutterType::LineNumbers => line_numbers_width(view, doc),
GutterType::Spacer => 1, GutterType::Spacer => 1,
GutterType::Diff => 1, GutterType::Diff => 1,
} }
@ -140,12 +140,13 @@ pub fn line_numbers<'doc>(
is_focused: bool, is_focused: bool,
) -> GutterFn<'doc> { ) -> GutterFn<'doc> {
let text = doc.text().slice(..); let text = doc.text().slice(..);
let last_line = view.last_line(doc); let width = line_numbers_width(view, doc);
let width = GutterType::LineNumbers.width(view, doc);
let last_line_in_view = view.last_line(doc);
// Whether to draw the line number for the last line of the // Whether to draw the line number for the last line of the
// document or not. We only draw it if it's not an empty line. // document or not. We only draw it if it's not an empty line.
let draw_last = text.line_to_byte(last_line) < text.len_bytes(); let draw_last = text.line_to_byte(last_line_in_view) < text.len_bytes();
let linenr = theme.get("ui.linenr"); let linenr = theme.get("ui.linenr");
let linenr_select = theme.get("ui.linenr.selected"); let linenr_select = theme.get("ui.linenr.selected");
@ -158,7 +159,7 @@ pub fn line_numbers<'doc>(
let mode = editor.mode; let mode = editor.mode;
Box::new(move |line: usize, selected: bool, out: &mut String| { Box::new(move |line: usize, selected: bool, out: &mut String| {
if line == last_line && !draw_last { if line == last_line_in_view && !draw_last {
write!(out, "{:>1$}", '~', width).unwrap(); write!(out, "{:>1$}", '~', width).unwrap();
Some(linenr) Some(linenr)
} else { } else {
@ -187,14 +188,19 @@ pub fn line_numbers<'doc>(
}) })
} }
pub fn line_numbers_width(_view: &View, doc: &Document) -> usize { /// The width of a "line-numbers" gutter
///
/// The width of the gutter depends on the number of lines in the document,
/// whether there is content on the last line (the `~` line), and the
/// `editor.gutters.line-numbers.min-width` settings.
fn line_numbers_width(view: &View, doc: &Document) -> usize {
let text = doc.text(); let text = doc.text();
let last_line = text.len_lines().saturating_sub(1); let last_line = text.len_lines().saturating_sub(1);
let draw_last = text.line_to_byte(last_line) < text.len_bytes(); let draw_last = text.line_to_byte(last_line) < text.len_bytes();
let last_drawn = if draw_last { last_line + 1 } else { last_line }; let last_drawn = if draw_last { last_line + 1 } else { last_line };
let digits = count_digits(last_drawn);
// set a lower bound to 2-chars to minimize ambiguous relative line numbers let n_min = view.gutters.line_numbers.min_width;
std::cmp::max(count_digits(last_drawn), 2) digits.max(n_min)
} }
pub fn padding<'doc>( pub fn padding<'doc>(
@ -282,3 +288,82 @@ pub fn diagnostics_or_breakpoints<'doc>(
breakpoints(line, selected, out).or_else(|| diagnostics(line, selected, out)) breakpoints(line, selected, out).or_else(|| diagnostics(line, selected, out))
}) })
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::document::Document;
use crate::editor::{GutterConfig, GutterLineNumbersConfig};
use crate::graphics::Rect;
use crate::DocumentId;
use helix_core::Rope;
#[test]
fn test_default_gutter_widths() {
let mut view = View::new(DocumentId::default(), GutterConfig::default());
view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("abc\n\tdef");
let doc = Document::from(rope, None);
assert_eq!(view.gutters.layout.len(), 5);
assert_eq!(view.gutters.layout[0].width(&view, &doc), 1);
assert_eq!(view.gutters.layout[1].width(&view, &doc), 1);
assert_eq!(view.gutters.layout[2].width(&view, &doc), 3);
assert_eq!(view.gutters.layout[3].width(&view, &doc), 1);
assert_eq!(view.gutters.layout[4].width(&view, &doc), 1);
}
#[test]
fn test_configured_gutter_widths() {
let gutters = GutterConfig {
layout: vec![GutterType::Diagnostics],
..Default::default()
};
let mut view = View::new(DocumentId::default(), gutters);
view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("abc\n\tdef");
let doc = Document::from(rope, None);
assert_eq!(view.gutters.layout.len(), 1);
assert_eq!(view.gutters.layout[0].width(&view, &doc), 1);
let gutters = GutterConfig {
layout: vec![GutterType::Diagnostics, GutterType::LineNumbers],
line_numbers: GutterLineNumbersConfig { min_width: 10 },
};
let mut view = View::new(DocumentId::default(), gutters);
view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("abc\n\tdef");
let doc = Document::from(rope, None);
assert_eq!(view.gutters.layout.len(), 2);
assert_eq!(view.gutters.layout[0].width(&view, &doc), 1);
assert_eq!(view.gutters.layout[1].width(&view, &doc), 10);
}
#[test]
fn test_line_numbers_gutter_width_resizes() {
let gutters = GutterConfig {
layout: vec![GutterType::Diagnostics, GutterType::LineNumbers],
line_numbers: GutterLineNumbersConfig { min_width: 1 },
};
let mut view = View::new(DocumentId::default(), gutters);
view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("a\nb");
let doc_short = Document::from(rope, None);
let rope = Rope::from_str("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np");
let doc_long = Document::from(rope, None);
assert_eq!(view.gutters.layout.len(), 2);
assert_eq!(view.gutters.layout[1].width(&view, &doc_short), 1);
assert_eq!(view.gutters.layout[1].width(&view, &doc_long), 2);
}
}

@ -2,7 +2,6 @@ use bitflags::bitflags;
bitflags! { bitflags! {
/// Represents key modifiers (shift, control, alt). /// Represents key modifiers (shift, control, alt).
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyModifiers: u8 { pub struct KeyModifiers: u8 {
const SHIFT = 0b0000_0001; const SHIFT = 0b0000_0001;
const CONTROL = 0b0000_0010; const CONTROL = 0b0000_0010;

@ -66,17 +66,6 @@ pub fn align_view(doc: &Document, view: &mut View, align: Align) {
view.offset.row = line.saturating_sub(relative); view.offset.row = line.saturating_sub(relative);
} }
/// Applies a [`helix_core::Transaction`] to the given [`Document`]
/// and [`View`].
pub fn apply_transaction(
transaction: &helix_core::Transaction,
doc: &mut Document,
view: &View,
) -> bool {
// TODO remove this helper function. Just call Document::apply everywhere directly.
doc.apply(transaction, view.id)
}
pub use document::Document; pub use document::Document;
pub use editor::Editor; pub use editor::Editor;
pub use theme::Theme; pub use theme::Theme;

@ -1,9 +1,10 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
path::{Path, PathBuf}, path::{Path, PathBuf},
str,
}; };
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Result};
use helix_core::hashmap; use helix_core::hashmap;
use helix_loader::merge_toml_values; use helix_loader::merge_toml_values;
use log::warn; use log::warn;
@ -15,12 +16,13 @@ use crate::graphics::UnderlineStyle;
pub use crate::graphics::{Color, Modifier, Style}; pub use crate::graphics::{Color, Modifier, Style};
pub static DEFAULT_THEME_DATA: Lazy<Value> = Lazy::new(|| { pub static DEFAULT_THEME_DATA: Lazy<Value> = Lazy::new(|| {
toml::from_slice(include_bytes!("../../theme.toml")).expect("Failed to parse default theme") let bytes = include_bytes!("../../theme.toml");
toml::from_str(str::from_utf8(bytes).unwrap()).expect("Failed to parse base default theme")
}); });
pub static BASE16_DEFAULT_THEME_DATA: Lazy<Value> = Lazy::new(|| { pub static BASE16_DEFAULT_THEME_DATA: Lazy<Value> = Lazy::new(|| {
toml::from_slice(include_bytes!("../../base16_theme.toml")) let bytes = include_bytes!("../../base16_theme.toml");
.expect("Failed to parse base 16 default theme") toml::from_str(str::from_utf8(bytes).unwrap()).expect("Failed to parse base 16 default theme")
}); });
pub static DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| Theme { pub static DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| Theme {
@ -70,7 +72,7 @@ impl Loader {
fn load_theme( fn load_theme(
&self, &self,
name: &str, name: &str,
base_them_name: &str, base_theme_name: &str,
only_default_dir: bool, only_default_dir: bool,
) -> Result<Value> { ) -> Result<Value> {
let path = self.path(name, only_default_dir); let path = self.path(name, only_default_dir);
@ -92,8 +94,8 @@ impl Loader {
"base16_default" => BASE16_DEFAULT_THEME_DATA.clone(), "base16_default" => BASE16_DEFAULT_THEME_DATA.clone(),
_ => self.load_theme( _ => self.load_theme(
parent_theme_name, parent_theme_name,
base_them_name, base_theme_name,
base_them_name == parent_theme_name, base_theme_name == parent_theme_name,
)?, )?,
}; };
@ -148,8 +150,8 @@ impl Loader {
// Loads the theme data as `toml::Value` first from the user_dir then in default_dir // Loads the theme data as `toml::Value` first from the user_dir then in default_dir
fn load_toml(&self, path: PathBuf) -> Result<Value> { fn load_toml(&self, path: PathBuf) -> Result<Value> {
let data = std::fs::read(&path)?; let data = std::fs::read_to_string(path)?;
let value = toml::from_slice(data.as_slice())?; let value = toml::from_str(&data)?;
Ok(value) Ok(value)
} }
@ -207,10 +209,8 @@ pub struct Theme {
impl From<Value> for Theme { impl From<Value> for Theme {
fn from(value: Value) -> Self { fn from(value: Value) -> Self {
let values: Result<HashMap<String, Value>> = if let Value::Table(table) = value {
toml::from_str(&value.to_string()).context("Failed to load theme"); let (styles, scopes, highlights) = build_theme_values(table);
let (styles, scopes, highlights) = build_theme_values(values);
Self { Self {
styles, styles,
@ -218,6 +218,10 @@ impl From<Value> for Theme {
highlights, highlights,
..Default::default() ..Default::default()
} }
} else {
warn!("Expected theme TOML value to be a table, found {:?}", value);
Default::default()
}
} }
} }
@ -226,9 +230,9 @@ impl<'de> Deserialize<'de> for Theme {
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let values = HashMap::<String, Value>::deserialize(deserializer)?; let values = Map::<String, Value>::deserialize(deserializer)?;
let (styles, scopes, highlights) = build_theme_values(Ok(values)); let (styles, scopes, highlights) = build_theme_values(values);
Ok(Self { Ok(Self {
styles, styles,
@ -240,15 +244,14 @@ impl<'de> Deserialize<'de> for Theme {
} }
fn build_theme_values( fn build_theme_values(
values: Result<HashMap<String, Value>>, mut values: Map<String, Value>,
) -> (HashMap<String, Style>, Vec<String>, Vec<Style>) { ) -> (HashMap<String, Style>, Vec<String>, Vec<Style>) {
let mut styles = HashMap::new(); let mut styles = HashMap::new();
let mut scopes = Vec::new(); let mut scopes = Vec::new();
let mut highlights = Vec::new(); let mut highlights = Vec::new();
if let Ok(mut colors) = values {
// TODO: alert user of parsing failures in editor // TODO: alert user of parsing failures in editor
let palette = colors let palette = values
.remove("palette") .remove("palette")
.map(|value| { .map(|value| {
ThemePalette::try_from(value).unwrap_or_else(|err| { ThemePalette::try_from(value).unwrap_or_else(|err| {
@ -258,11 +261,11 @@ fn build_theme_values(
}) })
.unwrap_or_default(); .unwrap_or_default();
// remove inherits from value to prevent errors // remove inherits from value to prevent errors
let _ = colors.remove("inherits"); let _ = values.remove("inherits");
styles.reserve(colors.len()); styles.reserve(values.len());
scopes.reserve(colors.len()); scopes.reserve(values.len());
highlights.reserve(colors.len()); highlights.reserve(values.len());
for (name, style_value) in colors { for (name, style_value) in values {
let mut style = Style::default(); let mut style = Style::default();
if let Err(err) = palette.parse_style(&mut style, style_value) { if let Err(err) = palette.parse_style(&mut style, style_value) {
warn!("{}", err); warn!("{}", err);
@ -273,7 +276,6 @@ fn build_theme_values(
scopes.push(name); scopes.push(name);
highlights.push(style); highlights.push(style);
} }
}
(styles, scopes, highlights) (styles, scopes, highlights)
} }
@ -515,11 +517,9 @@ mod tests {
let mut style = Style::default(); let mut style = Style::default();
let palette = ThemePalette::default(); let palette = ThemePalette::default();
if let Value::Table(entries) = table { for (_name, value) in table {
for (_name, value) in entries {
palette.parse_style(&mut style, value).unwrap(); palette.parse_style(&mut style, value).unwrap();
} }
}
assert_eq!( assert_eq!(
style, style,

@ -701,7 +701,7 @@ impl<'a> DoubleEndedIterator for Traverse<'a> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::editor::GutterType; use crate::editor::GutterConfig;
use crate::DocumentId; use crate::DocumentId;
#[test] #[test]
@ -712,34 +712,22 @@ mod test {
width: 180, width: 180,
height: 80, height: 80,
}); });
let mut view = View::new( let mut view = View::new(DocumentId::default(), GutterConfig::default());
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
view.area = Rect::new(0, 0, 180, 80); view.area = Rect::new(0, 0, 180, 80);
tree.insert(view); tree.insert(view);
let l0 = tree.focus; let l0 = tree.focus;
let view = View::new( let view = View::new(DocumentId::default(), GutterConfig::default());
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
tree.split(view, Layout::Vertical); tree.split(view, Layout::Vertical);
let r0 = tree.focus; let r0 = tree.focus;
tree.focus = l0; tree.focus = l0;
let view = View::new( let view = View::new(DocumentId::default(), GutterConfig::default());
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
tree.split(view, Layout::Horizontal); tree.split(view, Layout::Horizontal);
let l1 = tree.focus; let l1 = tree.focus;
tree.focus = l0; tree.focus = l0;
let view = View::new( let view = View::new(DocumentId::default(), GutterConfig::default());
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
tree.split(view, Layout::Vertical); tree.split(view, Layout::Vertical);
let l2 = tree.focus; let l2 = tree.focus;
@ -781,40 +769,28 @@ mod test {
}); });
let doc_l0 = DocumentId::default(); let doc_l0 = DocumentId::default();
let mut view = View::new( let mut view = View::new(doc_l0, GutterConfig::default());
doc_l0,
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
view.area = Rect::new(0, 0, 180, 80); view.area = Rect::new(0, 0, 180, 80);
tree.insert(view); tree.insert(view);
let l0 = tree.focus; let l0 = tree.focus;
let doc_r0 = DocumentId::default(); let doc_r0 = DocumentId::default();
let view = View::new( let view = View::new(doc_r0, GutterConfig::default());
doc_r0,
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
tree.split(view, Layout::Vertical); tree.split(view, Layout::Vertical);
let r0 = tree.focus; let r0 = tree.focus;
tree.focus = l0; tree.focus = l0;
let doc_l1 = DocumentId::default(); let doc_l1 = DocumentId::default();
let view = View::new( let view = View::new(doc_l1, GutterConfig::default());
doc_l1,
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
tree.split(view, Layout::Horizontal); tree.split(view, Layout::Horizontal);
let l1 = tree.focus; let l1 = tree.focus;
tree.focus = l0; tree.focus = l0;
let doc_l2 = DocumentId::default(); let doc_l2 = DocumentId::default();
let view = View::new( let view = View::new(doc_l2, GutterConfig::default());
doc_l2,
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
tree.split(view, Layout::Vertical); tree.split(view, Layout::Vertical);
let l2 = tree.focus; let l2 = tree.focus;

@ -1,4 +1,10 @@
use crate::{align_view, editor::GutterType, graphics::Rect, Align, Document, DocumentId, ViewId}; use crate::{
align_view,
editor::{GutterConfig, GutterType},
graphics::Rect,
Align, Document, DocumentId, ViewId,
};
use helix_core::{ use helix_core::{
pos_at_visual_coords, visual_coords_at_pos, Position, RopeSlice, Selection, Transaction, pos_at_visual_coords, visual_coords_at_pos, Position, RopeSlice, Selection, Transaction,
}; };
@ -103,8 +109,8 @@ pub struct View {
pub last_modified_docs: [Option<DocumentId>; 2], pub last_modified_docs: [Option<DocumentId>; 2],
/// used to store previous selections of tree-sitter objects /// used to store previous selections of tree-sitter objects
pub object_selections: Vec<Selection>, pub object_selections: Vec<Selection>,
/// GutterTypes used to fetch Gutter (constructor) and width for rendering /// all gutter-related configuration settings, used primarily for gutter rendering
gutters: Vec<GutterType>, pub gutters: GutterConfig,
/// A mapping between documents and the last history revision the view was updated at. /// A mapping between documents and the last history revision the view was updated at.
/// Changes between documents and views are synced lazily when switching windows. This /// Changes between documents and views are synced lazily when switching windows. This
/// mapping keeps track of the last applied history revision so that only new changes /// mapping keeps track of the last applied history revision so that only new changes
@ -123,7 +129,7 @@ impl fmt::Debug for View {
} }
impl View { impl View {
pub fn new(doc: DocumentId, gutter_types: Vec<crate::editor::GutterType>) -> Self { pub fn new(doc: DocumentId, gutters: GutterConfig) -> Self {
Self { Self {
id: ViewId::default(), id: ViewId::default(),
doc, doc,
@ -133,7 +139,7 @@ impl View {
docs_access_history: Vec::new(), docs_access_history: Vec::new(),
last_modified_docs: [None, None], last_modified_docs: [None, None],
object_selections: Vec::new(), object_selections: Vec::new(),
gutters: gutter_types, gutters,
doc_revisions: HashMap::new(), doc_revisions: HashMap::new(),
} }
} }
@ -154,11 +160,12 @@ impl View {
} }
pub fn gutters(&self) -> &[GutterType] { pub fn gutters(&self) -> &[GutterType] {
&self.gutters &self.gutters.layout
} }
pub fn gutter_offset(&self, doc: &Document) -> u16 { pub fn gutter_offset(&self, doc: &Document) -> u16 {
self.gutters self.gutters
.layout
.iter() .iter()
.map(|gutter| gutter.width(self, doc) as u16) .map(|gutter| gutter.width(self, doc) as u16)
.sum() .sum()
@ -380,8 +387,6 @@ impl View {
// } // }
/// Applies a [`Transaction`] to the view. /// Applies a [`Transaction`] to the view.
/// Instead of calling this function directly, use [crate::apply_transaction]
/// which applies a transaction to the [`Document`] and view together.
pub fn apply(&mut self, transaction: &Transaction, doc: &mut Document) { pub fn apply(&mut self, transaction: &Transaction, doc: &mut Document) {
self.jumps.apply(transaction, doc); self.jumps.apply(transaction, doc);
self.doc_revisions self.doc_revisions
@ -416,18 +421,19 @@ impl View {
mod tests { mod tests {
use super::*; use super::*;
use helix_core::Rope; use helix_core::Rope;
const OFFSET: u16 = 3; // 1 diagnostic + 2 linenr (< 100 lines)
const OFFSET_WITHOUT_LINE_NUMBERS: u16 = 1; // 1 diagnostic // 1 diagnostic + 1 spacer + 3 linenr (< 1000 lines) + 1 spacer + 1 diff
// const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum(); const DEFAULT_GUTTER_OFFSET: u16 = 7;
// 1 diagnostics + 1 spacer + 1 gutter
const DEFAULT_GUTTER_OFFSET_ONLY_DIAGNOSTICS: u16 = 3;
use crate::document::Document; use crate::document::Document;
use crate::editor::GutterType; use crate::editor::{GutterConfig, GutterLineNumbersConfig, GutterType};
#[test] #[test]
fn test_text_pos_at_screen_coords() { fn test_text_pos_at_screen_coords() {
let mut view = View::new( let mut view = View::new(DocumentId::default(), GutterConfig::default());
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
view.area = Rect::new(40, 40, 40, 40); view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("abc\n\tdef"); let rope = Rope::from_str("abc\n\tdef");
let doc = Document::from(rope, None); let doc = Document::from(rope, None);
@ -447,24 +453,24 @@ mod tests {
assert_eq!(view.text_pos_at_screen_coords(&doc, 78, 41, 4), None); assert_eq!(view.text_pos_at_screen_coords(&doc, 78, 41, 4), None);
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 3, 4), view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 3, 4),
Some(3) Some(3)
); );
assert_eq!(view.text_pos_at_screen_coords(&doc, 40, 80, 4), Some(3)); assert_eq!(view.text_pos_at_screen_coords(&doc, 40, 80, 4), Some(3));
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET + 1, 4), view.text_pos_at_screen_coords(&doc, 41, 40 + DEFAULT_GUTTER_OFFSET + 1, 4),
Some(4) Some(4)
); );
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET + 4, 4), view.text_pos_at_screen_coords(&doc, 41, 40 + DEFAULT_GUTTER_OFFSET + 4, 4),
Some(5) Some(5)
); );
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET + 7, 4), view.text_pos_at_screen_coords(&doc, 41, 40 + DEFAULT_GUTTER_OFFSET + 7, 4),
Some(8) Some(8)
); );
@ -473,19 +479,36 @@ mod tests {
#[test] #[test]
fn test_text_pos_at_screen_coords_without_line_numbers_gutter() { fn test_text_pos_at_screen_coords_without_line_numbers_gutter() {
let mut view = View::new(DocumentId::default(), vec![GutterType::Diagnostics]); let mut view = View::new(
DocumentId::default(),
GutterConfig {
layout: vec![GutterType::Diagnostics],
line_numbers: GutterLineNumbersConfig::default(),
},
);
view.area = Rect::new(40, 40, 40, 40); view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("abc\n\tdef"); let rope = Rope::from_str("abc\n\tdef");
let doc = Document::from(rope, None); let doc = Document::from(rope, None);
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET_WITHOUT_LINE_NUMBERS + 1, 4), view.text_pos_at_screen_coords(
&doc,
41,
40 + DEFAULT_GUTTER_OFFSET_ONLY_DIAGNOSTICS + 1,
4
),
Some(4) Some(4)
); );
} }
#[test] #[test]
fn test_text_pos_at_screen_coords_without_any_gutters() { fn test_text_pos_at_screen_coords_without_any_gutters() {
let mut view = View::new(DocumentId::default(), vec![]); let mut view = View::new(
DocumentId::default(),
GutterConfig {
layout: vec![],
line_numbers: GutterLineNumbersConfig::default(),
},
);
view.area = Rect::new(40, 40, 40, 40); view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("abc\n\tdef"); let rope = Rope::from_str("abc\n\tdef");
let doc = Document::from(rope, None); let doc = Document::from(rope, None);
@ -494,76 +517,70 @@ mod tests {
#[test] #[test]
fn test_text_pos_at_screen_coords_cjk() { fn test_text_pos_at_screen_coords_cjk() {
let mut view = View::new( let mut view = View::new(DocumentId::default(), GutterConfig::default());
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
view.area = Rect::new(40, 40, 40, 40); view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("Hi! こんにちは皆さん"); let rope = Rope::from_str("Hi! こんにちは皆さん");
let doc = Document::from(rope, None); let doc = Document::from(rope, None);
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET, 4), view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET, 4),
Some(0) Some(0)
); );
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 4, 4), view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 4, 4),
Some(4) Some(4)
); );
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 5, 4), view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 5, 4),
Some(4) Some(4)
); );
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 6, 4), view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 6, 4),
Some(5) Some(5)
); );
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 7, 4), view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 7, 4),
Some(5) Some(5)
); );
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 8, 4), view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 8, 4),
Some(6) Some(6)
); );
} }
#[test] #[test]
fn test_text_pos_at_screen_coords_graphemes() { fn test_text_pos_at_screen_coords_graphemes() {
let mut view = View::new( let mut view = View::new(DocumentId::default(), GutterConfig::default());
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
view.area = Rect::new(40, 40, 40, 40); view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("Hèl̀l̀ò world!"); let rope = Rope::from_str("Hèl̀l̀ò world!");
let doc = Document::from(rope, None); let doc = Document::from(rope, None);
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET, 4), view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET, 4),
Some(0) Some(0)
); );
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 1, 4), view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 1, 4),
Some(1) Some(1)
); );
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 2, 4), view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 2, 4),
Some(3) Some(3)
); );
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 3, 4), view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 3, 4),
Some(5) Some(5)
); );
assert_eq!( assert_eq!(
view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 4, 4), view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 4, 4),
Some(7) Some(7)
); );
} }

@ -473,7 +473,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-html", rev = "29f53
name = "python" name = "python"
scope = "source.python" scope = "source.python"
injection-regex = "python" injection-regex = "python"
file-types = ["py"] file-types = ["py","pyi","py3","pyw","ptl",".pythonstartup",".pythonrc","SConstruct"]
shebangs = ["python"] shebangs = ["python"]
roots = [] roots = []
comment-token = "#" comment-token = "#"
@ -652,7 +652,7 @@ name = "java"
scope = "source.java" scope = "source.java"
injection-regex = "java" injection-regex = "java"
file-types = ["java"] file-types = ["java"]
roots = ["pom.xml"] roots = ["pom.xml", "build.gradle"]
language-server = { command = "jdtls" } language-server = { command = "jdtls" }
indent = { tab-width = 4, unit = " " } indent = { tab-width = 4, unit = " " }
@ -717,6 +717,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-ocaml", rev = "23d4
[[language]] [[language]]
name = "lua" name = "lua"
injection-regex = "lua"
scope = "source.lua" scope = "source.lua"
file-types = ["lua"] file-types = ["lua"]
shebangs = ["lua"] shebangs = ["lua"]
@ -877,7 +878,7 @@ source = { git = "https://github.com/uyha/tree-sitter-cmake", rev = "6e51463ef30
[[language]] [[language]]
name = "make" name = "make"
scope = "source.make" scope = "source.make"
file-types = ["Makefile", "makefile", "mk", "justfile", ".justfile"] file-types = ["Makefile", "makefile", "mk", "Justfile", "justfile", ".justfile"]
injection-regex = "(make|makefile|Makefile|mk|just)" injection-regex = "(make|makefile|Makefile|mk|just)"
roots = [] roots = []
comment-token = "#" comment-token = "#"
@ -1018,7 +1019,7 @@ source = { git = "https://github.com/Flakebi/tree-sitter-tablegen", rev = "568dd
name = "markdown" name = "markdown"
scope = "source.md" scope = "source.md"
injection-regex = "md|markdown" injection-regex = "md|markdown"
file-types = ["md", "markdown"] file-types = ["md", "markdown", "PULLREQ_EDITMSG"]
roots = [".marksman.toml"] roots = [".marksman.toml"]
language-server = { command = "marksman", args=["server"] } language-server = { command = "marksman", args=["server"] }
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
@ -1065,7 +1066,7 @@ config = { "isHttpEnabled" = true }
[[grammar]] [[grammar]]
name = "scala" name = "scala"
source = { git = "https://github.com/tree-sitter/tree-sitter-scala", rev = "db1c8c23d7996476a791db85a0d292084c19c232" } source = { git = "https://github.com/tree-sitter/tree-sitter-scala", rev = "f6bbf35de41653b409ca9a3537a154f2b095ef64" }
[[language]] [[language]]
name = "dockerfile" name = "dockerfile"
@ -1602,7 +1603,7 @@ scope = "source.v"
file-types = ["v", "vv"] file-types = ["v", "vv"]
shebangs = ["v run"] shebangs = ["v run"]
roots = ["v.mod"] roots = ["v.mod"]
language-server = { command = "vls", args = [] } language-server = { command = "v", args = ["ls"] }
auto-format = true auto-format = true
comment-token = "//" comment-token = "//"
indent = { tab-width = 4, unit = "\t" } indent = { tab-width = 4, unit = "\t" }
@ -1777,6 +1778,7 @@ auto-format = true
comment-token = "//" comment-token = "//"
language-server = { command = "cuelsp" } language-server = { command = "cuelsp" }
indent = { tab-width = 4, unit = "\t" } indent = { tab-width = 4, unit = "\t" }
formatter = { command = "cue", args = ["fmt", "-"] }
[[grammar]] [[grammar]]
name = "cue" name = "cue"
@ -1979,7 +1981,7 @@ roots = []
[[grammar]] [[grammar]]
name = "xml" name = "xml"
source = { git = "https://github.com/RenjiSann/tree-sitter-xml", rev = "422528a43630db6dcc1e222d1c5ee3babd559473" } source = { git = "https://github.com/RenjiSann/tree-sitter-xml", rev = "48a7c2b6fb9d515577e115e6788937e837815651" }
[[language]] [[language]]
name = "wit" name = "wit"
@ -2108,3 +2110,13 @@ formatter = { command = "dhall" , args = ["format"] }
[[grammar]] [[grammar]]
name = "dhall" name = "dhall"
source = { git = "https://github.com/jbellerb/tree-sitter-dhall", rev = "affb6ee38d629c9296749767ab832d69bb0d9ea8" } source = { git = "https://github.com/jbellerb/tree-sitter-dhall", rev = "affb6ee38d629c9296749767ab832d69bb0d9ea8" }
[[language]]
name = "sage"
scope = "source.sage"
file-types = ["sage"]
injection-regex = "sage"
roots = []
comment-token = "#"
indent = { tab-width = 4, unit = " " }
grammar = "python"

@ -6,6 +6,8 @@
(indented_string_expression (string_fragment) @injection.content)) (indented_string_expression (string_fragment) @injection.content))
(#set! injection.combined)) (#set! injection.combined))
; Common attribute keys corresponding to scripts,
; such as those of stdenv.mkDerivation.
((binding ((binding
attrpath: (attrpath (identifier) @_path) attrpath: (attrpath (identifier) @_path)
expression: (indented_string_expression expression: (indented_string_expression
@ -14,6 +16,25 @@
(#set! injection.language "bash") (#set! injection.language "bash")
(#set! injection.combined)) (#set! injection.combined))
; builtins.{match,split} regex str
; Example: nix/tests/lang/eval-okay-regex-{match,split}.nix
((apply_expression
function: (_) @_func
argument: (indented_string_expression (string_fragment) @injection.content))
(#match? @_func "(^|\\.)match|split$")
(#set! injection.language "regex")
(#set! injection.combined))
; builtins.fromJSON json
; Example: nix/tests/lang/eval-okay-fromjson.nix
((apply_expression
function: (_) @_func
argument: (indented_string_expression (string_fragment) @injection.content))
(#match? @_func "(^|\\.)fromJSON$")
(#set! injection.language "json")
(#set! injection.combined))
; trivial-builders.nix pkgs.writeShellScript[Bin] name content
((apply_expression ((apply_expression
function: (apply_expression function: (_) @_func) function: (apply_expression function: (_) @_func)
argument: (indented_string_expression (string_fragment) @injection.content)) argument: (indented_string_expression (string_fragment) @injection.content))
@ -21,6 +42,8 @@
(#set! injection.language "bash") (#set! injection.language "bash")
(#set! injection.combined)) (#set! injection.combined))
; trivial-builders.nix, aliases.nix
; pkgs.runCommand[[No]CC][Local] name attrs content
(apply_expression (apply_expression
(apply_expression (apply_expression
function: (apply_expression function: (apply_expression
@ -30,6 +53,7 @@
(#set! injection.language "bash") (#set! injection.language "bash")
(#set! injection.combined)) (#set! injection.combined))
; trivial-builders.nix pkgs.writeShellApplication { text = content; }
(apply_expression (apply_expression
function: ((_) @_func) function: ((_) @_func)
argument: (_ (_)* (_ (_)* (binding argument: (_ (_)* (_ (_)* (binding
@ -40,3 +64,89 @@
(#match? @_path "^text$") (#match? @_path "^text$")
(#set! injection.language "bash") (#set! injection.language "bash")
(#set! injection.combined)) (#set! injection.combined))
; trivial-builders.nix pkgs.writeCBin name content
((apply_expression
function: (apply_expression function: (_) @_func)
argument: (indented_string_expression (string_fragment) @injection.content))
(#match? @_func "(^|\\.)writeC(Bin)?$")
(#set! injection.language "c")
(#set! injection.combined))
; pkgs.writers.* usage examples: nixpkgs/pkgs/build-support/writers/test.nix
; pkgs.writers.write{Bash,Dash}[Bin] name content
((apply_expression
function: (apply_expression function: (_) @_func)
argument: (indented_string_expression (string_fragment) @injection.content))
(#match? @_func "(^|\\.)write[BD]ash(Bin)?$")
(#set! injection.language "bash")
(#set! injection.combined))
; pkgs.writers.writeFish[Bin] name content
((apply_expression
function: (apply_expression function: (_) @_func)
argument: (indented_string_expression (string_fragment) @injection.content))
(#match? @_func "(^|\\.)writeFish(Bin)?$")
(#set! injection.language "fish")
(#set! injection.combined))
; pkgs.writers.writeRust[Bin] name attrs content
(apply_expression
(apply_expression
function: (apply_expression
function: ((_) @_func)))
argument: (indented_string_expression (string_fragment) @injection.content)
(#match? @_func "(^|\\.)writeRust(Bin)?$")
(#set! injection.language "rust")
(#set! injection.combined))
; pkgs.writers.writeHaskell[Bin] name attrs content
(apply_expression
(apply_expression
function: (apply_expression
function: ((_) @_func)))
argument: (indented_string_expression (string_fragment) @injection.content)
(#match? @_func "(^|\\.)writeHaskell(Bin)?$")
(#set! injection.language "haskell")
(#set! injection.combined))
; pkgs.writers.writeJS[Bin] name attrs content
(apply_expression
(apply_expression
function: (apply_expression
function: ((_) @_func)))
argument: (indented_string_expression (string_fragment) @injection.content)
(#match? @_func "(^|\\.)writeJS(Bin)?$")
(#set! injection.language "javascript")
(#set! injection.combined))
; pkgs.writers.writePerl[Bin] name attrs content
(apply_expression
(apply_expression
function: (apply_expression
function: ((_) @_func)))
argument: (indented_string_expression (string_fragment) @injection.content)
(#match? @_func "(^|\\.)writePerl(Bin)?$")
(#set! injection.language "perl")
(#set! injection.combined))
; pkgs.writers.write{Python,PyPy}{2,3}[Bin] name attrs content
(apply_expression
(apply_expression
function: (apply_expression
function: ((_) @_func)))
argument: (indented_string_expression (string_fragment) @injection.content)
(#match? @_func "(^|\\.)write(Python|PyPy)[23](Bin)?$")
(#set! injection.language "python")
(#set! injection.combined))
; pkgs.writers.writeFSharp[Bin] name content
; No query available for f-sharp as of the time of writing
; See: https://github.com/helix-editor/helix/issues/4943
; ((apply_expression
; function: (apply_expression function: (_) @_func)
; argument: (indented_string_expression (string_fragment) @injection.content))
; (#match? @_func "(^|\\.)writeFSharp(Bin)?$")
; (#set! injection.language "f-sharp")
; (#set! injection.combined))

@ -0,0 +1,2 @@
((comment) @injection.content
(#set! injection.language "comment"))

@ -112,6 +112,9 @@
(generic_function (generic_function
function: (identifier) @function) function: (identifier) @function)
(interpolated_string_expression
interpolator: (identifier) @function)
( (
(identifier) @function.builtin (identifier) @function.builtin
(#match? @function.builtin "^super$") (#match? @function.builtin "^super$")

@ -52,6 +52,7 @@
"markup.list" = "my_white2" "markup.list" = "my_white2"
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = "my_turquoise2" "markup.link.url" = "my_turquoise2"
"markup.link.text" = "my_white2" "markup.link.text" = "my_white2"
"markup.quote" = "my_brown" "markup.quote" = "my_brown"

@ -37,6 +37,7 @@
"markup.list" = "base08" "markup.list" = "base08"
"markup.bold" = { fg = "base0A", modifiers = ["bold"] } "markup.bold" = { fg = "base0A", modifiers = ["bold"] }
"markup.italic" = { fg = "base0E", modifiers = ["italic"] } "markup.italic" = { fg = "base0E", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "base09", modifiers = ["underlined"] } "markup.link.url" = { fg = "base09", modifiers = ["underlined"] }
"markup.link.text" = "base08" "markup.link.text" = "base08"
"markup.quote" = "base0C" "markup.quote" = "base0C"

@ -37,6 +37,7 @@
"markup.list" = "base08" "markup.list" = "base08"
"markup.bold" = { fg = "base0A", modifiers = ["bold"] } "markup.bold" = { fg = "base0A", modifiers = ["bold"] }
"markup.italic" = { fg = "base0E", modifiers = ["italic"] } "markup.italic" = { fg = "base0E", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "base09", modifiers = ["underlined"] } "markup.link.url" = { fg = "base09", modifiers = ["underlined"] }
"markup.link.text" = "base08" "markup.link.text" = "base08"
"markup.quote" = "base0C" "markup.quote" = "base0C"

@ -34,6 +34,7 @@
"markup.list" = "light-red" "markup.list" = "light-red"
"markup.bold" = { fg = "light-yellow", modifiers = ["bold"] } "markup.bold" = { fg = "light-yellow", modifiers = ["bold"] }
"markup.italic" = { fg = "light-magenta", modifiers = ["italic"] } "markup.italic" = { fg = "light-magenta", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "yellow", modifiers = ["underlined"] } "markup.link.url" = { fg = "yellow", modifiers = ["underlined"] }
"markup.link.text" = "light-red" "markup.link.text" = "light-red"
"markup.quote" = "light-cyan" "markup.quote" = "light-cyan"

@ -45,6 +45,7 @@
"markup.list" = "light-red" "markup.list" = "light-red"
"markup.bold" = { fg = "light-yellow", modifiers = ["bold"] } "markup.bold" = { fg = "light-yellow", modifiers = ["bold"] }
"markup.italic" = { fg = "light-magenta", modifiers = ["italic"] } "markup.italic" = { fg = "light-magenta", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "yellow", underline = { color = "yellow", style = "line"} } "markup.link.url" = { fg = "yellow", underline = { color = "yellow", style = "line"} }
"markup.link.text" = "light-red" "markup.link.text" = "light-red"
"markup.quote" = "light-cyan" "markup.quote" = "light-cyan"

@ -31,6 +31,7 @@
"markup.list" = "bogster-red" "markup.list" = "bogster-red"
"markup.bold" = { fg = "bogster-yellow", modifiers = ["bold"] } "markup.bold" = { fg = "bogster-yellow", modifiers = ["bold"] }
"markup.italic" = { fg = "bogster-purp", modifiers = ["italic"] } "markup.italic" = { fg = "bogster-purp", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "bogster-yellow", modifiers = ["underlined"] } "markup.link.url" = { fg = "bogster-yellow", modifiers = ["underlined"] }
"markup.link.text" = "bogster-red" "markup.link.text" = "bogster-red"
"markup.quote" = "bogster-teal" "markup.quote" = "bogster-teal"

@ -31,6 +31,7 @@
"markup.list" = "bogster-red" "markup.list" = "bogster-red"
"markup.bold" = { fg = "bogster-yellow", modifiers = ["bold"] } "markup.bold" = { fg = "bogster-yellow", modifiers = ["bold"] }
"markup.italic" = { fg = "magenta", modifiers = ["italic"] } "markup.italic" = { fg = "magenta", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "bogster-yellow", modifiers = ["underlined"] } "markup.link.url" = { fg = "bogster-yellow", modifiers = ["underlined"] }
"markup.link.text" = "bogster-red" "markup.link.text" = "bogster-red"
"markup.quote" = "bogster-lblue" "markup.quote" = "bogster-lblue"

@ -22,6 +22,7 @@
"markup.list" = { fg = "bubblegum" } "markup.list" = { fg = "bubblegum" }
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "violet", modifiers = ["underlined"] } "markup.link.url" = { fg = "violet", modifiers = ["underlined"] }
"markup.link.text" = { fg = "violet" } "markup.link.text" = { fg = "violet" }
"markup.quote" = { fg = "berry_desaturated" } "markup.quote" = { fg = "berry_desaturated" }

@ -50,6 +50,7 @@
"markup.list" = "mauve" "markup.list" = "mauve"
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "rosewater", modifiers = ["italic", "underlined"] } "markup.link.url" = { fg = "rosewater", modifiers = ["italic", "underlined"] }
"markup.link.text" = "blue" "markup.link.text" = "blue"
"markup.raw" = "flamingo" "markup.raw" = "flamingo"

@ -56,6 +56,7 @@
"markup.list" = "white" "markup.list" = "white"
"markup.bold" = { fg = "white", modifiers = ["bold"] } "markup.bold" = { fg = "white", modifiers = ["bold"] }
"markup.italic" = { fg = "white", modifiers = ["italic"] } "markup.italic" = { fg = "white", modifiers = ["italic"] }
"markup.strikethrough" = { fg = "white", modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "lightblue", modifiers = ["underlined"] } "markup.link.url" = { fg = "lightblue", modifiers = ["underlined"] }
"markup.link.text" = "white" "markup.link.text" = "white"
"markup.quote" = "darkgreen" "markup.quote" = "darkgreen"

@ -84,6 +84,7 @@
"markup.list" = "pink" "markup.list" = "pink"
"markup.bold" = { fg = "emerald_green", modifiers = ["bold"] } "markup.bold" = { fg = "emerald_green", modifiers = ["bold"] }
"markup.italic" = { fg = "blue", modifiers = ["italic"] } "markup.italic" = { fg = "blue", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "blue", underline = { color = "blue", style = "line" } } "markup.link.url" = { fg = "blue", underline = { color = "blue", style = "line" } }
"markup.link.text" = "pink" "markup.link.text" = "pink"
"markup.quote" = "yellow" "markup.quote" = "yellow"

@ -44,6 +44,7 @@
"markup.list" = "blue3" "markup.list" = "blue3"
"markup.bold" = { fg = "blue2", modifiers = ["bold"] } "markup.bold" = { fg = "blue2", modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { modifiers = ["underlined"] } "markup.link.url" = { modifiers = ["underlined"] }
"markup.link.text" = "orange" "markup.link.text" = "orange"
"markup.quote" = "dark_green" "markup.quote" = "dark_green"

@ -29,6 +29,7 @@
'markup.bold' = { fg = 'orange', modifiers = ['bold'] } 'markup.bold' = { fg = 'orange', modifiers = ['bold'] }
'markup.italic' = { fg = 'magenta', modifiers = ['italic'] } 'markup.italic' = { fg = 'magenta', modifiers = ['italic'] }
'markup.strikethrough' = { modifiers = ['crossed_out'] }
'markup.heading' = { fg = 'red' } 'markup.heading' = { fg = 'red' }
'markup.link' = { fg = 'orange' } 'markup.link' = { fg = 'orange' }
'markup.link.url' = { fg = 'magenta' } 'markup.link.url' = { fg = 'magenta' }

@ -40,7 +40,7 @@
"ui.text" = { fg = "foreground" } "ui.text" = { fg = "foreground" }
"ui.text.focus" = { fg = "cyan" } "ui.text.focus" = { fg = "cyan" }
"ui.window" = { fg = "foreground" } "ui.window" = { fg = "foreground" }
"ui.virtual.whitespace" = { fg = "comment" } "ui.virtual.whitespace" = { fg = "subtle" }
"ui.virtual.ruler" = { bg = "background_dark"} "ui.virtual.ruler" = { bg = "background_dark"}
"error" = { fg = "red" } "error" = { fg = "red" }
@ -50,6 +50,7 @@
"markup.list" = "cyan" "markup.list" = "cyan"
"markup.bold" = { fg = "orange", modifiers = ["bold"] } "markup.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.italic" = { fg = "yellow", modifiers = ["italic"] } "markup.italic" = { fg = "yellow", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = "cyan" "markup.link.url" = "cyan"
"markup.link.text" = "pink" "markup.link.text" = "pink"
"markup.quote" = { fg = "yellow", modifiers = ["italic"] } "markup.quote" = { fg = "yellow", modifiers = ["italic"] }
@ -63,6 +64,7 @@ background = "#282a36"
background_dark = "#21222c" background_dark = "#21222c"
primary_highlight = "#800049" primary_highlight = "#800049"
secondary_highlight = "#4d4f66" secondary_highlight = "#4d4f66"
subtle = "#424450"
foreground = "#f8f8f2" foreground = "#f8f8f2"
comment = "#6272a4" comment = "#6272a4"
red = "#ff5555" red = "#ff5555"
@ -72,3 +74,4 @@ green = "#50fa7b"
purple = "#bd93f9" purple = "#bd93f9"
cyan = "#8be9fd" cyan = "#8be9fd"
pink = "#ff79c6" pink = "#ff79c6"

@ -50,6 +50,7 @@
"markup.list" = "cyan" "markup.list" = "cyan"
"markup.bold" = { fg = "orange", modifiers = ["bold"] } "markup.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.italic" = { fg = "yellow", modifiers = ["italic"] } "markup.italic" = { fg = "yellow", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = "cyan" "markup.link.url" = "cyan"
"markup.link.text" = "pink" "markup.link.text" = "pink"
"markup.quote" = { fg = "yellow", modifiers = ["italic"] } "markup.quote" = { fg = "yellow", modifiers = ["italic"] }

@ -37,6 +37,7 @@
"markup.list" = { fg = "black" } "markup.list" = { fg = "black" }
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "royalblue3", modifiers = ["underlined"] } "markup.link.url" = { fg = "royalblue3", modifiers = ["underlined"] }
"markup.link.text" = { fg = "royalblue3" } "markup.link.text" = { fg = "royalblue3" }
"markup.quote" = { fg = "gray60" } "markup.quote" = { fg = "gray60" }

@ -46,6 +46,7 @@
"markup.list" = "red" "markup.list" = "red"
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "blue", modifiers = ["underlined"] } "markup.link.url" = { fg = "blue", modifiers = ["underlined"] }
"markup.link.text" = "purple" "markup.link.text" = "purple"
"markup.quote" = "grey2" "markup.quote" = "grey2"

@ -46,6 +46,7 @@
"markup.list" = "red" "markup.list" = "red"
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "blue", modifiers = ["underlined"] } "markup.link.url" = { fg = "blue", modifiers = ["underlined"] }
"markup.link.text" = "purple" "markup.link.text" = "purple"
"markup.quote" = "grey2" "markup.quote" = "grey2"

@ -37,14 +37,15 @@
"markup.raw" = { fg = "orange_text", bg = "orange_bg" } "markup.raw" = { fg = "orange_text", bg = "orange_bg" }
"markup.raw.inline" = { fg = "orange_text", bg = "orange_bg" } "markup.raw.inline" = { fg = "orange_text", bg = "orange_bg" }
"markup.raw.block" = { fg = "orange_text", bg = "orange_bg" } "markup.raw.block" = { fg = "orange_text", bg = "orange_bg" }
"markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "blue_text", bg = "blue_bg", modifiers = [ "markup.link.url" = { fg = "blue_text", bg = "blue_bg", modifiers = [
"underlined", "underlined",
] } ] }
"markup.link.label" = { fg = "blue_text", bg = "blue_bg" } "markup.link.label" = { fg = "blue_text", bg = "blue_bg" }
"markup.link.text" = { fg = "blue_text", bg = "blue_bg" } "markup.link.text" = { fg = "blue_text", bg = "blue_bg" }
"markup.quote" = { fg = "teal_text", bg = "teal_bg" } "markup.quote" = { fg = "teal_text", bg = "teal_bg" }
"markup.bold" = { modifiers = ["bold"] }
"markup.list" = { fg = "purple_text", bg = "purple_bg" } "markup.list" = { fg = "purple_text", bg = "purple_bg" }
"ui.background" = { fg = "base1", bg = "base7" } "ui.background" = { fg = "base1", bg = "base7" }

@ -46,6 +46,7 @@
# "markup.normal" = {} # .completion / .hover # "markup.normal" = {} # .completion / .hover
"markup.bold" = { fg = "lightest", modifiers = ["bold"] } "markup.bold" = { fg = "lightest", modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.heading" = { fg = "cyan", modifiers = ["bold"] } # .marker / .1 / .2 / .3 / .4 / .5 / .6 "markup.heading" = { fg = "cyan", modifiers = ["bold"] } # .marker / .1 / .2 / .3 / .4 / .5 / .6
"markup.list" = "pink" # .unnumbered / .numbered "markup.list" = "pink" # .unnumbered / .numbered
"markup.list.numbered" = "cyan" "markup.list.numbered" = "cyan"

@ -9,9 +9,9 @@ keyword = "scale.red.3"
namespace = "scale.orange.2" namespace = "scale.orange.2"
punctuation = "fg.default" punctuation = "fg.default"
"punctuation.delimiter" = "fg.default" "punctuation.delimiter" = "fg.default"
operator = "scale.red.3" operator = "scale.blue.1"
special = "scale.blue.1" special = "scale.blue.1"
"variable.other.member" = "fg.default" "variable.other.member" = "scale.blue.1"
variable = "fg.default" variable = "fg.default"
"variable.parameter" = "scale.orange.2" "variable.parameter" = "scale.orange.2"
"variable.builtin" = "scale.red.3" "variable.builtin" = "scale.red.3"
@ -33,6 +33,7 @@ label = "scale.red.3"
"markup.heading" = "scale.blue.2" "markup.heading" = "scale.blue.2"
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { modifiers = ["underlined"] } "markup.link.url" = { modifiers = ["underlined"] }
"markup.link.text" = { fg = "scale.blue.1", modifiers = ["underlined"] } "markup.link.text" = { fg = "scale.blue.1", modifiers = ["underlined"] }
"markup.raw" = "scale.blue.2" "markup.raw" = "scale.blue.2"

@ -9,9 +9,9 @@ keyword = "scale.red.5"
namespace = "scale.orange.6" namespace = "scale.orange.6"
punctuation = "fg.default" punctuation = "fg.default"
"punctuation.delimiter" = "fg.default" "punctuation.delimiter" = "fg.default"
operator = "scale.red.5" operator = "scale.blue.8"
special = "scale.blue.8" special = "scale.blue.8"
"variable.other.member" = "fg.default" "variable.other.member" = "scale.blue.8"
variable = "fg.default" variable = "fg.default"
"variable.parameter" = "scale.orange.6" "variable.parameter" = "scale.orange.6"
"variable.builtin" = "scale.red.5" "variable.builtin" = "scale.red.5"
@ -33,6 +33,7 @@ label = "scale.red.5"
"markup.heading" = "scale.blue.6" "markup.heading" = "scale.blue.6"
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { modifiers = ["underlined"] } "markup.link.url" = { modifiers = ["underlined"] }
"markup.link.text" = { fg = "scale.blue.8", modifiers = ["underlined"] } "markup.link.text" = { fg = "scale.blue.8", modifiers = ["underlined"] }
"markup.raw" = "scale.blue.6" "markup.raw" = "scale.blue.6"

@ -33,10 +33,10 @@
"diff.delta" = "orange1" "diff.delta" = "orange1"
"diff.minus" = "red1" "diff.minus" = "red1"
"warning" = { fg = "orange1", bg = "bg1" } "warning" = "orange1"
"error" = { fg = "red1", bg = "bg1" } "error" = "red1"
"info" = { fg = "aqua1", bg = "bg1" } "info" = "aqua1"
"hint" = { fg = "blue1", bg = "bg1" } "hint" = "blue1"
"ui.background" = { bg = "bg0" } "ui.background" = { bg = "bg0" }
"ui.linenr" = { fg = "bg4" } "ui.linenr" = { fg = "bg4" }
@ -69,6 +69,7 @@
"markup.heading" = "aqua1" "markup.heading" = "aqua1"
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "green1", modifiers = ["underlined"] } "markup.link.url" = { fg = "green1", modifiers = ["underlined"] }
"markup.link.text" = "red1" "markup.link.text" = "red1"
"markup.raw" = "red1" "markup.raw" = "red1"

@ -34,10 +34,10 @@
"diff.delta" = "orange1" "diff.delta" = "orange1"
"diff.minus" = "red1" "diff.minus" = "red1"
"warning" = { fg = "orange1", bg = "bg1" } "warning" = "orange1"
"error" = { fg = "red1", bg = "bg1" } "error" = "red1"
"info" = { fg = "aqua1", bg = "bg1" } "info" = "aqua1"
"hint" = { fg = "blue1", bg = "bg1" } "hint" = "blue1"
"diagnostic.error" = { underline = { style = "curl", color = "red0" } } "diagnostic.error" = { underline = { style = "curl", color = "red0" } }
"diagnostic.warning" = { underline = { style = "curl", color = "orange1" } } "diagnostic.warning" = { underline = { style = "curl", color = "orange1" } }
@ -70,6 +70,7 @@
"markup.heading" = "aqua1" "markup.heading" = "aqua1"
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "green1", modifiers = ["underlined"] } "markup.link.url" = { fg = "green1", modifiers = ["underlined"] }
"markup.link.text" = "red1" "markup.link.text" = "red1"
"markup.raw" = "red1" "markup.raw" = "red1"

@ -34,10 +34,10 @@
"diff.delta" = "orange1" "diff.delta" = "orange1"
"diff.minus" = "red1" "diff.minus" = "red1"
"warning" = { fg = "orange1", bg = "bg1" } "warning" = "orange1"
"error" = { fg = "red1", bg = "bg1" } "error" = "red1"
"info" = { fg = "aqua1", bg = "bg1" } "info" = "aqua1"
"hint" = { fg = "blue1", bg = "bg1" } "hint" = "blue1"
"ui.background" = { bg = "bg0" } "ui.background" = { bg = "bg0" }
"ui.linenr" = { fg = "bg4" } "ui.linenr" = { fg = "bg4" }
@ -70,6 +70,7 @@
"markup.heading" = "aqua1" "markup.heading" = "aqua1"
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "green1", modifiers = ["underlined"] } "markup.link.url" = { fg = "green1", modifiers = ["underlined"] }
"markup.link.text" = "red1" "markup.link.text" = "red1"
"markup.raw" = "red1" "markup.raw" = "red1"

@ -75,6 +75,7 @@
"markup.list" = { fg = "t4" } "markup.list" = { fg = "t4" }
"markup.bold" = { fg = "t4" } "markup.bold" = { fg = "t4" }
"markup.italic" = { fg = "t4" } "markup.italic" = { fg = "t4" }
"markup.strikethrough" = { fg = "t4", modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "t4", modifiers = ["underlined"] } "markup.link.url" = { fg = "t4", modifiers = ["underlined"] }
"markup.link.text" = { fg = "t4" } "markup.link.text" = { fg = "t4" }
"markup.quote" = { fg = "t4" } "markup.quote" = { fg = "t4" }

@ -33,6 +33,7 @@
"markup.list" = "red" "markup.list" = "red"
"markup.bold" = { fg = "yellow", modifiers = ["bold"] } "markup.bold" = { fg = "yellow", modifiers = ["bold"] }
"markup.italic" = { fg = "magenta", modifiers = ["italic"] } "markup.italic" = { fg = "magenta", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "yellow", modifiers = ["underlined"] } "markup.link.url" = { fg = "yellow", modifiers = ["underlined"] }
"markup.link.text" = "red" "markup.link.text" = "red"
"markup.quote" = "cyan" "markup.quote" = "cyan"

@ -14,30 +14,31 @@
"ui.linenr" = { fg = "sumiInk4" } "ui.linenr" = { fg = "sumiInk4" }
"ui.linenr.selected" = { fg = "roninYellow" } "ui.linenr.selected" = { fg = "roninYellow" }
"ui.virtual.ruler" = { bg = "sumiInk2" }
"ui.virtual.whitespace" = "waveBlue1"
"ui.virtual.indent-guide" = "sumiInk4" "ui.virtual.indent-guide" = "sumiInk4"
"ui.statusline" = { fg = "oldWhite", bg = "sumiInk0" } "ui.statusline" = { fg = "oldWhite", bg = "sumiInk0" }
"ui.statusline.inactive" = { fg = "fujiGray", bg = "sumiInk0" } "ui.statusline.inactive" = { fg = "fujiGray", bg = "sumiInk0" }
"ui.statusline.normal" = { fg = "sumiInk0", bg = "crystalBlue", modifiers = ["bold"] } "ui.statusline.normal" = { fg = "sumiInk0", bg = "crystalBlue", modifiers = ["bold"] }
"ui.statusline.insert" = { fg = "sumiInk0", bg = "autumnGreen" } "ui.statusline.insert" = { fg = "sumiInk0", bg = "autumnGreen", modifiers = ["bold"] }
"ui.statusline.select" = { fg = "sumiInk0", bg = "oniViolet" } "ui.statusline.select" = { fg = "sumiInk0", bg = "oniViolet", modifiers = ["bold"] }
"ui.bufferline" = { fg = "oldWhite", bg = "sumiInk0" } "ui.bufferline" = { fg = "oldWhite", bg = "sumiInk0" }
"ui.bufferline.inactive" = { fg = "fujiGray", bg = "sumiInk0" } "ui.bufferline.inactive" = { fg = "fujiGray", bg = "sumiInk0" }
"ui.popup" = { fg = "fujiWhite", bg = "sumiInk0" } "ui.popup" = { fg = "fujiWhite", bg = "sumiInk0" }
"ui.window" = { fg = "fujiWhite" } "ui.window" = { fg = "sumiInk0" }
"ui.help" = { fg = "fujiWhite", bg = "sumiInk1" } "ui.help" = { fg = "fujiWhite", bg = "sumiInk0" }
"ui.text" = "fujiWhite" "ui.text" = "fujiWhite"
"ui.text.focus" = { fg = "fujiWhite", bg = "waveBlue1", modifiers = ["bold"] } "ui.text.focus" = { fg = "fujiWhite", bg = "waveBlue1", modifiers = ["bold"] }
"ui.virtual" = "waveBlue1"
"ui.cursor" = { fg = "waveBlue1", bg = "fujiWhite"} "ui.cursor" = { fg = "waveBlue1", bg = "fujiWhite"}
"ui.cursor.primary" = { fg = "waveBlue1", bg = "seaFoam" } "ui.cursor.primary" = { fg = "waveBlue1", bg = "seaFoam" }
"ui.cursor.match" = { fg = "seaFoam", modifiers = ["bold"] } "ui.cursor.match" = { fg = "seaFoam", modifiers = ["bold"] }
"ui.highlight" = { fg = "fujiWhite", bg = "waveBlue2" } "ui.highlight" = { fg = "fujiWhite", bg = "waveBlue2" }
"ui.menu" = { fg = "fujiWhite", bg = "sumiInk1" } "ui.menu" = { fg = "fujiWhite", bg = "sumiInk0" }
"ui.menu.selected" = { fg = "fujiWhite", bg = "waveBlue1" } "ui.menu.selected" = { fg = "fujiWhite", bg = "waveBlue1", modifiers = ["bold"] }
"ui.cursorline.primary" = { bg = "sumiInk3"} "ui.cursorline.primary" = { bg = "sumiInk3"}
@ -96,6 +97,7 @@ hint = "dragonBlue"
"markup.list" = "oniViolet" "markup.list" = "oniViolet"
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "springBlue", modifiers = ["underlined"] } "markup.link.url" = { fg = "springBlue", modifiers = ["underlined"] }
"markup.link.text" = "crystalBlue" "markup.link.text" = "crystalBlue"
"markup.quote" = "seaFoam" "markup.quote" = "seaFoam"
@ -107,7 +109,8 @@ fujiWhite = "#DCD7BA" # default foreground
oldWhite = "#C8C093" # dark foreground, e.g. statuslines oldWhite = "#C8C093" # dark foreground, e.g. statuslines
sumiInk0 = "#16161D" # dark background, e.g. statuslines, floating windows sumiInk0 = "#16161D" # dark background, e.g. statuslines, floating windows
sumiInk1 = "#1F1F28" # default background sumiInk1 = "#1F1F28" # default background
sumiInk3 = "#363646" # lighter background, e.g. colorcolumns and folds sumiInk2 = "#2A2A37" # lighter background, e.g. colorcolumns, folds
sumiInk3 = "#363646" # lighter background, e.g. cursorline
sumiInk4 = "#54546D" # darker foreground, e.g. linenumbers, fold column sumiInk4 = "#54546D" # darker foreground, e.g. linenumbers, fold column
waveBlue1 = "#223249" # popup background, visual selection background waveBlue1 = "#223249" # popup background, visual selection background
waveBlue2 = "#2D4F67" # popup selection background, search background waveBlue2 = "#2D4F67" # popup selection background, search background

@ -67,8 +67,9 @@
"markup.heading" = { fg = "orange" } "markup.heading" = { fg = "orange" }
"markup.list" = { fg = "blue" } "markup.list" = { fg = "blue" }
"markup.bold" = { fg = "magenta" } "markup.bold" = { fg = "magenta", modifiers = ["bold"] }
"markup.italic" = { fg = "blue" } "markup.italic" = { fg = "blue", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "comment" , modifiers = ["underlined"] } "markup.link.url" = { fg = "comment" , modifiers = ["underlined"] }
"markup.link.text" = { fg = "comment" } "markup.link.text" = { fg = "comment" }
"markup.quote" = { fg = "yellow" } "markup.quote" = { fg = "yellow" }

@ -50,6 +50,7 @@
"markup.list" = "gray06" "markup.list" = "gray06"
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "green", modifiers = ["underlined"] } "markup.link.url" = { fg = "green", modifiers = ["underlined"] }
"markup.link.text" = { fg = "blue", modifiers = ["italic"] } "markup.link.text" = { fg = "blue", modifiers = ["italic"] }
"markup.raw" = "yellow" "markup.raw" = "yellow"

@ -46,6 +46,7 @@
"markup.list" = "red" "markup.list" = "red"
"markup.bold" = { fg = "yellow", modifiers = ["bold"] } "markup.bold" = { fg = "yellow", modifiers = ["bold"] }
"markup.italic" = { fg = "magenta", modifiers = ["italic"] } "markup.italic" = { fg = "magenta", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "yellow", modifiers = ["underlined"] } "markup.link.url" = { fg = "yellow", modifiers = ["underlined"] }
"markup.link.text" = "red" "markup.link.text" = "red"
"markup.quote" = "cyan" "markup.quote" = "cyan"

@ -0,0 +1,17 @@
inherits = "monokai"
"keyword.control.import" = { fg = "cyan", modifiers = ["italic"] }
"keyword.function" = { fg = "cyan", modifiers = ["italic"] }
"keyword.storage.type" = { fg = "cyan", modifiers = ["italic"] }
"namespace" = { fg = "text" }
"type" = { fg = "type", modifiers = ["bold"] }
"ui.statusline.normal" = { fg = "light-black", bg = "cyan" }
"ui.statusline.insert" = { fg = "light-black", bg = "green" }
"ui.statusline.select" = { fg = "light-black", bg = "purple" }
[palette]
cyan = "#66D9EF"
type = "#66D9EF"

@ -99,6 +99,7 @@
"markup.heading" = "green" "markup.heading" = "green"
"markup.bold" = { fg = "orange", modifiers = ["bold"] } "markup.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.italic" = { fg = "orange", modifiers = ["italic"] } "markup.italic" = { fg = "orange", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "orange", modifiers = ["underlined"] } "markup.link.url" = { fg = "orange", modifiers = ["underlined"] }
"markup.link.text" = "yellow" "markup.link.text" = "yellow"
"markup.quote" = "green" "markup.quote" = "green"

@ -96,6 +96,7 @@
"markup.heading" = "green" "markup.heading" = "green"
"markup.bold" = { fg = "orange", modifiers = ["bold"] } "markup.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.italic" = { fg = "orange", modifiers = ["italic"] } "markup.italic" = { fg = "orange", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "orange", modifiers = ["underlined"] } "markup.link.url" = { fg = "orange", modifiers = ["underlined"] }
"markup.link.text" = "yellow" "markup.link.text" = "yellow"
"markup.quote" = "green" "markup.quote" = "green"

@ -99,6 +99,7 @@
"markup.heading" = "green" "markup.heading" = "green"
"markup.bold" = { fg = "orange", modifiers = ["bold"] } "markup.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.italic" = { fg = "orange", modifiers = ["italic"] } "markup.italic" = { fg = "orange", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "orange", modifiers = ["underlined"] } "markup.link.url" = { fg = "orange", modifiers = ["underlined"] }
"markup.link.text" = "yellow" "markup.link.text" = "yellow"
"markup.quote" = "green" "markup.quote" = "green"

@ -96,6 +96,7 @@
"markup.heading" = "green" "markup.heading" = "green"
"markup.bold" = { fg = "orange", modifiers = ["bold"] } "markup.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.italic" = { fg = "orange", modifiers = ["italic"] } "markup.italic" = { fg = "orange", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "orange", modifiers = ["underlined"] } "markup.link.url" = { fg = "orange", modifiers = ["underlined"] }
"markup.link.text" = "yellow" "markup.link.text" = "yellow"
"markup.quote" = "green" "markup.quote" = "green"

@ -65,7 +65,7 @@
"variable.parameter" = "#f59762" "variable.parameter" = "#f59762"
# error # error
"error" = { bg = "magenta", fg = "yellow" } "error" = { fg = "red", modifiers = ["bold"] }
# annotations, decorators # annotations, decorators
"special" = "#f59762" "special" = "#f59762"
@ -88,7 +88,7 @@
# make diagnostic underlined, to distinguish with selection text. # make diagnostic underlined, to distinguish with selection text.
"diagnostic.warning" = { underline = { color = "orange", style = "curl" } } "diagnostic.warning" = { underline = { color = "orange", style = "curl" } }
"diagnostic.error" = { underline = { color = "magenta", style = "curl" } } # maybe should be red? "diagnostic.error" = { underline = { color = "red", style = "curl" } }
"diagnostic.info" = { underline = { color = "base8", style = "curl" } } "diagnostic.info" = { underline = { color = "base8", style = "curl" } }
"diagnostic.hint" = { underline = { color = "base8", style = "curl" } } "diagnostic.hint" = { underline = { color = "base8", style = "curl" } }
@ -96,6 +96,7 @@
"markup.heading" = "green" "markup.heading" = "green"
"markup.bold" = { fg = "orange", modifiers = ["bold"] } "markup.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.italic" = { fg = "orange", modifiers = ["italic"] } "markup.italic" = { fg = "orange", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "orange", modifiers = ["underlined"] } "markup.link.url" = { fg = "orange", modifiers = ["underlined"] }
"markup.link.text" = "yellow" "markup.link.text" = "yellow"
"markup.quote" = "green" "markup.quote" = "green"

@ -80,6 +80,7 @@
'markup.list' = { fg = 'pink' } 'markup.list' = { fg = 'pink' }
'markup.bold' = { fg = 'foreground', modifiers = ['bold'] } 'markup.bold' = { fg = 'foreground', modifiers = ['bold'] }
'markup.italic' = { fg = 'foreground', modifiers = ['italic'] } 'markup.italic' = { fg = 'foreground', modifiers = ['italic'] }
'markup.strikethrough' = { fg = 'foreground', modifiers = ['crossed_out'] }
'markup.link' = { fg = 'pink', modifiers = ['underlined'] } 'markup.link' = { fg = 'pink', modifiers = ['underlined'] }
'markup.link.url' = { fg = 'slate', modifiers = ['underlined'] } 'markup.link.url' = { fg = 'slate', modifiers = ['underlined'] }
'markup.quote' = { fg = 'green', modifiers = ['italic'] } 'markup.quote' = { fg = 'green', modifiers = ['italic'] }

@ -55,6 +55,7 @@
"markup.list" = { fg = "magenta", modifiers = ["bold"] } "markup.list" = { fg = "magenta", modifiers = ["bold"] }
"markup.bold" = { fg = "orange", modifiers = ["bold"] } "markup.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.italic" = { fg = "pink" } "markup.italic" = { fg = "pink" }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link" = { fg = "yellow-bright", modifiers = ["bold"] } "markup.link" = { fg = "yellow-bright", modifiers = ["bold"] }
"markup.quote" = { fg = "blue" } "markup.quote" = { fg = "blue" }

@ -150,6 +150,7 @@
'markup.bold' = { modifiers = ["bold"] } # Bold text. 'markup.bold' = { modifiers = ["bold"] } # Bold text.
'markup.italic' = { modifiers = ["italic"] } # Italicised text. 'markup.italic' = { modifiers = ["italic"] } # Italicised text.
"markup.strikethrough" = { modifiers = ["crossed_out"] } # Crossed out text.
'markup.link' = { fg = "light-blue", modifiers = ["underlined"] } 'markup.link' = { fg = "light-blue", modifiers = ["underlined"] }
'markup.link.url' = { } # Urls pointed to by links. 'markup.link.url' = { } # Urls pointed to by links.

@ -49,6 +49,7 @@
"markup.list" = "base08" "markup.list" = "base08"
"markup.quote" = "base0C" "markup.quote" = "base0C"
"markup.raw" = "base0B" "markup.raw" = "base0B"
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"diff.delta" = "base09" "diff.delta" = "base09"
"diff.plus" = "base0B" "diff.plus" = "base0B"

@ -95,6 +95,7 @@
"markup.list" = "nord9" "markup.list" = "nord9"
"markup.bold" = { modifiers = ["bold"] } "markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] } "markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.text" = "nord8" "markup.link.text" = "nord8"
"markup.raw" = "nord7" "markup.raw" = "nord7"

@ -30,6 +30,7 @@
"markup.raw.inline" = { fg = "green" } "markup.raw.inline" = { fg = "green" }
"markup.bold" = { fg = "gold", modifiers = ["bold"] } "markup.bold" = { fg = "gold", modifiers = ["bold"] }
"markup.italic" = { fg = "purple", modifiers = ["italic"] } "markup.italic" = { fg = "purple", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.list" = { fg = "red" } "markup.list" = { fg = "red" }
"markup.quote" = { fg = "yellow" } "markup.quote" = { fg = "yellow" }
"markup.link.url" = { fg = "cyan", modifiers = ["underlined"]} "markup.link.url" = { fg = "cyan", modifiers = ["underlined"]}

@ -31,6 +31,7 @@
"markup.raw.block" = { fg = "white" } "markup.raw.block" = { fg = "white" }
"markup.bold" = { fg = "gold", modifiers = ["bold"] } "markup.bold" = { fg = "gold", modifiers = ["bold"] }
"markup.italic" = { fg = "purple", modifiers = ["italic"] } "markup.italic" = { fg = "purple", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.list" = { fg = "red" } "markup.list" = { fg = "red" }
"markup.quote" = { fg = "yellow" } "markup.quote" = { fg = "yellow" }
"markup.link.url" = { fg = "blue", modifiers = ["underlined"]} "markup.link.url" = { fg = "blue", modifiers = ["underlined"]}

@ -48,6 +48,7 @@
"markup.raw.inline" = { fg = "green", bg = "grey-200" } "markup.raw.inline" = { fg = "green", bg = "grey-200" }
"markup.bold" = { fg = "yellow", modifiers = ["bold"] } "markup.bold" = { fg = "yellow", modifiers = ["bold"] }
"markup.italic" = { fg = "purple", modifiers = ["italic"] } "markup.italic" = { fg = "purple", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.list" = { fg = "light-blue" } "markup.list" = { fg = "light-blue" }
"markup.quote" = { fg = "gray" } "markup.quote" = { fg = "gray" }
"markup.link.url" = { fg = "cyan", modifiers = ["underlined"] } "markup.link.url" = { fg = "cyan", modifiers = ["underlined"] }

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save