aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/cachix.yml4
-rw-r--r--Cargo.lock27
-rw-r--r--Cargo.toml3
-rw-r--r--README.md34
-rw-r--r--book/src/configuration.md38
-rw-r--r--book/src/generated/lang-support.md5
-rw-r--r--book/src/generated/typable-cmd.md2
-rw-r--r--book/src/guides/indent.md14
-rw-r--r--book/src/install.md34
-rw-r--r--book/src/keymap.md118
-rw-r--r--book/src/remapping.md8
-rw-r--r--book/src/themes.md105
-rw-r--r--book/src/usage.md12
-rw-r--r--helix-core/Cargo.toml3
-rw-r--r--helix-core/src/indent.rs193
-rw-r--r--helix-core/src/movement.rs8
-rw-r--r--helix-core/src/selection.rs12
-rw-r--r--helix-core/src/syntax.rs81
-rw-r--r--helix-core/tests/indent.rs1
-rw-r--r--helix-dap/src/types.rs2
-rw-r--r--helix-lsp/Cargo.toml2
-rw-r--r--helix-lsp/src/client.rs6
-rw-r--r--helix-lsp/src/lib.rs35
-rw-r--r--helix-term/Cargo.toml2
-rw-r--r--helix-term/src/application.rs26
-rw-r--r--helix-term/src/args.rs10
-rw-r--r--helix-term/src/commands.rs277
-rw-r--r--helix-term/src/commands/lsp.rs8
-rw-r--r--helix-term/src/commands/typed.rs130
-rw-r--r--helix-term/src/keymap/default.rs42
-rw-r--r--helix-term/src/ui/completion.rs12
-rw-r--r--helix-term/src/ui/editor.rs191
-rw-r--r--helix-term/src/ui/fuzzy_match.rs74
-rw-r--r--helix-term/src/ui/fuzzy_match/test.rs47
-rw-r--r--helix-term/src/ui/lsp.rs3
-rw-r--r--helix-term/src/ui/menu.rs2
-rw-r--r--helix-term/src/ui/mod.rs42
-rw-r--r--helix-term/src/ui/picker.rs64
-rw-r--r--helix-term/src/ui/statusline.rs18
-rw-r--r--helix-term/tests/test/movement.rs149
-rw-r--r--helix-view/Cargo.toml1
-rw-r--r--helix-view/src/document.rs46
-rw-r--r--helix-view/src/editor.rs60
-rw-r--r--helix-view/src/handlers/dap.rs2
-rw-r--r--helix-view/src/info.rs33
-rw-r--r--helix-view/src/input.rs1
-rw-r--r--helix-view/src/lib.rs19
-rw-r--r--helix-view/src/theme.rs199
-rw-r--r--helix-view/src/view.rs28
-rw-r--r--languages.toml52
-rw-r--r--runtime/queries/c-sharp/highlights.scm193
-rw-r--r--runtime/queries/c/highlights.scm167
-rw-r--r--runtime/queries/cairo/highlights.scm2
-rw-r--r--runtime/queries/cpp/highlights.scm122
-rw-r--r--runtime/queries/lua/folds.scm10
-rw-r--r--runtime/queries/lua/highlights.scm77
-rw-r--r--runtime/queries/lua/indents.scm10
-rw-r--r--runtime/queries/lua/injections.scm3
-rw-r--r--runtime/queries/lua/textobjects.scm15
-rw-r--r--runtime/queries/markdown/injections.scm2
-rw-r--r--runtime/queries/purescript/highlights.scm1
-rw-r--r--runtime/queries/purescript/injections.scm1
-rw-r--r--runtime/queries/purescript/locals.scm1
-rw-r--r--runtime/queries/python/indents.scm (renamed from runtime/queries/python/indents.scm_)26
-rw-r--r--runtime/queries/rust/highlights.scm3
-rw-r--r--runtime/queries/twig/highlights.scm70
-rw-r--r--runtime/queries/wgsl/highlights.scm175
-rw-r--r--runtime/queries/zig/indents.scm8
-rw-r--r--runtime/themes/ayu_light.toml30
-rw-r--r--runtime/themes/ayu_mirage.toml3
-rw-r--r--runtime/themes/bogster.toml146
-rw-r--r--runtime/themes/bogster_light.toml97
-rw-r--r--runtime/themes/dark_high_contrast.toml105
-rw-r--r--runtime/themes/everforest_dark.toml31
-rw-r--r--runtime/themes/everforest_light.toml31
-rw-r--r--runtime/themes/heisenberg.toml86
-rw-r--r--runtime/themes/kanagawa.toml125
-rw-r--r--runtime/themes/monokai_pro_octagon.toml3
-rw-r--r--runtime/themes/onedarker.toml8
-rw-r--r--runtime/themes/pop-dark.toml1
-rw-r--r--runtime/themes/rose_pine.toml6
-rw-r--r--runtime/themes/rose_pine_dawn.toml6
-rw-r--r--runtime/themes/rose_pine_moon.toml4
-rw-r--r--runtime/themes/sonokai.toml4
-rw-r--r--runtime/tutor172
-rw-r--r--xtask/src/themelint.rs5
86 files changed, 2927 insertions, 1107 deletions
diff --git a/.github/workflows/cachix.yml b/.github/workflows/cachix.yml
index 2d37b36a..bc72bb78 100644
--- a/.github/workflows/cachix.yml
+++ b/.github/workflows/cachix.yml
@@ -14,10 +14,10 @@ jobs:
uses: actions/checkout@v3
- name: Install nix
- uses: cachix/install-nix-action@v17
+ uses: cachix/install-nix-action@v18
- name: Authenticate with Cachix
- uses: cachix/cachix-action@v10
+ uses: cachix/cachix-action@v11
with:
name: helix
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
diff --git a/Cargo.lock b/Cargo.lock
index 6af26d37..ec48c596 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -388,6 +388,7 @@ name = "helix-core"
version = "0.6.0"
dependencies = [
"arc-swap",
+ "bitflags",
"chrono",
"encoding_rs",
"etcetera",
@@ -524,6 +525,7 @@ dependencies = [
"futures-util",
"helix-core",
"helix-dap",
+ "helix-loader",
"helix-lsp",
"helix-tui",
"log",
@@ -937,9 +939,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.85"
+version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
+checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
dependencies = [
"itoa",
"ryu",
@@ -1025,9 +1027,9 @@ dependencies = [
[[package]]
name = "smallvec"
-version = "1.9.0"
+version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "smartstring"
@@ -1121,18 +1123,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.36"
+version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a99cb8c4b9a8ef0e7907cd3b617cc8dc04d571c4e73c8ae403d80ac160bb122"
+checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.36"
+version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a891860d3c8d66fec8e73ddb3765f90082374dbaaa833407b904a94f1a7eb43"
+checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
dependencies = [
"proc-macro2",
"quote",
@@ -1174,9 +1176,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
-version = "1.21.1"
+version = "1.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0020c875007ad96677dcc890298f4b942882c5d4eb7cc8f439fc3bf813dc9c95"
+checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
dependencies = [
"autocfg",
"bytes",
@@ -1184,7 +1186,6 @@ dependencies = [
"memchr",
"mio",
"num_cpus",
- "once_cell",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
@@ -1206,9 +1207,9 @@ dependencies = [
[[package]]
name = "tokio-stream"
-version = "0.1.10"
+version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af"
+checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce"
dependencies = [
"futures-core",
"pin-project-lite",
diff --git a/Cargo.toml b/Cargo.toml
index 780811f7..9e985ddc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,9 +14,6 @@ default-members = [
"helix-term"
]
-[profile.dev]
-split-debuginfo = "unpacked"
-
[profile.release]
lto = "thin"
# debug = true
diff --git a/README.md b/README.md
index ff0699c6..85f80b65 100644
--- a/README.md
+++ b/README.md
@@ -42,17 +42,15 @@ cargo install --path helix-term
```
This will install the `hx` binary to `$HOME/.cargo/bin` and build tree-sitter grammars.
-If you want to customize your `languages.toml` config,
-tree-sitter grammars may be manually fetched and built with `hx --grammar fetch` and `hx --grammar build`.
-Helix also needs its runtime files so make sure to copy/symlink the `runtime/` directory into the
+Helix needs its runtime files so make sure to copy/symlink the `runtime/` directory into the
config directory (for example `~/.config/helix/runtime` on Linux/macOS, or `%AppData%/helix/runtime` on Windows).
-| OS | Command |
-| -------------------- | -------------------------------------------- |
-| Windows (cmd.exe) | `xcopy /e /i runtime %AppData%\helix\runtime` |
-| Windows (PowerShell) | `xcopy /e /i runtime $Env:AppData\helix\runtime` |
-| Linux/macOS | `ln -s $PWD/runtime ~/.config/helix/runtime` |
+| OS | Command |
+| -------------------- | ------------------------------------------------ |
+| Windows (cmd.exe) | `xcopy /e /i runtime %AppData%\helix\runtime` |
+| Windows (PowerShell) | `xcopy /e /i runtime $Env:AppData\helix\runtime` |
+| Linux/macOS | `ln -s $PWD/runtime ~/.config/helix/runtime` |
This location can be overridden via the `HELIX_RUNTIME` environment variable.
@@ -62,12 +60,32 @@ that sets the variable to the install dir.
> NOTE: running via cargo also doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically
> detect the `runtime` directory in the project root.
+If you want to customize your `languages.toml` config,
+tree-sitter grammars may be manually fetched and built with `hx --grammar fetch` and `hx --grammar build`.
+
In order to use LSP features like auto-complete, you will need to
[install the appropriate Language Server](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
for a language.
[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
+## Adding Helix to your desktop environment
+
+If installing from source, to use Helix in desktop environments that supports [XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html), including Gnome and KDE, copy the provided `.desktop` file to the correct folder:
+
+```bash
+cp contrib/Helix.desktop ~/.local/share/applications
+```
+
+To use another terminal than the default, you will need to modify the `.desktop` file. For example, to use `kitty`:
+
+```bash
+sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
+sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
+```
+
+Please note: there is no icon for Helix yet, so the system default will be used.
+
## MacOS
Helix can be installed on MacOS through homebrew:
diff --git a/book/src/configuration.md b/book/src/configuration.md
index fdabe768..996c5fb6 100644
--- a/book/src/configuration.md
+++ b/book/src/configuration.md
@@ -28,19 +28,24 @@ hidden = false
You may also specify a file to use for configuration with the `-c` or
`--config` CLI argument: `hx -c path/to/custom-config.toml`.
+It is also possible to trigger configuration file reloading by sending the `USR1`
+signal to the helix process, e.g. via `pkill -USR1 hx`. This is only supported
+on unix operating systems.
+
## Editor
### `[editor]` Section
| Key | Description | Default |
|--|--|---------|
-| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling. | `3` |
+| `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` |
| `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` |
| `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
| `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers. | `absolute` |
| `cursorline` | Highlight all lines with a cursor. | `false` |
+| `cursorcolumn` | Highlight all columns with a cursor. | `false` |
| `gutters` | Gutters to display: Available are `diagnostics` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "line-numbers"]` |
| `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
| `auto-format` | Enable automatic formatting on save. | `true` |
@@ -68,17 +73,32 @@ left = ["mode", "spinner"]
center = ["file-name"]
right = ["diagnostics", "selections", "position", "file-encoding", "file-line-ending", "file-type"]
separator = "│"
+mode.normal = "NORMAL"
+mode.insert = "INSERT"
+mode.select = "SELECT"
```
+The `[editor.statusline]` key takes the following sub-keys:
+
+| Key | Description | Default |
+| --- | --- | --- |
+| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name"]` |
+| `center` | A list of elements aligned to the middle of the statusline | `[]` |
+| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "position", "file-encoding"]` |
+| `separator` | The character used to separate elements in the statusline | `"│"` |
+| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
+| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
+| `mode.select` | The text shown in the `mode` element for select mode | `"SEL"` |
-The following elements can be configured:
+The following statusline elements can be configured:
| Key | Description |
| ------ | ----------- |
-| `mode` | The current editor mode (`NOR`/`INS`/`SEL`) |
+| `mode` | The current editor mode (`mode.normal`/`mode.insert`/`mode.select`) |
| `spinner` | A progress spinner indicating LSP activity |
| `file-name` | The path/name of the opened file |
| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
| `file-line-ending` | The file line endings (CRLF or LF) |
+| `total-line-numbers` | The total line numbers of the opened file |
| `file-type` | The type of the opened file |
| `diagnostics` | The number of warnings and/or errors |
| `selections` | The number of active selections |
@@ -218,15 +238,17 @@ tabpad = "·" # Tabs will look like "→···" (depending on tab width)
Options for rendering vertical indent guides.
-| Key | Description | Default |
-| --- | --- | --- |
-| `render` | Whether to render indent guides. | `false` |
-| `character` | Literal character to use for rendering the indent guide | `│` |
+| Key | Description | Default |
+| --- | --- | --- |
+| `render` | Whether to render indent guides. | `false` |
+| `character` | Literal character to use for rendering the indent guide | `│` |
+| `skip-levels` | Number of indent levels to skip | `0` |
Example:
```toml
[editor.indent-guides]
render = true
-character = "╎"
+character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽"
+skip-levels = 1
```
diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md
index 5c64d097..3fe9ef70 100644
--- a/book/src/generated/lang-support.md
+++ b/book/src/generated/lang-support.md
@@ -66,7 +66,7 @@
| llvm | ✓ | ✓ | ✓ | |
| llvm-mir | ✓ | ✓ | ✓ | |
| llvm-mir-yaml | ✓ | | ✓ | |
-| lua | ✓ | | ✓ | `lua-language-server` |
+| lua | ✓ | ✓ | ✓ | `lua-language-server` |
| make | ✓ | | | |
| markdown | ✓ | | | `marksman` |
| markdown.inline | ✓ | | | |
@@ -86,7 +86,8 @@
| prisma | ✓ | | | `prisma-language-server` |
| prolog | | | | `swipl` |
| protobuf | ✓ | | ✓ | |
-| python | ✓ | ✓ | | `pylsp` |
+| purescript | ✓ | | | `purescript-language-server` |
+| python | ✓ | ✓ | ✓ | `pylsp` |
| r | ✓ | | | `R` |
| racket | | | | `racket` |
| regex | ✓ | | | |
diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md
index 4cbff306..05a0985b 100644
--- a/book/src/generated/typable-cmd.md
+++ b/book/src/generated/typable-cmd.md
@@ -65,7 +65,7 @@
| `:config-reload` | Refresh user config. |
| `:config-open` | Open the user config.toml file. |
| `:log-open` | Open the helix log file. |
-| `:insert-output` | Run shell command, inserting output after each selection. |
+| `:insert-output` | Run shell command, inserting output before each selection. |
| `:append-output` | Run shell command, appending output after each selection. |
| `:pipe` | Pipe each selection to the shell command. |
| `:run-shell-command`, `:sh` | Run a shell command |
diff --git a/book/src/guides/indent.md b/book/src/guides/indent.md
index f4d916b2..0e259289 100644
--- a/book/src/guides/indent.md
+++ b/book/src/guides/indent.md
@@ -46,6 +46,20 @@ capture on the same line, the indent level isn't changed at all.
- `@outdent` (default scope `all`):
Decrease the indent level by 1. The same rules as for `@indent` apply.
+- `@extend`:
+Extend the range of this node to the end of the line and to lines that
+are indented more than the line that this node starts on. This is useful
+for languages like Python, where for the purpose of indentation some nodes
+(like functions or classes) should also contain indented lines that follow them.
+
+- `@extend.prevent-once`:
+Prevents the first extension of an ancestor of this node. For example, in Python
+a return expression always ends the block that it is in. Note that this only stops the
+extension of the next `@extend` capture. If multiple ancestors are captured,
+only the extension of the innermost one is prevented. All other ancestors are unaffected
+(regardless of whether the innermost ancestor would actually have been extended).
+
+
## Predicates
In some cases, an S-expression cannot express exactly what pattern should be matched.
diff --git a/book/src/install.md b/book/src/install.md
index d7a51ac2..136e12c9 100644
--- a/book/src/install.md
+++ b/book/src/install.md
@@ -60,24 +60,40 @@ cargo install --path helix-term
This will install the `hx` binary to `$HOME/.cargo/bin`.
-Helix also needs it's runtime files so make sure to copy/symlink the `runtime/` directory into the
+Helix also needs its runtime files so make sure to copy/symlink the `runtime/` directory into the
config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overridden
via the `HELIX_RUNTIME` environment variable.
-| OS | command |
-|-------------------|-----------|
-|windows(cmd.exe) |`xcopy /e /i runtime %AppData%/helix/runtime` |
-|windows(powershell)|`xcopy /e /i runtime $Env:AppData\helix\runtime` |
-|linux/macos |`ln -s $PWD/runtime ~/.config/helix/runtime`|
+| OS | command |
+| ------------------- | ------------------------------------------------ |
+| windows(cmd.exe) | `xcopy /e /i runtime %AppData%/helix/runtime` |
+| windows(powershell) | `xcopy /e /i runtime $Env:AppData\helix\runtime` |
+| linux/macos | `ln -s $PWD/runtime ~/.config/helix/runtime` |
-## Finishing up the installation
+To use Helix in desktop environments that supports [XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html), including Gnome and KDE, copy the provided `.desktop` file to the correct folder:
+
+```bash
+cp contrib/Helix.desktop ~/.local/share/applications
+```
+
+To use another terminal than the default, you will need to modify the `.desktop` file. For example, to use `kitty`:
+
+```bash
+sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
+sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
+```
+
+Please note: there is no icon for Helix yet, so the system default will be used.
+
+## Finishing up the installation
+
+To make sure everything is set up as expected you should finally run the helix healthcheck via
-To make sure everything is set up as expected you should finally run the helix healthcheck via
```
hx --health
```
-For more information on the information displayed in the healthcheck results refer to [Healthcheck](https://github.com/helix-editor/helix/wiki/Healthcheck).
+For more information on the information displayed in the health check results refer to [Healthcheck](https://github.com/helix-editor/helix/wiki/Healthcheck).
### Building tree-sitter grammars
diff --git a/book/src/keymap.md b/book/src/keymap.md
index 6d90d802..6523b09f 100644
--- a/book/src/keymap.md
+++ b/book/src/keymap.md
@@ -68,8 +68,8 @@
| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` |
| `i` | Insert before selection | `insert_mode` |
| `a` | Insert after selection (append) | `append_mode` |
-| `I` | Insert at the start of the line | `prepend_to_line` |
-| `A` | Insert at the end of the line | `append_to_line` |
+| `I` | Insert at the start of the line | `insert_at_line_start` |
+| `A` | Insert at the end of the line | `insert_at_line_end` |
| `o` | Open new line below selection | `open_below` |
| `O` | Open new line above selection | `open_above` |
| `.` | Repeat last insert | N/A |
@@ -129,6 +129,7 @@
| `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` |
| `Alt-x` | Shrink selection to line bounds (line-wise selection) | `shrink_to_line_bounds` |
| `J` | Join lines inside selection | `join_selections` |
+| `Alt-J` | Join lines inside selection and select space | `join_selections_space` |
| `K` | Keep selections matching the regex | `keep_selections` |
| `Alt-K` | Remove selections matching the regex | `remove_selections` |
| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
@@ -166,10 +167,13 @@ These sub-modes are accessible from normal mode and typically switch back to nor
#### View mode
+Accessed by typing `z` in [normal mode](#normal-mode).
+
View mode is intended for scrolling and manipulating the view without changing
-the selection. The "sticky" variant of this mode is persistent; use the Escape
-key to return to normal mode after usage (useful when you're simply looking
-over text and not actively editing it).
+the selection. The "sticky" variant of this mode (accessed by typing `Z` in
+normal mode) is persistent; use the Escape key to return to normal mode after
+usage (useful when you're simply looking over text and not actively editing
+it).
| Key | Description | Command |
@@ -187,6 +191,8 @@ over text and not actively editing it).
#### Goto mode
+Accessed by typing `g` in [normal mode](#normal-mode).
+
Jumps to various locations.
| Key | Description | Command |
@@ -212,9 +218,10 @@ Jumps to various locations.
#### Match mode
-Enter this mode using `m` from normal mode. See the relevant section
-in [Usage](./usage.md) for an explanation about [surround](./usage.md#surround)
-and [textobject](./usage.md#textobject) usage.
+Accessed by typing `m` in [normal mode](#normal-mode).
+
+See the relevant section in [Usage](./usage.md) for an explanation about
+[surround](./usage.md#surround) and [textobject](./usage.md#textobjects) usage.
| Key | Description | Command |
| ----- | ----------- | ------- |
@@ -229,6 +236,8 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`).
#### Window mode
+Accessed by typing `Ctrl-w` in [normal mode](#normal-mode).
+
This layer is similar to Vim keybindings as Kakoune does not support window.
| Key | Description | Command |
@@ -251,8 +260,9 @@ This layer is similar to Vim keybindings as Kakoune does not support window.
#### Space mode
-This layer is a kludge of mappings, mostly pickers.
+Accessed by typing `Space` in [normal mode](#normal-mode).
+This layer is a kludge of mappings, mostly pickers.
| Key | Description | Command |
| ----- | ----------- | ------- |
@@ -263,8 +273,8 @@ This layer is a kludge of mappings, mostly pickers.
| `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` |
| `s` | Open document symbol picker (**LSP**) | `symbol_picker` |
| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` |
-| `g` | Open document diagnostics picker (**LSP**) | `diagnostics_picker` |
-| `G` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnostics_picker`
+| `d` | Open document diagnostics picker (**LSP**) | `diagnostics_picker` |
+| `D` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnostics_picker` |
| `r` | Rename symbol (**LSP**) | `rename_symbol` |
| `a` | Apply code action (**LSP**) | `code_action` |
| `'` | Open last fuzzy picker | `last_picker` |
@@ -277,7 +287,7 @@ This layer is a kludge of mappings, mostly pickers.
| `/` | Global search in workspace folder | `global_search` |
| `?` | Open command palette | `command_palette` |
-> TIP: 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
@@ -310,10 +320,10 @@ Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaire
| `]t` | Go to previous test (**TS**) | `goto_prev_test` |
| `]p` | Go to next paragraph | `goto_next_paragraph` |
| `[p` | Go to previous paragraph | `goto_prev_paragraph` |
-| `[space` | Add newline above | `add_newline_above` |
-| `]space` | Add newline below | `add_newline_below` |
+| `[Space` | Add newline above | `add_newline_above` |
+| `]Space` | Add newline below | `add_newline_below` |
-## Insert Mode
+## Insert mode
Insert mode bindings are somewhat minimal by default. Helix is designed to
be a modal editor, and this is reflected in the user experience and internal
@@ -322,44 +332,47 @@ escaping from insert mode to normal mode. For this reason, 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-x` | Autocomplete | `completion` |
-| `Ctrl-r` | Insert a register content | `insert_register` |
-| `Ctrl-w`, `Alt-Backspace`, `Ctrl-Backspace` | Delete previous word | `delete_word_backward` |
-| `Alt-d`, `Alt-Delete`, `Ctrl-Delete` | Delete next word | `delete_word_forward` |
-| `Ctrl-u` | Delete to start of line | `kill_to_line_start` |
-| `Ctrl-k` | Delete to end of line | `kill_to_line_end` |
-| `Ctrl-j`, `Enter` | Insert new line | `insert_newline` |
-| `Backspace`, `Ctrl-h` | Delete previous char | `delete_char_backward` |
-| `Delete`, `Ctrl-d` | Delete next char | `delete_char_forward` |
-
-However, if you really want navigation in insert mode, this is supported. An
-example config that gives the ability to use arrow keys while still in insert
-mode:
+| Key | Description | Command |
+| ----- | ----------- | ------- |
+| `Escape` | Switch to normal mode | `normal_mode` |
+| `Ctrl-s` | Commit undo checkpoint | `commit_undo_checkpoint` |
+| `Ctrl-x` | Autocomplete | `completion` |
+| `Ctrl-r` | Insert a register content | `insert_register` |
+| `Ctrl-w`, `Alt-Backspace` | Delete previous word | `delete_word_backward` |
+| `Alt-d`, `Alt-Delete` | Delete next word | `delete_word_forward` |
+| `Ctrl-u` | Delete to start of line | `kill_to_line_start` |
+| `Ctrl-k` | Delete to end of line | `kill_to_line_end` |
+| `Ctrl-h`, `Backspace` | Delete previous char | `delete_char_backward` |
+| `Ctrl-d`, `Delete` | Delete next char | `delete_char_forward` |
+| `Ctrl-j`, `Enter` | Insert new line | `insert_newline` |
+
+These keys are not recommended, but are included for new users less familiar
+with modal editors.
+
+| Key | Description | Command |
+| ----- | ----------- | ------- |
+| `Up` | Move to previous line | `move_line_up` |
+| `Down` | Move to next line | `move_line_down` |
+| `Left` | Backward a char | `move_char_left` |
+| `Right` | Forward a char | `move_char_right` |
+| `PageUp` | Move one page up | `page_up` |
+| `PageDown` | Move one page down | `page_down` |
+| `Home` | Move to line start | `goto_line_start` |
+| `End` | Move to line end | `goto_line_end_newline` |
+
+If you want to disable them in insert mode as you become more comfortable with modal editing, you can use
+the following in your `config.toml`:
```toml
[keys.insert]
-"up" = "move_line_up"
-"down" = "move_line_down"
-"left" = "move_char_left"
-"right" = "move_char_right"
-"C-b" = "move_char_left"
-"C-f" = "move_char_right"
-"A-b" = "move_prev_word_end"
-"C-left" = "move_prev_word_end"
-"A-f" = "move_next_word_start"
-"C-right" = "move_next_word_start"
-"A-<" = "goto_file_start"
-"A->" = "goto_file_end"
-"pageup" = "page_up"
-"pagedown" = "page_down"
-"home" = "goto_line_start"
-"C-a" = "goto_line_start"
-"end" = "goto_line_end_newline"
-"C-e" = "goto_line_end_newline"
-"A-left" = "goto_line_start"
+up = "no_op"
+down = "no_op"
+left = "no_op"
+right = "no_op"
+pageup = "no_op"
+pagedown = "no_op"
+home = "no_op"
+end = "no_op"
```
## Select / extend mode
@@ -387,7 +400,6 @@ Keys to use within picker. Remapping currently not supported.
| `PageDown`, `Ctrl-d` | Page down |
| `Home` | Go to first entry |
| `End` | Go to last entry |
-| `Ctrl-space` | Filter options |
| `Enter` | Open selected |
| `Ctrl-s` | Open horizontally |
| `Ctrl-v` | Open vertically |
@@ -411,8 +423,8 @@ Keys to use within prompt, Remapping currently not supported.
| `Alt-d`, `Alt-Delete`, `Ctrl-Delete` | Delete next word |
| `Ctrl-u` | Delete to start of line |
| `Ctrl-k` | Delete to end of line |
-| `backspace`, `Ctrl-h` | Delete previous char |
-| `delete`, `Ctrl-d` | Delete next char |
+| `Backspace`, `Ctrl-h` | Delete previous char |
+| `Delete`, `Ctrl-d` | Delete next char |
| `Ctrl-s` | Insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later |
| `Ctrl-p`, `Up` | Select previous history |
| `Ctrl-n`, `Down` | Select next history |
diff --git a/book/src/remapping.md b/book/src/remapping.md
index bd4ac7f8..e89c6611 100644
--- a/book/src/remapping.md
+++ b/book/src/remapping.md
@@ -11,11 +11,11 @@ this:
```toml
# At most one section each of 'keys.normal', 'keys.insert' and 'keys.select'
[keys.normal]
-C-s = ":w" # Maps the Control-s to the typable command :w which is an alias for :write (save file)
-C-o = ":open ~/.config/helix/config.toml" # Maps the Control-o to opening of the helix config file
+C-s = ":w" # Maps the Ctrl-s to the typable command :w which is an alias for :write (save file)
+C-o = ":open ~/.config/helix/config.toml" # Maps the Ctrl-o to opening of the helix config file
a = "move_char_left" # Maps the 'a' key to the move_char_left command
w = "move_line_up" # Maps the 'w' key move_line_up
-"C-S-esc" = "extend_line" # Maps Control-Shift-Escape to extend_line
+"C-S-esc" = "extend_line" # Maps Ctrl-Shift-Escape to extend_line
g = { a = "code_action" } # Maps `ga` to show possible code actions
"ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode
@@ -25,7 +25,7 @@ j = { k = "normal_mode" } # Maps `jk` to exit insert mode
```
> NOTE: Typable commands can also be remapped, remember to keep the `:` prefix to indicate it's a typable command.
-Control, Shift and Alt modifiers are encoded respectively with the prefixes
+Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes
`C-`, `S-` and `A-`. Special keys are encoded as follows:
| Key name | Representation |
diff --git a/book/src/themes.md b/book/src/themes.md
index 9738912c..392b5f8c 100644
--- a/book/src/themes.md
+++ b/book/src/themes.md
@@ -107,6 +107,21 @@ Some styles might not be supported by your terminal emulator.
| `double_line` |
+### Inheritance
+
+Extend upon other themes by setting the `inherits` property to an existing theme.
+
+```toml
+inherits = "boo_berry"
+
+# Override the theming for "keyword"s:
+"keyword" = { fg = "gold" }
+
+# Override colors in the palette:
+[palette]
+berry = "#2A2A4D"
+```
+
### Scopes
The following is a list of scopes available to use for styling.
@@ -228,49 +243,53 @@ These scopes are used for theming the editor interface.
- `hover` - for hover popup ui
-| Key | Notes |
-| --- | --- |
-| `ui.background` | |
-| `ui.background.separator` | Picker separator below input line |
-| `ui.cursor` | |
-| `ui.cursor.insert` | |
-| `ui.cursor.select` | |
-| `ui.cursor.match` | Matching bracket etc. |
-| `ui.cursor.primary` | Cursor with primary selection |
-| `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.popup.info` | Prompt for multiple key options |
-| `ui.window` | Border lines separating splits |
-| `ui.help` | Description box for commands |
-| `ui.text` | Command prompts, popup text, etc. |
-| `ui.text.focus` | |
-| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
-| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section])|
-| `ui.virtual.whitespace` | Visible white-space characters |
-| `ui.virtual.indent-guide` | Vertical indent width guides |
-| `ui.menu` | Code and command completion menus |
-| `ui.menu.selected` | Selected autocomplete item |
-| `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 |
-| `ui.cursorline.secondary` | The lines of any other cursors |
-| `warning` | Diagnostics warning (gutter) |
-| `error` | Diagnostics error (gutter) |
-| `info` | Diagnostics info (gutter) |
-| `hint` | Diagnostics hint (gutter) |
-| `diagnostic` | Diagnostics fallback style (editing area) |
-| `diagnostic.hint` | Diagnostics hint (editing area) |
-| `diagnostic.info` | Diagnostics info (editing area) |
-| `diagnostic.warning` | Diagnostics warning (editing area) |
-| `diagnostic.error` | Diagnostics error (editing area) |
+| Key | Notes |
+| --- | --- |
+| `ui.background` | |
+| `ui.background.separator` | Picker separator below input line |
+| `ui.cursor` | |
+| `ui.cursor.insert` | |
+| `ui.cursor.select` | |
+| `ui.cursor.match` | Matching bracket etc. |
+| `ui.cursor.primary` | Cursor with primary selection |
+| `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.popup.info` | Prompt for multiple key options |
+| `ui.window` | Border lines separating splits |
+| `ui.help` | Description box for commands |
+| `ui.text` | Command prompts, popup text, etc. |
+| `ui.text.focus` | |
+| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
+| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
+| `ui.virtual.whitespace` | Visible whitespace characters |
+| `ui.virtual.indent-guide` | Vertical indent width guides |
+| `ui.menu` | Code and command completion menus |
+| `ui.menu.selected` | Selected autocomplete item |
+| `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]) |
+| `warning` | Diagnostics warning (gutter) |
+| `error` | Diagnostics error (gutter) |
+| `info` | Diagnostics info (gutter) |
+| `hint` | Diagnostics hint (gutter) |
+| `diagnostic` | Diagnostics fallback style (editing area) |
+| `diagnostic.hint` | Diagnostics hint (editing area) |
+| `diagnostic.info` | Diagnostics info (editing area) |
+| `diagnostic.warning` | Diagnostics warning (editing area) |
+| `diagnostic.error` | Diagnostics error (editing area) |
You can check compliance to spec with
diff --git a/book/src/usage.md b/book/src/usage.md
index fc3a83ee..646bf926 100644
--- a/book/src/usage.md
+++ b/book/src/usage.md
@@ -53,7 +53,7 @@ Multiple characters are currently not supported, but planned.
## Syntax-tree Motions
-`A-p`, `A-o`, `A-i`, and `A-n` (or `Alt` and arrow keys) move the primary
+`Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) move the primary
selection according to the selection's place in the syntax tree. Let's walk
through an example to get familiar with them. Many languages have a syntax like
so for function calls:
@@ -100,13 +100,13 @@ in the tree above.
func([arg1], arg2, arg3)
```
-Using `A-n` would select the next sibling in the syntax tree: `arg2`.
+Using `Alt-n` would select the next sibling in the syntax tree: `arg2`.
```
func(arg1, [arg2], arg3)
```
-While `A-o` would expand the selection to the parent node. In the tree above we
+While `Alt-o` would expand the selection to the parent node. In the tree above we
can see that we would select the `arguments` node.
```
@@ -114,10 +114,10 @@ func[(arg1, arg2, arg3)]
```
There is also some nuanced behavior that prevents you from getting stuck on a
-node with no sibling. If we have a selection on `arg1`, `A-p` would bring us
+node with no sibling. If we have a selection on `arg1`, `Alt-p` would bring us
to the previous child node. Since `arg1` doesn't have a sibling to its left,
-though, we climb the syntax tree and then take the previous selection. So `A-p`
-will move the selection over to the "func" `identifier`.
+though, we climb the syntax tree and then take the previous selection. So
+`Alt-p` will move the selection over to the "func" `identifier`.
```
[func](arg1, arg2, arg3)
diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml
index ba6901ba..4eaadd1e 100644
--- a/helix-core/Cargo.toml
+++ b/helix-core/Cargo.toml
@@ -18,7 +18,7 @@ integration = []
helix-loader = { version = "0.6", path = "../helix-loader" }
ropey = { version = "1.5", default-features = false, features = ["simd"] }
-smallvec = "1.9"
+smallvec = "1.10"
smartstring = "1.0.1"
unicode-segmentation = "1.10"
unicode-width = "0.1"
@@ -29,6 +29,7 @@ tree-sitter = "0.20"
once_cell = "1.15"
arc-swap = "1"
regex = "1"
+bitflags = "1.3"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs
index ad079c25..9526fc8a 100644
--- a/helix-core/src/indent.rs
+++ b/helix-core/src/indent.rs
@@ -192,13 +192,15 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
/// Computes for node and all ancestors whether they are the first node on their line.
/// The first entry in the return value represents the root node, the last one the node itself
-fn get_first_in_line(mut node: Node, byte_pos: usize, new_line: bool) -> Vec<bool> {
+fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bool> {
let mut first_in_line = Vec::new();
loop {
if let Some(prev) = node.prev_sibling() {
// If we insert a new line, the first node at/after the cursor is considered to be the first in its line
let first = prev.end_position().row != node.start_position().row
- || (new_line && node.start_byte() >= byte_pos && prev.start_byte() < byte_pos);
+ || new_line_byte_pos.map_or(false, |byte_pos| {
+ node.start_byte() >= byte_pos && prev.start_byte() < byte_pos
+ });
first_in_line.push(Some(first));
} else {
// Nodes that have no previous siblings are first in their line if and only if their parent is
@@ -298,8 +300,21 @@ enum IndentScope {
Tail,
}
-/// Execute the indent query.
-/// Returns for each node (identified by its id) a list of indent captures for that node.
+/// A capture from the indent query which does not define an indent but extends
+/// the range of a node. This is used before the indent is calculated.
+enum ExtendCapture {
+ Extend,
+ PreventOnce,
+}
+
+/// The result of running a tree-sitter indent query. This stores for
+/// each node (identified by its ID) the relevant captures (already filtered
+/// by predicates).
+struct IndentQueryResult {
+ indent_captures: HashMap<usize, Vec<IndentCapture>>,
+ extend_captures: HashMap<usize, Vec<ExtendCapture>>,
+}
+
fn query_indents(
query: &Query,
syntax: &Syntax,
@@ -309,8 +324,9 @@ fn query_indents(
// Position of the (optional) newly inserted line break.
// Given as (line, byte_pos)
new_line_break: Option<(usize, usize)>,
-) -> HashMap<usize, Vec<IndentCapture>> {
+) -> IndentQueryResult {
let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
+ let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
cursor.set_byte_range(range);
// Iterate over all captures from the query
for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
@@ -374,10 +390,24 @@ fn query_indents(
continue;
}
for capture in m.captures {
- let capture_type = query.capture_names()[capture.index as usize].as_str();
- let capture_type = match capture_type {
+ let capture_name = query.capture_names()[capture.index as usize].as_str();
+ let capture_type = match capture_name {
"indent" => IndentCaptureType::Indent,
"outdent" => IndentCaptureType::Outdent,
+ "extend" => {
+ extend_captures
+ .entry(capture.node.id())
+ .or_insert_with(|| Vec::with_capacity(1))
+ .push(ExtendCapture::Extend);
+ continue;
+ }
+ "extend.prevent-once" => {
+ extend_captures
+ .entry(capture.node.id())
+ .or_insert_with(|| Vec::with_capacity(1))
+ .push(ExtendCapture::PreventOnce);
+ continue;
+ }
_ => {
// Ignore any unknown captures (these may be needed for predicates such as #match?)
continue;
@@ -420,7 +450,72 @@ fn query_indents(
.push(indent_capture);
}
}
- indent_captures
+ IndentQueryResult {
+ indent_captures,
+ extend_captures,
+ }
+}
+
+/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
+/// Any ancestor of deepest_preceding which is also a descendant of node may be "extended". In that case, node will be updated,
+/// so that the indent computation starts with the correct syntax node.
+fn extend_nodes<'a>(
+ node: &mut Node<'a>,
+ deepest_preceding: Option<Node<'a>>,
+ extend_captures: &HashMap<usize, Vec<ExtendCapture>>,
+ text: RopeSlice,
+ line: usize,
+ tab_width: usize,
+) {
+ if let Some(mut deepest_preceding) = deepest_preceding {
+ let mut stop_extend = false;
+ while deepest_preceding != *node {
+ let mut extend_node = false;
+ // This will be set to true if this node is captured, regardless of whether
+ // it actually will be extended (e.g. because the cursor isn't indented
+ // more than the node).
+ let mut node_captured = false;
+ if let Some(captures) = extend_captures.get(&deepest_preceding.id()) {
+ for capture in captures {
+ match capture {
+ ExtendCapture::PreventOnce => {
+ stop_extend = true;
+ }
+ ExtendCapture::Extend => {
+ node_captured = true;
+ // We extend the node if
+ // - the cursor is on the same line as the end of the node OR
+ // - the line that the cursor is on is more indented than the
+ // first line of the node
+ if deepest_preceding.end_position().row == line {
+ extend_node = true;
+ } else {
+ let cursor_indent =
+ indent_level_for_line(text.line(line), tab_width);
+ let node_indent = indent_level_for_line(
+ text.line(deepest_preceding.start_position().row),
+ tab_width,
+ );
+ if cursor_indent > node_indent {
+ extend_node = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ // If we encountered some `StopExtend` capture before, we don't
+ // extend the node even if we otherwise would
+ if node_captured && stop_extend {
+ stop_extend = false;
+ } else if extend_node && !stop_extend {
+ *node = deepest_preceding;
+ break;
+ }
+ // This parent always exists since node is an ancestor of deepest_preceding
+ deepest_preceding = deepest_preceding.parent().unwrap();
+ }
+ }
}
/// Use the syntax tree to determine the indentation for a given position.
@@ -459,40 +554,73 @@ fn query_indents(
/// },
/// );
/// ```
+#[allow(clippy::too_many_arguments)]
pub fn treesitter_indent_for_pos(
query: &Query,
syntax: &Syntax,
indent_style: &IndentStyle,
+ tab_width: usize,
text: RopeSlice,
line: usize,
pos: usize,
new_line: bool,
) -> Option<String> {
let byte_pos = text.char_to_byte(pos);
+ // The innermost tree-sitter node which is considered for the indent
+ // computation. It may change if some predeceding node is extended
let mut node = syntax
.tree()
.root_node()
.descendant_for_byte_range(byte_pos, byte_pos)?;
- let mut first_in_line = get_first_in_line(node, byte_pos, new_line);
- let new_line_break = if new_line {
- Some((line, byte_pos))
- } else {
- None
+ let (query_result, deepest_preceding) = {
+ // The query range should intersect with all nodes directly preceding
+ // the position of the indent query in case one of them is extended.
+ let mut deepest_preceding = None; // The deepest node preceding the indent query position
+ let mut tree_cursor = node.walk();
+ for child in node.children(&mut tree_cursor) {
+ if child.byte_range().end <= byte_pos {
+ deepest_preceding = Some(child);
+ }
+ }
+ deepest_preceding = deepest_preceding.map(|mut prec| {
+ // Get the deepest directly preceding node
+ while prec.child_count() > 0 {
+ prec = prec.child(prec.child_count() - 1).unwrap();
+ }
+ prec
+ });
+ let query_range = deepest_preceding
+ .map(|prec| prec.byte_range().end - 1..byte_pos + 1)
+ .unwrap_or(byte_pos..byte_pos + 1);
+
+ crate::syntax::PARSER.with(|ts_parser| {
+ let mut ts_parser = ts_parser.borrow_mut();
+ let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new);
+ let query_result = query_indents(
+ query,
+ syntax,
+ &mut cursor,
+ text,
+ query_range,
+ new_line.then(|| (line, byte_pos)),
+ );
+ ts_parser.cursors.push(cursor);
+ (query_result, deepest_preceding)
+ })
};
- let query_result = crate::syntax::PARSER.with(|ts_parser| {
- let mut ts_parser = ts_parser.borrow_mut();
- let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new);
- let query_result = query_indents(
- query,
- syntax,
- &mut cursor,
- text,
- byte_pos..byte_pos + 1,
- new_line_break,
- );
- ts_parser.cursors.push(cursor);
- query_result
- });
+ let indent_captures = query_result.indent_captures;
+ let extend_captures = query_result.extend_captures;
+
+ // Check for extend captures, potentially changing the node that the indent calculation starts with
+ extend_nodes(
+ &mut node,
+ deepest_preceding,
+ &extend_captures,
+ text,
+ line,
+ tab_width,
+ );
+ let mut first_in_line = get_first_in_line(node, new_line.then(|| byte_pos));
let mut result = Indentation::default();
// We always keep track of all the indent changes on one line, in order to only indent once
@@ -504,7 +632,7 @@ pub fn treesitter_indent_for_pos(
// one entry for each ancestor of the node (which is what we iterate over)
let is_first = *first_in_line.last().unwrap();
// Apply all indent definitions for this node
- if let Some(definitions) = query_result.get(&node.id()) {
+ if let Some(definitions) = indent_captures.get(&node.id()) {
for definition in definitions {
match definition.scope {
IndentScope::All => {
@@ -550,7 +678,13 @@ pub fn treesitter_indent_for_pos(
node = parent;
first_in_line.pop();
} else {
- result.add_line(&indent_for_line_below);
+ // Only add the indentation for the line below if that line
+ // is not after the line that the indentation is calculated for.
+ if (node.start_position().row < line)
+ || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
+ {
+ result.add_line(&indent_for_line_below);
+ }
result.add_line(&indent_for_line);
break;
}
@@ -579,6 +713,7 @@ pub fn indent_for_newline(
query,
syntax,
indent_style,
+ tab_width,
text,
line_before,
line_before_end_pos,
diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs
index c232484c..278375e8 100644
--- a/helix-core/src/movement.rs
+++ b/helix-core/src/movement.rs
@@ -389,6 +389,8 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
}
}
+/// Finds the range of the next or previous textobject in the syntax sub-tree of `node`.
+/// Returns the range in the forwards direction.
pub fn goto_treesitter_object(
slice: RopeSlice,
range: Range,
@@ -419,8 +421,8 @@ pub fn goto_treesitter_object(
.filter(|n| n.start_byte() > byte_pos)
.min_by_key(|n| n.start_byte())?,
Direction::Backward => nodes
- .filter(|n| n.start_byte() < byte_pos)
- .max_by_key(|n| n.start_byte())?,
+ .filter(|n| n.end_byte() < byte_pos)
+ .max_by_key(|n| n.end_byte())?,
};
let len = slice.len_bytes();
@@ -434,7 +436,7 @@ pub fn goto_treesitter_object(
let end_char = slice.byte_to_char(end_byte);
// head of range should be at beginning
- Some(Range::new(end_char, start_char))
+ Some(Range::new(start_char, end_char))
};
(0..count).fold(range, |range, _| get_range(range).unwrap_or(range))
}
diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs
index 3463c1d3..1f28ecef 100644
--- a/helix-core/src/selection.rs
+++ b/helix-core/src/selection.rs
@@ -122,7 +122,7 @@ impl Range {
}
}
- // flips the direction of the selection
+ /// Flips the direction of the selection
pub fn flip(&self) -> Self {
Self {
anchor: self.head,
@@ -131,6 +131,16 @@ impl Range {
}
}
+ /// Returns the selection if it goes in the direction of `direction`,
+ /// flipping the selection otherwise.
+ pub fn with_direction(self, direction: Direction) -> Self {
+ if self.direction() == direction {
+ self
+ } else {
+ self.flip()
+ }
+ }
+
/// Check two ranges for overlap.
#[must_use]
pub fn overlaps(&self, other: &Self) -> bool {
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index e0a984d2..61d382fd 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -8,13 +8,15 @@ use crate::{
};
use arc_swap::{ArcSwap, Guard};
+use bitflags::bitflags;
use slotmap::{DefaultKey as LayerId, HopSlotMap};
use std::{
borrow::Cow,
cell::RefCell,
- collections::{HashMap, HashSet, VecDeque},
+ collections::{HashMap, VecDeque},
fmt,
+ mem::replace,
path::Path,
str::FromStr,
sync::Arc,
@@ -366,7 +368,13 @@ impl LanguageConfiguration {
None
} else {
let language = get_language(self.grammar.as_deref().unwrap_or(&self.language_id))
- .map_err(|e| log::info!("{}", e))
+ .map_err(|err| {
+ log::error!(
+ "Failed to load tree-sitter parser for language {:?}: {}",
+ self.language_id,
+ err
+ )
+ })
.ok()?;
let config = HighlightConfiguration::new(
language,
@@ -594,6 +602,7 @@ impl Syntax {
tree: None,
config,
depth: 0,
+ flags: LayerUpdateFlags::empty(),
ranges: vec![Range {
start_byte: 0,
end_byte: usize::MAX,
@@ -656,9 +665,10 @@ impl Syntax {
}
}
- for layer in &mut self.layers.values_mut() {
+ for layer in self.layers.values_mut() {
// The root layer always covers the whole range (0..usize::MAX)
if layer.depth == 0 {
+ layer.flags = LayerUpdateFlags::MODIFIED;
continue;
}
@@ -689,6 +699,8 @@ impl Syntax {
edit.new_end_position,
point_sub(range.end_point, edit.old_end_position),
);
+
+ layer.flags |= LayerUpdateFlags::MOVED;
}
// if the edit starts in the space before and extends into the range
else if edit.start_byte < range.start_byte {
@@ -703,11 +715,13 @@ impl Syntax {
edit.new_end_position,
point_sub(range.end_point, edit.old_end_position),
);
+ layer.flags = LayerUpdateFlags::MODIFIED;
}
// If the edit is an insertion at the start of the tree, shift
else if edit.start_byte == range.start_byte && is_pure_insertion {
range.start_byte = edit.new_end_byte;
range.start_point = edit.new_end_position;
+ layer.flags |= LayerUpdateFlags::MOVED;
} else {
range.end_byte = range
.end_byte
@@ -717,6 +731,7 @@ impl Syntax {
edit.new_end_position,
point_sub(range.end_point, edit.old_end_position),
);
+ layer.flags = LayerUpdateFlags::MODIFIED;
}
}
}
@@ -731,27 +746,33 @@ impl Syntax {
let source_slice = source.slice(..);
- let mut touched = HashSet::new();
-
- // TODO: we should be able to avoid editing & parsing layers with ranges earlier in the document before the edit
-
while let Some(layer_id) = queue.pop_front() {
- // Mark the layer as touched
- touched.insert(layer_id);
-
let layer = &mut self.layers[layer_id];
+ // Mark the layer as touched
+ layer.flags |= LayerUpdateFlags::TOUCHED;
+
// If a tree already exists, notify it of changes.
if let Some(tree) = &mut layer.tree {
- for edit in edits.iter().rev() {
- // Apply the edits in reverse.
- // If we applied them in order then edit 1 would disrupt the positioning of edit 2.
- tree.edit(edit);
+ if layer
+ .flags
+ .intersects(LayerUpdateFlags::MODIFIED | LayerUpdateFlags::MOVED)
+ {
+ for edit in edits.iter().rev() {
+ // Apply the edits in reverse.
+ // If we applied them in order then edit 1 would disrupt the positioning of edit 2.
+ tree.edit(edit);
+ }
}
- }
- // Re-parse the tree.
- layer.parse(&mut ts_parser.parser, source)?;
+ if layer.flags.contains(LayerUpdateFlags::MODIFIED) {
+ // Re-parse the tree.
+ layer.parse(&mut ts_parser.parser, source)?;
+ }
+ } else {
+ // always parse if this layer has never been parsed before
+ layer.parse(&mut ts_parser.parser, source)?;
+ }
// Switch to an immutable borrow.
let layer = &self.layers[layer_id];
@@ -855,6 +876,8 @@ impl Syntax {
config,
depth,
ranges,
+ // set the modified flag to ensure the layer is parsed
+ flags: LayerUpdateFlags::empty(),
})
});
@@ -868,8 +891,11 @@ impl Syntax {
// Return the cursor back in the pool.
ts_parser.cursors.push(cursor);
- // Remove all untouched layers
- self.layers.retain(|id, _| touched.contains(&id));
+ // Reset all `LayerUpdateFlags` and remove all untouched layers
+ self.layers.retain(|_, layer| {
+ replace(&mut layer.flags, LayerUpdateFlags::empty())
+ .contains(LayerUpdateFlags::TOUCHED)
+ });
Ok(())
})
@@ -968,6 +994,16 @@ impl Syntax {
// TODO: Folding
}
+bitflags! {
+ /// Flags that track the status of a layer
+ /// in the `Sytaxn::update` function
+ struct LayerUpdateFlags : u32{
+ const MODIFIED = 0b001;
+ const MOVED = 0b010;
+ const TOUCHED = 0b100;
+ }
+}
+
#[derive(Debug)]
pub struct LanguageLayer {
// mode
@@ -975,7 +1011,8 @@ pub struct LanguageLayer {
pub config: Arc<HighlightConfiguration>,
pub(crate) tree: Option<Tree>,
pub ranges: Vec<Range>,
- pub depth: usize,
+ pub depth: u32,
+ flags: LayerUpdateFlags,
}
impl LanguageLayer {
@@ -1191,7 +1228,7 @@ struct HighlightIter<'a> {
layers: Vec<HighlightIterLayer<'a>>,
iter_count: usize,
next_event: Option<HighlightEvent>,
- last_highlight_range: Option<(usize, usize, usize)>,
+ last_highlight_range: Option<(usize, usize, u32)>,
}
// Adapter to convert rope chunks to bytes
@@ -1224,7 +1261,7 @@ struct HighlightIterLayer<'a> {
config: &'a HighlightConfiguration,
highlight_end_stack: Vec<usize>,
scope_stack: Vec<LocalScope<'a>>,
- depth: usize,
+ depth: u32,
ranges: &'a [Range],
}
diff --git a/helix-core/tests/indent.rs b/helix-core/tests/indent.rs
index ff04d05f..e1114f4a 100644
--- a/helix-core/tests/indent.rs
+++ b/helix-core/tests/indent.rs
@@ -50,6 +50,7 @@ fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
indent_query,
&syntax,
&IndentStyle::Spaces(4),
+ 4,
text,
i,
text.line_to_char(i) + pos,
diff --git a/helix-dap/src/types.rs b/helix-dap/src/types.rs
index 45f45cca..51ecfe1b 100644
--- a/helix-dap/src/types.rs
+++ b/helix-dap/src/types.rs
@@ -726,7 +726,7 @@ pub mod events {
#[serde(tag = "event", content = "body")]
// seq is omitted as unused and is not sent by some implementations
pub enum Event {
- Initialized,
+ Initialized(Option<DebuggerCapabilities>),
Stopped(Stopped),
Continued(Continued),
Exited(Exited),
diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml
index 536a6ba6..ad432d96 100644
--- a/helix-lsp/Cargo.toml
+++ b/helix-lsp/Cargo.toml
@@ -23,5 +23,5 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
tokio = { version = "1.21", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
-tokio-stream = "0.1.10"
+tokio-stream = "0.1.11"
which = "4.2"
diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs
index 497ce80c..0b443ccf 100644
--- a/helix-lsp/src/client.rs
+++ b/helix-lsp/src/client.rs
@@ -49,6 +49,7 @@ impl Client {
root_markers: &[String],
id: usize,
req_timeout: u64,
+ doc_path: Option<&std::path::PathBuf>,
) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
// Resolve path to the binary
let cmd = which::which(cmd).map_err(|err| anyhow::anyhow!(err))?;
@@ -72,7 +73,10 @@ impl Client {
let (server_rx, server_tx, initialize_notify) =
Transport::start(reader, writer, stderr, id);
- let root_path = find_root(None, root_markers);
+ let root_path = find_root(
+ doc_path.and_then(|x| x.parent().and_then(|x| x.to_str())),
+ root_markers,
+ );
let root_uri = lsp::Url::from_file_path(root_path.clone()).ok();
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs
index cb234357..feeedc96 100644
--- a/helix-lsp/src/lib.rs
+++ b/helix-lsp/src/lib.rs
@@ -204,6 +204,20 @@ pub mod util {
// in reverse order.
edits.sort_unstable_by_key(|edit| edit.range.start);
+ // Generate a diff if the edit is a full document replacement.
+ #[allow(clippy::collapsible_if)]
+ if edits.len() == 1 {
+ let is_document_replacement = edits.first().and_then(|edit| {
+ let start = lsp_pos_to_pos(doc, edit.range.start, offset_encoding)?;
+ let end = lsp_pos_to_pos(doc, edit.range.end, offset_encoding)?;
+ Some(start..end)
+ }) == Some(0..doc.len_chars());
+ if is_document_replacement {
+ let new_text = Rope::from(edits.pop().unwrap().new_text);
+ return helix_core::diff::compare_ropes(doc, &new_text);
+ }
+ }
+
Transaction::change(
doc,
edits.into_iter().map(|edit| {
@@ -339,6 +353,7 @@ impl Registry {
pub fn restart(
&mut self,
language_config: &LanguageConfiguration,
+ doc_path: Option<&std::path::PathBuf>,
) -> Result<Option<Arc<Client>>> {
let config = match &language_config.language_server {
Some(config) => config,
@@ -353,17 +368,26 @@ impl Registry {
// initialize a new client
let id = self.counter.fetch_add(1, Ordering::Relaxed);
- let NewClientResult(client, incoming) = start_client(id, language_config, config)?;
+ let NewClientResult(client, incoming) =
+ start_client(id, language_config, config, doc_path)?;
self.incoming.push(UnboundedReceiverStream::new(incoming));
- entry.insert((id, client.clone()));
+ let (_, old_client) = entry.insert((id, client.clone()));
+
+ tokio::spawn(async move {
+ let _ = old_client.force_shutdown().await;
+ });
Ok(Some(client))
}
}
}
- pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Option<Arc<Client>>> {
+ pub fn get(
+ &mut self,
+ language_config: &LanguageConfiguration,
+ doc_path: Option<&std::path::PathBuf>,
+ ) -> Result<Option<Arc<Client>>> {
let config = match &language_config.language_server {
Some(config) => config,
None => return Ok(None),
@@ -375,7 +399,8 @@ impl Registry {
// initialize a new client
let id = self.counter.fetch_add(1, Ordering::Relaxed);
- let NewClientResult(client, incoming) = start_client(id, language_config, config)?;
+ let NewClientResult(client, incoming) =
+ start_client(id, language_config, config, doc_path)?;
self.incoming.push(UnboundedReceiverStream::new(incoming));
entry.insert((id, client.clone()));
@@ -475,6 +500,7 @@ fn start_client(
id: usize,
config: &LanguageConfiguration,
ls_config: &LanguageServerConfiguration,
+ doc_path: Option<&std::path::PathBuf>,
) -> Result<NewClientResult> {
let (client, incoming, initialize_notify) = Client::start(
&ls_config.command,
@@ -483,6 +509,7 @@ fn start_client(
&config.roots,
id,
ls_config.timeout,
+ doc_path,
)?;
let client = Arc::new(client);
diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml
index ac50b610..30de5589 100644
--- a/helix-term/Cargo.toml
+++ b/helix-term/Cargo.toml
@@ -74,6 +74,6 @@ signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
helix-loader = { version = "0.6", path = "../helix-loader" }
[dev-dependencies]
-smallvec = "1.9"
+smallvec = "1.10"
indoc = "1.0.6"
tempfile = "3.3.0"
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index cd499f1c..4bb36b59 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -224,8 +224,8 @@ impl Application {
#[cfg(windows)]
let signals = futures_util::stream::empty();
#[cfg(not(windows))]
- let signals =
- Signals::new(&[signal::SIGTSTP, signal::SIGCONT]).context("build signal handler")?;
+ let signals = Signals::new(&[signal::SIGTSTP, signal::SIGCONT, signal::SIGUSR1])
+ .context("build signal handler")?;
let app = Self {
compositor,
@@ -426,23 +426,22 @@ impl Application {
self.compositor.load_cursor();
self.render();
}
+ signal::SIGUSR1 => {
+ self.refresh_config();
+ self.render();
+ }
_ => unreachable!(),
}
}
pub fn handle_idle_timeout(&mut self) {
- use crate::compositor::EventResult;
- let editor_view = self
- .compositor
- .find::<ui::EditorView>()
- .expect("expected at least one EditorView");
-
let mut cx = crate::compositor::Context {
editor: &mut self.editor,
jobs: &mut self.jobs,
scroll: None,
};
- if let EventResult::Consumed(_) = editor_view.handle_idle_timeout(&mut cx) {
+ let should_render = self.compositor.handle_event(&Event::IdleTimeout, &mut cx);
+ if should_render {
self.render();
}
}
@@ -866,9 +865,16 @@ impl Application {
}));
self.event_loop(input_stream).await;
- self.close().await?;
+
+ let err = self.close().await.err();
+
restore_term()?;
+ if let Some(err) = err {
+ self.editor.exit_code = 1;
+ eprintln!("Error: {}", err);
+ }
+
Ok(self.editor.exit_code)
}
diff --git a/helix-term/src/args.rs b/helix-term/src/args.rs
index 48c86633..dd787f1f 100644
--- a/helix-term/src/args.rs
+++ b/helix-term/src/args.rs
@@ -32,8 +32,14 @@ impl Args {
"--version" => args.display_version = true,
"--help" => args.display_help = true,
"--tutor" => args.load_tutor = true,
- "--vsplit" => args.split = Some(Layout::Vertical),
- "--hsplit" => args.split = Some(Layout::Horizontal),
+ "--vsplit" => match args.split {
+ Some(_) => anyhow::bail!("can only set a split once of a specific type"),
+ None => args.split = Some(Layout::Vertical),
+ },
+ "--hsplit" => match args.split {
+ Some(_) => anyhow::bail!("can only set a split once of a specific type"),
+ None => args.split = Some(Layout::Horizontal),
+ },
"--health" => {
args.health = true;
args.health_arg = argv.next_if(|opt| !opt.starts_with('-'));
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index fb1a4b38..5073651b 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -27,6 +27,7 @@ use helix_core::{
SmallVec, Tendril, Transaction,
};
use helix_view::{
+ apply_transaction,
clipboard::ClipboardType,
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
editor::{Action, Motion},
@@ -273,8 +274,8 @@ impl MappableCommand {
diagnostics_picker, "Open diagnostic picker",
workspace_diagnostics_picker, "Open workspace diagnostic picker",
last_picker, "Open last picker",
- prepend_to_line, "Insert at start of line",
- append_to_line, "Append to end of line",
+ insert_at_line_start, "Insert at start of line",
+ insert_at_line_end, "Insert at end of line",
open_below, "Open new line below selection",
open_above, "Open new line above selection",
normal_mode, "Enter normal mode",
@@ -346,6 +347,7 @@ impl MappableCommand {
unindent, "Unindent selection",
format_selections, "Format selection",
join_selections, "Join lines inside selection",
+ join_selections_space, "Join lines inside selection and select spaces",
keep_selections, "Keep selections matching regex",
remove_selections, "Remove selections matching regex",
align_selections, "Align selections in column",
@@ -858,7 +860,7 @@ fn align_selections(cx: &mut Context) {
changes.sort_unstable_by_key(|(from, _, _)| *from);
let transaction = Transaction::change(doc.text(), changes.into_iter());
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
fn goto_window(cx: &mut Context, align: Align) {
@@ -885,8 +887,12 @@ fn goto_window(cx: &mut Context, align: Align) {
.min(last_line.saturating_sub(scrolloff));
let pos = doc.text().line_to_char(line);
-
- doc.set_selection(view.id, Selection::point(pos));
+ let text = doc.text().slice(..);
+ let selection = doc
+ .selection(view.id)
+ .clone()
+ .transform(|range| range.put_cursor(text, pos, cx.editor.mode == Mode::Select));
+ doc.set_selection(view.id, selection);
}
fn goto_window_top(cx: &mut Context) {
@@ -1284,7 +1290,7 @@ fn replace(cx: &mut Context) {
}
});
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
})
}
@@ -1301,7 +1307,7 @@ where
(range.from(), range.to(), Some(text))
});
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
fn switch_case(cx: &mut Context) {
@@ -1511,7 +1517,8 @@ fn select_regex(cx: &mut Context) {
"select:".into(),
Some(reg),
ui::completers::none,
- move |view, doc, regex, event| {
+ move |editor, regex, event| {
+ let (view, doc) = current!(editor);
if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
return;
}
@@ -1532,7 +1539,8 @@ fn split_selection(cx: &mut Context) {
"split:".into(),
Some(reg),
ui::completers::none,
- move |view, doc, regex, event| {
+ move |editor, regex, event| {
+ let (view, doc) = current!(editor);
if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
return;
}
@@ -1556,15 +1564,16 @@ fn split_selection_on_newline(cx: &mut Context) {
#[allow(clippy::too_many_arguments)]
fn search_impl(
- doc: &mut Document,
- view: &mut View,
+ editor: &mut Editor,
contents: &str,
regex: &Regex,
movement: Movement,
direction: Direction,
scrolloff: usize,
wrap_around: bool,
+ show_warnings: bool,
) {
+ let (view, doc) = current!(editor);
let text = doc.text().slice(..);
let selection = doc.selection(view.id);
@@ -1594,17 +1603,29 @@ fn search_impl(
Direction::Backward => regex.find_iter(&contents[..start]).last(),
};
- if wrap_around && mat.is_none() {
- mat = match direction {
- Direction::Forward => regex.find(contents),
- Direction::Backward => {
- offset = start;
- regex.find_iter(&contents[start..]).last()
+ if mat.is_none() {
+ if wrap_around {
+ mat = match direction {
+ Direction::Forward => regex.find(contents),
+ Direction::Backward => {
+ offset = start;
+ regex.find_iter(&contents[start..]).last()
+ }
+ };
+ }
+ if show_warnings {
+ if wrap_around && mat.is_some() {
+ editor.set_status("Wrapped around document");
+ } else {
+ editor.set_error("No more matches");
}
}
- // TODO: message on wraparound
}
+ let (view, doc) = current!(editor);
+ let text = doc.text().slice(..);
+ let selection = doc.selection(view.id);
+
if let Some(mat) = mat {
let start = text.byte_to_char(mat.start() + offset);
let end = text.byte_to_char(mat.end() + offset);
@@ -1680,19 +1701,19 @@ fn searcher(cx: &mut Context, direction: Direction) {
.map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
.collect()
},
- move |view, doc, regex, event| {
+ move |editor, regex, event| {
if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
return;
}
search_impl(
- doc,
- view,
+ editor,
&contents,
&regex,
Movement::Move,
direction,
scrolloff,
wrap_around,
+ false,
);
},
);
@@ -1702,7 +1723,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
let count = cx.count();
let config = cx.editor.config();
let scrolloff = config.scrolloff;
- let (view, doc) = current!(cx.editor);
+ let (_, doc) = current!(cx.editor);
let registers = &cx.editor.registers;
if let Some(query) = registers.read('/').and_then(|query| query.last()) {
let contents = doc.text().slice(..).to_string();
@@ -1720,14 +1741,14 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
{
for _ in 0..count {
search_impl(
- doc,
- view,
+ cx.editor,
&contents,
&regex,
movement,
direction,
scrolloff,
wrap_around,
+ true,
);
}
} else {
@@ -1825,7 +1846,7 @@ fn global_search(cx: &mut Context) {
.map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
.collect()
},
- move |_view, _doc, regex, event| {
+ move |_editor, regex, event| {
if event != PromptEvent::Validate {
return;
}
@@ -1844,10 +1865,15 @@ fn global_search(cx: &mut Context) {
.hidden(file_picker_config.hidden)
.parents(file_picker_config.parents)
.ignore(file_picker_config.ignore)
+ .follow_links(file_picker_config.follow_symlinks)
.git_ignore(file_picker_config.git_ignore)
.git_global(file_picker_config.git_global)
.git_exclude(file_picker_config.git_exclude)
.max_depth(file_picker_config.max_depth)
+ // We always want to ignore the .git directory, otherwise if
+ // `ignore` is turned off above, we end up with a lot of noise
+ // in our picker.
+ .filter_entry(|entry| entry.file_name() != ".git")
.build_parallel()
.run(|| {
let mut searcher = searcher.clone();
@@ -2092,7 +2118,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)
});
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
match op {
Operation::Delete => {
@@ -2106,14 +2132,11 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
}
#[inline]
-fn delete_selection_insert_mode(doc: &mut Document, view: &View, selection: &Selection) {
- let view_id = view.id;
-
- // then delete
+fn delete_selection_insert_mode(doc: &mut Document, view: &mut View, selection: &Selection) {
let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
(range.from(), range.to(), None)
});
- doc.apply(&transaction, view_id);
+ apply_transaction(&transaction, doc, view);
}
fn delete_selection(cx: &mut Context) {
@@ -2161,10 +2184,7 @@ fn ensure_selections_forward(cx: &mut Context) {
let selection = doc
.selection(view.id)
.clone()
- .transform(|r| match r.direction() {
- Direction::Forward => r,
- Direction::Backward => r.flip(),
- });
+ .transform(|r| r.with_direction(Direction::Forward));
doc.set_selection(view.id, selection);
}
@@ -2207,12 +2227,12 @@ fn append_mode(cx: &mut Context) {
.iter()
.last()
.expect("selection should always have at least one range");
- if !last_range.is_empty() && last_range.head == end {
+ if !last_range.is_empty() && last_range.to() == end {
let transaction = Transaction::change(
doc.text(),
[(end, end, Some(doc.line_ending.as_str().into()))].into_iter(),
);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
let selection = doc.selection(view.id).clone().transform(|range| {
@@ -2410,12 +2430,12 @@ impl ui::menu::Item for MappableCommand {
match self {
MappableCommand::Typable { doc, name, .. } => match keymap.get(name as &String) {
- Some(bindings) => format!("{} ({})", doc, fmt_binding(bindings)).into(),
- None => doc.as_str().into(),
+ Some(bindings) => format!("{} ({}) [{}]", doc, fmt_binding(bindings), name).into(),
+ None => format!("{} [{}]", doc, name).into(),
},
MappableCommand::Static { doc, name, .. } => match keymap.get(*name) {
- Some(bindings) => format!("{} ({})", doc, fmt_binding(bindings)).into(),
- None => (*doc).into(),
+ Some(bindings) => format!("{} ({}) [{}]", doc, fmt_binding(bindings), name).into(),
+ None => format!("{} [{}]", doc, name).into(),
},
}
}
@@ -2465,13 +2485,13 @@ fn last_picker(cx: &mut Context) {
}
// I inserts at the first nonwhitespace character of each line with a selection
-fn prepend_to_line(cx: &mut Context) {
+fn insert_at_line_start(cx: &mut Context) {
goto_first_nonwhitespace(cx);
enter_insert_mode(cx);
}
// A inserts at the end of each line with a selection
-fn append_to_line(cx: &mut Context) {
+fn insert_at_line_end(cx: &mut Context) {
enter_insert_mode(cx);
let (view, doc) = current!(cx.editor);
@@ -2512,7 +2532,7 @@ async fn make_format_callback(
let doc = doc_mut!(editor, &doc_id);
let view = view_mut!(editor);
if doc.version() == doc_version {
- doc.apply(&format, view.id);
+ apply_transaction(&format, doc, view);
doc.append_changes_to_history(view.id);
doc.detect_indent_and_line_ending();
view.ensure_cursor_in_view(doc, scrolloff);
@@ -2599,7 +2619,7 @@ fn open(cx: &mut Context, open: Open) {
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
// o inserts a new line after each line with a selection
@@ -2620,7 +2640,7 @@ fn normal_mode(cx: &mut Context) {
cx.editor.mode = Mode::Normal;
let (view, doc) = current!(cx.editor);
- try_restore_indent(doc, view.id);
+ try_restore_indent(doc, view);
// if leaving append mode, move cursor back by 1
if doc.restore_cursor {
@@ -2637,7 +2657,7 @@ fn normal_mode(cx: &mut Context) {
}
}
-fn try_restore_indent(doc: &mut Document, view_id: ViewId) {
+fn try_restore_indent(doc: &mut Document, view: &mut View) {
use helix_core::chars::char_is_whitespace;
use helix_core::Operation;
@@ -2656,18 +2676,18 @@ fn try_restore_indent(doc: &mut Document, view_id: ViewId) {
let doc_changes = doc.changes().changes();
let text = doc.text().slice(..);
- let range = doc.selection(view_id).primary();
+ let range = doc.selection(view.id).primary();
let pos = range.cursor(text);
let line_end_pos = line_end_char_index(&text, range.cursor_line(text));
if inserted_a_new_blank_line(doc_changes, pos, line_end_pos) {
// Removes tailing whitespaces.
let transaction =
- Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
let line_start_pos = text.line_to_char(range.cursor_line(text));
(line_start_pos, pos, None)
});
- doc.apply(&transaction, view_id);
+ apply_transaction(&transaction, doc, view);
}
}
@@ -2865,7 +2885,7 @@ pub mod insert {
/// Exclude the cursor in range.
fn exclude_cursor(text: RopeSlice, range: Range, cursor: Range) -> Range {
- if range.to() == cursor.to() {
+ if range.to() == cursor.to() && text.len_chars() != cursor.to() {
Range::new(
range.from(),
graphemes::prev_grapheme_boundary(text, cursor.to()),
@@ -2981,7 +3001,7 @@ pub mod insert {
let (view, doc) = current!(cx.editor);
if let Some(t) = transaction {
- doc.apply(&t, view.id);
+ apply_transaction(&t, doc, view);
}
// TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc)
@@ -3003,7 +3023,7 @@ pub mod insert {
&doc.selection(view.id).clone().cursors(doc.text().slice(..)),
indent,
);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
pub fn insert_newline(cx: &mut Context) {
@@ -3090,7 +3110,7 @@ pub mod insert {
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
let (view, doc) = current!(cx.editor);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
pub fn delete_char_backward(cx: &mut Context) {
@@ -3184,7 +3204,7 @@ pub mod insert {
}
});
let (view, doc) = current!(cx.editor);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
}
@@ -3202,7 +3222,7 @@ pub mod insert {
None,
)
});
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
}
@@ -3243,7 +3263,7 @@ fn undo(cx: &mut Context) {
let count = cx.count();
let (view, doc) = current!(cx.editor);
for _ in 0..count {
- if !doc.undo(view.id) {
+ if !doc.undo(view) {
cx.editor.set_status("Already at oldest change");
break;
}
@@ -3254,7 +3274,7 @@ fn redo(cx: &mut Context) {
let count = cx.count();
let (view, doc) = current!(cx.editor);
for _ in 0..count {
- if !doc.redo(view.id) {
+ if !doc.redo(view) {
cx.editor.set_status("Already at newest change");
break;
}
@@ -3266,7 +3286,7 @@ fn earlier(cx: &mut Context) {
let (view, doc) = current!(cx.editor);
for _ in 0..count {
// rather than doing in batch we do this so get error halfway
- if !doc.earlier(view.id, UndoKind::Steps(1)) {
+ if !doc.earlier(view, UndoKind::Steps(1)) {
cx.editor.set_status("Already at oldest change");
break;
}
@@ -3278,7 +3298,7 @@ fn later(cx: &mut Context) {
let (view, doc) = current!(cx.editor);
for _ in 0..count {
// rather than doing in batch we do this so get error halfway
- if !doc.later(view.id, UndoKind::Steps(1)) {
+ if !doc.later(view, UndoKind::Steps(1)) {
cx.editor.set_status("Already at newest change");
break;
}
@@ -3330,9 +3350,15 @@ fn yank_joined_to_clipboard_impl(
.map(Cow::into_owned)
.collect();
+ let clipboard_text = match clipboard_type {
+ ClipboardType::Clipboard => "system clipboard",
+ ClipboardType::Selection => "primary clipboard",
+ };
+
let msg = format!(
- "joined and yanked {} selection(s) to system clipboard",
+ "joined and yanked {} selection(s) to {}",
values.len(),
+ clipboard_text,
);
let joined = values.join(separator);
@@ -3361,6 +3387,11 @@ fn yank_main_selection_to_clipboard_impl(
let (view, doc) = current!(editor);
let text = doc.text().slice(..);
+ let message_text = match clipboard_type {
+ ClipboardType::Clipboard => "yanked main selection to system clipboard",
+ ClipboardType::Selection => "yanked main selection to primary clipboard",
+ };
+
let value = doc.selection(view.id).primary().fragment(text);
if let Err(e) = editor
@@ -3370,7 +3401,7 @@ fn yank_main_selection_to_clipboard_impl(
bail!("Couldn't set system clipboard content: {}", e);
}
- editor.set_status("yanked main selection to system clipboard");
+ editor.set_status(message_text);
Ok(())
}
@@ -3396,7 +3427,7 @@ enum Paste {
Cursor,
}
-fn paste_impl(values: &[String], doc: &mut Document, view: &View, action: Paste, count: usize) {
+fn paste_impl(values: &[String], doc: &mut Document, view: &mut View, action: Paste, count: usize) {
let repeat = std::iter::repeat(
values
.last()
@@ -3439,7 +3470,7 @@ fn paste_impl(values: &[String], doc: &mut Document, view: &View, action: Paste,
};
(pos, pos, values.next())
});
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
@@ -3531,7 +3562,7 @@ fn replace_with_yanked(cx: &mut Context) {
}
});
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
}
}
@@ -3554,7 +3585,7 @@ fn replace_selections_with_clipboard_impl(
)
});
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
doc.append_changes_to_history(view.id);
Ok(())
}
@@ -3624,7 +3655,7 @@ fn indent(cx: &mut Context) {
Some((pos, pos, Some(indent.clone())))
}),
);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
fn unindent(cx: &mut Context) {
@@ -3663,7 +3694,7 @@ fn unindent(cx: &mut Context) {
let transaction = Transaction::change(doc.text(), changes.into_iter());
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
fn format_selections(cx: &mut Context) {
@@ -3710,11 +3741,11 @@ fn format_selections(cx: &mut Context) {
// language_server.offset_encoding(),
// );
- // doc.apply(&transaction, view.id);
+ // apply_transaction(&transaction, doc, view);
}
}
-fn join_selections(cx: &mut Context) {
+fn join_selections_inner(cx: &mut Context, select_space: bool) {
use movement::skip_while;
let (view, doc) = current!(cx.editor);
let text = doc.text();
@@ -3749,11 +3780,23 @@ fn join_selections(cx: &mut Context) {
// TODO: joining multiple empty lines should be replaced by a single space.
// need to merge change ranges that touch
- let transaction = Transaction::change(doc.text(), changes.into_iter());
- // TODO: select inserted spaces
- // .with_selection(selection);
+ // select inserted spaces
+ let transaction = if select_space {
+ let ranges: SmallVec<_> = changes
+ .iter()
+ .scan(0, |offset, change| {
+ let range = Range::point(change.0 - *offset);
+ *offset += change.1 - change.0 - 1; // -1 because cursor is 0-sized
+ Some(range)
+ })
+ .collect();
+ let selection = Selection::new(ranges, 0);
+ Transaction::change(doc.text(), changes.into_iter()).with_selection(selection)
+ } else {
+ Transaction::change(doc.text(), changes.into_iter())
+ };
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
@@ -3764,7 +3807,8 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
if remove { "remove:" } else { "keep:" }.into(),
Some(reg),
ui::completers::none,
- move |view, doc, regex, event| {
+ move |editor, regex, event| {
+ let (view, doc) = current!(editor);
if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
return;
}
@@ -3779,6 +3823,14 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
)
}
+fn join_selections(cx: &mut Context) {
+ join_selections_inner(cx, false)
+}
+
+fn join_selections_space(cx: &mut Context) {
+ join_selections_inner(cx, true)
+}
+
fn keep_selections(cx: &mut Context) {
keep_or_remove_selections_impl(cx, false)
}
@@ -3897,7 +3949,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);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
exit_select_mode(cx);
}
@@ -3953,7 +4005,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
.map(|(range, fragment)| (range.from(), range.to(), Some(fragment))),
);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
fn rotate_selection_contents_forward(cx: &mut Context) {
@@ -4268,7 +4320,7 @@ fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direct
let root = syntax.tree().root_node();
let selection = doc.selection(view.id).clone().transform(|range| {
- movement::goto_treesitter_object(
+ let new_range = movement::goto_treesitter_object(
text,
range,
object,
@@ -4276,7 +4328,19 @@ fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direct
root,
lang_config,
count,
- )
+ );
+
+ if editor.mode == Mode::Select {
+ let head = if new_range.head < range.anchor {
+ new_range.anchor
+ } else {
+ new_range.head
+ };
+
+ Range::new(range.anchor, head)
+ } else {
+ new_range.with_direction(direction)
+ }
});
doc.set_selection(view.id, selection);
@@ -4341,7 +4405,6 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
cx.on_next_key(move |cx, event| {
cx.editor.autoinfo = None;
- cx.editor.pseudo_pending = None;
if let Some(ch) = event.char() {
let textobject = move |editor: &mut Editor| {
let (view, doc) = current!(editor);
@@ -4390,33 +4453,25 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
}
});
- if let Some((title, abbrev)) = match objtype {
- textobject::TextObject::Inside => Some(("Match inside", "mi")),
- textobject::TextObject::Around => Some(("Match around", "ma")),
+ let title = match objtype {
+ textobject::TextObject::Inside => "Match inside",
+ textobject::TextObject::Around => "Match around",
_ => return,
- } {
- let help_text = [
- ("w", "Word"),
- ("W", "WORD"),
- ("p", "Paragraph"),
- ("c", "Class (tree-sitter)"),
- ("f", "Function (tree-sitter)"),
- ("a", "Argument/parameter (tree-sitter)"),
- ("o", "Comment (tree-sitter)"),
- ("t", "Test (tree-sitter)"),
- ("m", "Closest surrounding pair to cursor"),
- (" ", "... or any character acting as a pair"),
- ];
-
- cx.editor.autoinfo = Some(Info::new(
- title,
- help_text
- .into_iter()
- .map(|(col1, col2)| (col1.to_string(), col2.to_string()))
- .collect(),
- ));
- cx.editor.pseudo_pending = Some(abbrev.to_string());
};
+ let help_text = [
+ ("w", "Word"),
+ ("W", "WORD"),
+ ("p", "Paragraph"),
+ ("c", "Class (tree-sitter)"),
+ ("f", "Function (tree-sitter)"),
+ ("a", "Argument/parameter (tree-sitter)"),
+ ("o", "Comment (tree-sitter)"),
+ ("t", "Test (tree-sitter)"),
+ ("m", "Closest surrounding pair to cursor"),
+ (" ", "... or any character acting as a pair"),
+ ];
+
+ cx.editor.autoinfo = Some(Info::new(title, &help_text));
}
fn surround_add(cx: &mut Context) {
@@ -4440,7 +4495,7 @@ fn surround_add(cx: &mut Context) {
}
let transaction = Transaction::change(doc.text(), changes.into_iter());
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
})
}
@@ -4479,7 +4534,7 @@ fn surround_replace(cx: &mut Context) {
(pos, pos + 1, Some(t))
}),
);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
});
})
}
@@ -4506,7 +4561,7 @@ fn surround_delete(cx: &mut Context) {
let transaction =
Transaction::change(doc.text(), change_pos.into_iter().map(|p| (p, p + 1, None)));
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
})
}
@@ -4681,7 +4736,7 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
if behavior != &ShellBehavior::Ignore {
let transaction = Transaction::change(doc.text(), changes.into_iter());
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
doc.append_changes_to_history(view.id);
}
@@ -4744,7 +4799,7 @@ fn add_newline_impl(cx: &mut Context, open: Open) {
});
let transaction = Transaction::change(text, changes);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
/// Increment object under cursor by count.
@@ -4837,7 +4892,7 @@ fn increment_impl(cx: &mut Context, amount: i64) {
let transaction = Transaction::change(doc.text(), changes);
let transaction = transaction.with_selection(selection.clone());
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
}
diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs
index 1113b44e..3fa5c96f 100644
--- a/helix-term/src/commands/lsp.rs
+++ b/helix-term/src/commands/lsp.rs
@@ -9,7 +9,7 @@ use tui::text::{Span, Spans};
use super::{align_view, push_jump, Align, Context, Editor, Open};
use helix_core::{path, Selection};
-use helix_view::{editor::Action, theme::Style};
+use helix_view::{apply_transaction, editor::Action, theme::Style};
use crate::{
compositor::{self, Compositor},
@@ -596,9 +596,7 @@ pub fn apply_workspace_edit(
}
};
- let doc = editor
- .document_mut(doc_id)
- .expect("Document for document_changes not found");
+ let doc = doc_mut!(editor, &doc_id);
// Need to determine a view for apply/append_changes_to_history
let selections = doc.selections();
@@ -619,7 +617,7 @@ pub fn apply_workspace_edit(
text_edits,
offset_encoding,
);
- doc.apply(&transaction, view_id);
+ apply_transaction(&transaction, doc, view_mut!(editor, view_id));
doc.append_changes_to_history(view_id);
};
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index 6d0ced65..1bfc8153 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -2,7 +2,10 @@ use std::ops::Deref;
use super::*;
-use helix_view::editor::{Action, ConfigEvent};
+use helix_view::{
+ apply_transaction,
+ editor::{Action, CloseError, ConfigEvent},
+};
use ui::completers::{self, Completer};
#[derive(Clone)]
@@ -71,8 +74,29 @@ fn buffer_close_by_ids_impl(
doc_ids: &[DocumentId],
force: bool,
) -> anyhow::Result<()> {
- for &doc_id in doc_ids {
- editor.close_document(doc_id, force)?;
+ let (modified_ids, modified_names): (Vec<_>, Vec<_>) = doc_ids
+ .iter()
+ .filter_map(|&doc_id| {
+ if let Err(CloseError::BufferModified(name)) = editor.close_document(doc_id, force) {
+ Some((doc_id, name))
+ } else {
+ None
+ }
+ })
+ .unzip();
+
+ if let Some(first) = modified_ids.first() {
+ let current = doc!(editor);
+ // If the current document is unmodified, and there are modified
+ // documents, switch focus to the first modified doc.
+ if !modified_ids.contains(&current.id()) {
+ editor.switch(*first, Action::Replace);
+ }
+ bail!(
+ "{} unsaved buffer(s) remaining: {:?}",
+ modified_names.len(),
+ modified_names
+ );
}
Ok(())
@@ -441,7 +465,7 @@ fn set_line_ending(
}
}),
);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
doc.append_changes_to_history(view.id);
Ok(())
@@ -459,7 +483,7 @@ fn earlier(
let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
let (view, doc) = current!(cx.editor);
- let success = doc.earlier(view.id, uk);
+ let success = doc.earlier(view, uk);
if !success {
cx.editor.set_status("Already at oldest change");
}
@@ -478,7 +502,7 @@ fn later(
let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
let (view, doc) = current!(cx.editor);
- let success = doc.later(view.id, uk);
+ let success = doc.later(view, uk);
if !success {
cx.editor.set_status("Already at newest change");
}
@@ -513,23 +537,26 @@ fn force_write_quit(
force_quit(cx, &[], event)
}
-/// Results an error if there are modified buffers remaining and sets editor error,
-/// otherwise returns `Ok(())`
+/// Results in an error if there are modified buffers remaining and sets editor
+/// error, otherwise returns `Ok(())`. If the current document is unmodified,
+/// and there are modified documents, switches focus to one of them.
pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> {
- let modified: Vec<_> = editor
+ let (modified_ids, modified_names): (Vec<_>, Vec<_>) = editor
.documents()
.filter(|doc| doc.is_modified())
- .map(|doc| {
- doc.relative_path()
- .map(|path| path.to_string_lossy().to_string())
- .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into())
- })
- .collect();
- if !modified.is_empty() {
+ .map(|doc| (doc.id(), doc.display_name()))
+ .unzip();
+ if let Some(first) = modified_ids.first() {
+ let current = doc!(editor);
+ // If the current document is unmodified, and there are modified
+ // documents, switch focus to the first modified doc.
+ if !modified_ids.contains(&current.id()) {
+ editor.switch(*first, Action::Replace);
+ }
bail!(
"{} unsaved buffer(s) remaining: {:?}",
- modified.len(),
- modified
+ modified_names.len(),
+ modified_names
);
}
Ok(())
@@ -859,7 +886,7 @@ fn replace_selections_with_clipboard_impl(
(range.from(), range.to(), Some(contents.as_str().into()))
});
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
doc.append_changes_to_history(view.id);
Ok(())
}
@@ -980,7 +1007,7 @@ fn reload(
let scrolloff = cx.editor.config().scrolloff;
let (view, doc) = current!(cx.editor);
- doc.reload(view.id).map(|_| {
+ doc.reload(view).map(|_| {
view.ensure_cursor_in_view(doc, scrolloff);
})
}
@@ -1000,7 +1027,7 @@ fn lsp_restart(
.context("LSP not defined for the current document")?;
let scope = config.scope.clone();
- cx.editor.language_servers.restart(config)?;
+ cx.editor.language_servers.restart(config, doc.path())?;
// This collect is needed because refresh_language_server would need to re-borrow editor.
let document_ids_to_refresh: Vec<DocumentId> = cx
@@ -1033,7 +1060,21 @@ fn tree_sitter_scopes(
let pos = doc.selection(view.id).primary().cursor(text);
let scopes = indent::get_scopes(doc.syntax(), text, pos);
- cx.editor.set_status(format!("scopes: {:?}", &scopes));
+
+ let contents = format!("```json\n{:?}\n````", scopes);
+
+ let callback = async move {
+ let call: job::Callback =
+ Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
+ let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
+ let popup = Popup::new("hover", contents).auto_close(true);
+ compositor.replace_or_push("hover", popup);
+ });
+ Ok(call)
+ };
+
+ cx.jobs.callback(callback);
+
Ok(())
}
@@ -1196,18 +1237,41 @@ pub(super) fn goto_line_number(
args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
- if event != PromptEvent::Validate {
- return Ok(());
+ match event {
+ PromptEvent::Abort => {
+ if let Some(line_number) = cx.editor.last_line_number {
+ goto_line_impl(cx.editor, NonZeroUsize::new(line_number));
+ let (view, doc) = current!(cx.editor);
+ view.ensure_cursor_in_view(doc, line_number);
+ cx.editor.last_line_number = None;
+ }
+ return Ok(());
+ }
+ PromptEvent::Validate => {
+ ensure!(!args.is_empty(), "Line number required");
+ cx.editor.last_line_number = None;
+ }
+ PromptEvent::Update => {
+ if args.is_empty() {
+ if let Some(line_number) = cx.editor.last_line_number {
+ // When a user hits backspace and there are no numbers left,
+ // we can bring them back to their original line
+ goto_line_impl(cx.editor, NonZeroUsize::new(line_number));
+ let (view, doc) = current!(cx.editor);
+ view.ensure_cursor_in_view(doc, line_number);
+ cx.editor.last_line_number = None;
+ }
+ return Ok(());
+ }
+ let (view, doc) = current!(cx.editor);
+ let text = doc.text().slice(..);
+ let line = doc.selection(view.id).primary().cursor_line(text);
+ cx.editor.last_line_number.get_or_insert(line + 1);
+ }
}
-
- ensure!(!args.is_empty(), "Line number required");
-
let line = args[0].parse::<usize>()?;
-
goto_line_impl(cx.editor, NonZeroUsize::new(line));
-
let (view, doc) = current!(cx.editor);
-
view.ensure_cursor_in_view(doc, line);
Ok(())
}
@@ -1351,7 +1415,7 @@ fn sort_impl(
.map(|(s, fragment)| (s.from(), s.to(), Some(fragment))),
);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
doc.append_changes_to_history(view.id);
Ok(())
@@ -1395,7 +1459,7 @@ fn reflow(
(range.from(), range.to(), Some(reflowed_text))
});
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
doc.append_changes_to_history(view.id);
view.ensure_cursor_in_view(doc, scrolloff);
@@ -2021,7 +2085,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "insert-output",
aliases: &[],
- doc: "Run shell command, inserting output after each selection.",
+ doc: "Run shell command, inserting output before each selection.",
fun: insert_output,
completer: None,
},
diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs
index f07d4028..118764d9 100644
--- a/helix-term/src/keymap/default.rs
+++ b/helix-term/src/keymap/default.rs
@@ -59,9 +59,9 @@ pub fn default() -> HashMap<Mode, Keymap> {
":" => command_mode,
"i" => insert_mode,
- "I" => prepend_to_line,
+ "I" => insert_at_line_start,
"a" => append_mode,
- "A" => append_to_line,
+ "A" => insert_at_line_end,
"o" => open_below,
"O" => open_above,
@@ -144,6 +144,7 @@ pub fn default() -> HashMap<Mode, Keymap> {
"<" => unindent,
"=" => format_selections,
"J" => join_selections,
+ "A-J" => join_selections_space,
"K" => keep_selections,
"A-K" => remove_selections,
@@ -208,11 +209,11 @@ pub fn default() -> HashMap<Mode, Keymap> {
"j" => jumplist_picker,
"s" => symbol_picker,
"S" => workspace_symbol_picker,
- "g" => diagnostics_picker,
- "G" => workspace_diagnostics_picker,
+ "d" => diagnostics_picker,
+ "D" => workspace_diagnostics_picker,
"a" => code_action,
"'" => last_picker,
- "d" => { "Debug (experimental)" sticky=true
+ "g" => { "Debug (experimental)" sticky=true
"l" => dap_launch,
"b" => dap_toggle_breakpoint,
"c" => dap_continue,
@@ -342,24 +343,27 @@ pub fn default() -> HashMap<Mode, Keymap> {
let insert = keymap!({ "Insert mode"
"esc" => normal_mode,
- "backspace" => delete_char_backward,
- "C-h" => delete_char_backward,
- "del" => delete_char_forward,
- "C-d" => delete_char_forward,
- "ret" => insert_newline,
- "C-j" => insert_newline,
- "tab" => insert_tab,
- "C-w" => delete_word_backward,
- "A-backspace" => delete_word_backward,
- "A-d" => delete_word_forward,
- "A-del" => delete_word_forward,
"C-s" => commit_undo_checkpoint,
+ "C-x" => completion,
+ "C-r" => insert_register,
- "C-k" => kill_to_line_end,
+ "C-w" | "A-backspace" => delete_word_backward,
+ "A-d" | "A-del" => delete_word_forward,
"C-u" => kill_to_line_start,
+ "C-k" => kill_to_line_end,
+ "C-h" | "backspace" => delete_char_backward,
+ "C-d" | "del" => delete_char_forward,
+ "C-j" | "ret" => insert_newline,
+ "tab" => insert_tab,
- "C-x" => completion,
- "C-r" => insert_register,
+ "up" => move_line_up,
+ "down" => move_line_down,
+ "left" => move_char_left,
+ "right" => move_char_right,
+ "pageup" => page_up,
+ "pagedown" => page_down,
+ "home" => goto_line_start,
+ "end" => goto_line_end_newline,
});
hashmap!(
Mode::Normal => Keymap::new(normal),
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index 2d7d4f92..7348dcf4 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -1,5 +1,5 @@
use crate::compositor::{Component, Context, Event, EventResult};
-use helix_view::editor::CompleteAction;
+use helix_view::{apply_transaction, editor::CompleteAction};
use tui::buffer::Buffer as Surface;
use tui::text::Spans;
@@ -143,11 +143,11 @@ impl Completion {
let (view, doc) = current!(editor);
// if more text was entered, remove it
- doc.restore(view.id);
+ doc.restore(view);
match event {
PromptEvent::Abort => {
- doc.restore(view.id);
+ doc.restore(view);
editor.last_completion = None;
}
PromptEvent::Update => {
@@ -164,7 +164,7 @@ impl Completion {
// initialize a savepoint
doc.savepoint();
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
editor.last_completion = Some(CompleteAction {
trigger_offset,
@@ -183,7 +183,7 @@ impl Completion {
trigger_offset,
);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
editor.last_completion = Some(CompleteAction {
trigger_offset,
@@ -213,7 +213,7 @@ impl Completion {
additional_edits.clone(),
offset_encoding, // TODO: should probably transcode in Client
);
- doc.apply(&transaction, view.id);
+ apply_transaction(&transaction, doc, view);
}
}
}
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 7cb29c3b..3cd2130a 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -13,9 +13,10 @@ use helix_core::{
movement::Direction,
syntax::{self, HighlightEvent},
unicode::width::UnicodeWidthStr,
- LineEnding, Position, Range, Selection, Transaction,
+ 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},
@@ -23,7 +24,7 @@ use helix_view::{
keyboard::{KeyCode, KeyModifiers},
Document, Editor, Theme, View,
};
-use std::{borrow::Cow, path::PathBuf};
+use std::{borrow::Cow, cmp::min, path::PathBuf};
use tui::buffer::Buffer as Surface;
@@ -33,6 +34,7 @@ use super::statusline;
pub struct EditorView {
pub keymaps: Keymaps,
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
+ pseudo_pending: Vec<KeyEvent>,
last_insert: (commands::MappableCommand, Vec<InsertEvent>),
pub(crate) completion: Option<Completion>,
spinners: ProgressSpinners,
@@ -56,6 +58,7 @@ impl EditorView {
Self {
keymaps,
on_next_key: None,
+ pseudo_pending: Vec::new(),
last_insert: (commands::MappableCommand::normal_mode, Vec::new()),
completion: None,
spinners: ProgressSpinners::default(),
@@ -116,9 +119,19 @@ impl EditorView {
if is_focused && editor.config().cursorline {
Self::highlight_cursorline(doc, view, surface, theme);
}
+ if is_focused && editor.config().cursorcolumn {
+ Self::highlight_cursorcolumn(doc, view, surface, theme);
+ }
- let highlights = Self::doc_syntax_highlights(doc, view.offset, inner.height, theme);
- let highlights = syntax::merge(highlights, Self::doc_diagnostics_highlights(doc, theme));
+ let mut highlights = Self::doc_syntax_highlights(doc, view.offset, inner.height, theme);
+ for diagnostic in Self::doc_diagnostics_highlights(doc, theme) {
+ // Most of the `diagnostic` Vecs are empty most of the time. Skipping
+ // a merge for any empty Vec saves a significant amount of work.
+ if diagnostic.is_empty() {
+ continue;
+ }
+ highlights = Box::new(syntax::merge(highlights, diagnostic));
+ }
let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
Box::new(syntax::merge(
highlights,
@@ -262,7 +275,7 @@ impl EditorView {
pub fn doc_diagnostics_highlights(
doc: &Document,
theme: &Theme,
- ) -> Vec<(usize, std::ops::Range<usize>)> {
+ ) -> [Vec<(usize, std::ops::Range<usize>)>; 5] {
use helix_core::diagnostic::Severity;
let get_scope_of = |scope| {
theme
@@ -283,22 +296,38 @@ impl EditorView {
let error = get_scope_of("diagnostic.error");
let r#default = get_scope_of("diagnostic"); // this is a bit redundant but should be fine
- doc.diagnostics()
- .iter()
- .map(|diagnostic| {
- let diagnostic_scope = match diagnostic.severity {
- Some(Severity::Info) => info,
- Some(Severity::Hint) => hint,
- Some(Severity::Warning) => warning,
- Some(Severity::Error) => error,
- _ => r#default,
- };
- (
- diagnostic_scope,
- diagnostic.range.start..diagnostic.range.end,
- )
- })
- .collect()
+ let mut default_vec: Vec<(usize, std::ops::Range<usize>)> = Vec::new();
+ let mut info_vec = Vec::new();
+ let mut hint_vec = Vec::new();
+ let mut warning_vec = Vec::new();
+ let mut error_vec = Vec::new();
+
+ for diagnostic in doc.diagnostics() {
+ // Separate diagnostics into different Vecs by severity.
+ let (vec, scope) = match diagnostic.severity {
+ Some(Severity::Info) => (&mut info_vec, info),
+ Some(Severity::Hint) => (&mut hint_vec, hint),
+ Some(Severity::Warning) => (&mut warning_vec, warning),
+ Some(Severity::Error) => (&mut error_vec, error),
+ _ => (&mut default_vec, r#default),
+ };
+
+ // If any diagnostic overlaps ranges with the prior diagnostic,
+ // merge the two together. Otherwise push a new span.
+ match vec.last_mut() {
+ Some((_, range)) if diagnostic.range.start <= range.end => {
+ // This branch merges overlapping diagnostics, assuming that the current
+ // diagnostic starts on range.start or later. If this assertion fails,
+ // we will discard some part of `diagnostic`. This implies that
+ // `doc.diagnostics()` is not sorted by `diagnostic.range`.
+ debug_assert!(range.start <= diagnostic.range.start);
+ range.end = diagnostic.range.end.max(range.end)
+ }
+ _ => vec.push((scope, diagnostic.range.start..diagnostic.range.end)),
+ }
+ }
+
+ [default_vec, info_vec, hint_vec, warning_vec, error_vec]
}
/// Get highlight spans for selections in a document view.
@@ -399,7 +428,7 @@ impl EditorView {
let characters = &whitespace.characters;
let mut spans = Vec::new();
- let mut visual_x = 0u16;
+ let mut visual_x = 0usize;
let mut line = 0u16;
let tab_width = doc.tab_width();
let tab = if whitespace.render.tab() == WhitespaceRenderValue::All {
@@ -436,17 +465,22 @@ impl EditorView {
return;
}
- let starting_indent = (offset.col / tab_width) as u16;
- // TODO: limit to a max indent level too. It doesn't cause visual artifacts but it would avoid some
- // extra loops if the code is deeply nested.
-
- for i in starting_indent..(indent_level / tab_width as u16) {
- surface.set_string(
- viewport.x + (i * tab_width as u16) - offset.col as u16,
- viewport.y + line,
- &indent_guide_char,
- indent_guide_style,
- );
+ let starting_indent =
+ (offset.col / tab_width) + config.indent_guides.skip_levels as usize;
+
+ // Don't draw indent guides outside of view
+ let end_indent = min(
+ indent_level,
+ // Add tab_width - 1 to round up, since the first visible
+ // indent might be a bit after offset.col
+ offset.col + viewport.width as usize + (tab_width - 1),
+ ) / tab_width;
+
+ for i in starting_indent..end_indent {
+ let x = (viewport.x as usize + (i * tab_width) - offset.col) as u16;
+ let y = viewport.y + line;
+ debug_assert!(surface.in_bounds(x, y));
+ surface.set_string(x, y, &indent_guide_char, indent_guide_style);
}
};
@@ -488,14 +522,14 @@ impl EditorView {
use helix_core::graphemes::{grapheme_width, RopeGraphemes};
for grapheme in RopeGraphemes::new(text) {
- let out_of_bounds = visual_x < offset.col as u16
- || visual_x >= viewport.width + offset.col as u16;
+ let out_of_bounds = offset.col > (visual_x as usize)
+ || (visual_x as usize) >= viewport.width as usize + offset.col;
if LineEnding::from_rope_slice(&grapheme).is_some() {
if !out_of_bounds {
// we still want to render an empty cell with the style
surface.set_string(
- viewport.x + visual_x - offset.col as u16,
+ (viewport.x as usize + visual_x - offset.col) as u16,
viewport.y + line,
&newline,
style.patch(whitespace_style),
@@ -543,7 +577,7 @@ impl EditorView {
if !out_of_bounds {
// if we're offscreen just keep going until we hit a new line
surface.set_string(
- viewport.x + visual_x - offset.col as u16,
+ (viewport.x as usize + visual_x - offset.col) as u16,
viewport.y + line,
display_grapheme,
if is_whitespace {
@@ -576,7 +610,7 @@ impl EditorView {
last_line_indent_level = visual_x;
}
- visual_x = visual_x.saturating_add(width as u16);
+ visual_x = visual_x.saturating_add(width);
}
}
}
@@ -696,6 +730,7 @@ impl EditorView {
let mut offset = 0;
let gutter_style = theme.get("ui.gutter");
+ let gutter_selected_style = theme.get("ui.gutter.selected");
// avoid lots of small allocations by reusing a text buffer for each line
let mut text = String::with_capacity(8);
@@ -708,6 +743,12 @@ impl EditorView {
let x = viewport.x + offset;
let y = viewport.y + i as u16;
+ let gutter_style = if selected {
+ gutter_selected_style
+ } else {
+ gutter_style
+ };
+
if let Some(style) = gutter(line, selected, &mut text) {
surface.set_stringn(x, y, &text, *width, gutter_style.patch(style));
} else {
@@ -820,6 +861,53 @@ impl EditorView {
}
}
+ /// Apply the highlighting on the columns where a cursor is active
+ pub fn highlight_cursorcolumn(
+ doc: &Document,
+ view: &View,
+ surface: &mut Surface,
+ theme: &Theme,
+ ) {
+ let text = doc.text().slice(..);
+
+ // Manual fallback behaviour:
+ // ui.cursorcolumn.{p/s} -> ui.cursorcolumn -> ui.cursorline.{p/s}
+ let primary_style = theme
+ .try_get_exact("ui.cursorcolumn.primary")
+ .or_else(|| theme.try_get_exact("ui.cursorcolumn"))
+ .unwrap_or_else(|| theme.get("ui.cursorline.primary"));
+ let secondary_style = theme
+ .try_get_exact("ui.cursorcolumn.secondary")
+ .or_else(|| theme.try_get_exact("ui.cursorcolumn"))
+ .unwrap_or_else(|| theme.get("ui.cursorline.secondary"));
+
+ let inner_area = view.inner_area();
+ let offset = view.offset.col;
+
+ let selection = doc.selection(view.id);
+ let primary = selection.primary();
+ for range in selection.iter() {
+ let is_primary = primary == *range;
+
+ let Position { row: _, col } =
+ visual_coords_at_pos(text, range.cursor(text), doc.tab_width());
+ // if the cursor is horizontally in the view
+ if col >= offset && inner_area.width > (col - offset) as u16 {
+ let area = Rect::new(
+ inner_area.x + (col - offset) as u16,
+ view.area.y,
+ 1,
+ view.area.height,
+ );
+ if is_primary {
+ surface.set_style(area, primary_style)
+ } else {
+ surface.set_style(area, secondary_style)
+ }
+ }
+ }
+ }
+
/// Handle events by looking them up in `self.keymaps`. Returns None
/// if event was handled (a command was executed or a subkeymap was
/// activated). Only KeymapResult::{NotFound, Cancelled} is returned
@@ -831,6 +919,7 @@ impl EditorView {
event: KeyEvent,
) -> Option<KeymapResult> {
let mut last_mode = mode;
+ self.pseudo_pending.extend(self.keymaps.pending());
let key_result = self.keymaps.get(mode, event);
cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox());
@@ -927,7 +1016,7 @@ impl EditorView {
InsertEvent::CompletionApply(compl) => {
let (view, doc) = current!(cxt.editor);
- doc.restore(view.id);
+ doc.restore(view);
let text = doc.text().slice(..);
let cursor = doc.selection(view.id).primary().cursor(text);
@@ -941,7 +1030,7 @@ impl EditorView {
(shift_position(start), shift_position(end), t)
}),
);
- doc.apply(&tx, view.id);
+ apply_transaction(&tx, doc, view);
}
InsertEvent::TriggerCompletion => {
let (_, doc) = current!(cxt.editor);
@@ -1005,7 +1094,7 @@ impl EditorView {
editor.clear_idle_timer(); // don't retrigger
}
- pub fn handle_idle_timeout(&mut self, cx: &mut crate::compositor::Context) -> EventResult {
+ pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult {
if self.completion.is_some()
|| cx.editor.mode != Mode::Insert
|| !cx.editor.config().auto_completion
@@ -1013,15 +1102,7 @@ impl EditorView {
return EventResult::Ignored(None);
}
- let mut cx = commands::Context {
- register: None,
- editor: cx.editor,
- jobs: cx.jobs,
- count: None,
- callback: None,
- on_next_key_callback: None,
- };
- crate::commands::insert::idle_completion(&mut cx);
+ crate::commands::insert::idle_completion(cx);
EventResult::Consumed(None)
}
@@ -1308,6 +1389,11 @@ impl Component for EditorView {
}
self.on_next_key = cx.on_next_key_callback.take();
+ match self.on_next_key {
+ Some(_) => self.pseudo_pending.push(key),
+ None => self.pseudo_pending.clear(),
+ }
+
// appease borrowck
let callback = cx.callback.take();
@@ -1337,6 +1423,7 @@ impl Component for EditorView {
}
Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
+ Event::IdleTimeout => self.handle_idle_timeout(&mut cx),
Event::FocusGained | Event::FocusLost => EventResult::Ignored(None),
}
}
@@ -1408,8 +1495,8 @@ impl Component for EditorView {
for key in self.keymaps.pending() {
disp.push_str(&key.key_sequence_format());
}
- if let Some(pseudo_pending) = &cx.editor.pseudo_pending {
- disp.push_str(pseudo_pending.as_str())
+ for key in &self.pseudo_pending {
+ disp.push_str(&key.key_sequence_format());
}
let style = cx.editor.theme.get("ui.text");
let macro_width = if cx.editor.macro_recording.is_some() {
diff --git a/helix-term/src/ui/fuzzy_match.rs b/helix-term/src/ui/fuzzy_match.rs
new file mode 100644
index 00000000..e25d7328
--- /dev/null
+++ b/helix-term/src/ui/fuzzy_match.rs
@@ -0,0 +1,74 @@
+use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
+use fuzzy_matcher::FuzzyMatcher;
+
+#[cfg(test)]
+mod test;
+
+pub struct FuzzyQuery {
+ queries: Vec<String>,
+}
+
+impl FuzzyQuery {
+ pub fn new(query: &str) -> FuzzyQuery {
+ let mut saw_backslash = false;
+ let queries = query
+ .split(|c| {
+ saw_backslash = match c {
+ ' ' if !saw_backslash => return true,
+ '\\' => true,
+ _ => false,
+ };
+ false
+ })
+ .filter_map(|query| {
+ if query.is_empty() {
+ None
+ } else {
+ Some(query.replace("\\ ", " "))
+ }
+ })
+ .collect();
+ FuzzyQuery { queries }
+ }
+
+ pub fn fuzzy_match(&self, item: &str, matcher: &Matcher) -> Option<i64> {
+ // use the rank of the first query for the rank, because merging ranks is not really possible
+ // this behaviour matches fzf and skim
+ let score = matcher.fuzzy_match(item, self.queries.get(0)?)?;
+ if self
+ .queries
+ .iter()
+ .any(|query| matcher.fuzzy_match(item, query).is_none())
+ {
+ return None;
+ }
+ Some(score)
+ }
+
+ pub fn fuzzy_indicies(&self, item: &str, matcher: &Matcher) -> Option<(i64, Vec<usize>)> {
+ if self.queries.len() == 1 {
+ return matcher.fuzzy_indices(item, &self.queries[0]);
+ }
+
+ // use the rank of the first query for the rank, because merging ranks is not really possible
+ // this behaviour matches fzf and skim
+ let (score, mut indicies) = matcher.fuzzy_indices(item, self.queries.get(0)?)?;
+
+ // fast path for the common case of not using a space
+ // during matching this branch should be free thanks to branch prediction
+ if self.queries.len() == 1 {
+ return Some((score, indicies));
+ }
+
+ for query in &self.queries[1..] {
+ let (_, matched_indicies) = matcher.fuzzy_indices(item, query)?;
+ indicies.extend_from_slice(&matched_indicies);
+ }
+
+ // deadup and remove duplicate matches
+ indicies.sort_unstable();
+ indicies.dedup();
+
+ Some((score, indicies))
+ }
+}
diff --git a/helix-term/src/ui/fuzzy_match/test.rs b/helix-term/src/ui/fuzzy_match/test.rs
new file mode 100644
index 00000000..3f90ef68
--- /dev/null
+++ b/helix-term/src/ui/fuzzy_match/test.rs
@@ -0,0 +1,47 @@
+use crate::ui::fuzzy_match::FuzzyQuery;
+use crate::ui::fuzzy_match::Matcher;
+
+fn run_test<'a>(query: &str, items: &'a [&'a str]) -> Vec<String> {
+ let query = FuzzyQuery::new(query);
+ let matcher = Matcher::default();
+ items
+ .iter()
+ .filter_map(|item| {
+ let (_, indicies) = query.fuzzy_indicies(item, &matcher)?;
+ let matched_string = indicies
+ .iter()
+ .map(|&pos| item.chars().nth(pos).unwrap())
+ .collect();
+ Some(matched_string)
+ })
+ .collect()
+}
+
+#[test]
+fn match_single_value() {
+ let matches = run_test("foo", &["foobar", "foo", "bar"]);
+ assert_eq!(matches, &["foo", "foo"])
+}
+
+#[test]
+fn match_multiple_values() {
+ let matches = run_test(
+ "foo bar",
+ &["foo bar", "foo bar", "bar foo", "bar", "foo"],
+ );
+ assert_eq!(matches, &["foobar", "foobar", "barfoo"])
+}
+
+#[test]
+fn space_escape() {
+ let matches = run_test(r"foo\ bar", &["bar foo", "foo bar", "foobar"]);
+ assert_eq!(matches, &["foo bar"])
+}
+
+#[test]
+fn trim() {
+ let matches = run_test(r" foo bar ", &["bar foo", "foo bar", "foobar"]);
+ assert_eq!(matches, &["barfoo", "foobar", "foobar"]);
+ let matches = run_test(r" foo bar\ ", &["bar foo", "foo bar", "foobar"]);
+ assert_eq!(matches, &["bar foo"])
+}
diff --git a/helix-term/src/ui/lsp.rs b/helix-term/src/ui/lsp.rs
index f2854551..393d24c4 100644
--- a/helix-term/src/ui/lsp.rs
+++ b/helix-term/src/ui/lsp.rs
@@ -68,8 +68,9 @@ impl Component for SignatureHelp {
let (_, sig_text_height) = crate::ui::text::required_size(&sig_text, area.width);
let sig_text_area = area.clip_top(1).with_height(sig_text_height);
+ let sig_text_area = sig_text_area.inner(&margin).intersection(surface.area);
let sig_text_para = Paragraph::new(sig_text).wrap(Wrap { trim: false });
- sig_text_para.render(sig_text_area.inner(&margin), surface);
+ sig_text_para.render(sig_text_area, surface);
if self.signature_doc.is_none() {
return;
diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs
index 1d247b1a..f77f5e80 100644
--- a/helix-term/src/ui/menu.rs
+++ b/helix-term/src/ui/menu.rs
@@ -105,7 +105,7 @@ impl<T: Item> Menu<T> {
.iter()
.enumerate()
.filter_map(|(index, option)| {
- let text: String = option.filter_text(&self.editor_data).into();
+ let text = option.filter_text(&self.editor_data);
// TODO: using fuzzy_indices could give us the char idx for match highlighting
self.matcher
.fuzzy_match(&text, pattern)
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index 60ad3b24..6ac4dbb7 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -1,5 +1,6 @@
mod completion;
pub(crate) mod editor;
+mod fuzzy_match;
mod info;
pub mod lsp;
mod markdown;
@@ -12,6 +13,8 @@ mod spinner;
mod statusline;
mod text;
+use crate::compositor::{Component, Compositor};
+use crate::job;
pub use completion::Completion;
pub use editor::EditorView;
pub use markdown::Markdown;
@@ -24,7 +27,7 @@ pub use text::Text;
use helix_core::regex::Regex;
use helix_core::regex::RegexBuilder;
-use helix_view::{Document, Editor, View};
+use helix_view::Editor;
use std::path::PathBuf;
@@ -59,7 +62,7 @@ pub fn regex_prompt(
prompt: std::borrow::Cow<'static, str>,
history_register: Option<char>,
completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
- fun: impl Fn(&mut View, &mut Document, Regex, PromptEvent) + 'static,
+ fun: impl Fn(&mut Editor, Regex, PromptEvent) + 'static,
) {
let (view, doc) = current!(cx.editor);
let doc_id = view.doc;
@@ -106,11 +109,42 @@ pub fn regex_prompt(
view.jumps.push((doc_id, snapshot.clone()));
}
- fun(view, doc, regex, event);
+ fun(cx.editor, regex, event);
+ let (view, doc) = current!(cx.editor);
view.ensure_cursor_in_view(doc, config.scrolloff);
}
- Err(_err) => (), // TODO: mark command line as error
+ Err(err) => {
+ let (view, doc) = current!(cx.editor);
+ doc.set_selection(view.id, snapshot.clone());
+ view.offset = offset_snapshot;
+
+ if event == PromptEvent::Validate {
+ let callback = async move {
+ let call: job::Callback = Box::new(
+ move |_editor: &mut Editor, compositor: &mut Compositor| {
+ let contents = Text::new(format!("{}", err));
+ let size = compositor.size();
+ let mut popup = Popup::new("invalid-regex", contents)
+ .position(Some(helix_core::Position::new(
+ size.height as usize - 2, // 2 = statusline + commandline
+ 0,
+ )))
+ .auto_close(true);
+ popup.required_size((size.width, size.height));
+
+ compositor.replace_or_push("invalid-regex", popup);
+ },
+ );
+ Ok(call)
+ };
+
+ cx.jobs.callback(callback);
+ } else {
+ // Update
+ // TODO: mark command line as error
+ }
+ }
}
}
}
diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs
index a56455d7..c7149c61 100644
--- a/helix-term/src/ui/picker.rs
+++ b/helix-term/src/ui/picker.rs
@@ -1,7 +1,7 @@
use crate::{
compositor::{Component, Compositor, Context, Event, EventResult},
ctrl, key, shift,
- ui::{self, EditorView},
+ ui::{self, fuzzy_match::FuzzyQuery, EditorView},
};
use tui::{
buffer::Buffer as Surface,
@@ -9,7 +9,6 @@ use tui::{
};
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
-use fuzzy_matcher::FuzzyMatcher;
use tui::widgets::Widget;
use std::time::Instant;
@@ -161,6 +160,27 @@ impl<T: Item> FilePicker<T> {
self.preview_cache.insert(path.to_owned(), preview);
Preview::Cached(&self.preview_cache[path])
}
+
+ fn handle_idle_timeout(&mut self, cx: &mut Context) -> EventResult {
+ // Try to find a document in the cache
+ let doc = self
+ .current_file(cx.editor)
+ .and_then(|(path, _range)| self.preview_cache.get_mut(&path))
+ .and_then(|cache| match cache {
+ CachedPreview::Document(doc) => Some(doc),
+ _ => None,
+ });
+
+ // Then attempt to highlight it if it has no language set
+ if let Some(doc) = doc {
+ if doc.language_config().is_none() {
+ let loader = cx.editor.syn_loader.clone();
+ doc.detect_language(loader);
+ }
+ }
+
+ EventResult::Consumed(None)
+ }
}
impl<T: Item + 'static> Component for FilePicker<T> {
@@ -261,6 +281,9 @@ impl<T: Item + 'static> Component for FilePicker<T> {
}
fn handle_event(&mut self, event: &Event, ctx: &mut Context) -> EventResult {
+ if let Event::IdleTimeout = event {
+ return self.handle_idle_timeout(ctx);
+ }
// TODO: keybinds for scrolling preview
self.picker.handle_event(event, ctx)
}
@@ -287,8 +310,6 @@ pub struct Picker<T: Item> {
matcher: Box<Matcher>,
/// (index, score)
matches: Vec<(usize, i64)>,
- /// Filter over original options.
- filters: Vec<usize>, // could be optimized into bit but not worth it now
/// Current height of the completions box
completion_height: u16,
@@ -323,7 +344,6 @@ impl<T: Item> Picker<T> {
editor_data,
matcher: Box::new(Matcher::default()),
matches: Vec::new(),
- filters: Vec::new(),
cursor: 0,
prompt,
previous_pattern: String::new(),
@@ -365,13 +385,14 @@ impl<T: Item> Picker<T> {
.map(|(index, _option)| (index, 0)),
);
} else if pattern.starts_with(&self.previous_pattern) {
+ let query = FuzzyQuery::new(pattern);
// optimization: if the pattern is a more specific version of the previous one
// then we can score the filtered set.
self.matches.retain_mut(|(index, score)| {
let option = &self.options[*index];
let text = option.sort_text(&self.editor_data);
- match self.matcher.fuzzy_match(&text, pattern) {
+ match query.fuzzy_match(&text, &self.matcher) {
Some(s) => {
// Update the score
*score = s;
@@ -384,23 +405,17 @@ impl<T: Item> Picker<T> {
self.matches
.sort_unstable_by_key(|(_, score)| Reverse(*score));
} else {
+ let query = FuzzyQuery::new(pattern);
self.matches.clear();
self.matches.extend(
self.options
.iter()
.enumerate()
.filter_map(|(index, option)| {
- // filter options first before matching
- if !self.filters.is_empty() {
- // TODO: this filters functionality seems inefficient,
- // instead store and operate on filters if any
- self.filters.binary_search(&index).ok()?;
- }
-
let text = option.filter_text(&self.editor_data);
- self.matcher
- .fuzzy_match(&text, pattern)
+ query
+ .fuzzy_match(&text, &self.matcher)
.map(|score| (index, score))
}),
);
@@ -460,14 +475,6 @@ impl<T: Item> Picker<T> {
.map(|(index, _score)| &self.options[*index])
}
- pub fn save_filter(&mut self, cx: &Context) {
- self.filters.clear();
- self.filters
- .extend(self.matches.iter().map(|(index, _)| *index));
- self.filters.sort_unstable(); // used for binary search later
- self.prompt.clear(cx.editor);
- }
-
pub fn toggle_preview(&mut self) {
self.show_preview = !self.show_preview;
}
@@ -505,6 +512,9 @@ impl<T: Item + 'static> Component for Picker<T> {
compositor.last_picker = compositor.pop();
})));
+ // So that idle timeout retriggers
+ cx.editor.reset_idle_timer();
+
match key_event {
shift!(Tab) | key!(Up) | ctrl!('p') => {
self.move_by(1, Direction::Backward);
@@ -545,9 +555,6 @@ impl<T: Item + 'static> Component for Picker<T> {
}
return close_fn;
}
- ctrl!(' ') => {
- self.save_filter(cx);
- }
ctrl!('t') => {
self.toggle_preview();
}
@@ -630,9 +637,8 @@ impl<T: Item + 'static> Component for Picker<T> {
}
let spans = option.label(&self.editor_data);
- let (_score, highlights) = self
- .matcher
- .fuzzy_indices(&String::from(&spans), self.prompt.line())
+ let (_score, highlights) = FuzzyQuery::new(self.prompt.line())
+ .fuzzy_indicies(&String::from(&spans), &self.matcher)
.unwrap_or_default();
spans.0.into_iter().fold(inner, |pos, span| {
diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs
index 365e1ca9..b0e8ec5d 100644
--- a/helix-term/src/ui/statusline.rs
+++ b/helix-term/src/ui/statusline.rs
@@ -144,6 +144,7 @@ where
helix_view::editor::StatusLineElement::Selections => render_selections,
helix_view::editor::StatusLineElement::Position => render_position,
helix_view::editor::StatusLineElement::PositionPercentage => render_position_percentage,
+ helix_view::editor::StatusLineElement::TotalLineNumbers => render_total_line_numbers,
helix_view::editor::StatusLineElement::Separator => render_separator,
helix_view::editor::StatusLineElement::Spacer => render_spacer,
}
@@ -154,16 +155,16 @@ where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
{
let visible = context.focused;
-
+ let modenames = &context.editor.config().statusline.mode;
write(
context,
format!(
" {} ",
if visible {
match context.editor.mode() {
- Mode::Insert => "INS",
- Mode::Select => "SEL",
- Mode::Normal => "NOR",
+ Mode::Insert => &modenames.insert,
+ Mode::Select => &modenames.select,
+ Mode::Normal => &modenames.normal,
}
} else {
// If not focused, explicitly leave an empty space instead of returning None.
@@ -276,6 +277,15 @@ where
);
}
+fn render_total_line_numbers<F>(context: &mut RenderContext, write: F)
+where
+ F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
+{
+ let total_line_numbers = context.doc.text().len_lines();
+
+ write(context, format!(" {} ", total_line_numbers), None);
+}
+
fn render_position_percentage<F>(context: &mut RenderContext, write: F)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
diff --git a/helix-term/tests/test/movement.rs b/helix-term/tests/test/movement.rs
index 088685df..45aae39e 100644
--- a/helix-term/tests/test/movement.rs
+++ b/helix-term/tests/test/movement.rs
@@ -85,3 +85,152 @@ async fn cursor_position_newly_opened_file() -> anyhow::Result<()> {
Ok(())
}
+
+#[tokio::test]
+async fn cursor_position_append_eof() -> anyhow::Result<()> {
+ // Selection is fowards
+ test((
+ "#[foo|]#",
+ "abar<esc>",
+ helpers::platform_line("#[foobar|]#\n").as_ref(),
+ ))
+ .await?;
+
+ // Selection is backwards
+ test((
+ "#[|foo]#",
+ "abar<esc>",
+ helpers::platform_line("#[foobar|]#\n").as_ref(),
+ ))
+ .await?;
+
+ Ok(())
+}
+
+#[tokio::test]
+async fn select_mode_tree_sitter_next_function_is_union_of_objects() -> anyhow::Result<()> {
+ test_with_config(
+ Args {
+ files: vec![(PathBuf::from("foo.rs"), Position::default())],
+ ..Default::default()
+ },
+ Config::default(),
+ (
+ helpers::platform_line(indoc! {"\
+ #[/|]#// Increments
+ fn inc(x: usize) -> usize { x + 1 }
+ /// Decrements
+ fn dec(x: usize) -> usize { x - 1 }
+ "})
+ .as_ref(),
+ "]fv]f",
+ helpers::platform_line(indoc! {"\
+ /// Increments
+ #[fn inc(x: usize) -> usize { x + 1 }
+ /// Decrements
+ fn dec(x: usize) -> usize { x - 1 }|]#
+ "})
+ .as_ref(),
+ ),
+ )
+ .await?;
+
+ Ok(())
+}
+
+#[tokio::test]
+async fn select_mode_tree_sitter_prev_function_unselects_object() -> anyhow::Result<()> {
+ test_with_config(
+ Args {
+ files: vec![(PathBuf::from("foo.rs"), Position::default())],
+ ..Default::default()
+ },
+ Config::default(),
+ (
+ helpers::platform_line(indoc! {"\
+ /// Increments
+ #[fn inc(x: usize) -> usize { x + 1 }
+ /// Decrements
+ fn dec(x: usize) -> usize { x - 1 }|]#
+ "})
+ .as_ref(),
+ "v[f",
+ helpers::platform_line(indoc! {"\
+ /// Increments
+ #[fn inc(x: usize) -> usize { x + 1 }|]#
+ /// Decrements
+ fn dec(x: usize) -> usize { x - 1 }
+ "})
+ .as_ref(),
+ ),
+ )
+ .await?;
+
+ Ok(())
+}
+
+#[tokio::test]
+async fn select_mode_tree_sitter_prev_function_goes_backwards_to_object() -> anyhow::Result<()> {
+ // Note: the anchor stays put and the head moves back.
+ test_with_config(
+ Args {
+ files: vec![(PathBuf::from("foo.rs"), Position::default())],
+ ..Default::default()
+ },
+ Config::default(),
+ (
+ helpers::platform_line(indoc! {"\
+ /// Increments
+ fn inc(x: usize) -> usize { x + 1 }
+ /// Decrements
+ fn dec(x: usize) -> usize { x - 1 }
+ /// Identity
+ #[fn ident(x: usize) -> usize { x }|]#
+ "})
+ .as_ref(),
+ "v[f",
+ helpers::platform_line(indoc! {"\
+ /// Increments
+ fn inc(x: usize) -> usize { x + 1 }
+ /// Decrements
+ #[|fn dec(x: usize) -> usize { x - 1 }
+ /// Identity
+ ]#fn ident(x: usize) -> usize { x }
+ "})
+ .as_ref(),
+ ),
+ )
+ .await?;
+
+ test_with_config(
+ Args {
+ files: vec![(PathBuf::from("foo.rs"), Position::default())],
+ ..Default::default()
+ },
+ Config::default(),
+ (
+ helpers::platform_line(indoc! {"\
+ /// Increments
+ fn inc(x: usize) -> usize { x + 1 }
+ /// Decrements
+ fn dec(x: usize) -> usize { x - 1 }
+ /// Identity
+ #[fn ident(x: usize) -> usize { x }|]#
+ "})
+ .as_ref(),
+ "v[f[f",
+ helpers::platform_line(indoc! {"\
+ /// Increments
+ #[|fn inc(x: usize) -> usize { x + 1 }
+ /// Decrements
+ fn dec(x: usize) -> usize { x - 1 }
+ /// Identity
+ ]#fn ident(x: usize) -> usize { x }
+ "})
+ .as_ref(),
+ ),
+ )
+ .await?;
+
+ Ok(())
+}
diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml
index 266a5732..b96a537d 100644
--- a/helix-view/Cargo.toml
+++ b/helix-view/Cargo.toml
@@ -17,6 +17,7 @@ term = ["crossterm"]
bitflags = "1.3"
anyhow = "1"
helix-core = { version = "0.6", path = "../helix-core" }
+helix-loader = { version = "0.6", path = "../helix-loader" }
helix-lsp = { version = "0.6", path = "../helix-lsp" }
helix-dap = { version = "0.6", path = "../helix-dap" }
crossterm = { version = "0.25", optional = true }
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index 2ef99c6a..0daa983f 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -5,6 +5,7 @@ use helix_core::auto_pairs::AutoPairs;
use helix_core::Range;
use serde::de::{self, Deserialize, Deserializer};
use serde::Serialize;
+use std::borrow::Cow;
use std::cell::Cell;
use std::collections::HashMap;
use std::fmt::Display;
@@ -23,7 +24,7 @@ use helix_core::{
DEFAULT_LINE_ENDING,
};
-use crate::{DocumentId, Editor, ViewId};
+use crate::{apply_transaction, DocumentId, Editor, View, ViewId};
/// 8kB of buffer space for encoding and decoding `Rope`s.
const BUF_SIZE: usize = 8192;
@@ -600,7 +601,7 @@ impl Document {
}
/// Reload the document from its path.
- pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> {
+ pub fn reload(&mut self, view: &mut View) -> Result<(), Error> {
let encoding = &self.encoding;
let path = self.path().filter(|path| path.exists());
@@ -616,8 +617,8 @@ 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);
- self.apply(&transaction, view_id);
- self.append_changes_to_history(view_id);
+ apply_transaction(&transaction, self, view);
+ self.append_changes_to_history(view.id);
self.reset_modified();
self.detect_indent_and_line_ending();
@@ -809,6 +810,9 @@ 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.
@@ -830,11 +834,11 @@ impl Document {
success
}
- fn undo_redo_impl(&mut self, view_id: ViewId, undo: bool) -> bool {
+ fn undo_redo_impl(&mut self, view: &mut View, undo: bool) -> bool {
let mut history = self.history.take();
let txn = if undo { history.undo() } else { history.redo() };
let success = if let Some(txn) = txn {
- self.apply_impl(txn, view_id)
+ self.apply_impl(txn, view.id) && view.apply(txn, self)
} else {
false
};
@@ -848,26 +852,26 @@ impl Document {
}
/// Undo the last modification to the [`Document`]. Returns whether the undo was successful.
- pub fn undo(&mut self, view_id: ViewId) -> bool {
- self.undo_redo_impl(view_id, true)
+ pub fn undo(&mut self, view: &mut View) -> bool {
+ self.undo_redo_impl(view, true)
}
/// Redo the last modification to the [`Document`]. Returns whether the redo was successful.
- pub fn redo(&mut self, view_id: ViewId) -> bool {
- self.undo_redo_impl(view_id, false)
+ pub fn redo(&mut self, view: &mut View) -> bool {
+ self.undo_redo_impl(view, false)
}
pub fn savepoint(&mut self) {
self.savepoint = Some(Transaction::new(self.text()));
}
- pub fn restore(&mut self, view_id: ViewId) {
+ pub fn restore(&mut self, view: &mut View) {
if let Some(revert) = self.savepoint.take() {
- self.apply(&revert, view_id);
+ apply_transaction(&revert, self, view);
}
}
- fn earlier_later_impl(&mut self, view_id: ViewId, uk: UndoKind, earlier: bool) -> bool {
+ fn earlier_later_impl(&mut self, view: &mut View, uk: UndoKind, earlier: bool) -> bool {
let txns = if earlier {
self.history.get_mut().earlier(uk)
} else {
@@ -875,7 +879,7 @@ impl Document {
};
let mut success = false;
for txn in txns {
- if self.apply_impl(&txn, view_id) {
+ if self.apply_impl(&txn, view.id) && view.apply(&txn, self) {
success = true;
}
}
@@ -887,13 +891,13 @@ impl Document {
}
/// Undo modifications to the [`Document`] according to `uk`.
- pub fn earlier(&mut self, view_id: ViewId, uk: UndoKind) -> bool {
- self.earlier_later_impl(view_id, uk, true)
+ pub fn earlier(&mut self, view: &mut View, uk: UndoKind) -> bool {
+ self.earlier_later_impl(view, uk, true)
}
/// Redo modifications to the [`Document`] according to `uk`.
- pub fn later(&mut self, view_id: ViewId, uk: UndoKind) -> bool {
- self.earlier_later_impl(view_id, uk, false)
+ pub fn later(&mut self, view: &mut View, uk: UndoKind) -> bool {
+ self.earlier_later_impl(view, uk, false)
}
/// Commit pending changes to history
@@ -1038,6 +1042,12 @@ impl Document {
.map(helix_core::path::get_relative_path)
}
+ pub fn display_name(&self) -> Cow<'static, str> {
+ self.relative_path()
+ .map(|path| path.to_string_lossy().to_string().into())
+ .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into())
+ }
+
// transact(Fn) ?
// -- LSP methods
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 5eff9983..e9a3c639 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -1,6 +1,6 @@
use crate::{
clipboard::{get_clipboard_provider, ClipboardProvider},
- document::{Mode, SCRATCH_BUFFER_NAME},
+ document::Mode,
graphics::{CursorKind, Rect},
info::Info,
input::KeyEvent,
@@ -28,7 +28,7 @@ use tokio::{
time::{sleep, Duration, Instant, Sleep},
};
-use anyhow::{bail, Error};
+use anyhow::Error;
pub use helix_core::diagnostic::Severity;
pub use helix_core::register::Registers;
@@ -124,6 +124,8 @@ pub struct Config {
pub line_number: LineNumber,
/// Highlight the lines cursors are currently on. Defaults to false.
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>,
/// Middle click paste support. Defaults to true.
@@ -260,6 +262,7 @@ pub struct StatusLineConfig {
pub center: Vec<StatusLineElement>,
pub right: Vec<StatusLineElement>,
pub separator: String,
+ pub mode: ModeConfig,
}
impl Default for StatusLineConfig {
@@ -271,6 +274,25 @@ impl Default for StatusLineConfig {
center: vec![],
right: vec![E::Diagnostics, E::Selections, E::Position, E::FileEncoding],
separator: String::from("│"),
+ mode: ModeConfig::default(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
+pub struct ModeConfig {
+ pub normal: String,
+ pub insert: String,
+ pub select: String,
+}
+
+impl Default for ModeConfig {
+ fn default() -> Self {
+ Self {
+ normal: String::from("NOR"),
+ insert: String::from("INS"),
+ select: String::from("SEL"),
}
}
}
@@ -311,6 +333,9 @@ pub enum StatusLineElement {
/// The cursor position as a percent of the total file
PositionPercentage,
+ /// The total line numbers of the current file
+ TotalLineNumbers,
+
/// A single space
Spacer,
}
@@ -529,15 +554,17 @@ impl Default for WhitespaceCharacters {
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(default)]
+#[serde(default, rename_all = "kebab-case")]
pub struct IndentGuidesConfig {
pub render: bool,
pub character: char,
+ pub skip_levels: u8,
}
impl Default for IndentGuidesConfig {
fn default() -> Self {
Self {
+ skip_levels: 0,
render: false,
character: '│',
}
@@ -557,6 +584,7 @@ impl Default for Config {
},
line_number: LineNumber::Absolute,
cursorline: false,
+ cursorcolumn: false,
gutters: vec![GutterType::Diagnostics, GutterType::LineNumbers],
middle_click_paste: true,
auto_pairs: AutoPairConfig::default(),
@@ -643,7 +671,7 @@ pub struct Editor {
/// The currently applied editor theme. While previewing a theme, the previewed theme
/// is set here.
pub theme: Theme,
-
+ pub last_line_number: Option<usize>,
pub status_msg: Option<(Cow<'static, str>, Severity)>,
pub autoinfo: Option<Info>,
@@ -652,7 +680,6 @@ pub struct Editor {
pub idle_timer: Pin<Box<Sleep>>,
pub last_motion: Option<Motion>,
- pub pseudo_pending: Option<String>,
pub last_completion: Option<CompleteAction>,
@@ -686,6 +713,14 @@ pub enum Action {
VerticalSplit,
}
+/// Error thrown on failed document closed
+pub enum CloseError {
+ /// Document doesn't exist
+ DoesNotExist,
+ /// Buffer is modified
+ BufferModified(String),
+}
+
impl Editor {
pub fn new(
mut area: Rect,
@@ -717,6 +752,7 @@ impl Editor {
syn_loader,
theme_loader,
last_theme: None,
+ last_line_number: None,
registers: Registers::default(),
clipboard_provider: get_clipboard_provider(),
status_msg: None,
@@ -724,7 +760,6 @@ impl Editor {
idle_timer: Box::pin(sleep(conf.idle_timeout)),
last_motion: None,
last_completion: None,
- pseudo_pending: None,
config,
auto_pairs,
exit_code: 0,
@@ -844,7 +879,7 @@ impl Editor {
// try to find a language server based on the language name
let language_server = doc.language.as_ref().and_then(|language| {
- ls.get(language)
+ ls.get(language, doc.path())
.map_err(|e| {
log::error!(
"Failed to initialize the LSP for `{}` {{ {} }}",
@@ -1044,19 +1079,14 @@ impl Editor {
self._refresh();
}
- pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> anyhow::Result<()> {
+ pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> Result<(), CloseError> {
let doc = match self.documents.get(&doc_id) {
Some(doc) => doc,
- None => bail!("document does not exist"),
+ None => return Err(CloseError::DoesNotExist),
};
if !force && doc.is_modified() {
- bail!(
- "buffer {:?} is modified",
- doc.relative_path()
- .map(|path| path.to_string_lossy().to_string())
- .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into())
- );
+ return Err(CloseError::BufferModified(doc.display_name().into_owned()));
}
if let Some(language_server) = doc.language_server() {
diff --git a/helix-view/src/handlers/dap.rs b/helix-view/src/handlers/dap.rs
index e39584c3..2e86871b 100644
--- a/helix-view/src/handlers/dap.rs
+++ b/helix-view/src/handlers/dap.rs
@@ -262,7 +262,7 @@ impl Editor {
log::info!("{}", output);
self.set_status(format!("{} {}", prefix, output));
}
- Event::Initialized => {
+ Event::Initialized(_) => {
// send existing breakpoints
for (path, breakpoints) in &mut self.breakpoints {
// TODO: call futures in parallel, await all
diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs
index 5ad6a60c..3080cf8e 100644
--- a/helix-view/src/info.rs
+++ b/helix-view/src/info.rs
@@ -16,7 +16,11 @@ pub struct Info {
}
impl Info {
- pub fn new(title: &str, body: Vec<(String, String)>) -> Self {
+ pub fn new<T, U>(title: &str, body: &[(T, U)]) -> Self
+ where
+ T: AsRef<str>,
+ U: AsRef<str>,
+ {
if body.is_empty() {
return Self {
title: title.to_string(),
@@ -26,11 +30,21 @@ impl Info {
};
}
- let item_width = body.iter().map(|(item, _)| item.width()).max().unwrap();
+ let item_width = body
+ .iter()
+ .map(|(item, _)| item.as_ref().width())
+ .max()
+ .unwrap();
let mut text = String::new();
- for (item, desc) in &body {
- let _ = writeln!(text, "{:width$} {}", item, desc, width = item_width);
+ for (item, desc) in body {
+ let _ = writeln!(
+ text,
+ "{:width$} {}",
+ item.as_ref(),
+ desc.as_ref(),
+ width = item_width
+ );
}
Self {
@@ -42,19 +56,19 @@ impl Info {
}
pub fn from_keymap(title: &str, body: Vec<(&str, BTreeSet<KeyEvent>)>) -> Self {
- let body = body
+ let body: Vec<_> = body
.into_iter()
.map(|(desc, events)| {
let events = events.iter().map(ToString::to_string).collect::<Vec<_>>();
- (events.join(", "), desc.to_string())
+ (events.join(", "), desc)
})
.collect();
- Self::new(title, body)
+ Self::new(title, &body)
}
pub fn from_registers(registers: &Registers) -> Self {
- let body = registers
+ let body: Vec<_> = registers
.inner()
.iter()
.map(|(ch, reg)| {
@@ -62,13 +76,12 @@ impl Info {
.read()
.get(0)
.and_then(|s| s.lines().next())
- .map(String::from)
.unwrap_or_default();
(ch.to_string(), content)
})
.collect();
- let mut infobox = Self::new("Registers", body);
+ let mut infobox = Self::new("Registers", &body);
infobox.width = 30; // copied content could be very long
infobox
}
diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs
index 083a1e08..30fa72c4 100644
--- a/helix-view/src/input.rs
+++ b/helix-view/src/input.rs
@@ -14,6 +14,7 @@ pub enum Event {
Mouse(MouseEvent),
Paste(String),
Resize(u16, u16),
+ IdleTimeout,
}
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs
index 788304bc..276be441 100644
--- a/helix-view/src/lib.rs
+++ b/helix-view/src/lib.rs
@@ -53,17 +53,30 @@ pub fn align_view(doc: &Document, view: &mut View, align: Align) {
.cursor(doc.text().slice(..));
let line = doc.text().char_to_line(pos);
- let height = view.inner_area().height as usize;
+ let last_line_height = view.inner_area().height.saturating_sub(1) as usize;
let relative = match align {
- Align::Center => height / 2,
+ Align::Center => last_line_height / 2,
Align::Top => 0,
- Align::Bottom => height,
+ Align::Bottom => last_line_height,
};
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: &mut View,
+) -> bool {
+ // This is a short function but it's easy to call `Document::apply`
+ // without calling `View::apply` or in the wrong order. The transaction
+ // must be applied to the document before the view.
+ doc.apply(transaction, view.id) && view.apply(transaction, doc)
+}
+
pub use document::Document;
pub use editor::Editor;
pub use theme::Theme;
diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs
index aaef28b2..302844b7 100644
--- a/helix-view/src/theme.rs
+++ b/helix-view/src/theme.rs
@@ -3,20 +3,29 @@ use std::{
path::{Path, PathBuf},
};
-use anyhow::Context;
+use anyhow::{anyhow, Context, Result};
use helix_core::hashmap;
+use helix_loader::merge_toml_values;
use log::warn;
use once_cell::sync::Lazy;
use serde::{Deserialize, Deserializer};
-use toml::Value;
+use toml::{map::Map, Value};
use crate::graphics::UnderlineStyle;
pub use crate::graphics::{Color, Modifier, Style};
pub static DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| {
+ // let raw_theme: Value = toml::from_slice(include_bytes!("../../theme.toml"))
+ // .expect("Failed to parse default theme");
+ // Theme::from(raw_theme)
+
toml::from_slice(include_bytes!("../../theme.toml")).expect("Failed to parse default theme")
});
pub static BASE16_DEFAULT_THEME: Lazy<Theme> = Lazy::new(|| {
+ // let raw_theme: Value = toml::from_slice(include_bytes!("../../base16_theme.toml"))
+ // .expect("Failed to parse base 16 default theme");
+ // Theme::from(raw_theme)
+
toml::from_slice(include_bytes!("../../base16_theme.toml"))
.expect("Failed to parse base 16 default theme")
});
@@ -36,24 +45,51 @@ impl Loader {
}
/// Loads a theme first looking in the `user_dir` then in `default_dir`
- pub fn load(&self, name: &str) -> Result<Theme, anyhow::Error> {
+ pub fn load(&self, name: &str) -> Result<Theme> {
if name == "default" {
return Ok(self.default());
}
if name == "base16_default" {
return Ok(self.base16_default());
}
- let filename = format!("{}.toml", name);
- let user_path = self.user_dir.join(&filename);
- let path = if user_path.exists() {
- user_path
+ self.load_theme(name, name, false).map(Theme::from)
+ }
+
+ // load the theme and its parent recursively and merge them
+ // `base_theme_name` is the theme from the config.toml,
+ // used to prevent some circular loading scenarios
+ fn load_theme(
+ &self,
+ name: &str,
+ base_them_name: &str,
+ only_default_dir: bool,
+ ) -> Result<Value> {
+ let path = self.path(name, only_default_dir);
+ let theme_toml = self.load_toml(path)?;
+
+ let inherits = theme_toml.get("inherits");
+
+ let theme_toml = if let Some(parent_theme_name) = inherits {
+ let parent_theme_name = parent_theme_name.as_str().ok_or_else(|| {
+ anyhow!(
+ "Theme: expected 'inherits' to be a string: {}",
+ parent_theme_name
+ )
+ })?;
+
+ let parent_theme_toml = self.load_theme(
+ parent_theme_name,
+ base_them_name,
+ base_them_name == parent_theme_name,
+ )?;
+
+ self.merge_themes(parent_theme_toml, theme_toml)
} else {
- self.default_dir.join(filename)
+ theme_toml
};
- let data = std::fs::read(&path)?;
- toml::from_slice(data.as_slice()).context("Failed to deserialize theme")
+ Ok(theme_toml)
}
pub fn read_names(path: &Path) -> Vec<String> {
@@ -71,6 +107,53 @@ impl Loader {
.unwrap_or_default()
}
+ // merge one theme into the parent theme
+ fn merge_themes(&self, parent_theme_toml: Value, theme_toml: Value) -> Value {
+ let parent_palette = parent_theme_toml.get("palette");
+ let palette = theme_toml.get("palette");
+
+ // handle the table seperately since it needs a `merge_depth` of 2
+ // this would conflict with the rest of the theme merge strategy
+ let palette_values = match (parent_palette, palette) {
+ (Some(parent_palette), Some(palette)) => {
+ merge_toml_values(parent_palette.clone(), palette.clone(), 2)
+ }
+ (Some(parent_palette), None) => parent_palette.clone(),
+ (None, Some(palette)) => palette.clone(),
+ (None, None) => Map::new().into(),
+ };
+
+ // add the palette correctly as nested table
+ let mut palette = Map::new();
+ palette.insert(String::from("palette"), palette_values);
+
+ // merge the theme into the parent theme
+ let theme = merge_toml_values(parent_theme_toml, theme_toml, 1);
+ // merge the before specially handled palette into the theme
+ merge_toml_values(theme, palette.into(), 1)
+ }
+
+ // 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)?;
+
+ toml::from_slice(data.as_slice()).context("Failed to deserialize theme")
+ }
+
+ // Returns the path to the theme with the name
+ // With `only_default_dir` as false the path will first search for the user path
+ // disabled it ignores the user path and returns only the default path
+ fn path(&self, name: &str, only_default_dir: bool) -> PathBuf {
+ let filename = format!("{}.toml", name);
+
+ let user_path = self.user_dir.join(&filename);
+ if !only_default_dir && user_path.exists() {
+ user_path
+ } else {
+ self.default_dir.join(filename)
+ }
+ }
+
/// Lists all theme names available in default and user directory
pub fn names(&self) -> Vec<String> {
let mut names = Self::read_names(&self.user_dir);
@@ -106,52 +189,77 @@ pub struct Theme {
highlights: Vec<Style>,
}
+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);
+
+ Self {
+ styles,
+ scopes,
+ highlights,
+ }
+ }
+}
+
impl<'de> Deserialize<'de> for Theme {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
- let mut styles = HashMap::new();
- let mut scopes = Vec::new();
- let mut highlights = Vec::new();
-
- if let Ok(mut colors) = HashMap::<String, Value>::deserialize(deserializer) {
- // TODO: alert user of parsing failures in editor
- let palette = colors
- .remove("palette")
- .map(|value| {
- ThemePalette::try_from(value).unwrap_or_else(|err| {
- warn!("{}", err);
- ThemePalette::default()
- })
- })
- .unwrap_or_default();
-
- styles.reserve(colors.len());
- scopes.reserve(colors.len());
- highlights.reserve(colors.len());
+ let values = HashMap::<String, Value>::deserialize(deserializer)?;
- for (name, style_value) in colors {
- let mut style = Style::default();
- if let Err(err) = palette.parse_style(&mut style, style_value) {
- warn!("{}", err);
- }
-
- // these are used both as UI and as highlights
- styles.insert(name.clone(), style);
- scopes.push(name);
- highlights.push(style);
- }
- }
+ let (styles, scopes, highlights) = build_theme_values(Ok(values));
Ok(Self {
- scopes,
styles,
+ scopes,
highlights,
})
}
}
+fn build_theme_values(
+ values: Result<HashMap<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
+ .remove("palette")
+ .map(|value| {
+ ThemePalette::try_from(value).unwrap_or_else(|err| {
+ warn!("{}", err);
+ ThemePalette::default()
+ })
+ })
+ .unwrap_or_default();
+ // remove inherits from value to prevent errors
+ let _ = colors.remove("inherits");
+ styles.reserve(colors.len());
+ scopes.reserve(colors.len());
+ highlights.reserve(colors.len());
+ for (name, style_value) in colors {
+ let mut style = Style::default();
+ if let Err(err) = palette.parse_style(&mut style, style_value) {
+ warn!("{}", err);
+ }
+
+ // these are used both as UI and as highlights
+ styles.insert(name.clone(), style);
+ scopes.push(name);
+ highlights.push(style);
+ }
+ }
+
+ (styles, scopes, highlights)
+}
+
impl Theme {
#[inline]
pub fn highlight(&self, index: usize) -> Style {
@@ -170,6 +278,13 @@ impl Theme {
.find_map(|s| self.styles.get(s).copied())
}
+ /// Get the style of a scope, without falling back to dot separated broader
+ /// scopes. For example if `ui.text.focus` is not defined in the theme, it
+ /// will return `None`, even if `ui.text` is.
+ pub fn try_get_exact(&self, scope: &str) -> Option<Style> {
+ self.styles.get(scope).copied()
+ }
+
#[inline]
pub fn scopes(&self) -> &[String] {
&self.scopes
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index 3df533df..62984b88 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -3,7 +3,9 @@ use crate::{
gutter::{self, Gutter},
Document, DocumentId, ViewId,
};
-use helix_core::{pos_at_visual_coords, visual_coords_at_pos, Position, RopeSlice, Selection};
+use helix_core::{
+ pos_at_visual_coords, visual_coords_at_pos, Position, RopeSlice, Selection, Transaction,
+};
use std::fmt;
@@ -62,6 +64,22 @@ impl JumpList {
pub fn get(&self) -> &[Jump] {
&self.jumps
}
+
+ /// Applies a [`Transaction`] of changes to the jumplist.
+ /// This is necessary to ensure that changes to documents do not leave jump-list
+ /// selections pointing to parts of the text which no longer exist.
+ fn apply(&mut self, transaction: &Transaction, doc: &Document) {
+ let text = doc.text().slice(..);
+
+ for (doc_id, selection) in &mut self.jumps {
+ if doc.id() == *doc_id {
+ *selection = selection
+ .clone()
+ .map(transaction.changes())
+ .ensure_invariants(text);
+ }
+ }
+ }
}
#[derive(Clone)]
@@ -334,6 +352,14 @@ impl View {
// (None, None) => return,
// }
// }
+
+ /// 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: &Document) -> bool {
+ self.jumps.apply(transaction, doc);
+ true
+ }
}
#[cfg(test)]
diff --git a/languages.toml b/languages.toml
index d7909b6d..3c324c78 100644
--- a/languages.toml
+++ b/languages.toml
@@ -107,7 +107,7 @@ indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "elixir"
-source = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "1dabc1c790e07115175057863808085ea60dd08a" }
+source = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "b20eaa75565243c50be5e35e253d8beb58f45d56" }
[[language]]
name = "fish"
@@ -184,7 +184,7 @@ args = { console = "internalConsole", attachCommands = [ "platform select remote
[[grammar]]
name = "c"
-source = { git = "https://github.com/tree-sitter/tree-sitter-c", rev = "f05e279aedde06a25801c3f2b2cc8ac17fac52ae" }
+source = { git = "https://github.com/tree-sitter/tree-sitter-c", rev = "7175a6dd5fc1cee660dce6fe23f6043d75af424a" }
[[language]]
name = "cpp"
@@ -221,7 +221,7 @@ args = { console = "internalConsole", attachCommands = [ "platform select remote
[[grammar]]
name = "cpp"
-source = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "e8dcc9d2b404c542fd236ea5f7208f90be8a6e89" }
+source = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "d5e90fba898f320db48d81ddedd78d52c67c1fed" }
[[language]]
name = "c-sharp"
@@ -233,9 +233,28 @@ comment-token = "//"
indent = { tab-width = 4, unit = "\t" }
language-server = { command = "OmniSharp", args = [ "--languageserver" ] }
+[language.debugger]
+name = "netcoredbg"
+transport = "tcp"
+command = "netcoredbg"
+args = [ "--interpreter=vscode" ]
+port-arg = "--server={}"
+
+[[language.debugger.templates]]
+name = "launch"
+request = "launch"
+completion = [ { name = "path to dll", completion = "filename" } ]
+args = { type = "coreclr", console = "internalConsole", internalConsoleOptions = "openOnSessionStart", program = "{0}" }
+
+[[language.debugger.templates]]
+name = "attach"
+request = "attach"
+completion = [ "pid" ]
+args = { processId = "{0}" }
+
[[grammar]]
name = "c-sharp"
-source = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "9c494a503c8e2044bfffce57f70b480c01a82f03" }
+source = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "5b60f99545fea00a33bbfae5be956f684c4c69e2" }
[[language]]
name = "go"
@@ -537,7 +556,7 @@ indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "twig"
-source = { git = "https://github.com/eirabben/tree-sitter-twig", rev = "b7444181fb38e603e25ea8fcdac55f9492e49c27" }
+source = { git = "https://github.com/gbprod/tree-sitter-twig", rev = "807b293fec3fead64f54c64fdf6fb05516c032b9" }
[[language]]
name = "latex"
@@ -667,7 +686,7 @@ language-server = { command = "lua-language-server", args = [] }
[[grammar]]
name = "lua"
-source = { git = "https://github.com/nvim-treesitter/tree-sitter-lua", rev = "6f5d40190ec8a0aa8c8410699353d820f4f7d7a6" }
+source = { git = "https://github.com/MunifTanjim/tree-sitter-lua", rev = "887dfd4e83c469300c279314ff1619b1d0b85b91" }
[[language]]
name = "svelte"
@@ -724,6 +743,19 @@ name = "haskell"
source = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "b6ec26f181dd059eedd506fa5fbeae1b8e5556c8" }
[[language]]
+name = "purescript"
+scope = "source.purescript"
+injection-regex = "purescript"
+file-types = ["purs"]
+roots = ["spago.dhall", "bower.json"]
+comment-token = "--"
+language-server = { command = "purescript-language-server", args = ["--stdio"] }
+indent = { tab-width = 2, unit = " " }
+auto-format = true
+formatter = { command = "purs-tidy", args = ["format"] }
+grammar = "haskell"
+
+[[language]]
name = "zig"
scope = "source.zig"
injection-regex = "zig"
@@ -911,7 +943,7 @@ indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "markdown"
-source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "142a5b4a1b092b64c9f5db8f11558f9dd4009a1b", subpath = "tree-sitter-markdown" }
+source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "d5740f0fe4b8e4603f2229df107c5c9ef5eec389", subpath = "tree-sitter-markdown" }
[[language]]
name = "markdown.inline"
@@ -923,7 +955,7 @@ grammar = "markdown_inline"
[[grammar]]
name = "markdown_inline"
-source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "142a5b4a1b092b64c9f5db8f11558f9dd4009a1b", subpath = "tree-sitter-markdown-inline" }
+source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "d5740f0fe4b8e4603f2229df107c5c9ef5eec389", subpath = "tree-sitter-markdown-inline" }
[[language]]
name = "dart"
@@ -1408,12 +1440,12 @@ scope = "source.cairo"
injection-regex = "cairo"
file-types = ["cairo"]
roots = []
-comment-token = "#"
+comment-token = "//"
indent = { tab-width = 4, unit = " " }
[[grammar]]
name = "cairo"
-source = { git = "https://github.com/archseer/tree-sitter-cairo", rev = "5155c6eb40db6d437f4fa41b8bcd8890a1c91716" }
+source = { git = "https://github.com/archseer/tree-sitter-cairo", rev = "b249662a1eefeb4d71c9529cdd971e74fecc10fe" }
[[language]]
name = "cpon"
diff --git a/runtime/queries/c-sharp/highlights.scm b/runtime/queries/c-sharp/highlights.scm
index dbb7c778..38394862 100644
--- a/runtime/queries/c-sharp/highlights.scm
+++ b/runtime/queries/c-sharp/highlights.scm
@@ -10,24 +10,22 @@
name: (identifier) @function))
(invocation_expression
- (member_access_expression
- expression: (identifier) @variable))
-
-(invocation_expression
function: (conditional_access_expression
(member_binding_expression
name: (identifier) @function)))
(invocation_expression
- [(identifier) (qualified_name)] @function)
+ [(identifier) (qualified_name)] @function)
+
+(local_function_statement
+ name: (identifier) @function)
; Generic Method invocation with generic type
(invocation_expression
function: (generic_name
- . (identifier) @function))
+ . (identifier) @function))
;; Namespaces
-
(namespace_declaration
name: [(identifier) (qualified_name)] @namespace)
@@ -40,8 +38,11 @@
(namespace_declaration name: (identifier) @type)
(using_directive (_) @namespace)
(constructor_declaration name: (identifier) @type)
+(destructor_declaration name: (identifier) @type)
(object_creation_expression [(identifier) (qualified_name)] @type)
(type_parameter_list (type_parameter) @type)
+(array_type (identifier) @type)
+(for_each_statement type: (identifier) @type)
[
(implicit_type)
@@ -66,7 +67,7 @@
(object_creation_expression
(generic_name
- (identifier) @type))
+ (identifier) @type))
(property_declaration
(generic_name
@@ -74,7 +75,7 @@
(_
type: (generic_name
- (identifier) @type))
+ (identifier) @type))
;; Enum
(enum_member_declaration (identifier) @variable.other.member)
@@ -91,6 +92,7 @@
(verbatim_string_literal)
(interpolated_string_text)
(interpolated_verbatim_string_text)
+ (interpolation_format_clause)
"\""
"$\""
"@$\""
@@ -107,6 +109,9 @@
(comment) @comment
;; Tokens
+(type_argument_list ["<" ">"] @punctuation.bracket)
+(type_parameter_list ["<" ">"] @punctuation.bracket)
+
[
";"
"."
@@ -150,97 +155,112 @@
"%"
"%="
":"
+ "::"
".."
"&="
"->"
"??="
] @operator
-[
- "("
- ")"
- "["
- "]"
- "{"
- "}"
-] @punctuation.bracket
+["(" ")" "[" "]" "{" "}"] @punctuation.bracket
;; Keywords
-(modifier) @keyword
+(modifier) @keyword.storage.modifier
(this_expression) @keyword
(escape_sequence) @constant.character.escape
[
"as"
+ "await"
"base"
- "break"
- "case"
- "catch"
"checked"
- "class"
- "continue"
- "default"
- "delegate"
- "do"
- "else"
- "enum"
- "event"
- "explicit"
- "finally"
- "for"
- "foreach"
- "goto"
- "if"
- "implicit"
- "interface"
+ "from"
+ "get"
+ "in"
+ "init"
"is"
+ "let"
"lock"
- "namespace"
+ "new"
"operator"
+ "out"
"params"
- "return"
+ "ref"
+ "select"
+ "set"
"sizeof"
"stackalloc"
- "static"
- "struct"
- "switch"
- "throw"
- "try"
"typeof"
"unchecked"
"using"
- "while"
- "new"
- "await"
- "in"
- "yield"
- "get"
- "set"
"when"
- "out"
- "ref"
- "from"
"where"
- "select"
- "record"
- "init"
"with"
- "let"
+ "yield"
] @keyword
-(nullable_directive) @keyword.directive
-(define_directive) @keyword.directive
-(undef_directive) @keyword.directive
-(if_directive) @keyword.directive
-(else_directive) @keyword.directive
-(elif_directive) @keyword.directive
-(endif_directive) @keyword.directive
-(region_directive) @keyword.directive
-(endregion_directive) @keyword.directive
-(error_directive) @keyword.directive
-(warning_directive) @keyword.directive
-(line_directive) @keyword.directive
-(pragma_directive) @keyword.directive
+[
+ "class"
+ "delegate"
+ "enum"
+ "event"
+ "interface"
+ "namespace"
+ "struct"
+ "record"
+] @keyword.storage.type
+
+[
+ "explicit"
+ "implicit"
+ "static"
+] @keyword.storage.modifier
+
+[
+ "break"
+ "continue"
+ "goto"
+] @keyword.control
+
+[
+ "catch"
+ "finally"
+ "throw"
+ "try"
+] @keyword.control.exception
+
+[
+ "do"
+ "for"
+ "foreach"
+ "while"
+] @keyword.control.repeat
+
+[
+ "case"
+ "default"
+ "else"
+ "if"
+ "switch"
+] @keyword.control.conditional
+
+"return" @keyword.control.return
+
+[
+ (nullable_directive)
+ (define_directive)
+ (undef_directive)
+ (if_directive)
+ (else_directive)
+ (elif_directive)
+ (endif_directive)
+ (region_directive)
+ (endregion_directive)
+ (error_directive)
+ (warning_directive)
+ (line_directive)
+ (pragma_directive)
+] @keyword.directive
;; Linq
(from_clause (identifier) @variable)
@@ -259,10 +279,16 @@
(binary_expression [(identifier) (qualified_name)] @variable [(identifier) (qualified_name)] @variable)
(binary_expression [(identifier) (qualified_name)]* @variable)
(conditional_expression [(identifier) (qualified_name)] @variable)
+(conditional_access_expression [(identifier) (qualified_name)] @variable)
(prefix_unary_expression [(identifier) (qualified_name)] @variable)
(postfix_unary_expression [(identifier) (qualified_name)]* @variable)
(assignment_expression [(identifier) (qualified_name)] @variable)
(cast_expression [(identifier) (qualified_name)] @type [(identifier) (qualified_name)] @variable)
+(element_access_expression (identifier) @variable)
+(member_access_expression
+ expression: ([(identifier) (qualified_name)] @type
+ (#match? @type "^[A-Z]")))
+(member_access_expression [(identifier) (qualified_name)] @variable)
;; Class
(base_list (identifier) @type)
@@ -278,7 +304,6 @@
name: (identifier) @variable)
;; Delegate
-
(delegate_declaration (identifier) @type)
;; Lambda
@@ -296,11 +321,11 @@
(parameter_list
(parameter
- name: (identifier) @parameter))
+ name: (identifier) @parameter))
(parameter_list
(parameter
- type: [(identifier) (qualified_name)] @type))
+ type: [(identifier) (qualified_name)] @type))
;; Typeof
(type_of_expression [(identifier) (qualified_name)] @type)
@@ -315,7 +340,7 @@
;; Type
(generic_name (identifier) @type)
-(type_parameter [(identifier) (qualified_name)] @variable.parameter)
+(type_parameter [(identifier) (qualified_name)] @type)
(type_argument_list [(identifier) (qualified_name)] @type)
;; Type constraints
@@ -333,15 +358,21 @@
;; Lock statement
(lock_statement (identifier) @variable)
+;; Declaration expression
+(declaration_expression
+ type: (identifier) @type
+ name: (identifier) @variable)
+
;; Rest
-(member_access_expression) @variable
-(element_access_expression (identifier) @variable)
(argument (identifier) @variable)
+(name_colon (identifier) @variable)
+(if_statement (identifier) @variable)
(for_statement (identifier) @variable)
(for_each_statement (identifier) @variable)
(expression_statement (identifier) @variable)
-(member_access_expression expression: (identifier) @variable)
-(member_access_expression name: (identifier) @variable)
-(conditional_access_expression [(identifier) (qualified_name)] @variable)
+(array_rank_specifier (identifier) @variable)
+(equals_value_clause (identifier) @variable)
+(interpolation (identifier) @variable)
+(cast_expression (identifier) @variable)
((identifier) @comment.unused
- (#eq? @comment.unused "_"))
+ (#eq? @comment.unused "_"))
diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm
index 647df05d..8122216d 100644
--- a/runtime/queries/c/highlights.scm
+++ b/runtime/queries/c/highlights.scm
@@ -1,71 +1,111 @@
-(storage_class_specifier) @keyword.storage
-
-"goto" @keyword
-"register" @keyword
-"break" @keyword
-"case" @keyword
-"continue" @keyword
-"default" @keyword
-"do" @keyword
-"else" @keyword
-"enum" @keyword
-"extern" @keyword
-"for" @keyword
-"if" @keyword
-"inline" @keyword
-"return" @keyword
"sizeof" @keyword
-"struct" @keyword
-"switch" @keyword
-"typedef" @keyword
-"union" @keyword
-"volatile" @keyword
-"while" @keyword
-"const" @keyword
[
- "#define"
- "#elif"
- "#else"
- "#endif"
- "#if"
- "#ifdef"
- "#ifndef"
- "#include"
- (preproc_directive)
+ "enum"
+ "struct"
+ "typedef"
+ "union"
+] @keyword.storage.type
+
+[
+ "extern"
+ "register"
+ (type_qualifier)
+ (storage_class_specifier)
+] @keyword.storage.modifier
+
+[
+ "goto"
+ "break"
+ "continue"
+] @keyword.control
+
+[
+ "do"
+ "for"
+ "while"
+] @keyword.control.repeat
+
+[
+ "if"
+ "else"
+ "switch"
+ "case"
+ "default"
+] @keyword.control.conditional
+
+"return" @keyword.control.return
+
+[
+ "defined"
+ "#define"
+ "#elif"
+ "#else"
+ "#endif"
+ "#if"
+ "#ifdef"
+ "#ifndef"
+ "#include"
+ (preproc_directive)
] @keyword.directive
-"--" @operator
-"-" @operator
-"-=" @operator
-"->" @operator
-"=" @operator
-"!=" @operator
-"*" @operator
-"&" @operator
-"&&" @operator
-"+" @operator
-"++" @operator
-"+=" @operator
-"<" @operator
-"==" @operator
-">" @operator
-"||" @operator
-">=" @operator
-"<=" @operator
-
-"." @punctuation.delimiter
-";" @punctuation.delimiter
+(pointer_declarator "*" @type.builtin)
+(abstract_pointer_declarator "*" @type.builtin)
+
+[
+ "+"
+ "-"
+ "*"
+ "/"
+ "++"
+ "--"
+ "%"
+ "=="
+ "!="
+ ">"
+ "<"
+ ">="
+ "<="
+ "&&"
+ "||"
+ "!"
+ "&"
+ "|"
+ "^"
+ "~"
+ "<<"
+ ">>"
+ "="
+ "+="
+ "-="
+ "*="
+ "/="
+ "%="
+ "<<="
+ ">>="
+ "&="
+ "^="
+ "|="
+ "?"
+] @operator
+
+(conditional_expression ":" @operator)
+
+"..." @punctuation
+
+["," "." ":" ";" "->" "::"] @punctuation.delimiter
+
+["(" ")" "[" "]" "{" "}"] @punctuation.bracket
[(true) (false)] @constant.builtin.boolean
-(enumerator) @type.enum.variant
+(enumerator name: (identifier) @type.enum.variant)
(string_literal) @string
(system_lib_string) @string
(null) @constant
-(number_literal) @constant.numeric.integer
+(number_literal) @constant.numeric
(char_literal) @constant.character
(call_expression
@@ -73,19 +113,28 @@
(call_expression
function: (field_expression
field: (field_identifier) @function))
+(call_expression (argument_list (identifier) @variable))
(function_declarator
- declarator: (identifier) @function)
+ declarator: [(identifier) (field_identifier)] @function)
+(parameter_declaration
+ declarator: (identifier) @variable.parameter)
+(parameter_declaration
+ (pointer_declarator
+ declarator: (identifier) @variable.parameter))
(preproc_function_def
name: (identifier) @function.special)
+(attribute
+ name: (identifier) @attribute)
+
(field_identifier) @variable.other.member
(statement_identifier) @label
(type_identifier) @type
-(primitive_type) @type
-(sized_type_specifier) @type
+(primitive_type) @type.builtin
+(sized_type_specifier) @type.builtin
((identifier) @constant
- (#match? @constant "^[A-Z][A-Z\\d_]*$"))
+ (#match? @constant "^[A-Z][A-Z\\d_]*$"))
(identifier) @variable
diff --git a/runtime/queries/cairo/highlights.scm b/runtime/queries/cairo/highlights.scm
index 3a30d188..c10ce5e0 100644
--- a/runtime/queries/cairo/highlights.scm
+++ b/runtime/queries/cairo/highlights.scm
@@ -36,7 +36,6 @@
[
"if"
"else"
- "end"
"assert"
"with"
"with_attr"
@@ -54,7 +53,6 @@
"const"
"local"
"struct"
- "member"
"alloc_locals"
"tempvar"
] @keyword
diff --git a/runtime/queries/cpp/highlights.scm b/runtime/queries/cpp/highlights.scm
index 3348ef3c..8637489e 100644
--- a/runtime/queries/cpp/highlights.scm
+++ b/runtime/queries/cpp/highlights.scm
@@ -1,7 +1,11 @@
-; inherits: c
-
; Functions
+; These casts are parsed as function calls, but are not.
+((identifier) @keyword (#eq? @keyword "static_cast"))
+((identifier) @keyword (#eq? @keyword "dynamic_cast"))
+((identifier) @keyword (#eq? @keyword "reinterpret_cast"))
+((identifier) @keyword (#eq? @keyword "const_cast"))
+
(call_expression
function: (qualified_identifier
name: (identifier) @function))
@@ -12,56 +16,114 @@
(template_method
name: (field_identifier) @function)
-(template_function
- name: (identifier) @function)
-
(function_declarator
declarator: (qualified_identifier
name: (identifier) @function))
(function_declarator
declarator: (qualified_identifier
- name: (identifier) @function))
+ name: (qualified_identifier
+ name: (identifier) @function)))
(function_declarator
declarator: (field_identifier) @function)
; Types
-((namespace_identifier) @type
- (#match? @type "^[A-Z]"))
+(using_declaration ("using" "namespace" (identifier) @namespace))
+(using_declaration ("using" "namespace" (qualified_identifier name: (identifier) @namespace)))
+(namespace_definition name: (identifier) @namespace)
+(namespace_identifier) @namespace
+
+(qualified_identifier name: (identifier) @type.enum.variant)
(auto) @type
+"decltype" @type
+
+(ref_qualifier ["&" "&&"] @type.builtin)
+(reference_declarator ["&" "&&"] @type.builtin)
+(abstract_reference_declarator ["&" "&&"] @type.builtin)
; Constants
(this) @variable.builtin
-(nullptr) @constant
+(nullptr) @constant.builtin
; Keywords
-"catch" @keyword
-"class" @keyword
-"constexpr" @keyword
-"delete" @keyword
-"explicit" @keyword
-"final" @keyword
-"friend" @keyword
-"mutable" @keyword
-"namespace" @keyword
-"noexcept" @keyword
-"new" @keyword
-"override" @keyword
-"private" @keyword
-"protected" @keyword
-"public" @keyword
-"template" @keyword
-"throw" @keyword
-"try" @keyword
-"typename" @keyword
-"using" @keyword
-"virtual" @keyword
+(template_argument_list (["<" ">"] @punctuation.bracket))
+(template_parameter_list (["<" ">"] @punctuation.bracket))
+(default_method_clause "default" @keyword)
+
+"static_assert" @function.special
+
+[
+ "<=>"
+ "[]"
+ "()"
+] @operator
+
+[
+ "co_await"
+ "co_return"
+ "co_yield"
+ "concept"
+ "delete"
+ "new"
+ "operator"
+ "requires"
+ "using"
+] @keyword
+
+[
+ "catch"
+ "noexcept"
+ "throw"
+ "try"
+] @keyword.control.exception
+
+
+[
+ "and"
+ "and_eq"
+ "bitor"
+ "bitand"
+ "not"
+ "not_eq"
+ "or"
+ "or_eq"
+ "xor"
+ "xor_eq"
+] @keyword.operator
+
+[
+ "class"
+ "namespace"
+ "typename"
+ "template"
+] @keyword.storage.type
+
+[
+ "constexpr"
+ "constinit"
+ "consteval"
+ "mutable"
+] @keyword.storage.modifier
+
+; Modifiers that aren't plausibly type/storage related.
+[
+ "explicit"
+ "friend"
+ "virtual"
+ (virtual_specifier) ; override/final
+ "private"
+ "protected"
+ "public"
+ "inline" ; C++ meaning differs from C!
+] @keyword
; Strings
(raw_string_literal) @string
+
+; inherits: c
diff --git a/runtime/queries/lua/folds.scm b/runtime/queries/lua/folds.scm
new file mode 100644
index 00000000..d8f0b42d
--- /dev/null
+++ b/runtime/queries/lua/folds.scm
@@ -0,0 +1,10 @@
+[
+ (do_statement)
+ (while_statement)
+ (repeat_statement)
+ (if_statement)
+ (for_statement)
+ (function_declaration)
+ (function_definition)
+ (table_constructor)
+] @fold
diff --git a/runtime/queries/lua/highlights.scm b/runtime/queries/lua/highlights.scm
index e73b32d6..f48e607c 100644
--- a/runtime/queries/lua/highlights.scm
+++ b/runtime/queries/lua/highlights.scm
@@ -1,7 +1,8 @@
;;; Highlighting for lua
;;; Builtins
-(self) @variable.builtin
+((identifier) @variable.builtin
+ (#eq? @variable.builtin "self"))
;; Keywords
@@ -12,20 +13,20 @@
"end"
] @keyword.control.conditional)
+(elseif_statement
[
- "else"
"elseif"
"then"
-] @keyword.control.conditional
+ "end"
+] @keyword.control.conditional)
-(for_statement
+(else_statement
[
- "for"
- "do"
+ "else"
"end"
-] @keyword.control.repeat)
+] @keyword.control.conditional)
-(for_in_statement
+(for_statement
[
"for"
"do"
@@ -51,21 +52,34 @@
"end"
] @keyword)
+"return" @keyword.control.return
+
[
"in"
"local"
(break_statement)
"goto"
- "return"
] @keyword
+(function_declaration
+[
+ "function"
+ "end"
+] @keyword.function)
+
+(function_definition
+[
+ "function"
+ "end"
+] @keyword.function)
+
;; Operators
[
"not"
"and"
"or"
-] @operator
+] @keyword.operator
[
"="
@@ -95,6 +109,7 @@
["," "." ":" ";"] @punctuation.delimiter
;; Brackets
+
[
"("
")"
@@ -110,7 +125,8 @@
(true)
] @constant.builtin.boolean
(nil) @constant.builtin
-(spread) @constant ;; "..."
+(vararg_expression) @constant
+
((identifier) @constant
(#match? @constant "^[A-Z][A-Z_0-9]*$"))
@@ -119,45 +135,32 @@
(identifier) @variable.parameter)
; ;; Functions
-(function [(function_name) (identifier)] @function)
-(function ["function" "end"] @keyword.function)
-
-(function
- (function_name
- (function_name_field
- (property_identifier) @function .)))
-
-(local_function (identifier) @function)
-(local_function ["function" "end"] @keyword.function)
+(function_declaration name: (identifier) @function)
+(function_call name: (identifier) @function.call)
-(variable_declaration
- (variable_declarator (identifier) @function) (function_definition))
-(local_variable_declaration
- (variable_declarator (identifier) @function) (function_definition))
+(function_declaration name: (dot_index_expression field: (identifier) @function))
+(function_call name: (dot_index_expression field: (identifier) @function.call))
-(function_definition ["function" "end"] @keyword.function)
+; TODO: incorrectly highlights variable N in `N, nop = 42, function() end`
+(assignment_statement
+ (variable_list
+ name: (identifier) @function)
+ (expression_list
+ value: (function_definition)))
-(function_call
- [
- ((identifier) @variable (method) @function.method)
- ((_) (method) @function.method)
- (identifier) @function
- (field_expression (property_identifier) @function)
- ]
- . (arguments))
+(method_index_expression method: (identifier) @function.method)
;; Nodes
-(table ["{" "}"] @constructor)
(comment) @comment
(string) @string
(number) @constant.numeric.integer
(label_statement) @label
; A bit of a tricky one, this will only match field names
(field . (identifier) @variable.other.member (_))
-(shebang) @comment
+(hash_bang_line) @comment
;; Property
-(property_identifier) @variable.other.member
+(dot_index_expression field: (identifier) @variable.other.member)
;; Variable
(identifier) @variable
diff --git a/runtime/queries/lua/indents.scm b/runtime/queries/lua/indents.scm
index 55a812c5..2e5a108e 100644
--- a/runtime/queries/lua/indents.scm
+++ b/runtime/queries/lua/indents.scm
@@ -1,17 +1,13 @@
[
(function_definition)
- (variable_declaration)
- (local_variable_declaration)
+ (function_declaration)
+ (method_index_expression)
(field)
- (local_function)
- (function)
(if_statement)
(for_statement)
- (for_in_statement)
(repeat_statement)
- (return_statement)
(while_statement)
- (table)
+ (table_constructor)
(arguments)
(do_statement)
] @indent
diff --git a/runtime/queries/lua/injections.scm b/runtime/queries/lua/injections.scm
index 321c90ad..fca94d58 100644
--- a/runtime/queries/lua/injections.scm
+++ b/runtime/queries/lua/injections.scm
@@ -1,2 +1,3 @@
((comment) @injection.content
- (#set! injection.language "comment"))
+ (#set! injection.language "comment")
+ (#set! injection.include-children))
diff --git a/runtime/queries/lua/textobjects.scm b/runtime/queries/lua/textobjects.scm
new file mode 100644
index 00000000..6fb2000d
--- /dev/null
+++ b/runtime/queries/lua/textobjects.scm
@@ -0,0 +1,15 @@
+(function_definition
+ body: (_) @function.inside) @function.around
+
+(function_declaration
+ body: (_) @function.inside) @function.around
+
+(parameters
+ ((_) @parameter.inside . ","? @parameter.around) @parameter.around)
+
+(arguments
+ ((_) @parameter.inside . ","? @parameter.around) @parameter.around)
+
+(comment) @comment.inside
+
+(comment)+ @comment.around
diff --git a/runtime/queries/markdown/injections.scm b/runtime/queries/markdown/injections.scm
index f94b9f98..e184db15 100644
--- a/runtime/queries/markdown/injections.scm
+++ b/runtime/queries/markdown/injections.scm
@@ -7,6 +7,8 @@
((html_block) @injection.content (#set! injection.language "html") (#set! injection.include-unnamed-children))
+((pipe_table_cell) @injection.content (#set! injection.language "markdown.inline") (#set! injection.include-unnamed-children))
+
((minus_metadata) @injection.content (#set! injection.language "yaml") (#set! injection.include-unnamed-children))
((plus_metadata) @injection.content (#set! injection.language "toml") (#set! injection.include-unnamed-children))
diff --git a/runtime/queries/purescript/highlights.scm b/runtime/queries/purescript/highlights.scm
new file mode 100644
index 00000000..ef073eb2
--- /dev/null
+++ b/runtime/queries/purescript/highlights.scm
@@ -0,0 +1 @@
+; inherits: haskell
diff --git a/runtime/queries/purescript/injections.scm b/runtime/queries/purescript/injections.scm
new file mode 100644
index 00000000..ef073eb2
--- /dev/null
+++ b/runtime/queries/purescript/injections.scm
@@ -0,0 +1 @@
+; inherits: haskell
diff --git a/runtime/queries/purescript/locals.scm b/runtime/queries/purescript/locals.scm
new file mode 100644
index 00000000..ef073eb2
--- /dev/null
+++ b/runtime/queries/purescript/locals.scm
@@ -0,0 +1 @@
+; inherits: haskell
diff --git a/runtime/queries/python/indents.scm_ b/runtime/queries/python/indents.scm
index 810ff52f..b7b499c0 100644
--- a/runtime/queries/python/indents.scm_
+++ b/runtime/queries/python/indents.scm
@@ -28,11 +28,31 @@
] @indent
[
+ (if_statement)
+ (for_statement)
+ (while_statement)
+ (with_statement)
+ (try_statement)
+
+ (function_definition)
+ (class_definition)
+] @extend
+
+[
+ (return_statement)
+ (break_statement)
+ (continue_statement)
+ (raise_statement)
+ (pass_statement)
+] @extend.prevent-once
+
+[
")"
"]"
"}"
- (return_statement)
- (pass_statement)
- (raise_statement)
] @outdent
+(elif_clause
+ "elif" @outdent)
+(else_clause
+ "else" @outdent)
diff --git a/runtime/queries/rust/highlights.scm b/runtime/queries/rust/highlights.scm
index 7d0afcde..81f05f7a 100644
--- a/runtime/queries/rust/highlights.scm
+++ b/runtime/queries/rust/highlights.scm
@@ -253,6 +253,9 @@
(function_item
name: (identifier) @function)
+(function_signature_item
+ name: (identifier) @function)
+
; ---
; Macros
; ---
diff --git a/runtime/queries/twig/highlights.scm b/runtime/queries/twig/highlights.scm
index 2c95ab63..1ef8d06b 100644
--- a/runtime/queries/twig/highlights.scm
+++ b/runtime/queries/twig/highlights.scm
@@ -1,16 +1,60 @@
-(comment_directive) @comment
+(comment) @comment
+
+(filter_identifier) @function.method
+(function_identifier) @function.method
+(test) @function.builtin
+(variable) @variable
+(string) @string
+(interpolated_string) @string
+(operator) @operator
+(number) @constant.numeric.integer
+(boolean) @constant.builtin.boolean
+(null) @constant.builtin
+(keyword) @keyword
+(attribute) @attribute
+(tag) @tag
+(conditional) @keyword.control.conditional
+(repeat) @keyword.control.repeat
+(method) @function.method
+(parameter) @variable.parameter
[
- "{%"
- "{%-"
- "{%~"
- "%}"
- "-%}"
- "~%}"
- "{{"
- "{{-"
- "{{~"
- "}}"
- "-}}"
- "~}}"
+ "{{"
+ "}}"
+ "{{-"
+ "-}}"
+ "{{~"
+ "~}}"
+ "{%"
+ "%}"
+ "{%-"
+ "-%}"
+ "{%~"
+ "~%}"
] @keyword
+
+[
+ ","
+ "."
+ "?"
+ ":"
+ "="
+] @punctuation.delimiter
+
+(interpolated_string [
+ "#{"
+ "}"
+] @punctuation.delimiter)
+
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+] @punctuation.bracket
+
+(hash [
+ "}"
+] @punctuation.bracket)
+
diff --git a/runtime/queries/wgsl/highlights.scm b/runtime/queries/wgsl/highlights.scm
index baf9dd8f..2817ea3a 100644
--- a/runtime/queries/wgsl/highlights.scm
+++ b/runtime/queries/wgsl/highlights.scm
@@ -2,57 +2,26 @@
(float_literal) @constant.numeric.float
(bool_literal) @constant.builtin.boolean
-(global_constant_declaration) @variable
-(global_variable_declaration) @variable
-(compound_statement) @variable
-(const_expression) @function
-
-(variable_identifier_declaration
- (identifier) @variable
- (type_declaration) @type)
-
-(function_declaration
- (identifier) @function
- (function_return_type_declaration
- (type_declaration) @type))
-
-(parameter
- (variable_identifier_declaration
- (identifier) @variable.parameter
- (type_declaration) @type))
-
-(struct_declaration
- (identifier) @type)
-
-(struct_declaration
- (struct_member
- (variable_identifier_declaration
- (identifier) @variable.other.member
- (type_declaration) @type)))
+[
+ "bitcast"
+ "discard"
+ "enable"
+ "fallthrough"
+] @keyword
-(type_constructor_or_function_call_expression
- (type_declaration) @function)
+[
+ "let"
+ "override"
+ "struct"
+ "type"
+ "var"
+ (texel_format)
+] @keyword.storage.type
[
- "struct"
- "bitcast"
- "discard"
- "enable"
- "fallthrough"
- "fn"
- "let"
- "private"
- "read"
- "read_write"
- "storage"
- "type"
- "uniform"
- "var"
- "workgroup"
- "write"
- "override"
- (texel_format)
-] @keyword
+ (access_mode)
+ (address_space)
+] @keyword.storage.modifier
"fn" @keyword.function
@@ -62,53 +31,87 @@
["(" ")" "[" "]" "{" "}"] @punctuation.bracket
+(type_declaration ["<" ">"] @punctuation.bracket)
+
[
- "loop"
- "for"
- "while"
- "break"
- "continue"
- "continuing"
+ "break"
+ "continue"
+ "continuing"
+] @keyword.control
+
+[
+ "loop"
+ "for"
+ "while"
] @keyword.control.repeat
[
- "if"
- "else"
- "switch"
- "case"
- "default"
+ "if"
+ "else"
+ "switch"
+ "case"
+ "default"
] @keyword.control.conditional
[
- "&"
- "&&"
- "/"
- "!"
- "="
- "=="
- "!="
- ">"
- ">="
- ">>"
- "<"
- "<="
- "<<"
- "%"
- "-"
- "+"
- "|"
- "||"
- "*"
- "~"
- "^"
- "@"
- "++"
- "--"
+ "!"
+ "!="
+ "%"
+ "%="
+ "&"
+ "&&"
+ "&="
+ "*"
+ "*="
+ "+"
+ "++"
+ "+="
+ "-"
+ "--"
+ "-="
+ "->"
+ "/"
+ "/="
+ "<"
+ "<<"
+ "<="
+ "="
+ "=="
+ ">"
+ ">="
+ ">>"
+ "@"
+ "^"
+ "^="
+ "|"
+ "|="
+ "||"
+ "~"
] @operator
+(function_declaration
+ (identifier) @function)
+
+(parameter
+ (variable_identifier_declaration
+ (identifier) @variable.parameter))
+
+(struct_declaration
+ (identifier) @type)
+
+(struct_declaration
+ (struct_member
+ (variable_identifier_declaration
+ (identifier) @variable.other.member)))
+
+(type_constructor_or_function_call_expression
+ (type_declaration (identifier) @function))
+
+(type_declaration _ @type)
+
(attribute
- (identifier) @attribute)
+ (identifier) @attribute)
-(comment) @comment
+(identifier) @variable
-(ERROR) @error
+(comment) @comment
diff --git a/runtime/queries/zig/indents.scm b/runtime/queries/zig/indents.scm
index af25a9c3..b177b165 100644
--- a/runtime/queries/zig/indents.scm
+++ b/runtime/queries/zig/indents.scm
@@ -1,12 +1,14 @@
[
+ (AsmExpr)
+ (AssignExpr)
(Block)
(BlockExpr)
(ContainerDecl)
- (SwitchExpr)
- (AssignExpr)
(ErrorUnionExpr)
- (Statement)
(InitList)
+ (Statement)
+ (SwitchExpr)
+ (TestDecl)
] @indent
[
diff --git a/runtime/themes/ayu_light.toml b/runtime/themes/ayu_light.toml
index 7ea4ef58..80943613 100644
--- a/runtime/themes/ayu_light.toml
+++ b/runtime/themes/ayu_light.toml
@@ -34,23 +34,22 @@
# Interface
"ui.background"= { bg = "background" }
-"ui.cursor" = { bg = "yellow", fg = "dark_gray" }
+"ui.cursor" = { bg = "yellow", fg = "light_gray" }
"ui.cursor.match" = { fg = "orange" }
-"ui.linenr" = { fg = "dark_gray" }
+"ui.linenr" = { fg = "light_gray" }
"ui.linenr.selected" = { fg = "orange" }
"ui.cursorline" = { bg = "black" }
-"ui.statusline" = { fg = "foreground", bg = "black" }
"ui.popup" = { bg = "black" }
-"ui.window" = { fg = "dark_gray" }
+"ui.window" = { fg = "light_gray" }
"ui.help" = { fg = "foreground", bg = "black" }
"ui.text" = { fg = "foreground" }
-"ui.text.focus" = { bg = "dark_gray", fg = "foreground" }
+"ui.text.focus" = { bg = "light_gray", fg = "foreground" }
"ui.text.info" = { fg = "foreground" }
-"ui.virtual.whitespace" = { fg = "dark_gray" }
+"ui.virtual.whitespace" = { fg = "light_gray" }
"ui.virtual.ruler" = { bg = "black" }
"ui.menu" = { fg = "foreground", bg = "black" }
"ui.menu.selected" = { bg = "orange", fg = "background" }
-"ui.selection" = { bg = "dark_gray" }
+"ui.selection" = { bg = "light_gray" }
"warning" = { fg = "yellow" }
"error" = { fg = "red", modifiers = ["bold"] }
"info" = { fg = "blue", modifiers = ["bold"] }
@@ -59,8 +58,13 @@
"diagnostic.info"= { fg = "blue", modifiers = ["underlined"] }
"diagnostic.warning"= { fg = "yellow", modifiers = ["underlined"] }
"diagnostic.error"= { fg = "red", modifiers = ["underlined"] }
-"ui.bufferline" = { fg = "gray", bg = "dark_gray" }
-"ui.bufferline.active" = { fg = "dark", bg = "background" }
+"ui.bufferline" = { fg = "ui_foreground", bg = "ui_background" }
+"ui.bufferline.active" = { fg = "ui_background", bg = "ui_foreground" }
+"ui.statusline" = { fg = "ui_foreground", bg = "ui_background" }
+"ui.statusline.inactive" = { fg = "ui_foreground", bg = "ui_background" }
+"ui.statusline.normal" = { fg = "white", bg = "light_blue" }
+"ui.statusline.insert" = { fg = "white", bg = "orange" }
+"ui.statusline.select" = { fg = "white", bg = "magenta" }
"special" = { fg = "orange" }
@@ -68,14 +72,18 @@
background = "#fcfcfc"
foreground = "#5c6166"
+ui_foreground = "#8a9199"
+ui_background = "#f8f9fa"
+
black = "#e7eaed"
+white = "#fcfcfc"
blue = "#399ee6"
+light_blue = "#55b4d4"
cyan = "#478acc"
-dark_gray = "#e7eaed"
+light_gray = "#e7eaed"
gray = "#787b8099"
green = "#86b300"
magenta = "#a37acc"
orange = "#fa8d3e"
red = "#f07171"
yellow = "#ffaa33"
-dark = "#131721"
diff --git a/runtime/themes/ayu_mirage.toml b/runtime/themes/ayu_mirage.toml
index f3b49d87..e01866a9 100644
--- a/runtime/themes/ayu_mirage.toml
+++ b/runtime/themes/ayu_mirage.toml
@@ -34,7 +34,8 @@
# Interface
"ui.background"= { bg = "background" }
-"ui.cursor" = { bg = "yellow", fg = "dark_gray" }
+"ui.cursor" = { bg = "green", fg = "dark_gray" }
+"ui.cursor.primary" = { bg = "orange", fg = "dark_gray" }
"ui.cursor.match" = { fg = "orange" }
"ui.linenr" = { fg = "dark_gray" }
"ui.linenr.selected" = { fg = "orange" }
diff --git a/runtime/themes/bogster.toml b/runtime/themes/bogster.toml
index c1902b9b..eb6c4c50 100644
--- a/runtime/themes/bogster.toml
+++ b/runtime/themes/bogster.toml
@@ -1,76 +1,96 @@
# Author : Wojciech Kępka <wojciech@wkepka.dev>
-"attribute" = "#dc7759"
-"keyword" = { fg = "#dcb659", modifiers = ["bold"] }
-"keyword.directive" = "#dcb659"
-"namespace" = "#d32c5d"
-"punctuation" = "#dc7759"
-"punctuation.delimiter" = "#dc7759"
-"operator" = { fg = "#dc7759", modifiers = ["bold"] }
-"special" = "#7fdc59"
-"variable.other.member" = "#c6b8ad"
-"variable" = "#c6b8ad"
-"variable.parameter" = "#c6b8ad"
-"type" = "#dc597f"
-"type.builtin" = { fg = "#d32c5d", modifiers = ["bold"] }
-"constructor" = "#dc597f"
-"function" = "#59dcd8"
-"function.macro" = { fg = "#dc7759", modifiers = ["bold"] }
-"function.builtin" = { fg = "#59dcd8", modifiers = ["bold"] }
-"comment" = "#627d9d"
-"variable.builtin" = "#c6b8ad"
-"constant" = "#59dcb7"
-"constant.builtin" = "#59dcb7"
-"string" = "#59dcb7"
-"constant.numeric" = "#59c0dc"
-"constant.character.escape" = { fg = "#7fdc59", modifiers = ["bold"] }
-"label" = "#59c0dc"
+"attribute" = "bogster-orange"
+"keyword" = { fg = "bogster-yellow", modifiers = ["bold"] }
+"keyword.directive" = "bogster-yellow"
+"namespace" = "bogster-red"
+"punctuation" = "bogster-orange"
+"punctuation.delimiter" = "bogster-orange"
+"operator" = { fg = "bogster-orange", modifiers = ["bold"] }
+"special" = "bogster-lgreen"
+"variable.other.member" = "bogster-fg0"
+"variable" = "bogster-fg0"
+"variable.parameter" = "bogster-fg0"
+"type" = "bogster-lred"
+"type.builtin" = { fg = "bogster-red", modifiers = ["bold"] }
+"constructor" = "bogster-lred"
+"function" = "bogster-lblue"
+"function.macro" = { fg = "bogster-orange", modifiers = ["bold"] }
+"function.builtin" = { fg = "bogster-lblue", modifiers = ["bold"] }
+"comment" = "bogster-base5"
+"variable.builtin" = "bogster-fg0"
+"constant" = "bogster-teal"
+"constant.builtin" = "bogster-teal"
+"string" = "bogster-teal"
+"constant.numeric" = "bogster-blue"
+"constant.character.escape" = { fg = "bogster-lgreen", modifiers = ["bold"] }
+"label" = "bogster-blue"
+"module" = "bogster-red"
-"module" = "#d32c5d"
+"markup.heading" = "bogster-blue"
+"markup.list" = "bogster-red"
+"markup.bold" = { fg = "bogster-yellow", modifiers = ["bold"] }
+"markup.italic" = { fg = "bogster-purp", modifiers = ["italic"] }
+"markup.link.url" = { fg = "bogster-yellow", modifiers = ["underlined"] }
+"markup.link.text" = "bogster-red"
+"markup.quote" = "bogster-teal"
+"markup.raw" = "bogster-lgreen"
-# TODO
-"markup.heading" = "blue"
-"markup.list" = "red"
-"markup.bold" = { fg = "yellow", modifiers = ["bold"] }
-"markup.italic" = { fg = "magenta", modifiers = ["italic"] }
-"markup.link.url" = { fg = "yellow", modifiers = ["underlined"] }
-"markup.link.text" = "red"
-"markup.quote" = "cyan"
-"markup.raw" = "green"
+"diff.plus" = "bogster-teal"
+"diff.delta" = "bogster-orange"
+"diff.minus" = "bogster-lred"
-"diff.plus" = "#59dcb7"
-"diff.delta" = "#dc7759"
-"diff.minus" = "#dc597f"
+"ui.background" = { bg = "bogster-base1" }
+"ui.linenr" = { fg = "bogster-base4" }
+"ui.linenr.selected" = { fg = "bogster-fg1" }
+"ui.cursorline" = { bg = "bogster-base0" }
+"ui.statusline" = { fg = "bogster-fg1", bg = "bogster-base2" }
+"ui.statusline.inactive" = { fg = "bogster-fg0", bg = "bogster-base2" }
+"ui.popup" = { bg = "bogster-base2" }
+"ui.window" = { bg = "bogster-base2" }
+"ui.help" = { bg = "bogster-base2", fg = "bogster-fg1" }
-"ui.background" = { bg = "#161c23" }
-"ui.linenr" = { fg = "#415367" }
-"ui.linenr.selected" = { fg = "#e5ded6" } # TODO
-"ui.cursorline" = { bg = "#131920" }
-"ui.statusline" = { fg = "#e5ded6", bg = "#232d38" }
-"ui.statusline.inactive" = { fg = "#c6b8ad", bg = "#232d38" }
-"ui.bufferline" = { fg = "#627d9d", bg = "#131920" }
-"ui.bufferline.active" = { fg = "#e5ded6", bg = "#232d38" }
-"ui.popup" = { bg = "#232d38" }
-"ui.window" = { bg = "#232d38" }
-"ui.help" = { bg = "#232d38", fg = "#e5ded6" }
+"ui.statusline.normal" = { fg = "bogster-base1", bg = "bogster-blue", modifiers = [ "bold" ]}
+"ui.statusline.insert" = { fg = "bogster-base1", bg = "bogster-lgreen", modifiers = [ "bold" ]}
+"ui.statusline.select" = { fg = "bogster-base1", bg = "bogster-red", modifiers = [ "bold" ] }
-"ui.text" = { fg = "#e5ded6" }
-"ui.text.focus" = { fg = "#e5ded6", modifiers= ["bold"] }
-"ui.virtual.whitespace" = "#627d9d"
-"ui.virtual.ruler" = { bg = "#131920" }
+"ui.text" = { fg = "bogster-fg1" }
+"ui.text.focus" = { fg = "bogster-fg1", modifiers= ["bold"] }
+"ui.virtual.whitespace" = "bogster-base5"
+"ui.virtual.ruler" = { bg = "bogster-base0" }
-"ui.selection" = { bg = "#313f4e" }
-# "ui.cursor.match" # TODO might want to override this because dimmed is not widely supported
-"ui.cursor.match" = { fg = "#313f4e", bg = "#dc7759" }
-"ui.cursor" = { fg = "#ABB2BF", modifiers = ["reversed"] }
+"ui.selection" = { bg = "bogster-base3" }
+"ui.cursor.match" = { fg = "bogster-base3", bg = "bogster-orange" }
+"ui.cursor" = { fg = "bogster-base5", modifiers = ["reversed"] }
-"ui.menu" = { fg = "#e5ded6bg", bg = "#232d38" }
-"ui.menu.selected" = { bg = "#313f4e" }
+"ui.menu" = { fg = "bogster-fg1", bg = "bogster-base2" }
+"ui.menu.selected" = { bg = "bogster-base3" }
-"warning" = "#dc7759"
-"error" = "#dc597f"
-"info" = "#59dcb7"
-"hint" = "#59c0dc"
+"warning" = "bogster-orange"
+"error" = "bogster-lred"
+"info" = "bogster-teal"
+"hint" = "bogster-blue"
# make diagnostic underlined, to distinguish with selection text.
diagnostic = { modifiers = ["underlined"] }
+
+[palette]
+bogster-yellow = "#dcb659"
+bogster-lblue = "#59dcd8"
+bogster-teal = "#59dcb7"
+bogster-blue = "#36b2d4"
+bogster-orange = "#dc7759"
+bogster-red = "#d32c5d"
+bogster-lgreen = "#7fdc59"
+bogster-lred = "#dc597f"
+bogster-purp = "#b759dc"
+
+bogster-base0 = "#13181e"
+bogster-base1 = "#161c23"
+bogster-base2 = "#232d38"
+bogster-base3 = "#313f4e"
+bogster-base4 = "#415367"
+bogster-base5 = "#abb2bf"
+
+bogster-fg0 = "#c6b8ad"
+bogster-fg1 = "#e5ded6"
diff --git a/runtime/themes/bogster_light.toml b/runtime/themes/bogster_light.toml
new file mode 100644
index 00000000..25aa4c80
--- /dev/null
+++ b/runtime/themes/bogster_light.toml
@@ -0,0 +1,97 @@
+# Author : Wojciech Kępka <wojciech@wkepka.dev>
+
+"attribute" = "bogster-orange"
+"keyword" = { fg = "bogster-yellow", modifiers = ["bold"] }
+"keyword.directive" = "bogster-yellow"
+"namespace" = "bogster-red"
+"punctuation" = "bogster-orange"
+"punctuation.delimiter" = "bogster-orange"
+"operator" = { fg = "bogster-orange", modifiers = ["bold"] }
+"special" = "bogster-lgreen"
+"variable.other.member" = "bogster-fg0"
+"variable" = "bogster-fg0"
+"variable.parameter" = "bogster-fg0"
+"type" = "bogster-lred"
+"type.builtin" = { fg = "bogster-red", modifiers = ["bold"] }
+"constructor" = "bogster-lred"
+"function" = "bogster-lblue"
+"function.macro" = { fg = "bogster-orange", modifiers = ["bold"] }
+"function.builtin" = { fg = "bogster-lblue", modifiers = ["bold"] }
+"comment" = "bogster-base5"
+"variable.builtin" = "bogster-fg0"
+"constant" = "bogster-teal"
+"constant.builtin" = "bogster-teal"
+"string" = "bogster-teal"
+"constant.numeric" = "bogster-blue"
+"constant.character.escape" = { fg = "bogster-lgreen", modifiers = ["bold"] }
+"label" = "bogster-blue"
+"module" = "bogster-red"
+
+"markup.heading" = "bogster-blue"
+"markup.list" = "bogster-red"
+"markup.bold" = { fg = "bogster-yellow", modifiers = ["bold"] }
+"markup.italic" = { fg = "magenta", modifiers = ["italic"] }
+"markup.link.url" = { fg = "bogster-yellow", modifiers = ["underlined"] }
+"markup.link.text" = "bogster-red"
+"markup.quote" = "bogster-lblue"
+"markup.raw" = "bogster-teal"
+
+"diff.plus" = "bogster-teal"
+"diff.delta" = "bogster-orange"
+"diff.minus" = "bogster-lred"
+
+"ui.background" = { bg = "bogster-base0" }
+"ui.linenr" = { fg = "bogster-base3" }
+"ui.linenr.selected" = { fg = "bogster-fg1" }
+"ui.cursorline" = { bg = "bogster-base00" }
+"ui.statusline" = { fg = "bogster-fg1", bg = "bogster-base1" }
+"ui.statusline.inactive" = { fg = "bogster-fg0", bg = "bogster-base1" }
+"ui.popup" = { bg = "bogster-base1" }
+"ui.window" = { bg = "bogster-base1" }
+"ui.help" = { bg = "bogster-base1", fg = "bogster-fg1" }
+
+"ui.statusline.normal" = { fg = "bogster-base0", bg = "bogster-blue", modifiers = [ "bold" ]}
+"ui.statusline.insert" = { fg = "bogster-base0", bg = "bogster-lgreen", modifiers = [ "bold" ]}
+"ui.statusline.select" = { fg = "bogster-base0", bg = "bogster-red", modifiers = [ "bold" ] }
+
+"ui.text" = { fg = "bogster-fg1" }
+"ui.text.focus" = { fg = "bogster-fg1", modifiers= ["bold"] }
+"ui.virtual.whitespace" = "bogster-base5"
+"ui.virtual.ruler" = { bg = "bogster-base00" }
+
+"ui.selection" = { bg = "bogster-base1" }
+"ui.cursor.match" = { fg = "bogster-base2", bg = "bogster-orange" }
+"ui.cursor" = { fg = "bogster-gray", modifiers = ["reversed"] }
+
+"ui.menu" = { fg = "bogster-fg1", bg = "bogster-base1" }
+"ui.menu.selected" = { bg = "bogster-base2" }
+
+"warning" = "bogster-orange"
+"error" = "bogster-lred"
+"info" = "bogster-teal"
+"hint" = "bogster-blue"
+
+# make diagnostic underlined, to distinguish with selection text.
+diagnostic = { modifiers = ["underlined"] }
+
+[palette]
+bogster-orange = "#dc7759"
+bogster-yellow = "#a58023"
+bogster-red = "#d32c5d"
+bogster-blue = "#59c0dc"
+bogster-gray = "#abb2bf"
+bogster-lgreen = "#7fdc59"
+bogster-lred = "#dc597f"
+bogster-lblue = "#289cbc"
+bogster-teal = "#23a580"
+
+bogster-fg1 = "#161c23"
+bogster-fg0 = "#232d38"
+
+bogster-base00 = "#d7dbbb"
+bogster-base0 = "#f6fbd6"
+bogster-base1 = "#c7c7ba"
+bogster-base2 = "#aaaa97"
+bogster-base3 = "#415367"
+bogster-base5 = "#627d9d"
+
diff --git a/runtime/themes/dark_high_contrast.toml b/runtime/themes/dark_high_contrast.toml
new file mode 100644
index 00000000..c65750f4
--- /dev/null
+++ b/runtime/themes/dark_high_contrast.toml
@@ -0,0 +1,105 @@
+# GreasySlug : dark high contrast <9619abgoni@gmail.com>
+
+# Interface
+"ui.background" = { bg = "black" }
+"ui.window" = { fg = "aqua" }
+"special" = "orange" # file picker fuzzy match
+"ui.background.separator" = { fg = "white" }
+"ui.text" = "white"
+"ui.text.focus" = { modifiers = ["reversed"] } # file picker selected
+
+"ui.virtual.whitespace" = "gray"
+"ui.virtual.ruler" = { fg = "gray", bg = "white", modifiers = ["reversed"] }
+"ui.virtual.indent-guide" = "white"
+
+"ui.statusline" = { fg = "white", bg = "deep_blue" }
+"ui.statusline.inactive" = { fg = "gray" }
+"ui.statusline.normal" = { fg = "black", bg = "aqua" }
+"ui.statusline.insert" = { fg = "black", bg = "orange" }
+"ui.statusline.select" = { fg = "black", bg = "purple" }
+# "ui.statusline.separator" = { fg = "aqua", bg = "white" }
+
+"ui.cursor" = { fg = "black", bg = "white" }
+"ui.cursor.insert" = { fg = "black", bg = "white" }
+"ui.cursor.select" = { fg = "black", bg = "white" }
+"ui.cursor.match" = { bg = "white", modifiers = ["dim"] }
+"ui.cursor.primary" = { fg = "black", bg = "white", modifiers = ["slow_blink"] }
+
+"ui.cursor.secondary" = "white"
+"ui.cursorline.primary" = { fg = "orange", modifiers = ["underlined"] }
+"ui.cursorline.secondary" = { fg = "orange", modifiers = ["underlined"] }
+"ui.selection" = { fg = "deep_blue", bg = "white" }
+
+"ui.menu" = { fg = "white", bg = "deep_blue" }
+"ui.menu.selected" = { fg = "orange", modifiers = ["underlined"] }
+"ui.menu.scroll" = { fg = "aqua", bg = "black" }
+"ui.help" = { fg = "white", bg = "deep_blue" }
+
+"ui.popup" = { bg = "deep_blue" }
+"ui.popup.info" = { fg = "white", bg = "deep_blue" }
+
+"ui.gutter" = { bg = "black" }
+"ui.linenr" = { fg = "white", bg = "black" }
+"ui.linenr.selected" = { fg = "orange", bg = "black", modifiers = ["bold"] }
+
+# Diagnostic
+"diagnostic" = "white"
+"diagnostic.info" = { bg = "white", modifiers = ["underlined"] }
+"diagnostic.hint" = { fg = "yellow", modifiers = ["underlined"] }
+"diagnostic.warning" = { fg = "orange", modifiers = ["underlined"] }
+"diagnostic.error" = { fg = "red", modifiers = ["underlined"] }
+"info" = "white"
+"hint" = "yellow"
+"warning" = "orange"
+"error" = "red"
+"debug" = "red"
+
+"diff.plus" = "green"
+"diff.delta" ="blue"
+"diff.minus" = "pink"
+
+# Syntax high light
+"type" = { fg = "emerald_green" }
+"type.buildin" = "emerald_green"
+"comment" = { fg = "white" }
+"operator" = "white"
+"variable" = { fg = "aqua" }
+"variable.other.member" = "brown"
+"constant" = "aqua"
+"constant.numeric" = "emerald_green"
+"attributes" = { fg = "blue" }
+"namespace" = "blue"
+"string" = "brown"
+"string.special.url" = { fg = "brown", modifiers =["underlined"]}
+"constant.character.escape" = "yellow"
+"constructor" = "aqua"
+"keyword" = { fg = "blue", modifiers = ["underlined"] }
+"keyword.control" = "purple"
+"function" = "yellow"
+"label" = "blue"
+
+# Markup
+"markup.heading" = { fg = "yellow", modifiers = ["bold", "underlined"] }
+"markup.list" = { fg = "pink" }
+"markup.bold" = { fg = "emerald_green", modifiers = ["bold"] }
+"markup.italic" = { fg = "blue", modifiers = ["italic"] }
+"markup.link.url" = { fg = "blue", modifiers = ["underlined"] }
+"markup.link.text" = "pink"
+"markup.quote" = "yellow"
+"markup.raw" = "brown"
+
+[palette]
+black = "#000000"
+gray = "#585858"
+white = "#ffffff"
+blue = "#66a4ff"
+deep_blue = "#142743"
+aqua = "#6fc3df"
+purple = "#c586c0"
+red = "#b65f5f"
+pink = "#ff5c8d"
+orange = "#f38518"
+brown = "#ce9178"
+yellow = "#cedc84"
+green = "#427a2d"
+emerald_green = "#4ec9b0"
diff --git a/runtime/themes/everforest_dark.toml b/runtime/themes/everforest_dark.toml
index 625e7df9..63997c9c 100644
--- a/runtime/themes/everforest_dark.toml
+++ b/runtime/themes/everforest_dark.toml
@@ -8,11 +8,12 @@
# Email: sainnhe@gmail.com
# License: MIT License
-"constant.character.escape" = "orange"
"type" = "yellow"
"constant" = "purple"
"constant.numeric" = "purple"
+"constant.character.escape" = "orange"
"string" = "green"
+"string.regexp" = "blue"
"comment" = "grey0"
"variable" = "fg"
"variable.builtin" = "blue"
@@ -23,6 +24,7 @@
"punctuation.delimiter" = "grey2"
"punctuation.bracket" = "fg"
"keyword" = "red"
+"keyword.directive" = "aqua"
"operator" = "orange"
"function" = "green"
"function.builtin" = "blue"
@@ -54,25 +56,33 @@
"diff.minus" = "red"
"ui.background" = { bg = "bg0" }
+"ui.background.separator" = "grey0"
"ui.cursor" = { fg = "bg0", bg = "fg" }
"ui.cursor.match" = { fg = "orange", bg = "bg_yellow" }
"ui.cursor.insert" = { fg = "bg0", bg = "grey1" }
"ui.cursor.select" = { fg = "bg0", bg = "blue" }
+"ui.cursorline.primary" = { bg = "bg1" }
+"ui.cursorline.secondary" = { bg = "bg1" }
+"ui.selection" = { bg = "bg3" }
"ui.linenr" = "grey0"
"ui.linenr.selected" = "fg"
-"ui.cursorline" = { bg = "bg1" }
-"ui.statusline" = { fg = "grey2", bg = "bg2" }
+"ui.statusline" = { fg = "grey2", bg = "bg3" }
"ui.statusline.inactive" = { fg = "grey0", bg = "bg1" }
-"ui.popup" = { fg = "grey2", bg = "bg1" }
-"ui.window" = { fg = "grey2", bg = "bg1" }
-"ui.help" = { fg = "fg", bg = "bg1" }
+"ui.statusline.normal" = { fg = "bg0", bg = "grey2", modifiers = ["bold"] }
+"ui.statusline.insert" = { fg = "bg0", bg = "yellow", modifiers = ["bold"] }
+"ui.statusline.select" = { fg = "bg0", bg = "blue", modifiers = ["bold"] }
+"ui.bufferline" = { fg = "grey0", bg = "bg1" }
+"ui.bufferline.active" = { fg = "fg", bg = "bg3", modifiers = ["bold"] }
+"ui.popup" = { fg = "grey2", bg = "bg2" }
+"ui.window" = { fg = "grey0", bg = "bg0" }
+"ui.help" = { fg = "fg", bg = "bg2" }
"ui.text" = "fg"
"ui.text.focus" = "fg"
-"ui.menu" = { fg = "fg", bg = "bg2" }
+"ui.menu" = { fg = "fg", bg = "bg3" }
"ui.menu.selected" = { fg = "bg0", bg = "green" }
-"ui.selection" = { bg = "bg3" }
-"ui.virtual.whitespace" = "grey0"
-"ui.virtual.ruler" = { bg = "bg1" }
+"ui.virtual.whitespace" = { fg = "bg4" }
+"ui.virtual.indent-guide" = { fg = "bg4" }
+"ui.virtual.ruler" = { bg = "bg3" }
"hint" = "blue"
"info" = "aqua"
@@ -80,7 +90,6 @@
"error" = "red"
"diagnostic" = { modifiers = ["underlined"] }
-
[palette]
bg0 = "#2b3339"
diff --git a/runtime/themes/everforest_light.toml b/runtime/themes/everforest_light.toml
index 43f94618..08876c0c 100644
--- a/runtime/themes/everforest_light.toml
+++ b/runtime/themes/everforest_light.toml
@@ -8,11 +8,12 @@
# Email: sainnhe@gmail.com
# License: MIT License
-"constant.character.escape" = "orange"
"type" = "yellow"
"constant" = "purple"
"constant.numeric" = "purple"
+"constant.character.escape" = "orange"
"string" = "green"
+"string.regexp" = "blue"
"comment" = "grey0"
"variable" = "fg"
"variable.builtin" = "blue"
@@ -23,6 +24,7 @@
"punctuation.delimiter" = "grey2"
"punctuation.bracket" = "fg"
"keyword" = "red"
+"keyword.directive" = "aqua"
"operator" = "orange"
"function" = "green"
"function.builtin" = "blue"
@@ -54,25 +56,33 @@
"diff.minus" = "red"
"ui.background" = { bg = "bg0" }
+"ui.background.separator" = "grey0"
"ui.cursor" = { fg = "bg0", bg = "fg" }
"ui.cursor.match" = { fg = "orange", bg = "bg_yellow" }
"ui.cursor.insert" = { fg = "bg0", bg = "grey1" }
"ui.cursor.select" = { fg = "bg0", bg = "blue" }
+"ui.cursorline.primary" = { bg = "bg1" }
+"ui.cursorline.secondary" = { bg = "bg1" }
+"ui.selection" = { bg = "bg3" }
"ui.linenr" = "grey0"
"ui.linenr.selected" = "fg"
-"ui.cursorline" = { bg = "bg1" }
-"ui.statusline" = { fg = "grey2", bg = "bg2" }
+"ui.statusline" = { fg = "grey2", bg = "bg3" }
"ui.statusline.inactive" = { fg = "grey0", bg = "bg1" }
-"ui.popup" = { fg = "grey2", bg = "bg1" }
-"ui.window" = { fg = "grey2", bg = "bg1" }
-"ui.help" = { fg = "fg", bg = "bg1" }
+"ui.statusline.normal" = { fg = "bg0", bg = "grey2", modifiers = ["bold"] }
+"ui.statusline.insert" = { fg = "bg0", bg = "yellow", modifiers = ["bold"] }
+"ui.statusline.select" = { fg = "bg0", bg = "blue", modifiers = ["bold"] }
+"ui.bufferline" = { fg = "grey0", bg = "bg1" }
+"ui.bufferline.active" = { fg = "fg", bg = "bg3", modifiers = ["bold"] }
+"ui.popup" = { fg = "grey2", bg = "bg2" }
+"ui.window" = { fg = "grey0", bg = "bg0" }
+"ui.help" = { fg = "fg", bg = "bg2" }
"ui.text" = "fg"
"ui.text.focus" = "fg"
-"ui.menu" = { fg = "fg", bg = "bg2" }
+"ui.menu" = { fg = "fg", bg = "bg3" }
"ui.menu.selected" = { fg = "bg0", bg = "green" }
-"ui.selection" = { bg = "bg3" }
-"ui.virtual.whitespace" = "grey0"
-"ui.virtual.ruler" = { bg = "bg1" }
+"ui.virtual.whitespace" = { fg = "bg4" }
+"ui.virtual.indent-guide" = { fg = "bg4" }
+"ui.virtual.ruler" = { bg = "bg3" }
"hint" = "blue"
"info" = "aqua"
@@ -80,7 +90,6 @@
"error" = "red"
"diagnostic" = { modifiers = ["underlined"] }
-
[palette]
bg0 = "#fff9e8"
diff --git a/runtime/themes/heisenberg.toml b/runtime/themes/heisenberg.toml
new file mode 100644
index 00000000..b20d3e9c
--- /dev/null
+++ b/runtime/themes/heisenberg.toml
@@ -0,0 +1,86 @@
+# Author: IrishMaestro <github.com/irishmaestro>
+
+# Syntax highlighting
+"type" = { fg = "crystal_blue" }
+"type.builtin" = { fg = "crystal_blue" }
+"constructor" = { fg = "barium_green" }
+"constant" = { fg = "hazmat_yellow" }
+"string" = { fg = "crystal_blue" }
+"string.regexp" = { fg = "orange" }
+"string.special" = { fg = "vapor_yellow" }
+"comment" = { fg = "teddy_bear_pink", modifiers = ["slow_blink", "italic"] }
+"variable" = { fg = "element_green" }
+"variable.parameter" = { fg = "hazmat_yellow" }
+"label" = { fg = "vapor_yellow" }
+"punctuation" = { fg = "barium_green" }
+"keyword" = { fg = "barium_green" }
+"keyword.control" = { fg = "barium_green" }
+"keyword.directive" = { fg = "teddy_bear_pink" }
+"operator" = { fg = "hazmat_yellow" }
+"function" = { fg = "cash_green", modifiers = ["bold"] }
+"tag" = { fg = "crystal_blue" }
+"namespace" = { fg = "hazmat_yellow" }
+"markup.heading" = { fg = "hazmat_yellow" }
+"markup.list" = { fg = "vapor_yellow" }
+"markup.raw.block" = { bg = "background", fg = "orange" }
+"markup.link.url" = { fg = "crystal_blue" }
+"markup.link.text" = { fg = "vapor_yellow" }
+"markup.link.label" = { fg = "barium_green" }
+"markup.quote" = { fg = "vapor_yellow" }
+"diff.plus" = { fg = "cash_green" }
+"diff.minus" = { fg = "chili_powder_red" }
+"diff.delta" = { fg = "orange" }
+
+# Interface
+"ui.background" = { bg = "background" }
+"ui.cursor" = { bg = "crystal_blue", fg = "background" }
+"ui.cursor.match" = { fg = "hazmat_yellow" }
+"ui.linenr" = { fg = "crystal_blue" }
+"ui.linenr.selected" = { fg = "barium_green" }
+"ui.statusline" = { fg = "crystal_blue", bg = "black" }
+"ui.statusline.normal" = { fg = "crystal_blue", bg = "black" }
+"ui.statusline.insert" = { fg = "barium_green", bg = "black"}
+"ui.statusline.select" = { fg = "hazmat_yellow", bg = "black" }
+"ui.cursorline.primary" = { bg = "#041B0E" }
+"ui.popup" = { fg = "crystal_blue", bg = "background" }
+"ui.window" = { fg = "barium_green" }
+"ui.help" = { fg = "crystal_blue", bg = "background"}
+"ui.text" = { fg = "crystal_blue" }
+"ui.text.focus" = { bg = "background", fg = "barium_green" }
+"ui.text.info" = { fg = "crystal_blue" }
+"ui.virtual.whitespace" = { fg = "#08341B" }
+"ui.virtual.ruler" = { bg = "#041B0E" }
+"ui.virtual.indent-guide" = { fg = "#08341B" }
+"ui.menu" = { fg = "crystal_blue", bg = "background" }
+"ui.menu.selected" = { bg = "hazmat_yellow", fg = "background" }
+"ui.selection" = { bg = "#1B0334" }
+"ui.selection.primary" = { bg = "desert_maroon" }
+"warning" = { fg = "vapor_yellow" }
+"error" = { fg = "chili_powder_red", modifiers = ["bold"] }
+"info" = { fg = "crystal_blue", modifiers = ["bold"] }
+"hint" = { fg = "crystal_blue", modifiers = ["bold"] }
+"diagnostic"= { fg = "chili_powder_red", modifiers = ["underlined"] }
+"diagnostic.info"= { fg = "crystal_blue", modifiers = ["underlined"] }
+"diagnostic.warning"= { fg = "vapor_yellow", modifiers = ["underlined"] }
+"diagnostic.error"= { fg = "chili_powder_red", modifiers = ["underlined"] }
+"ui.bufferline" = { fg = "gray", bg = "background" }
+"ui.bufferline.active" = { fg = "foreground", bg = "dark_gray" }
+
+"special" = { fg = "cash_green" }
+
+[palette]
+background = "#000000"
+foreground = "#cccccc"
+black = "#121212"
+dark_gray = "#2d3640"
+gray = "#5c6773"
+orange = "#ff8f40"
+barium_green = "#009669"
+hazmat_yellow = "#f7b90c"
+vapor_yellow = "#cecd19"
+teddy_bear_pink = "#bd5173"
+crystal_blue = "#32c9fa"
+cash_green = "#00ff80"
+element_green = "#186800"
+desert_maroon = "#2B0C02"
+chili_powder_red = "#c32101"
diff --git a/runtime/themes/kanagawa.toml b/runtime/themes/kanagawa.toml
new file mode 100644
index 00000000..50a7c0a7
--- /dev/null
+++ b/runtime/themes/kanagawa.toml
@@ -0,0 +1,125 @@
+# Kanagawa
+# Author: zetashift
+
+# Adaptation of https://github.com/rebelot/kanagawa.nvim
+# Original author: rebelot
+# All credits to the original author, the palette is taken from the README
+# because of some theming differences, it's not an exact copy of the original.
+
+## User interface
+"ui.selection" = { bg = "waveBlue1" }
+"ui.background" = { fg = "fujiWhite", bg = "sumiInk1" }
+
+"ui.linenr" = { fg = "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.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 = "waveBlue1" }
+"ui.text" = "fujiWhite"
+"ui.text.focus" = { fg = "fujiWhite", bg = "waveBlue1", modifiers = ["bold"] }
+"ui.virtual" = "waveBlue1"
+
+"ui.cursor" = { fg = "fujiWhite", bg = "waveBlue1"}
+"ui.cursor.primary" = { fg = "seaFoam", bg = "waveBlue1" }
+"ui.cursor.match" = { fg = "seaFoam", modifiers = ["bold"] }
+"ui.highlight" = { fg = "fujiWhite", bg = "waveBlue2" }
+
+diagnostic = { modifiers = ["underlined"] }
+
+error = "samuraiRed"
+warning = "roninYellow"
+info = "waveAqua1"
+hint = "dragonBlue"
+
+## Syntax highlighting
+"type" = "waveAqua2"
+"constant" = "surimiOrange"
+"constant.numeric" = "sakuraPink"
+"constant.character.escape" = "springBlue"
+"string" = "springGreen"
+"string.regexp" = "boatYellow2"
+"comment" = "fujiGray"
+"variable" = "fujiWhite"
+"variable.builtin" = "waveRed"
+"variable.parameter" = "carpYellow"
+"variable.other.member" = "carpYellow"
+"label" = "springBlue"
+"punctuation" = "springViolet2"
+"punctuation.delimiter" = "springViolet2"
+"punctuation.bracket" = "springViolet2"
+"keyword" = "oniViolet"
+"keyword.directive" = "peachRed"
+"operator" = "boatYellow2"
+"function" = "crystalBlue"
+"function.builtin" = "peachRed"
+"function.macro" = "waveRed"
+"tag" = "springBlue"
+"namespace" = "surimiOrange"
+"attribute" = "peachRed"
+"constructor" = "springBlue"
+"module" = "waveAqua2"
+"special" = "peachRed"
+
+## Markup modifiers
+"markup.heading.marker" = "fujiGray"
+"markup.heading.1" = { fg = "surimiOrange", modifiers = ["bold"] }
+"markup.heading.2" = { fg = "carpYellow", modifiers = ["bold"] }
+"markup.heading.3" = { fg = "waveAqua2", modifiers = ["bold"] }
+"markup.heading.4" = { fg = "springGreen", modifiers = ["bold"] }
+"markup.heading.5" = { fg = "waveRed", modifiers = ["bold"] }
+"markup.heading.6" = { fg = "autumnRed", modifiers = ["bold"] }
+"markup.list" = "oniViolet"
+"markup.bold" = { modifiers = ["bold"] }
+"markup.italic" = { modifiers = ["italic"] }
+"markup.link.url" = { fg = "springBlue", modifiers = ["underlined"] }
+"markup.link.text" = "crystalBlue"
+"markup.quote" = "seaFoam"
+"markup.raw" = "seaFoam"
+
+[palette]
+seaFoam = "#C7CCD1" # custom lighter foreground
+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
+sumiInk4 = "#54546D" # darker foreground, e.g. linenumbers, fold column
+waveBlue1 = "#223249" # popup background, visual selection background
+waveBlue2 = "#2D4F67" # popup selection background, search background
+winterGreen = "#2B3328" # diff add background
+winterYellow = "#49443C" # diff change background
+winterRed = "#43242B" # diff deleted background
+winterBlue = "#252535" # diff line background
+autumnGreen = "#76946A" # git add
+autumnRed = "#C34043" # git delete
+autumnYellow = "#DCA561" # git change
+samuraiRed = "#E82424" # diagnostic error
+roninYellow = "#FF9E3B" # diagnostic warning
+waveAqua1 = "#6A9589" # diagnostic info
+dragonBlue = "#658594" # diagnostic hint
+fujiGray = "#727169" # comments
+springViolet1 = "#938AA9" # light foreground
+oniViolet = "#957FB8" # statements and keywords
+crystalBlue = "#7E9CD8" # functions and titles
+springViolet2 = "#9CABCA" # brackets and punctuation
+springBlue = "#7FB4CA" # specials and builtins
+lightBlue = "#A3D4D5" # not used!
+waveAqua2 = "#7AA89F" # types
+springGreen = "#98BB6C" # strings
+boatYellow1 = "#938056" # not used
+boatYellow2 = "#C0A36E" # operators, regex
+carpYellow = "#E6C384" # identifiers
+sakuraPink = "#D27E99" # numbers
+waveRed = "#E46876" # standout specials 1, e.g. builtin variables
+peachRed = "#FF5D62" # standout specials 2, e.g. exception handling, returns
+surimiOrange = "#FFA066" # constants, imports, booleans
+katanaGray = "#717C7C" # deprecated \ No newline at end of file
diff --git a/runtime/themes/monokai_pro_octagon.toml b/runtime/themes/monokai_pro_octagon.toml
index c6feae8c..8bcaeebc 100644
--- a/runtime/themes/monokai_pro_octagon.toml
+++ b/runtime/themes/monokai_pro_octagon.toml
@@ -18,6 +18,9 @@
# status bars, panels, modals, autocompletion
"ui.statusline" = { fg = "base8", bg = "base4" }
+"ui.statusline.normal" = { fg = "base4", bg = "blue" }
+"ui.statusline.insert" = { fg = "base4", bg = "green" }
+"ui.statusline.select" = { fg = "base4", bg = "purple" }
"ui.popup" = { bg = "base3" }
"ui.window" = { bg = "base3" }
"ui.help" = { fg = "base8", bg = "base3" }
diff --git a/runtime/themes/onedarker.toml b/runtime/themes/onedarker.toml
index 1ad6f7c3..665f10ed 100644
--- a/runtime/themes/onedarker.toml
+++ b/runtime/themes/onedarker.toml
@@ -2,7 +2,7 @@
"attribute" = { fg = "yellow" }
"comment" = { fg = "light-gray", modifiers = ["italic"] }
-"constant" = { fg = "green" }
+"constant" = { fg = "gold" }
"constant.numeric" = { fg = "gold" }
"constant.builtin" = { fg = "gold" }
"constant.character.escape" = { fg = "gold" }
@@ -69,7 +69,7 @@ diagnostic = { modifiers = ["underlined"] }
"ui.statusline.normal" = { fg = "light-black", bg = "purple" }
"ui.statusline.insert" = { fg = "light-black", bg = "green" }
"ui.statusline.select" = { fg = "light-black", bg = "cyan" }
-"ui.text" = { fg = "purple" }
+"ui.text" = { fg = "white" }
"ui.text.focus" = { fg = "white", bg = "light-black", modifiers = ["bold"] }
"ui.help" = { fg = "white", bg = "gray" }
@@ -91,7 +91,7 @@ cyan = "#46A6B2"
white = "#ABB2BF"
black = "#16181A"
light-black = "#2C323C"
-gray = "#454D50"
+gray = "#252D30"
faint-gray = "#ABB2BF"
-light-gray = "#C8CCD4"
+light-gray = "#636C6E"
linenr = "#282C34"
diff --git a/runtime/themes/pop-dark.toml b/runtime/themes/pop-dark.toml
index bac0d0f9..6ad0111b 100644
--- a/runtime/themes/pop-dark.toml
+++ b/runtime/themes/pop-dark.toml
@@ -43,7 +43,6 @@ module = { bg = 'orangeL' }
special = { fg = 'orangeW' }
operator = { fg = 'orangeY' }
attribute = { fg = 'orangeL' }
-attribute = { fg = 'orangeL' }
namespace = { fg = 'orangeL' }
'type' = { fg = 'redH' }
'type.builtin' = { fg = 'orangeL' }
diff --git a/runtime/themes/rose_pine.toml b/runtime/themes/rose_pine.toml
index 06be3083..14e240dd 100644
--- a/runtime/themes/rose_pine.toml
+++ b/runtime/themes/rose_pine.toml
@@ -79,6 +79,6 @@ rose = "#ebbcba"
pine = "#31748f"
foam = "#9ccfd8"
iris = "#c4a7e7"
-highlight = "#2a2837"
-highlightInactive = "#211f2d"
-highlightOverlay = "#3a384a"
+highlight = "#403d52"
+highlightInactive = "#21202e"
+highlightOverlay = "#524f67"
diff --git a/runtime/themes/rose_pine_dawn.toml b/runtime/themes/rose_pine_dawn.toml
index 129ae9c7..c6609c21 100644
--- a/runtime/themes/rose_pine_dawn.toml
+++ b/runtime/themes/rose_pine_dawn.toml
@@ -76,6 +76,6 @@ rose = "#d7827e"
pine = "#286983"
foam = "#56949f"
iris = "#907aa9"
-highlight = "#eee9e6"
-highlightInactive = "#f2ede9"
-highlightOverlay = "#e4dfde"
+highlight = "#dfdad9"
+highlightInactive = "#f4ede8"
+highlightOverlay = "#cecacd"
diff --git a/runtime/themes/rose_pine_moon.toml b/runtime/themes/rose_pine_moon.toml
index 4706bb17..fc27d2c0 100644
--- a/runtime/themes/rose_pine_moon.toml
+++ b/runtime/themes/rose_pine_moon.toml
@@ -83,6 +83,6 @@ rose = "#ea9a97"
pine = "#3e8fb0"
foam = "#9ccfd8"
iris = "#c4a7e7"
-highlight = "#2a283e"
-highlightInactive = "#44415a"
+highlight = "#44415a"
+highlightInactive = "#2a283e"
highlightOverlay = "#56526e"
diff --git a/runtime/themes/sonokai.toml b/runtime/themes/sonokai.toml
index 54804697..18417f00 100644
--- a/runtime/themes/sonokai.toml
+++ b/runtime/themes/sonokai.toml
@@ -58,7 +58,7 @@
"ui.selection" = { bg = "bg4" }
"ui.linenr" = "grey"
"ui.linenr.selected" = "fg"
-"ui.cursorline.primary" = { bg = "bg2" }
+"ui.cursorline.primary" = { bg = "bg1" }
"ui.statusline" = { fg = "fg", bg = "bg3" }
"ui.statusline.inactive" = { fg = "grey", bg = "bg1" }
"ui.popup" = { fg = "grey", bg = "bg2" }
@@ -86,7 +86,7 @@ bg0 = "#2c2e34"
bg1 = "#33353f"
bg2 = "#363944"
bg3 = "#3b3e48"
-bg4 = "#414550"
+bg4 = "#545862"
bg_red = "#ff6077"
diff_red = "#55393d"
bg_green = "#a7df78"
diff --git a/runtime/tutor b/runtime/tutor
index 8eef33af..43edb360 100644
--- a/runtime/tutor
+++ b/runtime/tutor
@@ -16,7 +16,7 @@ _________________________________________________________________
perform various actions with the text. This allows for more
efficient editing. This tutor will teach you how you can make
use of Helix's modal editing features. To begin, ensure your
- caps-lock key is not pressed and hold the j key until you reach
+ CapsLock key is not pressed and hold the j key until you reach
the first lesson.
@@ -31,7 +31,7 @@ _________________________________________________________________
The cursor can be moved using the h, j, k, l keys, as shown
- above. The cursor/arrow keys will also work, but it is faster
+ above. The cursor / arrow keys will also work, but it is faster
to use the hjkl keys as they are closer to the other keys you
will be using. Try moving around to get a feel for hjkl.
Once you're ready, hold j to continue to the next lesson.
@@ -48,13 +48,13 @@ _________________________________________________________________
1. Type : to enter Command mode. Your cursor will
move to the bottom of the screen.
- 2. Type q or quit and type <ENTER> to exit Helix.
+ 2. Type q or quit and type Enter to exit Helix.
Note: The quit command will fail if there are unsaved changes.
To force quit and DISCARD these changes, type q! or quit!.
You will learn how to save files later.
- To exit Command mode without entering a command, type <ESC>.
+ To exit Command mode without entering a command, type Escape.
Now, move on to the next lesson.
@@ -96,7 +96,7 @@ _________________________________________________________________
2. Move to a place in the line which is missing text and type
i to enter Insert mode. Keys you type will now type text.
3. Enter the missing text.
- 4. type <ESC> to exit Insert mode and return to Normal mode.
+ 4. type Escape to exit Insert mode and return to Normal mode.
5. Repeat until the line matches the line below it.
--> Th stce misg so.
@@ -112,18 +112,18 @@ _________________________________________________________________
= 1.5 SAVING A FILE =
=================================================================
- Type :w/:write to save a file.
+ Type :w / :write to save a file.
1. Exit Helix using :q! as explained before, or open a new
terminal.
2. Open a file in Helix by running: hx FILENAME
3. Make some edits to the file.
4. Type : to enter Command mode.
- 5. Type w or write, and type <ENTER> to save the file.
+ 5. Type w or write, and type Enter to save the file.
You can also type wq or write-quit to save and exit.
- Note: You can optionally enter a filepath after the w/write
+ Note: You can optionally enter a filepath after the w / write
command in order to save to that path.
Note: If there are any unsaved changes to a file, a plus [+]
will appear next to the file name in the status bar.
@@ -137,15 +137,15 @@ _________________________________________________________________
* Use the h,j,k,l keys to move the cursor.
* Type : to enter Command mode.
- * The q/quit and q!/quit! commands will exit Helix. The
+ * The q / quit and q! / quit! commands will exit Helix. The
former fails when there are unsaved changes. The latter
discards them.
- * The w/write command will save the file.
- * The wq/write-quit command will do both.
+ * The w / write command will save the file.
+ * The wq / write-quit command will do both.
* Type d to delete the character at the cursor.
- * Type i to enter Insert mode and type text. Type <ESC> to
+ * Type i to enter Insert mode and type text. Type Escape to
return to Normal mode.
@@ -167,7 +167,7 @@ _________________________________________________________________
A - Insert at the end of the line.
1. Move to anywhere in the line marked '-->' below.
- 2. Type A (<SHIFT> + a), your cursor will move to the end of
+ 2. Type A (Shift-a), your cursor will move to the end of
the line and you will be able to type.
3. Type the text necessary to match the line below.
@@ -207,7 +207,7 @@ _________________________________________________________________
* Type A to enter Insert mode at the end of a line.
- * Use o and O to open lines below/above the cursor respectively.
+ * Use o and O to open lines below and above the cursor respectively.
@@ -307,11 +307,11 @@ _________________________________________________________________
=================================================================
-= 3.5 SELECT/EXTEND MODE =
+= 3.5 SELECT / EXTEND MODE =
=================================================================
Type v to enter Select mode.
- Type v again or <ESC> to return to Normal mode
+ Type v again or Escape to return to Normal mode
In Select mode every movement will extend the selection, as
opposed to replacing it.
@@ -366,8 +366,8 @@ _________________________________________________________________
--> This is an error-free line with words to move around in.
- Note: This works the same in select mode.
- Note: Another related command is A-; which flips selections.
+ Note: This works the same in Select mode.
+ Note: Another related command is Alt-; which flips selections.
@@ -405,7 +405,7 @@ _________________________________________________________________
3. Type u to undo your deletion.
4. Fix all the errors on the line.
5. Type u several times to undo your fixes.
- 6. Type U (<SHIFT> + u) several times to redo your fixes.
+ 6. Type U (Shift-u) several times to redo your fixes.
--> Fiix the errors on thhis line and reeplace them witth undo.
@@ -434,32 +434,32 @@ _________________________________________________________________
1 banana 2 banana 3 banana 4
Note: Whenever you delete or change text, Helix will copy the
- altered text. Use alt-d/c instead to avoid this.
+ altered text. Use Alt-d / Alt-c instead to avoid this.
Note: Helix doesn't share the system clipboard by default. Type
- space-y/p to yank/paste on your computer's main clipboard.
+ Space + y / p to yank / paste on the system's clipboard.
=================================================================
= 4.3 SEARCHING IN FILE =
=================================================================
- Type / to search forward in file, enter to confirm search.
+ Type / to search forward in file, Enter to confirm search.
Type n to go to the next search match.
Type N to go to the previous search match.
1. Type / and type in a common word, like 'banana'.
- 2. Type enter to confirm the search.
+ 2. Type Enter to confirm the search.
3. Use n and N to cycle through the matches.
- Like the select command, searching also uses regex.
+ Searching uses regular expressions, allowing you to target more
+ complex expressions, which you'll learn about in the lesson on
+ the select command.
- Note: To search backwards, type ? (shift-/).
+ Note: To search backwards, type ? (Shift-/).
Note: Unlike Vim, ? doesn't change the search direction.
N always goes backwards and n always goes forwards.
-
-
=================================================================
= CHAPTER 4 RECAP =
=================================================================
@@ -467,7 +467,7 @@ _________________________________________________________________
* Type u to undo. Type U to redo.
* Type y to yank (copy) text and p to paste.
- * Use space-Y and space-P to yank/paste on the system
+ * Use Space + y and Space + p to yank / paste on the system
clipboard.
* Type / to search forward in file, and ? to search backwards.
@@ -488,7 +488,8 @@ _________________________________________________________________
Type C to duplicate the cursor to the next suitable line.
- 1. Move the cursor to the first line marked '-->' below.
+ 1. Move the cursor to the first line marked '-->' below. Place
+ the cursor somewhere past the '-->'.
2. Type C to duplicate the cursor to the next suitable line.
Notice how it skips the line in the middle. Keys you type
will now affect both cursors.
@@ -499,10 +500,9 @@ _________________________________________________________________
--> Fix th two nes at same ime.
-->
--> Fix th two nes at same ime.
-
Fix these two lines at the same time.
- Note: Type alt-C to do the same above the cursor.
+ Note: Type Alt-C to do the same above the cursor.
=================================================================
= 5.2 THE SELECT COMMAND =
@@ -513,11 +513,11 @@ _________________________________________________________________
1. Move the cursor to the line marked '-->' below.
2. Type x to select the line.
3. Type s. A prompt will appear.
- 4. Type 'apples' and type <ENTER>. Both occurrences of
+ 4. Type 'apples' and type Enter. Both occurrences of
'apples' in the line will be selected.
5. You can now type c and change 'apples' to something else,
like 'oranges'.
- 6. Type <ESC> to exit Insert mode.
+ 6. Type Escape to exit Insert mode.
7. Type , to remove the second cursor.
--> I like to eat apples since my favorite fruit is apples.
@@ -530,8 +530,8 @@ _________________________________________________________________
= 5.3 SELECTING VIA REGEX =
=================================================================
- The select command selects regular expressions, not just exact
- matches, allowing you to target more complex patterns.
+ Like searching, the select command selects regular expressions,
+ not just exact matches.
1. Move the cursor to the line marked '-->' below.
2. Select the line with x and then type s.
@@ -574,11 +574,11 @@ _________________________________________________________________
= 5.5 SPLIT SELECTION INTO LINES =
=================================================================
- Type A-s (Alt-s) to split the selection(s) on newlines.
+ Type Alt-s to split the selection(s) on newlines.
- 1. Move the first row of the table below.
+ 1. Move the cursor to the first row of the table below.
2. Select the entire table with 6x.
- 3. Type A-s to split into selections at each line.
+ 3. Type Alt-s to split into selections at each line.
4. Align the table with &.
| FRUIT | AMOUNT |
@@ -604,7 +604,7 @@ _________________________________________________________________
* Type & to align selections.
- * Type A-s to split the selection into lines.
+ * Type Alt-s to split the selection into lines.
@@ -627,7 +627,7 @@ _________________________________________________________________
2. Type f[ to select to the square bracket.
3. Type d to delete your selection.
4. Go to the end of the line and repeat with F].
- 5. Move to the second line marked -->, just after the arrow.
+ 5. Move to the second line marked '-->', just after the arrow.
6. Use t and T to delete the dashes around the sentence.
--> -----[Free this sentence of its brackets!]-----
@@ -663,16 +663,16 @@ _________________________________________________________________
=================================================================
Type . to repeat the last insert command.
- Type A-. to repeat the last f / t selection.
+ Type Alt-. to repeat the last f / t selection.
1. Move the cursor to the line marked '-->' below.
2. Make a change, insertion or appendage and repeat it with . .
- 3. Try using A-. with f and t, to select multiple sentences for
- instance.
+ 3. Try using Alt-. with f and t, to select multiple sentences
+ for instance.
--> This is some text for you to repeat things. You can repeat
- insertions like changing words, or repeat selections like f/t.
-
+ insertions like changing words, or repeat selections like
+ f / t.
@@ -690,7 +690,7 @@ _________________________________________________________________
* Type r to replace selected characters.
* Type . to repeat the last insertion.
- * Type A-. to repeat the last f / t selection.
+ * Type Alt-. to repeat the last f / t selection.
@@ -772,13 +772,13 @@ lines.
= 7.4 INCREMENTING AND DECREMENTING =
=================================================================
- Type C-a to increment the number under selection.
- Type C-x to decrement the number under selection.
+ Type Ctrl-a to increment the number under selection.
+ Type Ctrl-x to decrement the number under selection.
1. Move the cursor to the third line marked '-->' below.
- 2. Type C-a to increment the second point marked 2.
+ 2. Type Ctrl-a to increment the second point marked 2.
3. Repeat for the point marked 3.
- 4. Move to the last point and type C-x to decrement the 6.
+ 4. Move to the last point and type Ctrl-x to decrement the 6.
--> 1) First point.
--> 2) Added point.
@@ -800,8 +800,8 @@ lines.
* Type < and > to indent / outdent lines.
- * Type C-a to increment the selected number.
- * Type C-x to decrement the selected number.
+ * Type Ctrl-a to increment the selected number.
+ * Type Ctrl-x to decrement the selected number.
@@ -886,8 +886,9 @@ lines.
n and N both refer to register /, this means we can set that
register without having to type in a search.
- Type * to copy the primary selection into register /, setting
- the search term to the selection.
+ Type * to copy the selection into register /, setting the search
+ term to the selection. This copies the primary selection, which
+ you will learn about in the section on cycling selections.
1. Move the cursor to the line marked '-->' below.
2. Select "horse" with e and type *.
@@ -899,18 +900,17 @@ lines.
Note: * is like a shorthand for "/ y as all it really does is
copy the selection into the / register.
-
=================================================================
= 9.2 ADDING SELECTION ON NEXT SEARCH MATCH =
=================================================================
- A property of select mode (v) when using n and N is that instead
+ A property of Select mode (v) when using n and N is that instead
of moving the selection to the next match, it adds a new
selection on each match.
1. Move the cursor to the line marked '-->' below.
2. Select the first "bat" and type * to set it to search.
- 3. Type v to enter select mode.
+ 3. Type v to enter Select mode.
4. Type n to select the other "bat".
5. Use c or r to change the "bat"s to "cat".
@@ -930,15 +930,15 @@ lines.
searching or jumping to the definition of a function in code. It
stores these in what's called the jumplist.
- Type C-s (ctrl-s) to manually save your current position to
+ Type Ctrl-s to manually save your current position to
the jumplist.
- Type C-i ("in") and C-o ("out") to move forward and backwards in
- the jumplist respectively.
+ Type Ctrl-i ("in") and Ctrl-o ("out") to move forward and
+ backwards in the jumplist respectively.
- 1. Type C-s somewhere.
+ 1. Type Ctrl-s somewhere.
2. Move far away in the file.
- 3. Type C-o (just once!) to come back to where you saved.
+ 3. Type Ctrl-o (just once!) to come back to where you saved.
@@ -950,12 +950,12 @@ lines.
* Type * to set the search register to the primary selection.
- * Type n / N in visual mode to add selections on each search
+ * Type n / N in Visual mode to add selections on each search
match.
- * Type C-s to save position to the jumplist.
- * Type C-i and C-o to go forward and backward in the jumplist.
-
+ * Type Ctrl-s to save position to the jumplist.
+ * Type Ctrl-i and Ctrl-o to go forward and backward in the
+ jumplist.
@@ -967,25 +967,25 @@ lines.
=================================================================
-= 10.1 CYCLING AND REMOVING SELECIONS =
+= 10.1 CYCLING AND REMOVING SELECTIONS =
=================================================================
Type ) and ( to cycle the primary selection forward and backward
through selections respectively.
- Type A-, to remove the primary selection.
+ Type Alt-, to remove the primary selection.
1. Move the cursor to the line marked '-->' below.
2. Select both lines with xx or 2x.
3. Type s to select, type "would" and enter.
4. Use ( and ) to cycle the primary selection and remove the
- very second "would" with A-, .
+ very second "would" with Alt-, .
5. Type c "wood" to change the remaining "would"s to "wood".
--> How much would would a wouldchuck chuck
--> if a wouldchuck could chuck would?
- Note: Additionally, A-( and A-) cycle the *contents* of the
+ Note: Additionally, Alt-( and Alt-) cycle the *contents* of the
selections as well.
=================================================================
@@ -999,10 +999,10 @@ lines.
1. Move the cursor to the first line marked '-->' below.
2. Select each wrongly capitalised or lowercase letter
and type ~ over them.
- 3. Move to the second line marked -->.
+ 3. Move to the second line marked '-->'.
4. Type x to select the line.
5. Type ` to change the line to lowercase.
- 6. Move to the third line marked -->.
+ 6. Move to the third line marked '-->'.
7. Type x to select the line.
8. Type Alt-` to change the line to uppercase.
@@ -1018,13 +1018,13 @@ lines.
1. Move the cursor to the line under ---.
2. Type xx / 2x to select the lines.
- 3. Type S then \. |! <enter> (note the spaces after . and !).
+ 3. Type S then \. |! Enter (note the spaces after . and !).
This effectively splits the selection into sentences at each
dot or exclamation mark.
- 4. Type A-; to reverse the selections.
+ 4. Type Alt-; to reverse the selections.
5. Type ; to reduce selections to a single character - the first
letter of each sentence.
- 6. Type A-` to convert all selected letters to uppercase.
+ 6. Type Alt-` to convert all selected letters to uppercase.
---
these are sentences. some sentences don't start with uppercase
@@ -1038,10 +1038,10 @@ letters! that is not good grammar. you can fix this.
* Use ) and ( to cycle the primary selection back and forward
through selections respectively.
- * Type A-, to remove the primary selection.
+ * Type Alt-, to remove the primary selection.
* Type ~ to alternate case of selected letters.
- * Use ` and A-` to set the case of selected letters to
+ * Use ` and Alt-` to set the case of selected letters to
upper and lower respectively.
* Type S to split selections on regex.
@@ -1055,5 +1055,23 @@ letters! that is not good grammar. you can fix this.
=================================================================
+= =
+=================================================================
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+=================================================================
This tutorial is still a work-in-progress.
More sections are planned.
diff --git a/xtask/src/themelint.rs b/xtask/src/themelint.rs
index 26d1cb04..06dfae40 100644
--- a/xtask/src/themelint.rs
+++ b/xtask/src/themelint.rs
@@ -171,7 +171,7 @@ pub fn lint(file: String) -> Result<(), DynError> {
let message = m.replace("$THEME", theme.as_str());
println!("{}", message);
});
- Err(format!("{} has issues", file.clone().as_str()).into())
+ Err(format!("{} has issues", file).into())
} else {
Ok(())
}
@@ -183,8 +183,7 @@ pub fn lint_all() -> Result<(), DynError> {
let ok_files_count = files
.into_iter()
.filter_map(|path| lint(path.replace(".toml", "")).ok())
- .collect::<Vec<()>>()
- .len();
+ .count();
if files_count != ok_files_count {
Err(format!(