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

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

@ -6,8 +6,7 @@ in your config directory:
- Linux and Mac: `~/.config/helix/config.toml`
- Windows: `%AppData%\helix\config.toml`
> 💡 You can easily open the config file by typing `:config-open` within Helix
> normal mode.
> 💡 You can easily open the config file by typing `:config-open` within Helix normal mode.
Example config:
@ -28,17 +27,16 @@ hidden = false
```
You can use a custom configuration file by specifying it with the `-c` or
`--config` command line argument, for example
`hx -c path/to/custom-config.toml`. Additionally, you can reload the
configuration file by sending the USR1 signal to the Helix process on Unix
operating systems, such as by using the command `pkill -USR1 hx`.
`--config` command line argument, for example `hx -c path/to/custom-config.toml`.
Additionally, you can reload the configuration file by sending the USR1
signal to the Helix process on Unix operating systems, such as by using the command `pkill -USR1 hx`.
## Editor
### `[editor]` Section
| Key | Description | Default |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
|--|--|---------|
| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling. | `5` |
| `mouse` | Enable mouse mode. | `true` |
| `middle-click-paste` | Middle click paste support. | `true` |
@ -127,8 +125,8 @@ The following statusline elements can be configured:
### `[editor.cursor-shape]` Section
Defines the shape of cursor in each mode. Valid values for these options are
`block`, `bar`, `underline`,or `hidden`.
Defines the shape of cursor in each mode.
Valid values for these options are `block`, `bar`, `underline`, or `hidden`.
> 💡 Due to limitations of the terminal environment, only the primary cursor can
> change shape.
@ -145,20 +143,20 @@ Defines the shape of cursor in each mode. Valid values for these options are
### `[editor.file-picker]` Section
Set options for file picker and global search. Ignoring a file means it is not
visible in the Helix file picker and global search.
Sets options for file picker and global search. Ignoring a file means it is
not visible in the Helix file picker and global search.
All git related options are only enabled in a git repository.
| Key | Description | Default |
| ------------- | -------------------------------------------------------------------------------------------------------- | ------------------- |
| `hidden` | Enables ignoring hidden files. | true |
| `parents` | Enables reading ignore files from parent directories. | true |
| `ignore` | Enables reading `.ignore` 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-exclude` | Enables reading `.git/info/exclude` files. | true |
| `max-depth` | Set with an integer value for maximum depth to recurse. | Defaults to `None`. |
|--|--|---------|
|`hidden` | Enables ignoring hidden files. | true
|`parents` | Enables reading ignore files from parent directories. | true
|`ignore` | Enables reading `.ignore` 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-exclude` | Enables reading `.git/info/exclude` files. | true
|`max-depth` | Set with an integer value for maximum depth to recurse. | Defaults to `None`.
### `[editor.auto-pairs]` Section
@ -210,7 +208,7 @@ name = "rust"
Search specific options.
| Key | Description | Default |
| ------------- | -------------------------------------------------------------------------------------------------- | ------- |
|--|--|---------|
| `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` |
@ -262,3 +260,54 @@ render = true
character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽"
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 | ✓ | | ✓ | |
| ruby | ✓ | ✓ | ✓ | `solargraph` |
| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
| sage | ✓ | ✓ | | |
| scala | ✓ | | ✓ | `metals` |
| scheme | ✓ | | | |
| scss | ✓ | | | `vscode-css-language-server` |
@ -129,7 +130,7 @@
| twig | ✓ | | | |
| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
| ungrammar | ✓ | | | |
| v | ✓ | | | `vls` |
| v | ✓ | | | `v` |
| vala | ✓ | | | `vala-language-server` |
| verilog | ✓ | ✓ | | `svlangserver` |
| vhs | ✓ | | | |

@ -50,10 +50,3 @@ below.
grammars.
- 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`.
[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
Helix supports textobjects that are language specific, such as functions,
classes, etc. These textobjects require an accompanying tree-sitter grammar and
a `textobjects.scm` query file to work properly. Tree-sitter allows us to query
the source code syntax tree and capture specific parts of it. The queries are
written in a lisp dialect. More information on how to write queries can be found
in the [official tree-sitter documentation][tree-sitter-queries].
Helix supports textobjects that are language specific, such as functions, classes, etc.
These textobjects require an accompanying tree-sitter grammar and a `textobjects.scm` query file
to work properly. Tree-sitter allows us to query the source code syntax tree
and capture specific parts of it. The queries are written in a lisp dialect.
More information on how to write queries can be found in the [official tree-sitter
documentation][tree-sitter-queries].
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
put them under your local runtime directory (`~/.config/helix/runtime` on Linux
when contributing to Helix. Note that to test the query files locally you should put
them under your local runtime directory (`~/.config/helix/runtime` on Linux
for example).
The following [captures][tree-sitter-captures] are recognized:
@ -31,23 +31,18 @@ repository.
## Queries for Textobject Based Navigation
Tree-sitter based navigation in Helix is done using captures in the following
order:
Tree-sitter based navigation in Helix is done using captures in the
following order:
- `object.movement`
- `object.around`
- `object.inside`
For example if a `function.around` capture has been already defined for a
language in its `textobjects.scm` file, function navigation should also work
automatically. `function.movement` should be defined only if the node captured
by `function.around` doesn't make sense in a navigation context.
[textobjects]: ../usage.md#textobjects
[textobjects-nav]: ../usage.md#tree-sitter-textobject-based-navigation
[tree-sitter-queries]:
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=
For example if a `function.around` capture has been already defined for a language
in its `textobjects.scm` file, function navigation should also work automatically.
`function.movement` should be defined only if the node captured by `function.around`
doesn't make sense in a navigation context.
[tree-sitter-queries]: 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
<!--toc:start-->
- [Installing Helix](#installing-helix)
- [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)
@ -33,7 +32,7 @@ line.
## Installing Helix on Linux through the Official Package Manager
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:
[![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.10 (Kinetic Kudu)
Via
[Maveonair's PPA](https://launchpad.net/~maveonair/+archive/ubuntu/helix-editor):
Via [Maveonair's PPA](https://launchpad.net/~maveonair/+archive/ubuntu/helix-editor)
```sh
sudo add-apt-repository ppa:maveonair/helix-editor
@ -97,8 +95,8 @@ brew install helix
## Installing Helix on Windows
Install on Windows using [Scoop](https://scoop.sh/),
[Chocolatey](https://chocolatey.org/) or [MSYS2](https://msys2.org/).
Install on Windows using [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
or [MSYS2](https://msys2.org/).
**Scoop:**
@ -120,7 +118,7 @@ For 64-bit Windows 8.1 or above:
pacman -S mingw-w64-ucrt-x86_64-helix
```
## Building from Source
2. Compile Helix:
1. Clone the repository:
@ -139,6 +137,13 @@ This command will create the `hx` executable and construct the tree-sitter
grammars in the `runtime` folder, or in the folder specified in `HELIX_RUNTIME`
(as described below).
> 💡 If you are using the musl-libc instead of glibc the following environment variable must be set during the build
> to ensure tree sitter grammars can be loaded correctly:
>
> ```sh
> RUSTFLAGS="-C target-feature=-crt-static"
> ```
3. Configure Helix's runtime files
**IMPORTANT**: The runtime files must be accessible to the newly created binary.
@ -162,7 +167,7 @@ Either,
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.
```sh
@ -174,7 +179,7 @@ And optionally:
3. Configure the Desktop Shortcut
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
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
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
Cmd:

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

@ -1,11 +1,10 @@
# Language Support
The following languages and Language Servers are supported. To use Language
Server features, you must first [install][lsp-install-wiki] the appropriate
Language Server.
The following languages and Language Servers are supported. To use
Language Server features, you must first [install][lsp-install-wiki] the
appropriate Language Server.
You can check the language support in your installed Helix version with
`hx --health`.
You can check the language support in your installed helix version with `hx --health`.
Also see the [Language Configuration][lang-config] docs and the [Adding
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:
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.
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
Helix determines which language configuration to use based on the `file-types`
key from the above section. `file-types` is a list of strings or tables, for
Helix determines which language configuration to use based on the `file-types` key
from the above section. `file-types` is a list of strings or tables, for
example:
```toml

@ -1,10 +1,10 @@
# Key Remapping
Helix currently supports one-way key remapping through a simple TOML
configuration file. (More powerful solutions such as rebinding via commands will
be available in the future).
Helix currently supports one-way key remapping through a simple TOML configuration
file. (More powerful solutions such as rebinding via commands will be
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
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:
> `g = { a = "code_action"}` adds a new entry to the `goto` mode.
## Minor modes
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-`,
`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.
You can find a list of available commands at
[Keymap](https://docs.helix-editor.com/keymap.html)
You can find a list of available commands in the [Keymap](https://docs.helix-editor.com/keymap.html) documentation.
> 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`.
> 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`.

@ -1,8 +1,6 @@
# Themes
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>`.
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>`.
## Creating a Theme
@ -11,13 +9,18 @@ To use a theme, add `theme = "<name>"` to the top of your
To create a theme file:
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.
`mytheme.toml`) and place it in your `themes` folder.
> 💡 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
> 💡 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
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"] }
```
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.
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.
To specify only the foreground color:
@ -37,8 +37,7 @@ To specify only the foreground color:
key = "#ffffff"
```
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).
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).
```toml
"key.key" = "#ffffff"
@ -62,9 +61,9 @@ cargo xtask themelint onedark # replace onedark with <name>
### Color palettes
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
your theme file:
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 your theme file:
```toml
"ui.background" = "white"
@ -75,8 +74,8 @@ white = "#ffffff"
black = "#000000"
```
Keep in mind that the [palette] table includes all keys after its header, so it
should be defined after the normal theme options.
Keep in mind that the `[palette]` table includes all keys after its header,
so it should be defined after the normal theme options.
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
@ -103,11 +102,11 @@ over it and is merged into the default palette.
### 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.
| Modifier |
| ------------- |
| --- |
| `bold` |
| `dim` |
| `italic` |
@ -118,14 +117,22 @@ your terminal emulator.
| `hidden` |
| `crossed_out` |
> 💡 The `underlined` modifier is deprecated and only available for backwards
> compatibility. Its behavior is equivalent to setting `underline.style="line"`.
> 💡 The `underlined` modifier is deprecated and only available for backwards compatibility.
> Its behavior is equivalent to setting `underline.style="line"`.
### 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.
| Modifier |
| --- |
| `line` |
| `curl` |
| `dashed` |
| `dotted` |
| `double_line` |
| Modifier |
| ------------- |
| `line` |
@ -136,7 +143,7 @@ supported by your terminal emulator.
### 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
inherits = "boo_berry"
@ -158,9 +165,7 @@ The following is a list of scopes available to use for styling:
These keys match
[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
used. For example, if the highlight is `function.builtin.static,` the key
`function.builtin` will be used instead of function.
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`.
We use a similar set of scopes as
[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`
- `constructor`
- `constant` (TODO: constant.other.placeholder for `%v)`
- `builtin` Special constants provided by the language (`true`, `false`, `nil`
etc.)
- `constant` (TODO: constant.other.placeholder for `%v`)
- `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
- `boolean`
- `character`
- `escape`
@ -285,26 +288,31 @@ These scopes are used for theming the editor interface:
- `completion` - for completion doc popup UI
- `hover` - for hover popup UI
| Key | Notes |
| --------------------------- | ------------------------------------------------------------------------------------------------ |
| --- | --- |
| `ui.background` | |
| `ui.background.separator` | Picker separator below input line |
| `ui.cursor` | |
| `ui.cursor.normal` | |
| `ui.cursor.insert` | |
| `ui.cursor.select` | |
| `ui.cursor.match` | Matching bracket etc. |
| `ui.cursor.primary` | Cursor with primary selection |
| `ui.cursor.primary.normal` | |
| `ui.cursor.primary.insert` | |
| `ui.cursor.primary.select` | |
| `ui.gutter` | Gutter |
| `ui.gutter.selected` | Gutter for the line the cursor is on |
| `ui.linenr` | Line numbers |
| `ui.linenr.selected` | Line number for the line the cursor is on |
| `ui.statusline` | `statusline` |
| `ui.statusline.inactive` | `statusline` (unfocused document) |
| `ui.statusline.normal` | `statusline` mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.insert` | `statusline` mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.select` | `statusline` mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.separator` | Separator character in `statusline` |
| `ui.popup` | Documentation popups (e.g. Space + k) |
| `ui.statusline` | Statusline |
| `ui.statusline.inactive` | Statusline (unfocused document) |
| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.separator` | Separator character in statusline |
| `ui.popup` | Documentation popups (e.g Space + k) |
| `ui.popup.info` | Prompt for multiple key options |
| `ui.window` | Border lines separating splits |
| `ui.help` | Description box for commands |
@ -320,10 +328,10 @@ These scopes are used for theming the editor interface:
| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
| `ui.selection` | For selections in the editing area |
| `ui.selection.primary` | |
| `ui.cursorline.primary` | The line of the primary cursor ([if `cursorline` is enabled][editor-section]) |
| `ui.cursorline.secondary` | The lines of any other cursors ([if `cursorline` is enabled][editor-section]) |
| `ui.cursorcolumn.primary` | The column of the primary cursor ([if `cursorcolumn` is enabled][editor-section]) |
| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if `cursorcolumn` is enabled][editor-section]) |
| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
| `warning` | Diagnostics warning (gutter) |
| `error` | Diagnostics error (gutter) |
| `info` | Diagnostics info (gutter) |

@ -1,7 +1,6 @@
# Using Helix
<!--toc:start-->
- [Using Helix](#using-helix)
- [Registers](#registers)
- [User-defined Registers](#user-defined-registers)
@ -34,8 +33,7 @@ example:
- `"ay` - Yank the current selection to register `a`.
- `"op` - Paste the text in register `o` after the selection.
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:
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:
- `"hc` - Store the selection in register `h` and then change it (delete and
enter insert mode).
@ -50,19 +48,15 @@ selection will be stored in the register and the action will be carried out:
| `"` | Last yanked text |
| `_` | Black hole |
The system clipboard is not directly supported by a built-in register. Instead,
special commands and keybindings are provided. Refer to the
The system clipboard is not directly supported by a built-in register. Instead, special commands and keybindings are provided. Refer to the
[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
or written to it.
The black hole register is a no-op register, meaning that no data will be read or written to it.
## Surround
Helix includes built-in functionality similar to
[vim-surround](https://github.com/tpope/vim-surround). The key mappings for this
functionality have been inspired by
[vim-sandwich](https://github.com/machakann/vim-sandwich).
Helix includes built-in functionality similar to [vim-surround](https://github.com/tpope/vim-surround).
The keymappings have been inspired from [vim-sandwich](https://github.com/machakann/vim-sandwich):
![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.
Surround can also act on multiple selections. For example, to change every
occurrence of `(use)` to `[use]`:
Surround can also act on multiple selections. For example, to change every occurrence of `(use)` to `[use]`:
1. `%` to select the whole file
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
`Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) allow you to
move the primary selection according to its location in the syntax tree. For
example, many languages have the following syntax for function calls:
`Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) allow you to move the primary
selection according to its location in the syntax tree. For example, many languages have the
following syntax for function calls:
```js
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`.
```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
@ -173,24 +169,25 @@ function or block of code.
| `t` | Test |
| `g` | Change |
> 💡`f`, `c`, etc. need a tree-sitter grammar active for the current document
> and a special tree-sitter query file to work properly. [Only some
> grammars][lang-support] currently have the query file implemented.
> Contributions are welcome!
> 💡 `f`, `c`, etc need a tree-sitter grammar active for the current
document and a special tree-sitter query file to work properly. [Only
some grammars][lang-support] currently have the query file implemented.
Contributions are welcome!
## Navigating Using Tree-sitter Textobjects
## Navigating Using Tree-sitter Textobject
Navigating between functions, classes, parameters, and other elements is
possible using tree-sitter and Textobject queries. For example, to move to the
next function use `]f`, to move to previous class use `[c`, and so on.
possible using tree-sitter and Textobject queries. For
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]
For the full reference see the [unimpaired][unimpaired-keybinds] section of the
key bind documentation.
For the full reference see the [unimpaired][unimpaired-keybinds] section of the key bind
documentation.
> 💡 This feature relies on tree-sitter Textobjects and requires the
> corresponding query file to work properly.
> 💡 This feature relies on tree-sitter Textobjects
> and requires the corresponding query file to work properly.
[lang-support]: ./lang-support.md
[unimpaired-keybinds]: ./keymap.md#unimpaired

@ -48,6 +48,18 @@
--searchresults-border-color: #888;
--searchresults-li-bg: #252932;
--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 {
@ -88,6 +100,18 @@
--searchresults-border-color: #98a3ad;
--searchresults-li-bg: #2b2b2f;
--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 {
@ -128,6 +152,14 @@
--searchresults-border-color: #888;
--searchresults-li-bg: #e4f2fe;
--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 {
@ -168,6 +200,19 @@
--searchresults-border-color: #5c5c68;
--searchresults-li-bg: #242430;
--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 {
@ -208,6 +253,14 @@
--searchresults-border-color: #888;
--searchresults-li-bg: #dec2a2;
--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) {
@ -292,7 +345,15 @@
--searchresults-header-fg: #5f5f71;
--searchresults-border-color: #5c5c68;
--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 {
@ -338,5 +399,13 @@
--searchresults-border-color: #5c5c68;
--searchresults-li-bg: #242430;
--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
}
.hljs {
background:#2f1e2e;
color:#a39e9b
background: var(--hljs-background);
color: var(--hljs-color);
}
.hljs-comment,
.hljs-quote {
color:#8d8687
color: var(--hljs-quote)
}
.hljs-link,
.hljs-meta,
@ -23,7 +23,7 @@ code.hljs {
.hljs-tag,
.hljs-template-variable,
.hljs-variable {
color:#ef6155
color: var(--hljs-variable)
}
.hljs-built_in,
.hljs-deletion,
@ -31,22 +31,22 @@ code.hljs {
.hljs-number,
.hljs-params,
.hljs-type {
color:#f99b15
color: var(--hljs-type)
}
.hljs-attribute,
.hljs-section,
.hljs-title {
color:#fec418
color: var(--hljs-title)
}
.hljs-addition,
.hljs-bullet,
.hljs-string,
.hljs-symbol {
color:#48b685
color: var(--hljs-symbol)
}
.hljs-keyword,
.hljs-selector-tag {
color:#815ba4
color: var(--hljs-selector-tag)
}
.hljs-emphasis {
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.
* 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
* `git tag -s -m "<tag>" -a <tag> && git push`
* Make sure to switch to master and pull first

@ -31,12 +31,12 @@ arc-swap = "1"
regex = "1"
bitflags = "1.3"
ahash = "0.8.2"
hashbrown = { version = "0.13.1", features = ["raw"] }
hashbrown = { version = "0.13.2", features = ["raw"] }
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.5"
toml = "0.6"
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;
config_file.push("languages.toml");
let config = std::fs::read(config_file).unwrap();
let config = toml::from_slice(&config).unwrap();
let config = std::fs::read_to_string(config_file).unwrap();
let config = toml::from_str(&config).unwrap();
let loader = Loader::new(config);
// set runtime path so we can find the queries

@ -19,7 +19,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "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"] }
which = "4.2"
which = "4.4"
[dev-dependencies]
fern = "0.6"

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

@ -1,6 +1,9 @@
use std::str::from_utf8;
/// Default built-in languages.toml.
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")
}
@ -11,8 +14,8 @@ pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
.chain([crate::config_dir()].into_iter())
.map(|path| path.join("languages.toml"))
.filter_map(|file| {
std::fs::read(&file)
.map(|config| toml::from_slice(&config))
std::fs::read_to_string(file)
.map(|config| toml::from_str(&config))
.ok()
})
.collect::<Result<Vec<_>, _>>()?

@ -515,5 +515,5 @@ pub fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::
.join("queries")
.join(language)
.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)]
mod merge_toml_tests {
use std::str;
use super::merge_toml_values;
use toml::Value;
@ -191,8 +193,9 @@ mod merge_toml_tests {
indent = { tab-width = 4, unit = " ", test = "aaa" }
"#;
let base: Value = toml::from_slice(include_bytes!("../../languages.toml"))
.expect("Couldn't parse built-in languages config");
let base = include_bytes!("../../languages.toml");
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 merged = merge_toml_values(base, user, 3);
@ -224,8 +227,9 @@ mod merge_toml_tests {
language-server = { command = "deno", args = ["lsp"] }
"#;
let base: Value = toml::from_slice(include_bytes!("../../languages.toml"))
.expect("Couldn't parse built-in languages config");
let base = include_bytes!("../../languages.toml");
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 merged = merge_toml_values(base, user, 3);

@ -25,4 +25,4 @@ serde_json = "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-stream = "0.1.11"
which = "4.2"
which = "4.4"

@ -37,7 +37,7 @@ helix-loader = { version = "0.6", path = "../helix-loader" }
anyhow = "1"
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"] }
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"
# config
toml = "0.5"
toml = "0.6"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }

@ -5,7 +5,7 @@ pub(crate) mod typed;
pub use dap::*;
use helix_vcs::Hunk;
pub use lsp::*;
use tui::text::Spans;
use tui::widgets::Row;
pub use typed::*;
use helix_core::{
@ -26,7 +26,6 @@ use helix_core::{
SmallVec, Tendril, Transaction,
};
use helix_view::{
apply_transaction,
clipboard::ClipboardType,
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
editor::{Action, Motion},
@ -384,6 +383,7 @@ impl MappableCommand {
swap_view_down, "Swap with split below",
transpose_view, "Transpose splits",
rotate_view, "Goto next window",
rotate_view_reverse, "Goto previous window",
hsplit, "Horizontal bottom split",
hsplit_new, "Horizontal bottom split scratch buffer",
vsplit, "Vertical right split",
@ -448,9 +448,16 @@ impl MappableCommand {
impl fmt::Debug for MappableCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("MappableCommand")
.field(&self.name())
.finish()
match self {
MappableCommand::Static { name, .. } => {
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) {
(
MappableCommand::Typable {
name: first_name, ..
name: first_name,
args: first_args,
..
},
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 {
name: first_name, ..
@ -863,7 +874,7 @@ fn align_selections(cx: &mut Context) {
changes.sort_unstable_by_key(|(from, _, _)| *from);
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) {
@ -1315,7 +1326,7 @@ fn replace(cx: &mut Context) {
}
});
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
exit_select_mode(cx);
}
})
@ -1333,7 +1344,7 @@ where
(range.from(), range.to(), Some(text))
});
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
}
fn switch_case(cx: &mut Context) {
@ -1863,7 +1874,7 @@ fn global_search(cx: &mut Context) {
impl ui::menu::Item for FileResult {
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)
.to_string_lossy()
.into_owned();
@ -2003,6 +2014,10 @@ fn global_search(cx: &mut Context) {
let line_num = *line_num;
let (view, doc) = current!(cx.editor);
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 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| {
(range.from(), range.to(), None)
});
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
match op {
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| {
(range.from(), range.to(), None)
});
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
}
fn delete_selection(cx: &mut Context) {
@ -2272,7 +2287,7 @@ fn append_mode(cx: &mut Context) {
doc.text(),
[(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| {
@ -2311,7 +2326,7 @@ fn buffer_picker(cx: &mut Context) {
impl ui::menu::Item for BufferMeta {
type Data = ();
fn label(&self, _data: &Self::Data) -> Spans {
fn format(&self, _data: &Self::Data) -> Row {
let path = self
.path
.as_deref()
@ -2380,7 +2395,7 @@ fn jumplist_picker(cx: &mut Context) {
impl ui::menu::Item for JumpMeta {
type Data = ();
fn label(&self, _data: &Self::Data) -> Spans {
fn format(&self, _data: &Self::Data) -> Row {
let path = self
.path
.as_deref()
@ -2453,7 +2468,7 @@ fn jumplist_picker(cx: &mut Context) {
impl ui::menu::Item for MappableCommand {
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 {
bindings.iter().fold(String::new(), |mut acc, bind| {
if !acc.is_empty() {
@ -2582,7 +2597,7 @@ async fn make_format_callback(
if let Ok(format) = format {
if doc.version() == doc_version {
apply_transaction(&format, doc, view);
doc.apply(&format, view.id);
doc.append_changes_to_history(view);
doc.detect_indent_and_line_ending();
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()));
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
}
// o inserts a new line after each line with a selection
@ -3103,7 +3118,7 @@ pub mod insert {
let (view, doc) = current!(cx.editor);
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)
@ -3125,7 +3140,7 @@ pub mod insert {
&doc.selection(view.id).clone().cursors(doc.text().slice(..)),
indent,
);
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
}
pub fn insert_newline(cx: &mut Context) {
@ -3230,7 +3245,7 @@ pub mod insert {
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
let (view, doc) = current!(cx.editor);
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
}
pub fn delete_char_backward(cx: &mut Context) {
@ -3325,7 +3340,7 @@ pub mod insert {
}
});
let (view, doc) = current!(cx.editor);
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
}
@ -3343,7 +3358,7 @@ pub mod insert {
None,
)
});
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
}
@ -3624,7 +3639,7 @@ fn paste_impl(
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) {
@ -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);
}
}
@ -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);
}
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())))
}),
);
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
}
fn unindent(cx: &mut Context) {
@ -3851,7 +3866,7 @@ fn unindent(cx: &mut Context) {
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) {
@ -3906,7 +3921,7 @@ fn format_selections(cx: &mut Context) {
language_server.offset_encoding(),
);
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
}
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.dedup();
@ -3960,7 +3980,7 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
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) {
@ -4103,7 +4123,7 @@ fn toggle_comments(cx: &mut Context) {
.map(|tc| tc.as_ref());
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);
}
@ -4159,7 +4179,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
.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) {
@ -4317,6 +4337,10 @@ fn rotate_view(cx: &mut Context) {
cx.editor.focus_next()
}
fn rotate_view_reverse(cx: &mut Context) {
cx.editor.focus_prev()
}
fn jump_view_right(cx: &mut Context) {
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())
.with_selection(Selection::new(ranges, selection.primary_index()));
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
exit_select_mode(cx);
})
}
@ -4728,7 +4752,7 @@ fn surround_replace(cx: &mut Context) {
(pos, pos + 1, Some(t))
}),
);
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
exit_select_mode(cx);
});
})
@ -4756,7 +4780,7 @@ fn surround_delete(cx: &mut Context) {
let transaction =
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);
})
}
@ -4971,7 +4995,7 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
if behavior != &ShellBehavior::Ignore {
let transaction = Transaction::change(doc.text(), changes.into_iter())
.with_selection(Selection::new(ranges, selection.primary_index()));
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
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);
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
}
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 transaction = Transaction::change(doc.text(), changes.into_iter());
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 tokio_stream::wrappers::UnboundedReceiverStream;
use tui::text::Spans;
use tui::{text::Spans, widgets::Row};
use std::collections::HashMap;
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 {
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
}
}
@ -33,7 +33,7 @@ impl ui::menu::Item for StackFrame {
impl ui::menu::Item for DebugTemplate {
type Data = ();
fn label(&self, _data: &Self::Data) -> Spans {
fn format(&self, _data: &Self::Data) -> Row {
self.name.as_str().into()
}
}
@ -41,7 +41,7 @@ impl ui::menu::Item for DebugTemplate {
impl ui::menu::Item for Thread {
type Data = ThreadStates;
fn label(&self, thread_states: &Self::Data) -> Spans {
fn format(&self, thread_states: &Self::Data) -> Row {
format!(
"{} ({})",
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},
OffsetEncoding,
};
use tui::text::{Span, Spans};
use tui::{
text::{Span, Spans},
widgets::Row,
};
use super::{align_view, push_jump, Align, Context, Editor, Open};
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::{
compositor::{self, Compositor},
@ -46,7 +49,7 @@ impl ui::menu::Item for lsp::Location {
/// Current working directory.
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
// 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
@ -80,7 +83,7 @@ impl ui::menu::Item for lsp::SymbolInformation {
/// Path to currently focussed document
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) {
self.name.as_str().into()
} else {
@ -110,7 +113,7 @@ struct PickerDiagnostic {
impl ui::menu::Item for PickerDiagnostic {
type Data = (DiagnosticStyles, DiagnosticsFormat);
fn label(&self, (styles, format): &Self::Data) -> Spans {
fn format(&self, (styles, format): &Self::Data) -> Row {
let mut style = self
.diag
.severity
@ -149,6 +152,7 @@ impl ui::menu::Item for PickerDiagnostic {
Span::styled(&self.diag.message, 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 {
type Data = ();
fn label(&self, _data: &Self::Data) -> Spans {
fn format(&self, _data: &Self::Data) -> Row {
match self {
lsp::CodeActionOrCommand::CodeAction(action) => action.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 {
type Data = ();
fn label(&self, _data: &Self::Data) -> Spans {
fn format(&self, _data: &Self::Data) -> Row {
self.title.as_str().into()
}
}
@ -796,7 +800,7 @@ pub fn apply_workspace_edit(
offset_encoding,
);
let view = view_mut!(editor, view_id);
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
};

@ -4,10 +4,7 @@ use crate::job::Job;
use super::*;
use helix_view::{
apply_transaction,
editor::{Action, CloseError, ConfigEvent},
};
use helix_view::editor::{Action, CloseError, ConfigEvent};
use ui::completers::{self, Completer};
#[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);
Ok(())
@ -925,7 +922,7 @@ fn replace_selections_with_clipboard_impl(
(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);
Ok(())
}
@ -1596,7 +1593,7 @@ fn sort_impl(
.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);
Ok(())
@ -1640,7 +1637,7 @@ fn reflow(
(range.from(), range.to(), Some(reflowed_text))
});
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
view.ensure_cursor_in_view(doc, scrolloff);

@ -184,7 +184,7 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
S: serde::de::SeqAccess<'de>,
{
let mut commands = Vec::new();
while let Some(command) = seq.next_element::<&str>()? {
while let Some(command) = seq.next_element::<String>()? {
commands.push(
command
.parse::<MappableCommand>()
@ -600,4 +600,43 @@ mod tests {
"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 helix_view::{apply_transaction, editor::CompleteAction, ViewId};
use helix_view::{editor::CompleteAction, ViewId};
use tui::buffer::Buffer as Surface;
use tui::text::Spans;
use std::borrow::Cow;
@ -33,11 +32,7 @@ impl menu::Item for CompletionItem {
.into()
}
fn label(&self, _data: &Self::Data) -> Spans {
self.label.as_str().into()
}
fn row(&self, _data: &Self::Data) -> menu::Row {
fn format(&self, _data: &Self::Data) -> menu::Row {
menu::Row::new(vec![
menu::Cell::from(self.label.as_str()),
menu::Cell::from(match self.kind {
@ -188,7 +183,7 @@ impl Completion {
// initialize a savepoint
doc.savepoint();
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
editor.last_completion = Some(CompleteAction {
trigger_offset,
@ -208,7 +203,7 @@ impl Completion {
trigger_offset,
);
apply_transaction(&transaction, doc, view);
doc.apply(&transaction, view.id);
editor.last_completion = Some(CompleteAction {
trigger_offset,
@ -238,7 +233,7 @@ impl Completion {
additional_edits.clone(),
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,
};
use helix_view::{
apply_transaction,
document::{Mode, SCRATCH_BUFFER_NAME},
editor::{CompleteAction, CursorShapeConfig},
graphics::{Color, CursorKind, Modifier, Rect, Style},
@ -342,23 +341,29 @@ impl EditorView {
let selection_scope = theme
.find_scope_index("ui.selection")
.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
.find_scope_index("ui.cursor")
.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 {
Mode::Insert => theme.find_scope_index("ui.cursor.insert"),
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);
let primary_cursor_scope = theme
.find_scope_index("ui.cursor.primary")
.unwrap_or(cursor_scope);
let primary_selection_scope = theme
.find_scope_index("ui.selection.primary")
.unwrap_or(selection_scope);
let primary_cursor_scope = match mode {
Mode::Insert => theme.find_scope_index("ui.cursor.primary.insert"),
Mode::Select => theme.find_scope_index("ui.cursor.primary.select"),
Mode::Normal => theme.find_scope_index("ui.cursor.primary.normal"),
}
.unwrap_or(base_primary_cursor_scope);
let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
for (i, range) in selection.iter().enumerate() {
@ -386,7 +391,14 @@ impl EditorView {
if range.head > range.anchor {
// Standard case.
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 {
spans.push((cursor_scope, cursor_start..range.head));
}
@ -396,7 +408,16 @@ impl EditorView {
if !selection_is_primary || cursor_is_block {
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)
}),
);
apply_transaction(&tx, doc, view);
doc.apply(&tx, view.id);
}
InsertEvent::TriggerCompletion => {
let (_, doc) = current!(cxt.editor);

@ -4,7 +4,7 @@ use crate::{
compositor::{Callback, Component, Compositor, Context, Event, EventResult},
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};
@ -18,28 +18,24 @@ pub trait Item {
/// Additional editor state that is used for label calculation.
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> {
let label: String = self.label(data).into();
let label: String = self.format(data).cell_text().collect();
label.into()
}
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()
}
fn row(&self, data: &Self::Data) -> Row {
Row::new(vec![Cell::from(self.label(data))])
}
}
impl Item for PathBuf {
/// Root prefix to strip.
type Data = PathBuf;
fn label(&self, root_path: &Self::Data) -> Spans {
fn format(&self, root_path: &Self::Data) -> Row {
self.strip_prefix(root_path)
.unwrap_or(self)
.to_string_lossy()
@ -81,7 +77,7 @@ impl<T: Item> Menu<T> {
Self {
options,
editor_data,
matcher: Box::new(Matcher::default()),
matcher: Box::default(),
matches,
cursor: None,
widths: Vec::new(),
@ -144,10 +140,10 @@ impl<T: Item> Menu<T> {
let n = self
.options
.first()
.map(|option| option.row(&self.editor_data).cells.len())
.map(|option| option.format(&self.editor_data).cells.len())
.unwrap_or_default();
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
for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) {
let width = cell.content.width();
@ -331,7 +327,9 @@ impl<T: Item + 'static> Component for Menu<T> {
(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)
.style(style)
.highlight_style(selected)

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

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

@ -433,7 +433,7 @@ impl Buffer {
(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 x = x;
for span in &spans.0 {
@ -454,7 +454,7 @@ impl Buffer {
(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)
}
@ -521,10 +521,10 @@ impl Buffer {
pub fn merge(&mut self, other: &Buffer) {
let area = self.area.union(other.area);
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
let size = self.area.area() as usize;
let size = self.area.area();
for i in (0..size).rev() {
let (x, y) = self.pos_of(i);
// New index in content
@ -537,7 +537,7 @@ impl Buffer {
// Push content of the other buffer into this one (may erase previous
// data)
let size = other.area.area() as usize;
let size = other.area.area();
for i in 0..size {
let (x, y) = other.pos_of(i);
// 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> {
type Item = Spans<'a>;
type IntoIter = std::vec::IntoIter<Self::Item>;

@ -4,14 +4,8 @@ use crate::{
text::Text,
widgets::{Block, Widget},
};
use cassowary::{
strength::{MEDIUM, REQUIRED, WEAK},
WeightedRelation::*,
{Expression, Solver},
};
use helix_core::unicode::width::UnicodeWidthStr;
use helix_view::graphics::{Rect, Style};
use std::collections::HashMap;
/// 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 {
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.
@ -260,69 +265,32 @@ impl<'a> Table<'a> {
}
fn get_columns_widths(&self, max_width: u16, has_selection: bool) -> Vec<u16> {
let mut solver = Solver::new();
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);
let mut constraints = Vec::with_capacity(self.widths.len() * 2 + 1);
if has_selection {
let highlight_symbol_width =
self.highlight_symbol.map(|s| s.width() as u16).unwrap_or(0);
available_width = available_width.saturating_sub(highlight_symbol_width);
}
for (i, constraint) in self.widths.iter().enumerate() {
ccs.push(variables[i] | GE(WEAK) | 0.);
ccs.push(match *constraint {
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
.add_constraint(
variables
.iter()
.fold(Expression::from_constant(0.), |acc, v| acc + *v)
| LE(REQUIRED)
| f64::from(available_width),
)
.unwrap();
solver.add_constraints(&ccs).unwrap();
let mut widths = vec![0; variables.len()];
for &(var, value) in solver.fetch_changes() {
let index = var_indices[&var];
let value = if value.is_sign_negative() {
0
} else {
value.round() as u16
};
widths[index] = value;
}
// Cassowary could still return columns widths greater than the max width when there are
// fixed length constraints that cannot be satisfied. Therefore, we clamp the widths from
// left to right.
let mut available_width = max_width;
for w in &mut widths {
*w = available_width.min(*w);
available_width = available_width
.saturating_sub(*w)
.saturating_sub(self.column_spacing);
constraints.push(Constraint::Length(highlight_symbol_width));
}
for constraint in self.widths {
constraints.push(*constraint);
constraints.push(Constraint::Length(self.column_spacing));
}
if !self.widths.is_empty() {
constraints.pop();
}
let mut chunks = crate::layout::Layout::default()
.direction(crate::layout::Direction::Horizontal)
.constraints(constraints)
.split(Rect {
x: 0,
y: 0,
width: max_width,
height: 1,
});
if has_selection {
chunks.remove(0);
}
widths
chunks.iter().step_by(2).map(|c| c.width).collect()
}
fn get_row_bounds(
@ -477,6 +445,9 @@ impl<'a> Table<'a> {
};
let mut col = table_row_start_col;
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(
buf,
cell,
@ -489,9 +460,6 @@ impl<'a> Table<'a> {
);
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_json = "1.0"
toml = "0.5"
toml = "0.6"
log = "~0.4"
which = "4.2"
which = "4.4"
[target.'cfg(windows)'.dependencies]

@ -27,7 +27,7 @@ use helix_core::{
};
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.
const BUF_SIZE: usize = 8192;
@ -650,7 +650,7 @@ impl Document {
// This is not considered a modification of the contents of the file regardless
// of the encoding.
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.reset_modified();
@ -852,9 +852,6 @@ impl Document {
}
/// 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 {
// store the state just before any changes are made. This allows us to undo to the
// state just before a transaction was applied.
@ -911,7 +908,7 @@ impl Document {
pub fn restore(&mut self, view: &mut View) {
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)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct FilePickerConfig {
@ -132,8 +222,8 @@ pub struct Config {
pub cursorline: bool,
/// Highlight the columns cursors are currently on. Defaults to false.
pub cursorcolumn: bool,
/// Gutters. Default ["diagnostics", "line-numbers"]
pub gutters: Vec<GutterType>,
#[serde(deserialize_with = "deserialize_gutter_seq_or_struct")]
pub gutters: GutterConfig,
/// Middle click paste support. Defaults to true.
pub middle_click_paste: bool,
/// 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(),
args: vec!["cmd".to_string(), "/C".to_string()],
});
})
}
#[cfg(not(any(windows, target_os = "wasm32")))]
@ -606,13 +696,7 @@ impl Default for Config {
line_number: LineNumber::Absolute,
cursorline: false,
cursorcolumn: false,
gutters: vec![
GutterType::Diagnostics,
GutterType::Spacer,
GutterType::LineNumbers,
GutterType::Spacer,
GutterType::Diff,
],
gutters: GutterConfig::default(),
middle_click_paste: true,
auto_pairs: AutoPairConfig::default(),
auto_completion: true,
@ -844,6 +928,7 @@ impl Editor {
let config = self.config();
self.auto_pairs = (&config.auto_pairs).into();
self.reset_idle_timer();
self._refresh();
}
pub fn clear_idle_timer(&mut self) {
@ -984,6 +1069,7 @@ impl Editor {
for (view, _) in self.tree.views_mut() {
let doc = doc_mut!(self, &view.doc);
view.sync_changes(doc);
view.gutters = config.gutters.clone();
view.ensure_cursor_in_view(doc, config.scrolloff)
}
}
@ -1285,6 +1371,10 @@ impl Editor {
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) {
let current_view = self.tree.focus;
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));
(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)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Color {
Reset,
Black,
@ -353,7 +352,6 @@ bitflags! {
///
/// let m = Modifier::BOLD | Modifier::ITALIC;
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Modifier: u16 {
const BOLD = 0b0000_0000_0001;
const DIM = 0b0000_0000_0010;
@ -450,7 +448,6 @@ impl FromStr for Modifier {
/// );
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Style {
pub fg: 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 {
GutterType::Diagnostics => 1,
GutterType::LineNumbers => line_numbers_width(_view, doc),
GutterType::LineNumbers => line_numbers_width(view, doc),
GutterType::Spacer => 1,
GutterType::Diff => 1,
}
@ -140,12 +140,13 @@ pub fn line_numbers<'doc>(
is_focused: bool,
) -> GutterFn<'doc> {
let text = doc.text().slice(..);
let last_line = view.last_line(doc);
let width = GutterType::LineNumbers.width(view, doc);
let width = line_numbers_width(view, doc);
let last_line_in_view = view.last_line(doc);
// 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.
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_select = theme.get("ui.linenr.selected");
@ -158,7 +159,7 @@ pub fn line_numbers<'doc>(
let mode = editor.mode;
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();
Some(linenr)
} 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 last_line = text.len_lines().saturating_sub(1);
let draw_last = text.line_to_byte(last_line) < text.len_bytes();
let last_drawn = if draw_last { last_line + 1 } else { last_line };
// set a lower bound to 2-chars to minimize ambiguous relative line numbers
std::cmp::max(count_digits(last_drawn), 2)
let digits = count_digits(last_drawn);
let n_min = view.gutters.line_numbers.min_width;
digits.max(n_min)
}
pub fn padding<'doc>(
@ -282,3 +288,82 @@ pub fn diagnostics_or_breakpoints<'doc>(
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! {
/// Represents key modifiers (shift, control, alt).
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyModifiers: u8 {
const SHIFT = 0b0000_0001;
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);
}
/// 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 editor::Editor;
pub use theme::Theme;

@ -1,9 +1,10 @@
use std::{
collections::HashMap,
path::{Path, PathBuf},
str,
};
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, Result};
use helix_core::hashmap;
use helix_loader::merge_toml_values;
use log::warn;
@ -15,12 +16,13 @@ use crate::graphics::UnderlineStyle;
pub use crate::graphics::{Color, Modifier, Style};
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(|| {
toml::from_slice(include_bytes!("../../base16_theme.toml"))
.expect("Failed to parse base 16 default theme")
let bytes = include_bytes!("../../base16_theme.toml");
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 {
@ -70,7 +72,7 @@ impl Loader {
fn load_theme(
&self,
name: &str,
base_them_name: &str,
base_theme_name: &str,
only_default_dir: bool,
) -> Result<Value> {
let path = self.path(name, only_default_dir);
@ -92,8 +94,8 @@ impl Loader {
"base16_default" => BASE16_DEFAULT_THEME_DATA.clone(),
_ => self.load_theme(
parent_theme_name,
base_them_name,
base_them_name == parent_theme_name,
base_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
fn load_toml(&self, path: PathBuf) -> Result<Value> {
let data = std::fs::read(&path)?;
let value = toml::from_slice(data.as_slice())?;
let data = std::fs::read_to_string(path)?;
let value = toml::from_str(&data)?;
Ok(value)
}
@ -207,10 +209,8 @@ pub struct Theme {
impl From<Value> for Theme {
fn from(value: Value) -> Self {
let values: Result<HashMap<String, Value>> =
toml::from_str(&value.to_string()).context("Failed to load theme");
let (styles, scopes, highlights) = build_theme_values(values);
if let Value::Table(table) = value {
let (styles, scopes, highlights) = build_theme_values(table);
Self {
styles,
@ -218,6 +218,10 @@ impl From<Value> for Theme {
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
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 {
styles,
@ -240,15 +244,14 @@ impl<'de> Deserialize<'de> for Theme {
}
fn build_theme_values(
values: Result<HashMap<String, Value>>,
mut values: Map<String, Value>,
) -> (HashMap<String, Style>, Vec<String>, Vec<Style>) {
let mut styles = HashMap::new();
let mut scopes = Vec::new();
let mut highlights = Vec::new();
if let Ok(mut colors) = values {
// TODO: alert user of parsing failures in editor
let palette = colors
let palette = values
.remove("palette")
.map(|value| {
ThemePalette::try_from(value).unwrap_or_else(|err| {
@ -258,11 +261,11 @@ fn build_theme_values(
})
.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 _ = 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);
@ -273,7 +276,6 @@ fn build_theme_values(
scopes.push(name);
highlights.push(style);
}
}
(styles, scopes, highlights)
}
@ -515,11 +517,9 @@ mod tests {
let mut style = Style::default();
let palette = ThemePalette::default();
if let Value::Table(entries) = table {
for (_name, value) in entries {
for (_name, value) in table {
palette.parse_style(&mut style, value).unwrap();
}
}
assert_eq!(
style,

@ -701,7 +701,7 @@ impl<'a> DoubleEndedIterator for Traverse<'a> {
#[cfg(test)]
mod test {
use super::*;
use crate::editor::GutterType;
use crate::editor::GutterConfig;
use crate::DocumentId;
#[test]
@ -712,34 +712,22 @@ mod test {
width: 180,
height: 80,
});
let mut view = View::new(
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
let mut view = View::new(DocumentId::default(), GutterConfig::default());
view.area = Rect::new(0, 0, 180, 80);
tree.insert(view);
let l0 = tree.focus;
let view = View::new(
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
let view = View::new(DocumentId::default(), GutterConfig::default());
tree.split(view, Layout::Vertical);
let r0 = tree.focus;
tree.focus = l0;
let view = View::new(
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
let view = View::new(DocumentId::default(), GutterConfig::default());
tree.split(view, Layout::Horizontal);
let l1 = tree.focus;
tree.focus = l0;
let view = View::new(
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
let view = View::new(DocumentId::default(), GutterConfig::default());
tree.split(view, Layout::Vertical);
let l2 = tree.focus;
@ -781,40 +769,28 @@ mod test {
});
let doc_l0 = DocumentId::default();
let mut view = View::new(
doc_l0,
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
let mut view = View::new(doc_l0, GutterConfig::default());
view.area = Rect::new(0, 0, 180, 80);
tree.insert(view);
let l0 = tree.focus;
let doc_r0 = DocumentId::default();
let view = View::new(
doc_r0,
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
let view = View::new(doc_r0, GutterConfig::default());
tree.split(view, Layout::Vertical);
let r0 = tree.focus;
tree.focus = l0;
let doc_l1 = DocumentId::default();
let view = View::new(
doc_l1,
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
let view = View::new(doc_l1, GutterConfig::default());
tree.split(view, Layout::Horizontal);
let l1 = tree.focus;
tree.focus = l0;
let doc_l2 = DocumentId::default();
let view = View::new(
doc_l2,
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
let view = View::new(doc_l2, GutterConfig::default());
tree.split(view, Layout::Vertical);
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::{
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],
/// used to store previous selections of tree-sitter objects
pub object_selections: Vec<Selection>,
/// GutterTypes used to fetch Gutter (constructor) and width for rendering
gutters: Vec<GutterType>,
/// all gutter-related configuration settings, used primarily for gutter rendering
pub gutters: GutterConfig,
/// 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
/// 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 {
pub fn new(doc: DocumentId, gutter_types: Vec<crate::editor::GutterType>) -> Self {
pub fn new(doc: DocumentId, gutters: GutterConfig) -> Self {
Self {
id: ViewId::default(),
doc,
@ -133,7 +139,7 @@ impl View {
docs_access_history: Vec::new(),
last_modified_docs: [None, None],
object_selections: Vec::new(),
gutters: gutter_types,
gutters,
doc_revisions: HashMap::new(),
}
}
@ -154,11 +160,12 @@ impl View {
}
pub fn gutters(&self) -> &[GutterType] {
&self.gutters
&self.gutters.layout
}
pub fn gutter_offset(&self, doc: &Document) -> u16 {
self.gutters
.layout
.iter()
.map(|gutter| gutter.width(self, doc) as u16)
.sum()
@ -380,8 +387,6 @@ impl 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) {
self.jumps.apply(transaction, doc);
self.doc_revisions
@ -416,18 +421,19 @@ impl View {
mod tests {
use super::*;
use helix_core::Rope;
const OFFSET: u16 = 3; // 1 diagnostic + 2 linenr (< 100 lines)
const OFFSET_WITHOUT_LINE_NUMBERS: u16 = 1; // 1 diagnostic
// const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum();
// 1 diagnostic + 1 spacer + 3 linenr (< 1000 lines) + 1 spacer + 1 diff
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::editor::GutterType;
use crate::editor::{GutterConfig, GutterLineNumbersConfig, GutterType};
#[test]
fn test_text_pos_at_screen_coords() {
let mut view = View::new(
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
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);
@ -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, 40, 40 + OFFSET + 3, 4),
view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 3, 4),
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, 41, 40 + OFFSET + 1, 4),
view.text_pos_at_screen_coords(&doc, 41, 40 + DEFAULT_GUTTER_OFFSET + 1, 4),
Some(4)
);
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)
);
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)
);
@ -473,19 +479,36 @@ mod tests {
#[test]
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);
let rope = Rope::from_str("abc\n\tdef");
let doc = Document::from(rope, None);
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)
);
}
#[test]
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);
let rope = Rope::from_str("abc\n\tdef");
let doc = Document::from(rope, None);
@ -494,76 +517,70 @@ mod tests {
#[test]
fn test_text_pos_at_screen_coords_cjk() {
let mut view = View::new(
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
let mut view = View::new(DocumentId::default(), GutterConfig::default());
view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("Hi! こんにちは皆さん");
let doc = Document::from(rope, None);
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)
);
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)
);
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)
);
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)
);
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)
);
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)
);
}
#[test]
fn test_text_pos_at_screen_coords_graphemes() {
let mut view = View::new(
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::LineNumbers],
);
let mut view = View::new(DocumentId::default(), GutterConfig::default());
view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("Hèl̀l̀ò world!");
let doc = Document::from(rope, None);
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)
);
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)
);
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)
);
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)
);
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)
);
}

@ -473,7 +473,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-html", rev = "29f53
name = "python"
scope = "source.python"
injection-regex = "python"
file-types = ["py"]
file-types = ["py","pyi","py3","pyw","ptl",".pythonstartup",".pythonrc","SConstruct"]
shebangs = ["python"]
roots = []
comment-token = "#"
@ -652,7 +652,7 @@ name = "java"
scope = "source.java"
injection-regex = "java"
file-types = ["java"]
roots = ["pom.xml"]
roots = ["pom.xml", "build.gradle"]
language-server = { command = "jdtls" }
indent = { tab-width = 4, unit = " " }
@ -717,6 +717,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-ocaml", rev = "23d4
[[language]]
name = "lua"
injection-regex = "lua"
scope = "source.lua"
file-types = ["lua"]
shebangs = ["lua"]
@ -877,7 +878,7 @@ source = { git = "https://github.com/uyha/tree-sitter-cmake", rev = "6e51463ef30
[[language]]
name = "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)"
roots = []
comment-token = "#"
@ -1018,7 +1019,7 @@ source = { git = "https://github.com/Flakebi/tree-sitter-tablegen", rev = "568dd
name = "markdown"
scope = "source.md"
injection-regex = "md|markdown"
file-types = ["md", "markdown"]
file-types = ["md", "markdown", "PULLREQ_EDITMSG"]
roots = [".marksman.toml"]
language-server = { command = "marksman", args=["server"] }
indent = { tab-width = 2, unit = " " }
@ -1065,7 +1066,7 @@ config = { "isHttpEnabled" = true }
[[grammar]]
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]]
name = "dockerfile"
@ -1602,7 +1603,7 @@ scope = "source.v"
file-types = ["v", "vv"]
shebangs = ["v run"]
roots = ["v.mod"]
language-server = { command = "vls", args = [] }
language-server = { command = "v", args = ["ls"] }
auto-format = true
comment-token = "//"
indent = { tab-width = 4, unit = "\t" }
@ -1777,6 +1778,7 @@ auto-format = true
comment-token = "//"
language-server = { command = "cuelsp" }
indent = { tab-width = 4, unit = "\t" }
formatter = { command = "cue", args = ["fmt", "-"] }
[[grammar]]
name = "cue"
@ -1979,7 +1981,7 @@ roots = []
[[grammar]]
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]]
name = "wit"
@ -2108,3 +2110,13 @@ formatter = { command = "dhall" , args = ["format"] }
[[grammar]]
name = "dhall"
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))
(#set! injection.combined))
; Common attribute keys corresponding to scripts,
; such as those of stdenv.mkDerivation.
((binding
attrpath: (attrpath (identifier) @_path)
expression: (indented_string_expression
@ -14,6 +16,25 @@
(#set! injection.language "bash")
(#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
function: (apply_expression function: (_) @_func)
argument: (indented_string_expression (string_fragment) @injection.content))
@ -21,6 +42,8 @@
(#set! injection.language "bash")
(#set! injection.combined))
; trivial-builders.nix, aliases.nix
; pkgs.runCommand[[No]CC][Local] name attrs content
(apply_expression
(apply_expression
function: (apply_expression
@ -30,6 +53,7 @@
(#set! injection.language "bash")
(#set! injection.combined))
; trivial-builders.nix pkgs.writeShellApplication { text = content; }
(apply_expression
function: ((_) @_func)
argument: (_ (_)* (_ (_)* (binding
@ -40,3 +64,89 @@
(#match? @_path "^text$")
(#set! injection.language "bash")
(#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
function: (identifier) @function)
(interpolated_string_expression
interpolator: (identifier) @function)
(
(identifier) @function.builtin
(#match? @function.builtin "^super$")

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -37,14 +37,15 @@
"markup.raw" = { 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.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "blue_text", bg = "blue_bg", modifiers = [
"underlined",
] }
"markup.link.label" = { fg = "blue_text", bg = "blue_bg" }
"markup.link.text" = { fg = "blue_text", bg = "blue_bg" }
"markup.quote" = { fg = "teal_text", bg = "teal_bg" }
"markup.bold" = { modifiers = ["bold"] }
"markup.list" = { fg = "purple_text", bg = "purple_bg" }
"ui.background" = { fg = "base1", bg = "base7" }

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

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

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

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

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

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

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

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

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

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

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

@ -46,6 +46,7 @@
"markup.list" = "red"
"markup.bold" = { fg = "yellow", modifiers = ["bold"] }
"markup.italic" = { fg = "magenta", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "yellow", modifiers = ["underlined"] }
"markup.link.text" = "red"
"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.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.italic" = { fg = "orange", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "orange", modifiers = ["underlined"] }
"markup.link.text" = "yellow"
"markup.quote" = "green"

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

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

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

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

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

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

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

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

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

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

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

@ -48,6 +48,7 @@
"markup.raw.inline" = { fg = "green", bg = "grey-200" }
"markup.bold" = { fg = "yellow", modifiers = ["bold"] }
"markup.italic" = { fg = "purple", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.list" = { fg = "light-blue" }
"markup.quote" = { fg = "gray" }
"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