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

pull/5534/head
David-Else 2 years 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,36 +27,35 @@ 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` |
| `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` | | `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` |
| `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` | | `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
| `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers. | `absolute` | | `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers. | `absolute` |
| `cursorline` | Highlight all lines with a cursor. | `false` | | `cursorline` | Highlight all lines with a cursor. | `false` |
| `cursorcolumn` | Highlight all columns with a cursor. | `false` | | `cursorcolumn` | Highlight all columns with a cursor. | `false` |
| `gutters` | Gutters to display: Available are `diagnostics` and `diff` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` | | `gutters` | Gutters to display: Available are `diagnostics` and `diff` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
| `auto-completion` | Enable automatic pop up of auto-completion. | `true` | | `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
| `auto-format` | Enable automatic formatting on save. | `true` | | `auto-format` | Enable automatic formatting on save. | `true` |
| `auto-save` | Enable automatic saving on the focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal. | `false` | | `auto-save` | Enable automatic saving on the focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal. | `false` |
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` | | `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` | | `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
| `auto-info` | Whether to display infoboxes | `true` | | `auto-info` | Whether to display infoboxes | `true` |
| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. | `false` | | `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. | `false` |
| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file. | `[]` | | `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file. | `[]` |
| `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` | | `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` |
| `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` | | `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` |
### `[editor.statusline]` Section ### `[editor.statusline]` Section
@ -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
@ -209,10 +207,10 @@ 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,7 +137,14 @@ 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).
3. Configure Helix's runtime files > 💡 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
**IMPORTANT**: The runtime files must be accessible to the newly created binary. **IMPORTANT**: The runtime files must be accessible to the newly created binary.
They are currently located in the source code `runtime` directory. To make them They are currently located in the source code `runtime` directory. To make them
@ -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,35 +27,35 @@
### 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` |
| `l`, `Right` | Move right | `move_char_right` | | `l`, `Right` | Move right | `move_char_right` |
| `w` | Move next word start | `move_next_word_start` | | `w` | Move next word start | `move_next_word_start` |
| `b` | Move previous word start | `move_prev_word_start` | | `b` | Move previous word start | `move_prev_word_start` |
| `e` | Move next word end | `move_next_word_end` | | `e` | Move next word end | `move_next_word_end` |
| `W` | Move next WORD start | `move_next_long_word_start` | | `W` | Move next WORD start | `move_next_long_word_start` |
| `B` | Move previous WORD start | `move_prev_long_word_start` | | `B` | Move previous WORD start | `move_prev_long_word_start` |
| `E` | Move next WORD end | `move_next_long_word_end` | | `E` | Move next WORD end | `move_next_long_word_end` |
| `t` | Find 'till next char | `find_till_char` | | `t` | Find 'till next char | `find_till_char` |
| `f` | Find next char | `find_next_char` | | `f` | Find next char | `find_next_char` |
| `T` | Find 'till previous char | `till_prev_char` | | `T` | Find 'till previous char | `till_prev_char` |
| `F` | Find previous char | `find_prev_char` | | `F` | Find previous char | `find_prev_char` |
| `G` | Go to line number `<n>` | `goto_line` | | `G` | Go to line number `<n>` | `goto_line` |
| `Alt-.` | Repeat last motion (`f`, `t` or `m`) | `repeat_last_motion` | | `Alt-.` | Repeat last motion (`f`, `t` or `m`) | `repeat_last_motion` |
| `Home` | Move to the start of the line | `goto_line_start` | | `Home` | Move to the start of the line | `goto_line_start` |
| `End` | Move to the end of the line | `goto_line_end` | | `End` | Move to the end of the line | `goto_line_end` |
| `Ctrl-b`, `PageUp` | Move page up | `page_up` | | `Ctrl-b`, `PageUp` | Move page up | `page_up` |
| `Ctrl-f`, `PageDown` | Move page down | `page_down` | | `Ctrl-f`, `PageDown` | Move page down | `page_down` |
| `Ctrl-u` | Move half page up | `half_page_up` | | `Ctrl-u` | Move half page up | `half_page_up` |
| `Ctrl-d` | Move half page down | `half_page_down` | | `Ctrl-d` | Move half page down | `half_page_down` |
| `Ctrl-i` | Jump forward on the jump list | `jump_forward` | | `Ctrl-i` | Jump forward on the jump list | `jump_forward` |
| `Ctrl-o` | Jump backward on the jump list | `jump_backward` | | `Ctrl-o` | Jump backward on the jump list | `jump_backward` |
| `Ctrl-s` | Save the current selection to the jump list | `save_selection` | | `Ctrl-s` | Save the current selection to the jump list | `save_selection` |
### Changes ### Changes
@ -105,43 +105,42 @@
### 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` |
| `Alt-_ ` | Merge consecutive selections | `merge_consecutive_selections` | | `Alt-_ ` | Merge consecutive selections | `merge_consecutive_selections` |
| `&` | Align selection in columns | `align_selections` | | `&` | Align selection in columns | `align_selections` |
| `_` | Trim whitespace from the selection | `trim_selections` | | `_` | Trim whitespace from the selection | `trim_selections` |
| `;` | Collapse selection onto a single cursor | `collapse_selection` | | `;` | Collapse selection onto a single cursor | `collapse_selection` |
| `Alt-;` | Flip selection cursor and anchor | `flip_selections` | | `Alt-;` | Flip selection cursor and anchor | `flip_selections` |
| `Alt-:` | Ensures the selection is in forward direction | `ensure_selections_forward` | | `Alt-:` | Ensures the selection is in forward direction | `ensure_selections_forward` |
| `,` | Keep only the primary selection | `keep_primary_selection` | | `,` | Keep only the primary selection | `keep_primary_selection` |
| `Alt-,` | Remove the primary selection | `remove_primary_selection` | | `Alt-,` | Remove the primary selection | `remove_primary_selection` |
| `C` | Copy selection onto the next line (Add cursor below) | `copy_selection_on_next_line` | | `C` | Copy selection onto the next line (Add cursor below) | `copy_selection_on_next_line` |
| `Alt-C` | Copy selection onto the previous line (Add cursor above) | `copy_selection_on_prev_line` | | `Alt-C` | Copy selection onto the previous line (Add cursor above) | `copy_selection_on_prev_line` |
| `(` | Rotate main selection backward | `rotate_selections_backward` | | `(` | Rotate main selection backward | `rotate_selections_backward` |
| `)` | Rotate main selection forward | `rotate_selections_forward` | | `)` | Rotate main selection forward | `rotate_selections_forward` |
| `Alt-(` | Rotate selection contents backward | `rotate_selection_contents_backward` | | `Alt-(` | Rotate selection contents backward | `rotate_selection_contents_backward` |
| `Alt-)` | Rotate selection contents forward | `rotate_selection_contents_forward` | | `Alt-)` | Rotate selection contents forward | `rotate_selection_contents_forward` |
| `%` | Select entire file | `select_all` | | `%` | Select entire file | `select_all` |
| `x` | Select current line, if already selected, extend to next line | `extend_line_below` | | `x` | Select current line, if already selected, extend to next line | `extend_line_below` |
| `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` | | `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` |
| `Alt-x` | Shrink selection to line bounds (line-wise selection) | `shrink_to_line_bounds` | | `Alt-x` | Shrink selection to line bounds (line-wise selection) | `shrink_to_line_bounds` |
| `J` | Join lines inside selection | `join_selections` | | `J` | Join lines inside selection | `join_selections` |
| `Alt-J` | Join lines inside selection and select space | `join_selections_space` | | `Alt-J` | Join lines inside selection and select space | `join_selections_space` |
| `K` | Keep selections matching the regex | `keep_selections` | | `K` | Keep selections matching the regex | `keep_selections` |
| `Alt-K` | Remove selections matching the regex | `remove_selections` | | `Alt-K` | Remove selections matching the regex | `remove_selections` |
| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` | | `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
| `Alt-o`, `Alt-up` | Expand selection to parent syntax node (**TS**) | `expand_selection` | | `Alt-o`, `Alt-up` | Expand selection to parent syntax node (**TS**) | `expand_selection` |
| `Alt-i`, `Alt-down` | Shrink syntax tree object selection (**TS**) | `shrink_selection` | | `Alt-i`, `Alt-down` | Shrink syntax tree object selection (**TS**) | `shrink_selection` |
| `Alt-p`, `Alt-left` | Select previous sibling node in syntax tree (**TS**) | `select_prev_sibling` | | `Alt-p`, `Alt-left` | Select previous sibling node in syntax tree (**TS**) | `select_prev_sibling` |
| `Alt-n`, `Alt-right` | Select next sibling node in syntax tree (**TS**) | `select_next_sibling` | | `Alt-n`, `Alt-right` | Select next sibling node in syntax tree (**TS**) | `select_next_sibling` |
### 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 |
| -------------------- | --------------------------------------------------------- | ------------------- | | -------------------- | --------------------------------------------------------- | ------------------- |
@ -193,26 +195,26 @@ simply looking over text and not actively editing it.
Goto mode is accessed by typing `g` in [normal mode](#normal-mode), it jumps to 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` |
| `h` | Go to the start of the line | `goto_line_start` | | `h` | Go to the start of the line | `goto_line_start` |
| `l` | Go to the end of the line | `goto_line_end` | | `l` | Go to the end of the line | `goto_line_end` |
| `s` | Go to first non-whitespace character of the line | `goto_first_nonwhitespace` | | `s` | Go to first non-whitespace character of the line | `goto_first_nonwhitespace` |
| `t` | Go to the top of the screen | `goto_window_top` | | `t` | Go to the top of the screen | `goto_window_top` |
| `c` | Go to the middle of the screen | `goto_window_center` | | `c` | Go to the middle of the screen | `goto_window_center` |
| `b` | Go to the bottom of the screen | `goto_window_bottom` | | `b` | Go to the bottom of the screen | `goto_window_bottom` |
| `d` | Go to definition (**LSP**) | `goto_definition` | | `d` | Go to definition (**LSP**) | `goto_definition` |
| `y` | Go to type definition (**LSP**) | `goto_type_definition` | | `y` | Go to type definition (**LSP**) | `goto_type_definition` |
| `r` | Go to references (**LSP**) | `goto_reference` | | `r` | Go to references (**LSP**) | `goto_reference` |
| `i` | Go to implementation (**LSP**) | `goto_implementation` | | `i` | Go to implementation (**LSP**) | `goto_implementation` |
| `a` | Go to the last accessed/alternate file | `goto_last_accessed_file` | | `a` | Go to the last accessed/alternate file | `goto_last_accessed_file` |
| `m` | Go to the last modified/alternate file | `goto_last_modified_file` | | `m` | Go to the last modified/alternate file | `goto_last_modified_file` |
| `n` | Go to next buffer | `goto_next_buffer` | | `n` | Go to next buffer | `goto_next_buffer` |
| `p` | Go to previous buffer | `goto_previous_buffer` | | `p` | Go to previous buffer | `goto_previous_buffer` |
| `.` | Go to last modification in current file | `goto_last_modification` | | `.` | Go to last modification in current file | `goto_last_modification` |
#### Match mode #### Match mode
@ -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 |
| ---------------------- | ---------------------------------------------------- | ----------------- | | ---------------------- | ---------------------------------------------------- | ----------------- |
@ -261,31 +263,30 @@ 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` |
| `d` | Open document diagnostics picker (**LSP**) | `diagnostics_picker` | | `d` | Open document diagnostics picker (**LSP**) | `diagnostics_picker` |
| `D` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnostics_picker` | | `D` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnostics_picker` |
| `r` | Rename symbol (**LSP**) | `rename_symbol` | | `r` | Rename symbol (**LSP**) | `rename_symbol` |
| `a` | Apply code action (**LSP**) | `code_action` | | `a` | Apply code action (**LSP**) | `code_action` |
| `'` | Open last fuzzy picker | `last_picker` | | `'` | Open last fuzzy picker | `last_picker` |
| `w` | Enter [window mode](#window-mode) | N/A | | `w` | Enter [window mode](#window-mode) | N/A |
| `p` | Paste system clipboard after selections | `paste_clipboard_after` | | `p` | Paste system clipboard after selections | `paste_clipboard_after` |
| `P` | Paste system clipboard before selections | `paste_clipboard_before` | | `P` | Paste system clipboard before selections | `paste_clipboard_before` |
| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` | | `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` |
| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` | | `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` | | `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
| `/` | 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,34 +322,34 @@ 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` |
| `Ctrl-r` | Insert a register content | `insert_register` | | `Ctrl-r` | Insert a register content | `insert_register` |
| `Ctrl-w`, `Alt-Backspace` | Delete previous word | `delete_word_backward` | | `Ctrl-w`, `Alt-Backspace` | Delete previous word | `delete_word_backward` |
| `Alt-d`, `Alt-Delete` | Delete next word | `delete_word_forward` | | `Alt-d`, `Alt-Delete` | Delete next word | `delete_word_forward` |
| `Ctrl-u` | Delete to start of line | `kill_to_line_start` | | `Ctrl-u` | Delete to start of line | `kill_to_line_start` |
| `Ctrl-k` | Delete to end of line | `kill_to_line_end` | | `Ctrl-k` | Delete to end of line | `kill_to_line_end` |
| `Ctrl-h`, `Backspace` | Delete previous char | `delete_char_backward` | | `Ctrl-h`, `Backspace` | Delete previous char | `delete_char_backward` |
| `Ctrl-d`, `Delete` | Delete next char | `delete_char_forward` | | `Ctrl-d`, `Delete` | Delete next char | `delete_char_forward` |
| `Ctrl-j`, `Enter` | Insert new line | `insert_newline` | | `Ctrl-j`, `Enter` | Insert new line | `insert_newline` |
These keys are not recommended, but are included for new users less familiar These keys are not recommended, but are included for new users less familiar
with modal editors. with modal editors.
@ -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"
@ -55,16 +54,16 @@ If you plan to submit your theme for inclusion in Helix, it is recommended to
use the supplied linting tool to ensure compliance with the specifications: use the supplied linting tool to ensure compliance with the specifications:
```sh ```sh
cargo xtask themelint onedark # replace onedark with <name> cargo xtask themelint onedark # replace onedark with <name>
``` ```
## The Details of Theme Creation ## The Details of Theme Creation
### 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,29 +102,37 @@ 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` |
| `underlined` | | `underlined` |
| `slow_blink` | | `slow_blink` |
| `rapid_blink` | | `rapid_blink` |
| `reversed` | | `reversed` |
| `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,53 +288,58 @@ 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.separator` | Picker separator below input line | | `ui.background` | |
| `ui.cursor` | | | `ui.background.separator` | Picker separator below input line |
| `ui.cursor.insert` | | | `ui.cursor` | |
| `ui.cursor.select` | | | `ui.cursor.normal` | |
| `ui.cursor.match` | Matching bracket etc. | | `ui.cursor.insert` | |
| `ui.cursor.primary` | Cursor with primary selection | | `ui.cursor.select` | |
| `ui.gutter` | Gutter | | `ui.cursor.match` | Matching bracket etc. |
| `ui.gutter.selected` | Gutter for the line the cursor is on | | `ui.cursor.primary` | Cursor with primary selection |
| `ui.linenr` | Line numbers | | `ui.cursor.primary.normal` | |
| `ui.linenr.selected` | Line number for the line the cursor is on | | `ui.cursor.primary.insert` | |
| `ui.statusline` | `statusline` | | `ui.cursor.primary.select` | |
| `ui.statusline.inactive` | `statusline` (unfocused document) | | `ui.gutter` | Gutter |
| `ui.statusline.normal` | `statusline` mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.gutter.selected` | Gutter for the line the cursor is on |
| `ui.statusline.insert` | `statusline` mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.linenr` | Line numbers |
| `ui.statusline.select` | `statusline` mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.linenr.selected` | Line number for the line the cursor is on |
| `ui.statusline.separator` | Separator character in `statusline` | | `ui.statusline` | Statusline |
| `ui.popup` | Documentation popups (e.g. Space + k) | | `ui.statusline.inactive` | Statusline (unfocused document) |
| `ui.popup.info` | Prompt for multiple key options | | `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.window` | Borderlines separating splits | | `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.help` | Description box for commands | | `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.text` | Command prompts, popup text, etc. | | `ui.statusline.separator` | Separator character in statusline |
| `ui.text.focus` | | | `ui.popup` | Documentation popups (e.g Space + k) |
| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) | | `ui.popup.info` | Prompt for multiple key options |
| `ui.text.info` | The key: command text in `ui.popup.info` boxes | | `ui.window` | Border lines separating splits |
| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) | | `ui.help` | Description box for commands |
| `ui.virtual.whitespace` | Visible whitespace characters | | `ui.text` | Command prompts, popup text, etc. |
| `ui.virtual.indent-guide` | Vertical indent width guides | | `ui.text.focus` | |
| `ui.menu` | Code and command completion menus | | `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
| `ui.menu.selected` | Selected autocomplete item | | `ui.text.info` | The key: command text in `ui.popup.info` boxes |
| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar | | `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
| `ui.selection` | For selections in the editing area | | `ui.virtual.whitespace` | Visible whitespace characters |
| `ui.selection.primary` | | | `ui.virtual.indent-guide` | Vertical indent width guides |
| `ui.cursorline.primary` | The line of the primary cursor ([if `cursorline` is enabled][editor-section]) | | `ui.menu` | Code and command completion menus |
| `ui.cursorline.secondary` | The lines of any other cursors ([if `cursorline` is enabled][editor-section]) | | `ui.menu.selected` | Selected autocomplete item |
| `ui.cursorcolumn.primary` | The column of the primary cursor ([if `cursorcolumn` is enabled][editor-section]) | | `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if `cursorcolumn` is enabled][editor-section]) | | `ui.selection` | For selections in the editing area |
| `warning` | Diagnostics warning (gutter) | | `ui.selection.primary` | |
| `error` | Diagnostics error (gutter) | | `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
| `info` | Diagnostics info (gutter) | | `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
| `hint` | Diagnostics hint (gutter) | | `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
| `diagnostic` | Diagnostics fallback style (editing area) | | `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
| `diagnostic.hint` | Diagnostics hint (editing area) | | `warning` | Diagnostics warning (gutter) |
| `diagnostic.info` | Diagnostics info (editing area) | | `error` | Diagnostics error (gutter) |
| `diagnostic.warning` | Diagnostics warning (editing area) | | `info` | Diagnostics info (gutter) |
| `diagnostic.error` | Diagnostics error (editing area) | | `hint` | Diagnostics hint (gutter) |
| `diagnostic` | Diagnostics fallback style (editing area) |
| `diagnostic.hint` | Diagnostics hint (editing area) |
| `diagnostic.info` | Diagnostics info (editing area) |
| `diagnostic.warning` | Diagnostics warning (editing area) |
| `diagnostic.error` | Diagnostics error (editing area) |
[editor-section]: ./configuration.md#editor-section [editor-section]: ./configuration.md#editor-section

@ -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), // Items are filtered by using the text returned by menu::Item::filter_text
selected, // 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())
.fuzzy_indicies(&line, &self.matcher)
.unwrap_or_default();
let highlight_byte_ranges: Vec<_> = line
.char_indices()
.enumerate()
.filter_map(|(char_idx, (byte_offset, ch))| {
highlights
.contains(&char_idx)
.then(|| byte_offset..byte_offset + ch.len_utf8())
})
.collect();
// The starting byte index of the current (iterating) cell
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 spans = option.label(&self.editor_data); let mut cell_len = 0;
let (_score, highlights) = FuzzyQuery::new(self.prompt.line())
.fuzzy_indicies(&String::from(&spans), &self.matcher) let graphemes_with_style: Vec<_> = spans
.unwrap_or_default(); .0
.iter()
spans.0.into_iter().fold(inner, |pos, span| { .flat_map(|span| {
let new_x = surface span.content
.set_string_truncated( .grapheme_indices(true)
pos.x, .zip(std::iter::repeat(span.style))
pos.y + i as u16, })
&span.content, .map(|((grapheme_byte_offset, grapheme), style)| {
pos.width as usize, cell_len += grapheme.len();
|idx| { let start = cell_start_byte_offset;
if highlights.contains(&idx) {
highlighted.patch(span.style) let grapheme_byte_range =
} else if is_active { grapheme_byte_offset..grapheme_byte_offset + grapheme.len();
selected.patch(span.style)
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 { } else {
text_style.patch(span.style) (grapheme, style)
} }
}, })
true, .collect();
self.truncate_start,
) let mut span_list: Vec<(String, Style)> = Vec::new();
.0; for (grapheme, style) in graphemes_with_style {
pos.clip_left(new_x - pos.x) if span_list.last().map(|(_, sty)| sty) == Some(&style) {
let (string, _) = span_list.last_mut().unwrap();
string.push_str(grapheme);
} else {
span_list.push((String::from(grapheme), style))
}
}
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) => {
variables[i] | EQ(WEAK) | (f64::from(v * available_width) / 100.0)
}
Constraint::Ratio(n, d) => {
variables[i]
| EQ(WEAK)
| (f64::from(available_width) * f64::from(n) / f64::from(d))
}
Constraint::Min(v) => variables[i] | GE(WEAK) | f64::from(v),
Constraint::Max(v) => variables[i] | LE(WEAK) | f64::from(v),
})
} }
solver if !self.widths.is_empty() {
.add_constraint( constraints.pop();
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 let mut chunks = crate::layout::Layout::default()
// fixed length constraints that cannot be satisfied. Therefore, we clamp the widths from .direction(crate::layout::Direction::Horizontal)
// left to right. .constraints(constraints)
let mut available_width = max_width; .split(Rect {
for w in &mut widths { x: 0,
*w = available_width.min(*w); y: 0,
available_width = available_width width: max_width,
.saturating_sub(*w) height: 1,
.saturating_sub(self.column_spacing); });
if has_selection {
chunks.remove(0);
} }
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,16 +209,18 @@ 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 {
styles,
Self { scopes,
styles, highlights,
scopes, ..Default::default()
highlights, }
..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,39 +244,37 @@ 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 = values
let palette = colors .remove("palette")
.remove("palette") .map(|value| {
.map(|value| { ThemePalette::try_from(value).unwrap_or_else(|err| {
ThemePalette::try_from(value).unwrap_or_else(|err| {
warn!("{}", err);
ThemePalette::default()
})
})
.unwrap_or_default();
// remove inherits from value to prevent errors
let _ = colors.remove("inherits");
styles.reserve(colors.len());
scopes.reserve(colors.len());
highlights.reserve(colors.len());
for (name, style_value) in colors {
let mut style = Style::default();
if let Err(err) = palette.parse_style(&mut style, style_value) {
warn!("{}", err); warn!("{}", err);
} ThemePalette::default()
})
// these are used both as UI and as highlights })
styles.insert(name.clone(), style); .unwrap_or_default();
scopes.push(name); // remove inherits from value to prevent errors
highlights.push(style); let _ = values.remove("inherits");
styles.reserve(values.len());
scopes.reserve(values.len());
highlights.reserve(values.len());
for (name, style_value) in values {
let mut style = Style::default();
if let Err(err) = palette.parse_style(&mut style, style_value) {
warn!("{}", err);
} }
// these are used both as UI and as highlights
styles.insert(name.clone(), style);
scopes.push(name);
highlights.push(style);
} }
(styles, scopes, highlights) (styles, scopes, highlights)
@ -515,10 +517,8 @@ 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!(

@ -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$")

@ -51,7 +51,8 @@
"markup.heading" = "my_yellow1" "markup.heading" = "my_yellow1"
"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