aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGokul Soumya2022-01-06 05:56:35 +0000
committerGokul Soumya2022-01-06 06:02:03 +0000
commit449624965b05fd2abc9e3ba2f791f8de8b1eeb3e (patch)
treecbe060df6a61330e6a470c521ed254f5c7dc4f4f
parentc0bbadcaaf42698d102fa03f6f9267021f3efec0 (diff)
parent2e02a1d6bc004212033b9c4e5ed0de0fd880796c (diff)
Merge branch 'master' into cursor-shape-new
-rw-r--r--.github/workflows/release.yml2
-rw-r--r--.gitmodules38
-rw-r--r--CHANGELOG.md119
-rw-r--r--Cargo.lock81
-rw-r--r--base16_theme.toml4
-rw-r--r--book/src/generated/lang-support.md21
-rw-r--r--book/src/generated/typable-cmd.md4
-rw-r--r--book/src/guides/adding_languages.md37
-rw-r--r--book/src/keymap.md70
-rw-r--r--book/src/languages.md1
-rw-r--r--book/src/themes.md12
-rw-r--r--flake.lock24
-rw-r--r--helix-core/Cargo.toml7
-rw-r--r--helix-core/src/auto_pairs.rs335
-rw-r--r--helix-core/src/diagnostic.rs15
-rw-r--r--helix-core/src/indent.rs143
-rw-r--r--helix-core/src/lib.rs34
-rw-r--r--helix-core/src/match_brackets.rs2
-rw-r--r--helix-core/src/movement.rs52
-rw-r--r--helix-core/src/object.rs29
-rw-r--r--helix-core/src/selection.rs93
-rw-r--r--helix-core/src/syntax.rs16
-rw-r--r--helix-lsp/Cargo.toml6
-rw-r--r--helix-lsp/src/client.rs28
-rw-r--r--helix-lsp/src/lib.rs31
-rw-r--r--helix-syntax/Cargo.toml2
-rw-r--r--helix-syntax/README.md13
m---------helix-syntax/languages/tree-sitter-comment0
m---------helix-syntax/languages/tree-sitter-dart0
m---------helix-syntax/languages/tree-sitter-dockerfile0
m---------helix-syntax/languages/tree-sitter-fish0
m---------helix-syntax/languages/tree-sitter-git-commit0
m---------helix-syntax/languages/tree-sitter-git-diff0
m---------helix-syntax/languages/tree-sitter-git-rebase0
m---------helix-syntax/languages/tree-sitter-llvm0
m---------helix-syntax/languages/tree-sitter-llvm-mir0
m---------helix-syntax/languages/tree-sitter-tablegen0
-rw-r--r--helix-term/Cargo.toml10
-rw-r--r--helix-term/src/application.rs79
-rw-r--r--helix-term/src/commands.rs506
-rw-r--r--helix-term/src/keymap.rs8
-rw-r--r--helix-term/src/ui/completion.rs40
-rw-r--r--helix-term/src/ui/editor.rs99
-rw-r--r--helix-term/src/ui/mod.rs29
-rw-r--r--helix-term/src/ui/prompt.rs4
-rw-r--r--helix-tui/Cargo.toml6
-rw-r--r--helix-tui/README.md2
-rw-r--r--helix-view/Cargo.toml9
-rw-r--r--helix-view/src/document.rs47
-rw-r--r--helix-view/src/editor.rs34
-rw-r--r--helix-view/src/graphics.rs4
-rw-r--r--helix-view/src/input.rs153
-rw-r--r--helix-view/src/view.rs3
-rw-r--r--languages.toml110
-rw-r--r--runtime/queries/c/indents.toml16
-rw-r--r--runtime/queries/c/injections.scm2
-rw-r--r--runtime/queries/c/textobjects.scm13
-rw-r--r--runtime/queries/cmake/indents.toml12
-rw-r--r--runtime/queries/cmake/injections.scm4
-rw-r--r--runtime/queries/cmake/textobjects.scm3
-rw-r--r--runtime/queries/comment/highlights.scm30
-rw-r--r--runtime/queries/cpp/indents.toml17
-rw-r--r--runtime/queries/cpp/injections.scm1
-rw-r--r--runtime/queries/cpp/textobjects.scm7
-rw-r--r--runtime/queries/dart/highlights.scm237
-rw-r--r--runtime/queries/dart/indents.toml20
-rw-r--r--runtime/queries/dart/locals.scm20
-rw-r--r--runtime/queries/dockerfile/highlights.scm51
-rw-r--r--runtime/queries/dockerfile/injections.scm6
-rw-r--r--runtime/queries/elixir/injections.scm2
-rw-r--r--runtime/queries/fish/highlights.scm156
-rw-r--r--runtime/queries/fish/indents.toml12
-rw-r--r--runtime/queries/fish/injections.scm2
-rw-r--r--runtime/queries/fish/textobjects.scm1
-rw-r--r--runtime/queries/git-commit/highlights.scm14
-rw-r--r--runtime/queries/git-commit/injections.scm8
-rw-r--r--runtime/queries/git-diff/highlights.scm6
-rw-r--r--runtime/queries/git-rebase/highlights.scm11
-rw-r--r--runtime/queries/git-rebase/injections.scm4
-rw-r--r--runtime/queries/glsl/injections.scm5
-rw-r--r--runtime/queries/julia/injections.scm8
-rw-r--r--runtime/queries/latex/highlights.scm2
-rw-r--r--runtime/queries/ledger/injections.scm4
-rw-r--r--runtime/queries/llvm-mir-yaml/highlights.scm1
-rw-r--r--runtime/queries/llvm-mir-yaml/indents.toml3
-rw-r--r--runtime/queries/llvm-mir-yaml/injections.scm9
-rw-r--r--runtime/queries/llvm-mir/highlights.scm136
-rw-r--r--runtime/queries/llvm-mir/indents.toml7
-rw-r--r--runtime/queries/llvm-mir/injections.scm2
-rw-r--r--runtime/queries/llvm-mir/textobjects.scm3
-rw-r--r--runtime/queries/llvm/highlights.scm162
-rw-r--r--runtime/queries/llvm/indents.toml8
-rw-r--r--runtime/queries/llvm/injections.scm2
-rw-r--r--runtime/queries/llvm/locals.scm14
-rw-r--r--runtime/queries/llvm/textobjects.scm16
-rw-r--r--runtime/queries/markdown/highlights.scm7
-rw-r--r--runtime/queries/markdown/injections.scm3
-rw-r--r--runtime/queries/nix/highlights.scm7
-rw-r--r--runtime/queries/ocaml/indents.toml2
-rw-r--r--runtime/queries/python/injections.scm2
-rw-r--r--runtime/queries/ruby/indents.toml25
-rw-r--r--runtime/queries/rust/highlights.scm14
-rw-r--r--runtime/queries/rust/indents.toml1
-rw-r--r--runtime/queries/rust/injections.scm3
-rw-r--r--runtime/queries/scala/highlights.scm203
-rw-r--r--runtime/queries/scala/indents.toml23
-rw-r--r--runtime/queries/scala/injections.scm1
-rw-r--r--runtime/queries/svelte/highlights.scm4
-rw-r--r--runtime/queries/svelte/injections.scm4
-rw-r--r--runtime/queries/tablegen/highlights.scm90
-rw-r--r--runtime/queries/tablegen/indents.toml7
-rw-r--r--runtime/queries/tablegen/injections.scm2
-rw-r--r--runtime/queries/tablegen/textobjects.scm7
-rw-r--r--runtime/queries/tsq/injections.scm2
-rw-r--r--runtime/queries/yaml/highlights.scm16
-rw-r--r--runtime/queries/yaml/injections.scm2
-rw-r--r--runtime/themes/base16_default_dark.toml4
-rw-r--r--runtime/themes/base16_default_light.toml4
-rw-r--r--runtime/themes/base16_terminal.toml4
-rw-r--r--runtime/themes/bogster.toml7
-rw-r--r--runtime/themes/dark_plus.toml4
-rw-r--r--runtime/themes/dracula.toml54
-rw-r--r--runtime/themes/everforest_dark.toml6
-rw-r--r--runtime/themes/everforest_light.toml90
-rw-r--r--runtime/themes/gruvbox.toml4
-rw-r--r--runtime/themes/ingrid.toml4
-rw-r--r--runtime/themes/monokai.toml6
-rw-r--r--runtime/themes/monokai_pro.toml5
-rw-r--r--runtime/themes/monokai_pro_machine.toml5
-rw-r--r--runtime/themes/monokai_pro_octagon.toml5
-rw-r--r--runtime/themes/monokai_pro_ristretto.toml5
-rw-r--r--runtime/themes/monokai_pro_spectrum.toml5
-rw-r--r--runtime/themes/nord.toml5
-rw-r--r--runtime/themes/onedark.toml23
-rw-r--r--runtime/themes/rose_pine.toml13
-rw-r--r--runtime/themes/rose_pine_dawn.toml18
-rw-r--r--runtime/themes/solarized_dark.toml4
-rw-r--r--runtime/themes/solarized_light.toml4
-rw-r--r--runtime/themes/spacebones_light.toml4
-rw-r--r--theme.toml6
-rw-r--r--xtask/Cargo.toml6
141 files changed, 3498 insertions, 621 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1ce3e092..7b0b7ee2 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -102,7 +102,7 @@ jobs:
fi
cp -r runtime dist
- - uses: actions/upload-artifact@v2.3.0
+ - uses: actions/upload-artifact@v2.3.1
with:
name: bins-${{ matrix.build }}
path: dist
diff --git a/.gitmodules b/.gitmodules
index 9c10846d..9297708a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -142,11 +142,15 @@
path = helix-syntax/languages/tree-sitter-perl
url = https://github.com/ganezdragon/tree-sitter-perl
shallow = true
+[submodule "helix-syntax/languages/tree-sitter-comment"]
+ path = helix-syntax/languages/tree-sitter-comment
+ url = https://github.com/stsewd/tree-sitter-comment
+ shallow = true
[submodule "helix-syntax/languages/tree-sitter-wgsl"]
path = helix-syntax/languages/tree-sitter-wgsl
url = https://github.com/szebniok/tree-sitter-wgsl
shallow = true
-[submodule "helix-syntax/tree-sitter-llvm"]
+[submodule "helix-syntax/languages/tree-sitter-llvm"]
path = helix-syntax/languages/tree-sitter-llvm
url = https://github.com/benwilliamgraham/tree-sitter-llvm
shallow = true
@@ -154,3 +158,35 @@
path = helix-syntax/languages/tree-sitter-markdown
url = https://github.com/MDeiml/tree-sitter-markdown
shallow = true
+[submodule "helix-syntax/languages/tree-sitter-dart"]
+ path = helix-syntax/languages/tree-sitter-dart
+ url = https://github.com/UserNobody14/tree-sitter-dart.git
+ shallow = true
+[submodule "helix-syntax/languages/tree-sitter-dockerfile"]
+ path = helix-syntax/languages/tree-sitter-dockerfile
+ url = https://github.com/camdencheek/tree-sitter-dockerfile.git
+ shallow = true
+[submodule "helix-syntax/languages/tree-sitter-fish"]
+ path = helix-syntax/languages/tree-sitter-fish
+ url = https://github.com/ram02z/tree-sitter-fish
+ shallow = true
+[submodule "helix-syntax/languages/tree-sitter-git-commit"]
+ path = helix-syntax/languages/tree-sitter-git-commit
+ url = https://github.com/the-mikedavis/tree-sitter-git-commit.git
+ shallow = true
+[submodule "helix-syntax/languages/tree-sitter-llvm-mir"]
+ path = helix-syntax/languages/tree-sitter-llvm-mir
+ url = https://github.com/Flakebi/tree-sitter-llvm-mir.git
+ shallow = true
+[submodule "helix-syntax/languages/tree-sitter-git-diff"]
+ path = helix-syntax/languages/tree-sitter-git-diff
+ url = https://github.com/the-mikedavis/tree-sitter-git-diff.git
+ shallow = true
+[submodule "helix-syntax/languages/tree-sitter-tablegen"]
+ path = helix-syntax/languages/tree-sitter-tablegen
+ url = https://github.com/Flakebi/tree-sitter-tablegen
+ shallow = true
+[submodule "helix-syntax/languages/tree-sitter-git-rebase"]
+ path = helix-syntax/languages/tree-sitter-git-rebase
+ url = https://github.com/the-mikedavis/tree-sitter-git-rebase.git
+ shallow = true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 52ca2d60..38927991 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,123 @@
+# 0.6.0 (2022-01-04)
+
+Happy new year and a big shout out to all the contributors! We had 55 contributors in this release.
+
+Helix has popped up in DPorts and Fedora Linux via COPR ([#1270](https://github.com/helix-editor/helix/pull/1270))
+
+As usual the following is a brief summary, refer to the git history for a full log:
+
+Breaking changes:
+
+- fix: Normalize backtab into shift-tab
+
+Features:
+
+- Macros ([#1234](https://github.com/helix-editor/helix/pull/1234))
+- Add reverse search functionality ([#958](https://github.com/helix-editor/helix/pull/958))
+- Allow keys to be mapped to sequences of commands ([#589](https://github.com/helix-editor/helix/pull/589))
+- Make it possible to keybind TypableCommands ([#1169](https://github.com/helix-editor/helix/pull/1169))
+- Detect workspace root using language markers ([#1370](https://github.com/helix-editor/helix/pull/1370))
+- Add WORD textobject ([#991](https://github.com/helix-editor/helix/pull/991))
+- Add LSP rename_symbol (space-r) ([#1011](https://github.com/helix-editor/helix/pull/1011))
+- Added workspace_symbol_picker ([#1041](https://github.com/helix-editor/helix/pull/1041))
+- Detect filetype from shebang line ([#1001](https://github.com/helix-editor/helix/pull/1001))
+- Allow piping from stdin into a buffer on startup ([#996](https://github.com/helix-editor/helix/pull/996))
+- Add auto pairs for same-char pairs ([#1219](https://github.com/helix-editor/helix/pull/1219))
+- Update settings at runtime ([#798](https://github.com/helix-editor/helix/pull/798))
+- Enable thin LTO (cccc194)
+
+Commands:
+- :wonly -- window only ([#1057](https://github.com/helix-editor/helix/pull/1057))
+- buffer-close (:bc, :bclose) ([#1035](https://github.com/helix-editor/helix/pull/1035))
+- Add :<line> and :goto <line> commands ([#1128](https://github.com/helix-editor/helix/pull/1128))
+- :sort command ([#1288](https://github.com/helix-editor/helix/pull/1288))
+- Add m textobject for pair under cursor ([#961](https://github.com/helix-editor/helix/pull/961))
+- Implement "Goto next buffer / Goto previous buffer" commands ([#950](https://github.com/helix-editor/helix/pull/950))
+- Implement "Goto last modification" command ([#1067](https://github.com/helix-editor/helix/pull/1067))
+- Add trim_selections command ([#1092](https://github.com/helix-editor/helix/pull/1092))
+- Add movement shortcut for history ([#1088](https://github.com/helix-editor/helix/pull/1088))
+- Add command to inc/dec number under cursor ([#1027](https://github.com/helix-editor/helix/pull/1027))
+ - Add support for dates for increment/decrement
+- Align selections (&) ([#1101](https://github.com/helix-editor/helix/pull/1101))
+- Implement no-yank delete/change ([#1099](https://github.com/helix-editor/helix/pull/1099))
+- Implement black hole register ([#1165](https://github.com/helix-editor/helix/pull/1165))
+- gf as goto_file (gf) ([#1102](https://github.com/helix-editor/helix/pull/1102))
+- Add last modified file (gm) ([#1093](https://github.com/helix-editor/helix/pull/1093))
+- ensure_selections_forward ([#1393](https://github.com/helix-editor/helix/pull/1393))
+- Readline style insert mode ([#1039](https://github.com/helix-editor/helix/pull/1039))
+
+Usability improvements and fixes:
+
+- Detect filetype on :write ([#1141](https://github.com/helix-editor/helix/pull/1141))
+- Add single and double quotes to matching pairs ([#995](https://github.com/helix-editor/helix/pull/995))
+- Launch with defaults upon invalid config/theme (rather than panicking) ([#982](https://github.com/helix-editor/helix/pull/982))
+- If switching away from an empty scratch buffer, remove it ([#935](https://github.com/helix-editor/helix/pull/935))
+- Truncate the starts of file paths instead of the ends in picker ([#951](https://github.com/helix-editor/helix/pull/951))
+- Truncate the start of file paths in the StatusLine ([#1351](https://github.com/helix-editor/helix/pull/1351))
+- Prevent picker from previewing binaries or large file ([#939](https://github.com/helix-editor/helix/pull/939))
+- Inform when reaching undo/redo bounds ([#981](https://github.com/helix-editor/helix/pull/981))
+- search_impl will only align cursor center when it isn't in view ([#959](https://github.com/helix-editor/helix/pull/959))
+- Add <C-h>, <C-u>, <C-d>, Delete in prompt mode ([#1034](https://github.com/helix-editor/helix/pull/1034))
+- Restore screen position when aborting search ([#1047](https://github.com/helix-editor/helix/pull/1047))
+- Buffer picker: show is_modifier flag ([#1020](https://github.com/helix-editor/helix/pull/1020))
+- Add commit hash to version info, if present ([#957](https://github.com/helix-editor/helix/pull/957))
+- Implement indent-aware delete ([#1120](https://github.com/helix-editor/helix/pull/1120))
+- Jump to end char of surrounding pair from any cursor pos ([#1121](https://github.com/helix-editor/helix/pull/1121))
+- File picker configuration ([#988](https://github.com/helix-editor/helix/pull/988))
+- Fix surround cursor position calculation ([#1183](https://github.com/helix-editor/helix/pull/1183))
+- Accept count for goto_window ([#1033](https://github.com/helix-editor/helix/pull/1033))
+- Make kill_to_line_end behave like emacs ([#1235](https://github.com/helix-editor/helix/pull/1235))
+- Only use a single documentation popup ([#1241](https://github.com/helix-editor/helix/pull/1241))
+- ui: popup: Don't allow scrolling past the end of content (3307f44c)
+- Open files with spaces in filename, allow opening multiple files ([#1231](https://github.com/helix-editor/helix/pull/1231))
+- Allow paste commands to take a count ([#1261](https://github.com/helix-editor/helix/pull/1261))
+- Auto pairs selection ([#1254](https://github.com/helix-editor/helix/pull/1254))
+- Use a fuzzy matcher for commands ([#1386](https://github.com/helix-editor/helix/pull/1386))
+- Add c-s to pick word under doc cursor to prompt line & search completion ([#831](https://github.com/helix-editor/helix/pull/831))
+- Fix :earlier/:later missing changeset update ([#1069](https://github.com/helix-editor/helix/pull/1069))
+- Support extend for multiple goto ([#909](https://github.com/helix-editor/helix/pull/909))
+- Add arrow-key bindings for window switching ([#933](https://github.com/helix-editor/helix/pull/933))
+- Implement key ordering for info box ([#952](https://github.com/helix-editor/helix/pull/952))
+
+LSP:
+- Implement MarkedString rendering (e128a8702)
+- Don't panic if init fails (d31bef7)
+- Configurable diagnostic severity ([#1325](https://github.com/helix-editor/helix/pull/1325))
+- Resolve completion item ([#1315](https://github.com/helix-editor/helix/pull/1315))
+- Code action command support ([#1304](https://github.com/helix-editor/helix/pull/1304))
+
+Grammars:
+
+- Adds mint language server ([#974](https://github.com/helix-editor/helix/pull/974))
+- Perl ([#978](https://github.com/helix-editor/helix/pull/978)) ([#1280](https://github.com/helix-editor/helix/pull/1280))
+- GLSL ([#993](https://github.com/helix-editor/helix/pull/993))
+- Racket ([#1143](https://github.com/helix-editor/helix/pull/1143))
+- WGSL ([#1166](https://github.com/helix-editor/helix/pull/1166))
+- LLVM ([#1167](https://github.com/helix-editor/helix/pull/1167)) ([#1388](https://github.com/helix-editor/helix/pull/1388)) ([#1409](https://github.com/helix-editor/helix/pull/1409)) ([#1398](https://github.com/helix-editor/helix/pull/1398))
+- Markdown (49e06787)
+- Scala ([#1278](https://github.com/helix-editor/helix/pull/1278))
+- Dart ([#1250](https://github.com/helix-editor/helix/pull/1250))
+- Fish ([#1308](https://github.com/helix-editor/helix/pull/1308))
+- Dockerfile ([#1303](https://github.com/helix-editor/helix/pull/1303))
+- Git (commit, rebase, diff) ([#1338](https://github.com/helix-editor/helix/pull/1338)) ([#1402](https://github.com/helix-editor/helix/pull/1402)) ([#1373](https://github.com/helix-editor/helix/pull/1373))
+- tree-sitter-comment ([#1300](https://github.com/helix-editor/helix/pull/1300))
+- Highlight comments in c, cpp, cmake and llvm ([#1309](https://github.com/helix-editor/helix/pull/1309))
+- Improve yaml syntax highlighting highlighting ([#1294](https://github.com/helix-editor/helix/pull/1294))
+- Improve rust syntax highlighting ([#1295](https://github.com/helix-editor/helix/pull/1295))
+- Add textobjects and indents to cmake ([#1307](https://github.com/helix-editor/helix/pull/1307))
+- Add textobjects and indents to c and cpp ([#1293](https://github.com/helix-editor/helix/pull/1293))
+
+New themes:
+
+- Solarized dark ([#999](https://github.com/helix-editor/helix/pull/999))
+- Solarized light ([#1010](https://github.com/helix-editor/helix/pull/1010))
+- Spacebones light ([#1131](https://github.com/helix-editor/helix/pull/1131))
+- Monokai Pro ([#1206](https://github.com/helix-editor/helix/pull/1206))
+- Base16 Light and Terminal ([#1078](https://github.com/helix-editor/helix/pull/1078))
+ - and a default 16 color theme, truecolor detection
+- Dracula ([#1258](https://github.com/helix-editor/helix/pull/1258))
+
# 0.5.0 (2021-10-28)
A big shout out to all the contributors! We had 46 contributors in this release.
diff --git a/Cargo.lock b/Cargo.lock
index cd6c0496..1a01da82 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -13,9 +13,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.51"
+version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
+checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
[[package]]
name = "arc-swap"
@@ -78,9 +78,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chardetng"
-version = "0.1.15"
+version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83ee29c16b81c32fbc882ecc568305793338a8353952573db837f4f4a6cd5c2e"
+checksum = "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea"
dependencies = [
"cfg-if",
"encoding_rs",
@@ -258,15 +258,15 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.18"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445"
+checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7"
[[package]]
name = "futures-executor"
-version = "0.3.18"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97"
+checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a"
dependencies = [
"futures-core",
"futures-task",
@@ -275,15 +275,15 @@ dependencies = [
[[package]]
name = "futures-task"
-version = "0.3.18"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12"
+checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72"
[[package]]
name = "futures-util"
-version = "0.3.18"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
+checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164"
dependencies = [
"futures-core",
"futures-task",
@@ -366,10 +366,11 @@ dependencies = [
[[package]]
name = "helix-core"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"arc-swap",
"chrono",
+ "encoding_rs",
"etcetera",
"helix-syntax",
"log",
@@ -391,7 +392,7 @@ dependencies = [
[[package]]
name = "helix-lsp"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"anyhow",
"futures-executor",
@@ -409,7 +410,7 @@ dependencies = [
[[package]]
name = "helix-syntax"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"anyhow",
"cc",
@@ -420,7 +421,7 @@ dependencies = [
[[package]]
name = "helix-term"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"anyhow",
"chrono",
@@ -451,7 +452,7 @@ dependencies = [
[[package]]
name = "helix-tui"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"bitflags",
"cassowary",
@@ -464,14 +465,13 @@ dependencies = [
[[package]]
name = "helix-view"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"anyhow",
"bitflags",
"chardetng",
"clipboard-win",
"crossterm",
- "encoding_rs",
"futures-util",
"helix-core",
"helix-lsp",
@@ -690,9 +690,9 @@ dependencies = [
[[package]]
name = "num_cpus"
-version = "1.13.0"
+version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
@@ -700,9 +700,9 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.8.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "parking_lot"
@@ -847,9 +847,9 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "ropey"
-version = "1.3.1"
+version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9150aff6deb25b20ed110889f070a678bcd1033e46e5e9d6fb1abeab17947f28"
+checksum = "e6b9aa65bcd9f308d37c7158b4a1afaaa32b8450213e20c9b98e7d5b3cc2fec3"
dependencies = [
"smallvec",
]
@@ -877,18 +877,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
-version = "1.0.131"
+version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1"
+checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.131"
+version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2"
+checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
dependencies = [
"proc-macro2",
"quote",
@@ -897,9 +897,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.73"
+version = "1.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5"
+checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142"
dependencies = [
"itoa",
"ryu",
@@ -919,9 +919,9 @@ dependencies = [
[[package]]
name = "signal-hook"
-version = "0.3.12"
+version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c35dfd12afb7828318348b8c408383cf5071a086c1d4ab1c0f9840ec92dbb922"
+checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
dependencies = [
"libc",
"signal-hook-registry",
@@ -1069,11 +1069,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
-version = "1.14.0"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
+checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
dependencies = [
- "autocfg",
"bytes",
"libc",
"memchr",
@@ -1089,9 +1088,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
-version = "1.6.0"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e"
+checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
dependencies = [
"proc-macro2",
"quote",
@@ -1120,9 +1119,9 @@ dependencies = [
[[package]]
name = "tree-sitter"
-version = "0.20.1"
+version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9394e9dbfe967b5f3d6ab79e302e78b5fb7b530c368d634ff3b8d67ede138bf1"
+checksum = "c36be3222512d85a112491ae0cc280a38076022414f00b64582da1b7565ffd82"
dependencies = [
"cc",
"regex",
@@ -1262,7 +1261,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "xtask"
-version = "0.5.0"
+version = "0.6.0"
dependencies = [
"helix-core",
"helix-term",
diff --git a/base16_theme.toml b/base16_theme.toml
index 5ec74bcc..bb60a3ea 100644
--- a/base16_theme.toml
+++ b/base16_theme.toml
@@ -29,6 +29,10 @@
"namespace" = "magenta"
"ui.help" = { fg = "white", bg = "black" }
+"diff.plus" = "green"
+"diff.delta" = "yellow"
+"diff.minus" = "red"
+
"diagnostic" = { modifiers = ["underlined"] }
"ui.gutter" = { bg = "black" }
"info" = "blue"
diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md
index 80989e63..a1fbf172 100644
--- a/book/src/generated/lang-support.md
+++ b/book/src/generated/lang-support.md
@@ -1,12 +1,19 @@
| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
| --- | --- | --- | --- | --- |
| bash | ✓ | | | `bash-language-server` |
-| c | ✓ | | | `clangd` |
+| c | ✓ | ✓ | ✓ | `clangd` |
| c-sharp | ✓ | | | |
-| cmake | ✓ | | | `cmake-language-server` |
-| cpp | ✓ | | | `clangd` |
+| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
+| comment | ✓ | | | |
+| cpp | ✓ | ✓ | ✓ | `clangd` |
| css | ✓ | | | |
+| dart | ✓ | | ✓ | `dart` |
+| dockerfile | ✓ | | | `docker-langserver` |
| elixir | ✓ | | | `elixir-ls` |
+| fish | ✓ | ✓ | ✓ | |
+| git-commit | ✓ | | | |
+| git-diff | ✓ | | | |
+| git-rebase | ✓ | | | |
| glsl | ✓ | | ✓ | |
| go | ✓ | ✓ | ✓ | `gopls` |
| html | ✓ | | | |
@@ -16,7 +23,9 @@
| julia | ✓ | | | `julia` |
| latex | ✓ | | | |
| ledger | ✓ | | | |
-| llvm | ✓ | | | |
+| llvm | ✓ | ✓ | ✓ | |
+| llvm-mir | ✓ | ✓ | ✓ | |
+| llvm-mir-yaml | ✓ | | ✓ | |
| lua | ✓ | | ✓ | |
| markdown | ✓ | | | |
| mint | | | | `mint` |
@@ -29,9 +38,11 @@
| protobuf | ✓ | | ✓ | |
| python | ✓ | ✓ | ✓ | `pylsp` |
| racket | | | | `racket` |
-| ruby | ✓ | | | `solargraph` |
+| ruby | ✓ | | ✓ | `solargraph` |
| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
+| scala | ✓ | | ✓ | `metals` |
| svelte | ✓ | | ✓ | `svelteserver` |
+| tablegen | ✓ | ✓ | ✓ | |
| toml | ✓ | | | |
| tsq | ✓ | | | |
| tsx | ✓ | | | `typescript-language-server` |
diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md
index bb21fd6b..65b2dc5f 100644
--- a/book/src/generated/typable-cmd.md
+++ b/book/src/generated/typable-cmd.md
@@ -20,6 +20,7 @@
| `:quit-all`, `:qa` | Close all views. |
| `:quit-all!`, `:qa!` | Close all views forcefully (ignoring unsaved changes). |
| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
+| `:cquit!`, `:cq!` | Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2). |
| `:theme` | Change the editor theme. |
| `:clipboard-yank` | Yank main selection into system clipboard. |
| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
@@ -41,3 +42,6 @@
| `:hsplit`, `:hs`, `:sp` | Open the file in a horizontal split. |
| `:tutor` | Open the tutorial. |
| `:goto`, `:g` | Go to line number. |
+| `:set-option`, `:set` | Set a config option at runtime |
+| `:sort` | Sort ranges in selection. |
+| `:rsort` | Sort ranges in selection in reverse order. |
diff --git a/book/src/guides/adding_languages.md b/book/src/guides/adding_languages.md
index 9ad2c285..5844a48e 100644
--- a/book/src/guides/adding_languages.md
+++ b/book/src/guides/adding_languages.md
@@ -27,22 +27,32 @@ directory](../configuration.md).
These are the available keys and descriptions for the file.
-| Key | Description |
-| ---- | ----------- |
-| name | The name of the language |
-| scope | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.<name>` or `text.<name>` in case of markup languages |
-| injection-regex | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. |
-| file-types | The filetypes of the language, for example `["yml", "yaml"]` |
-| shebangs | The interpreters from the shebang line, for example `["sh", "bash"]` |
-| roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
-| auto-format | Whether to autoformat this language when saving |
-| comment-token | The token to use as a comment-token |
-| indent | The indent to use. Has sub keys `tab-width` and `unit` |
-| config | Language server configuration |
+| Key | Description |
+| ---- | ----------- |
+| name | The name of the language |
+| scope | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.<name>` or `text.<name>` in case of markup languages |
+| injection-regex | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. |
+| file-types | The filetypes of the language, for example `["yml", "yaml"]` |
+| shebangs | The interpreters from the shebang line, for example `["sh", "bash"]` |
+| roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
+| auto-format | Whether to autoformat this language when saving |
+| diagnostic-severity | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
+| comment-token | The token to use as a comment-token |
+| indent | The indent to use. Has sub keys `tab-width` and `unit` |
+| config | Language server configuration |
## Queries
-For a language to have syntax-highlighting and indentation among other things, you have to add queries. Add a directory for your language with the path `runtime/queries/<name>/`. The tree-sitter [website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries) gives more info on how to write queries.
+For a language to have syntax-highlighting and indentation among
+other things, you have to add queries. Add a directory for your
+language with the path `runtime/queries/<name>/`. The tree-sitter
+[website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
+gives more info on how to write queries.
+
+> NOTE: When evaluating queries, the first matching query takes
+precedence, which is different from other editors like neovim where
+the last matching query supercedes the ones before it. See
+[this issue][neovim-query-precedence] for an example.
## Common Issues
@@ -58,3 +68,4 @@ For a language to have syntax-highlighting and indentation among other things, y
[treesitter-language-injection]: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
[languages.toml]: https://github.com/helix-editor/helix/blob/master/languages.toml
+[neovim-query-precedence]: https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090
diff --git a/book/src/keymap.md b/book/src/keymap.md
index f0a2cb30..70ec13b3 100644
--- a/book/src/keymap.md
+++ b/book/src/keymap.md
@@ -46,39 +46,39 @@
### Changes
-| Key | Description | Command |
-| ----- | ----------- | ------- |
-| `r` | Replace with a character | `replace` |
-| `R` | Replace with yanked text | `replace_with_yanked` |
-| `~` | Switch case of the selected text | `switch_case` |
-| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` |
-| `` 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` |
-| `o` | Open new line below selection | `open_below` |
-| `O` | Open new line above selection | `open_above` |
-| `.` | Repeat last change | N/A |
-| `u` | Undo change | `undo` |
-| `U` | Redo change | `redo` |
-| `Alt-u` | Move backward in history | `earlier` |
-| `Alt-U` | Move forward in history | `later` |
-| `y` | Yank selection | `yank` |
-| `p` | Paste after selection | `paste_after` |
-| `P` | Paste before selection | `paste_before` |
-| `"` `<reg>` | Select a register to yank to or paste from | `select_register` |
-| `>` | Indent selection | `indent` |
-| `<` | Unindent selection | `unindent` |
-| `=` | Format selection (currently nonfunctional/disabled) (**LSP**) | `format_selections` |
-| `d` | Delete selection | `delete_selection` |
-| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` |
-| `c` | Change selection (delete and enter insert mode) | `change_selection` |
-| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` |
-| `Ctrl-a` | Increment object (number) under cursor | `increment` |
-| `Ctrl-x` | Decrement object (number) under cursor | `decrement` |
-| `q` | Start/stop macro recording to the selected register | `record_macro` |
-| `Q` | Play back a recorded macro from the selected register | `play_macro` |
+| Key | Description | Command |
+| ----- | ----------- | ------- |
+| `r` | Replace with a character | `replace` |
+| `R` | Replace with yanked text | `replace_with_yanked` |
+| `~` | Switch case of the selected text | `switch_case` |
+| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` |
+| `` 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` |
+| `o` | Open new line below selection | `open_below` |
+| `O` | Open new line above selection | `open_above` |
+| `.` | Repeat last change | N/A |
+| `u` | Undo change | `undo` |
+| `U` | Redo change | `redo` |
+| `Alt-u` | Move backward in history | `earlier` |
+| `Alt-U` | Move forward in history | `later` |
+| `y` | Yank selection | `yank` |
+| `p` | Paste after selection | `paste_after` |
+| `P` | Paste before selection | `paste_before` |
+| `"` `<reg>` | Select a register to yank to or paste from | `select_register` |
+| `>` | Indent selection | `indent` |
+| `<` | Unindent selection | `unindent` |
+| `=` | Format selection (currently nonfunctional/disabled) (**LSP**) | `format_selections` |
+| `d` | Delete selection | `delete_selection` |
+| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` |
+| `c` | Change selection (delete and enter insert mode) | `change_selection` |
+| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` |
+| `Ctrl-a` | Increment object (number) under cursor | `increment` |
+| `Ctrl-x` | Decrement object (number) under cursor | `decrement` |
+| `Q` | Start/stop macro recording to the selected register (experimental) | `record_macro` |
+| `q` | Play back a recorded macro from the selected register (experimental) | `replay_macro` |
#### Shell
@@ -161,7 +161,7 @@ Jumps to various locations.
| Key | Description | Command |
| ----- | ----------- | ------- |
-| `g` | Go to the start of the file | `goto_file_start` |
+| `g` | Go to line number `<n>` else start of file | `goto_file_start` |
| `e` | Go to the end of the file | `goto_last_line` |
| `f` | Go to files in the selection | `goto_file` |
| `h` | Go to the start of the line | `goto_line_start` |
@@ -261,6 +261,8 @@ Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaire
| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
| `[space` | Add newline above | `add_newline_above` |
| `]space` | Add newline below | `add_newline_below` |
+| `]o` | Expand syntax tree object selection. | `expand_selection` |
+| `[o` | Shrink syntax tree object selection. | `shrink_selection` |
## Insert Mode
diff --git a/book/src/languages.md b/book/src/languages.md
index cef61501..4c4dc326 100644
--- a/book/src/languages.md
+++ b/book/src/languages.md
@@ -11,4 +11,3 @@ Changes made to the `languages.toml` file in a user's [configuration directory](
name = "rust"
auto-format = false
```
-
diff --git a/book/src/themes.md b/book/src/themes.md
index b6de7002..8eee334b 100644
--- a/book/src/themes.md
+++ b/book/src/themes.md
@@ -105,6 +105,7 @@ We use a similar set of scopes as
- `type` - Types
- `builtin` - Primitive types provided by the language (`int`, `usize`)
+- `constructor`
- `constant` (TODO: constant.other.placeholder for %v)
- `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
@@ -169,13 +170,20 @@ We use a similar set of scopes as
- `numbered`
- `bold`
- `italic`
- - `underline`
- - `link`
+ - `link`
+ - `url`
+ - `label`
- `quote`
- `raw`
- `inline`
- `block`
+- `diff` - version control changes
+ - `plus` - additions
+ - `minus` - deletions
+ - `delta` - modifications
+ - `moved` - renamed or moved files/changes
+
#### Interface
These scopes are used for theming the editor interface.
diff --git a/flake.lock b/flake.lock
index 2c59f993..acd10f76 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"devshell": {
"locked": {
- "lastModified": 1637575296,
- "narHash": "sha256-ZY8YR5u8aglZPe27+AJMnPTG6645WuavB+w0xmhTarw=",
+ "lastModified": 1639692811,
+ "narHash": "sha256-wOOBH0fVsfNqw/5ZWRoKspyesoXBgiwEOUBH4c7JKEo=",
"owner": "numtide",
"repo": "devshell",
- "rev": "0e56ef21ba1a717169953122c7415fa6a8cd2618",
+ "rev": "d3a1f5bec3632b33346865b1c165bf2420bb2f52",
"type": "github"
},
"original": {
@@ -41,11 +41,11 @@
]
},
"locked": {
- "lastModified": 1638425401,
- "narHash": "sha256-xc8ayvR3u90hSCMEy0zHHKav7lEgljAFXL4oIkWRp3M=",
+ "lastModified": 1639807801,
+ "narHash": "sha256-y32tMq1LTRVbMW3QN5i98iOQjQt2QSsif3ayUkD1o3g=",
"owner": "yusdacra",
"repo": "nix-cargo-integration",
- "rev": "1f8b511bb30f7d7b9051dfbb4784390bc0d48d37",
+ "rev": "b5bbaa4f5239e6f0619846f9a5380f07baa853d3",
"type": "github"
},
"original": {
@@ -56,11 +56,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1638376152,
- "narHash": "sha256-ucgLpVqhFnClH7YRUHBHnmiOd82RZdFR3XJt36ks5fE=",
+ "lastModified": 1639699734,
+ "narHash": "sha256-tlX6WebGmiHb2Hmniff+ltYp+7dRfdsBxw9YczLsP60=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "6daa4a5c045d40e6eae60a3b6e427e8700f1c07f",
+ "rev": "03ec468b14067729a285c2c7cfa7b9434a04816c",
"type": "github"
},
"original": {
@@ -99,11 +99,11 @@
"nixpkgs": "nixpkgs_2"
},
"locked": {
- "lastModified": 1638497756,
- "narHash": "sha256-zKOvMKqGp71ZBnR+hBlPcv4TwNN82COW9EF+6ygrFs8=",
+ "lastModified": 1639880499,
+ "narHash": "sha256-/BibDmFwgWuuTUkNVO6YlvuTSWM9dpBvlZoTAPs7ORI=",
"owner": "oxalica",
"repo": "rust-overlay",
- "rev": "783722a22ee5d762ac5c1c7b418b57b3010c827a",
+ "rev": "c6c83589ae048af20d93d01eb07a4176012093d0",
"type": "github"
},
"original": {
diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml
index 0a2a56d9..3c11ec76 100644
--- a/helix-core/Cargo.toml
+++ b/helix-core/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "helix-core"
-version = "0.5.0"
+version = "0.6.0"
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
edition = "2021"
license = "MPL-2.0"
@@ -13,7 +13,7 @@ include = ["src/**/*", "README.md"]
[features]
[dependencies]
-helix-syntax = { version = "0.5", path = "../helix-syntax" }
+helix-syntax = { version = "0.6", path = "../helix-syntax" }
ropey = "1.3"
smallvec = "1.7"
@@ -23,7 +23,7 @@ unicode-width = "0.1"
unicode-general-category = "0.4"
# slab = "0.4.2"
tree-sitter = "0.20"
-once_cell = "1.8"
+once_cell = "1.9"
arc-swap = "1"
regex = "1"
@@ -35,6 +35,7 @@ toml = "0.5"
similar = "2.1"
etcetera = "0.3"
+encoding_rs = "0.8"
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
diff --git a/helix-core/src/auto_pairs.rs b/helix-core/src/auto_pairs.rs
index c037afef..1b3de6ea 100644
--- a/helix-core/src/auto_pairs.rs
+++ b/helix-core/src/auto_pairs.rs
@@ -1,7 +1,7 @@
//! When typing the opening character of one of the possible pairs defined below,
//! this module provides the functionality to insert the paired closing character.
-use crate::{Range, Rope, Selection, Tendril, Transaction};
+use crate::{movement::Direction, Range, Rope, Selection, Tendril, Transaction};
use log::debug;
use smallvec::SmallVec;
@@ -30,7 +30,6 @@ const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{20
// [TODO]
// * delete implementation where it erases the whole bracket (|) -> |
-// * do not reduce to cursors; use whole selections, and surround with pair
// * change to multi character pairs to handle cases like placing the cursor in the
// middle of triple quotes, and more exotic pairs like Jinja's {% %}
@@ -38,20 +37,18 @@ const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{20
pub fn hook(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
debug!("autopairs hook selection: {:#?}", selection);
- let cursors = selection.clone().cursors(doc.slice(..));
-
for &(open, close) in PAIRS {
if open == ch {
if open == close {
- return Some(handle_same(doc, &cursors, open, CLOSE_BEFORE, OPEN_BEFORE));
+ return Some(handle_same(doc, selection, open, CLOSE_BEFORE, OPEN_BEFORE));
} else {
- return Some(handle_open(doc, &cursors, open, close, CLOSE_BEFORE));
+ return Some(handle_open(doc, selection, open, close, CLOSE_BEFORE));
}
}
if close == ch {
// && char_at pos == close
- return Some(handle_close(doc, &cursors, open, close));
+ return Some(handle_close(doc, selection, open, close));
}
}
@@ -66,6 +63,36 @@ fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
doc.get_char(pos - 1)
}
+/// calculate what the resulting range should be for an auto pair insertion
+fn get_next_range(
+ start_range: &Range,
+ offset: usize,
+ typed_char: char,
+ len_inserted: usize,
+) -> Range {
+ let end_head = start_range.head + offset + typed_char.len_utf8();
+
+ let end_anchor = match (start_range.len(), start_range.direction()) {
+ // if we have a zero width cursor, it shifts to the same number
+ (0, _) => end_head,
+
+ // if we are inserting for a regular one-width cursor, the anchor
+ // moves with the head
+ (1, Direction::Forward) => end_head - 1,
+ (1, Direction::Backward) => end_head + 1,
+
+ // if we are appending, the anchor stays where it is; only offset
+ // for multiple range insertions
+ (_, Direction::Forward) => start_range.anchor + offset,
+
+ // when we are inserting in front of a selection, we need to move
+ // the anchor over by however many characters were inserted overall
+ (_, Direction::Backward) => start_range.anchor + offset + len_inserted,
+ };
+
+ Range::new(end_anchor, end_head)
+}
+
fn handle_open(
doc: &Rope,
selection: &Selection,
@@ -74,36 +101,32 @@ fn handle_open(
close_before: &str,
) -> Transaction {
let mut end_ranges = SmallVec::with_capacity(selection.len());
-
let mut offs = 0;
let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
- let start_head = start_range.head;
-
- let next = doc.get_char(start_head);
- let end_head = start_head + offs + open.len_utf8();
-
- let end_anchor = if start_range.is_empty() {
- end_head
- } else {
- start_range.anchor + offs
- };
-
- end_ranges.push(Range::new(end_anchor, end_head));
+ let cursor = start_range.cursor(doc.slice(..));
+ let next_char = doc.get_char(cursor);
+ let len_inserted;
- match next {
+ let change = match next_char {
Some(ch) if !close_before.contains(ch) => {
- offs += open.len_utf8();
- (start_head, start_head, Some(Tendril::from_char(open)))
+ len_inserted = open.len_utf8();
+ (cursor, cursor, Some(Tendril::from_char(open)))
}
// None | Some(ch) if close_before.contains(ch) => {}
_ => {
// insert open & close
let pair = Tendril::from_iter([open, close]);
- offs += open.len_utf8() + close.len_utf8();
- (start_head, start_head, Some(pair))
+ len_inserted = open.len_utf8() + close.len_utf8();
+ (cursor, cursor, Some(pair))
}
- }
+ };
+
+ let next_range = get_next_range(start_range, offs, open, len_inserted);
+ end_ranges.push(next_range);
+ offs += len_inserted;
+
+ change
});
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
@@ -117,28 +140,28 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) ->
let mut offs = 0;
let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
- let start_head = start_range.head;
- let next = doc.get_char(start_head);
- let end_head = start_head + offs + close.len_utf8();
+ let cursor = start_range.cursor(doc.slice(..));
+ let next_char = doc.get_char(cursor);
+ let mut len_inserted = 0;
- let end_anchor = if start_range.is_empty() {
- end_head
+ let change = if next_char == Some(close) {
+ // return transaction that moves past close
+ (cursor, cursor, None) // no-op
} else {
- start_range.anchor + offs
+ len_inserted += close.len_utf8();
+ (cursor, cursor, Some(Tendril::from_char(close)))
};
- end_ranges.push(Range::new(end_anchor, end_head));
+ let next_range = get_next_range(start_range, offs, close, len_inserted);
+ end_ranges.push(next_range);
+ offs += len_inserted;
- if next == Some(close) {
- // return transaction that moves past close
- (start_head, start_head, None) // no-op
- } else {
- offs += close.len_utf8();
- (start_head, start_head, Some(Tendril::from_char(close)))
- }
+ change
});
- transaction.with_selection(Selection::new(end_ranges, selection.primary_index()))
+ let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
+ debug!("auto pair transaction: {:#?}", t);
+ t
}
/// handle cases where open and close is the same, or in triples ("""docstring""")
@@ -154,42 +177,41 @@ fn handle_same(
let mut offs = 0;
let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
- let start_head = start_range.head;
- let end_head = start_head + offs + token.len_utf8();
+ let cursor = start_range.cursor(doc.slice(..));
+ let mut len_inserted = 0;
- // if selection, retain anchor, if cursor, move over
- let end_anchor = if start_range.is_empty() {
- end_head
- } else {
- start_range.anchor + offs
- };
+ let next_char = doc.get_char(cursor);
+ let prev_char = prev_char(doc, cursor);
- end_ranges.push(Range::new(end_anchor, end_head));
-
- let next = doc.get_char(start_head);
- let prev = prev_char(doc, start_head);
-
- if next == Some(token) {
+ let change = if next_char == Some(token) {
// return transaction that moves past close
- (start_head, start_head, None) // no-op
+ (cursor, cursor, None) // no-op
} else {
let mut pair = Tendril::with_capacity(2 * token.len_utf8() as u32);
pair.push_char(token);
// for equal pairs, don't insert both open and close if either
// side has a non-pair char
- if (next.is_none() || close_before.contains(next.unwrap()))
- && (prev.is_none() || open_before.contains(prev.unwrap()))
+ if (next_char.is_none() || close_before.contains(next_char.unwrap()))
+ && (prev_char.is_none() || open_before.contains(prev_char.unwrap()))
{
pair.push_char(token);
}
- offs += pair.len();
- (start_head, start_head, Some(pair))
- }
+ len_inserted += pair.len();
+ (cursor, cursor, Some(pair))
+ };
+
+ let next_range = get_next_range(start_range, offs, token, len_inserted);
+ end_ranges.push(next_range);
+ offs += len_inserted;
+
+ change
});
- transaction.with_selection(Selection::new(end_ranges, selection.primary_index()))
+ let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
+ debug!("auto pair transaction: {:#?}", t);
+ t
}
#[cfg(test)]
@@ -252,7 +274,20 @@ mod test {
&Selection::single(1, 0),
PAIRS,
|open, close| format!("{}{}", open, close),
- &Selection::single(1, 1),
+ &Selection::single(2, 1),
+ );
+ }
+
+ /// [] -> append ( -> ([])
+ #[test]
+ fn test_append_blank() {
+ test_hooks_with_pairs(
+ // this is what happens when you have a totally blank document and then append
+ &Rope::from("\n\n"),
+ &Selection::single(0, 2),
+ PAIRS,
+ |open, close| format!("\n{}{}\n", open, close),
+ &Selection::single(0, 3),
);
}
@@ -276,26 +311,50 @@ mod test {
)
},
&Selection::new(
- smallvec!(Range::point(1), Range::point(4), Range::point(7),),
+ smallvec!(Range::new(2, 1), Range::new(5, 4), Range::new(8, 7),),
0,
),
);
}
- // [TODO] broken until it works with selections
/// fo[o] -> append ( -> fo[o(])
- #[ignore]
#[test]
fn test_append() {
test_hooks_with_pairs(
- &Rope::from("foo"),
+ &Rope::from("foo\n"),
&Selection::single(2, 4),
- PAIRS,
- |open, close| format!("foo{}{}", open, close),
+ differing_pairs(),
+ |open, close| format!("foo{}{}\n", open, close),
&Selection::single(2, 5),
);
}
+ /// fo[o] fo[o(])
+ /// fo[o] -> append ( -> fo[o(])
+ /// fo[o] fo[o(])
+ #[test]
+ fn test_append_multi() {
+ test_hooks_with_pairs(
+ &Rope::from("foo\nfoo\nfoo\n"),
+ &Selection::new(
+ smallvec!(Range::new(2, 4), Range::new(6, 8), Range::new(10, 12)),
+ 0,
+ ),
+ differing_pairs(),
+ |open, close| {
+ format!(
+ "foo{open}{close}\nfoo{open}{close}\nfoo{open}{close}\n",
+ open = open,
+ close = close
+ )
+ },
+ &Selection::new(
+ smallvec!(Range::new(2, 5), Range::new(8, 11), Range::new(14, 17)),
+ 0,
+ ),
+ );
+ }
+
/// ([]) -> insert ) -> ()[]
#[test]
fn test_insert_close_inside_pair() {
@@ -307,7 +366,23 @@ mod test {
&Selection::single(2, 1),
*close,
&doc,
- &Selection::point(2),
+ &Selection::single(3, 2),
+ );
+ }
+ }
+
+ /// [(]) -> append ) -> [()]
+ #[test]
+ fn test_append_close_inside_pair() {
+ for (open, close) in PAIRS {
+ let doc = Rope::from(format!("{}{}\n", open, close));
+
+ test_hooks(
+ &doc,
+ &Selection::single(0, 2),
+ *close,
+ &doc,
+ &Selection::single(0, 3),
);
}
}
@@ -323,8 +398,33 @@ mod test {
);
let expected_sel = Selection::new(
- // smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),),
- smallvec!(Range::point(2), Range::point(5), Range::point(8),),
+ smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),),
+ 0,
+ );
+
+ for (open, close) in PAIRS {
+ let doc = Rope::from(format!(
+ "{open}{close}\n{open}{close}\n{open}{close}\n",
+ open = open,
+ close = close
+ ));
+
+ test_hooks(&doc, &sel, *close, &doc, &expected_sel);
+ }
+ }
+
+ /// [(]) [()]
+ /// [(]) -> append ) -> [()]
+ /// [(]) [()]
+ #[test]
+ fn test_append_close_inside_pair_multi_cursor() {
+ let sel = Selection::new(
+ smallvec!(Range::new(0, 2), Range::new(3, 5), Range::new(6, 8),),
+ 0,
+ );
+
+ let expected_sel = Selection::new(
+ smallvec!(Range::new(0, 3), Range::new(3, 6), Range::new(6, 9),),
0,
);
@@ -343,7 +443,7 @@ mod test {
#[test]
fn test_insert_open_inside_pair() {
let sel = Selection::single(2, 1);
- let expected_sel = Selection::point(2);
+ let expected_sel = Selection::single(3, 2);
for (open, close) in differing_pairs() {
let doc = Rope::from(format!("{}{}", open, close));
@@ -357,11 +457,49 @@ mod test {
}
}
+ /// [word(]) -> append ( -> [word((]))
+ #[test]
+ fn test_append_open_inside_pair() {
+ let sel = Selection::single(0, 6);
+ let expected_sel = Selection::single(0, 7);
+
+ for (open, close) in differing_pairs() {
+ let doc = Rope::from(format!("word{}{}", open, close));
+ let expected_doc = Rope::from(format!(
+ "word{open}{open}{close}{close}",
+ open = open,
+ close = close
+ ));
+
+ test_hooks(&doc, &sel, *open, &expected_doc, &expected_sel);
+ }
+ }
+
/// ([]) -> insert " -> ("[]")
#[test]
fn test_insert_nested_open_inside_pair() {
let sel = Selection::single(2, 1);
- let expected_sel = Selection::point(2);
+ let expected_sel = Selection::single(3, 2);
+
+ for (outer_open, outer_close) in differing_pairs() {
+ let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
+
+ for (inner_open, inner_close) in matching_pairs() {
+ let expected_doc = Rope::from(format!(
+ "{}{}{}{}",
+ outer_open, inner_open, inner_close, outer_close
+ ));
+
+ test_hooks(&doc, &sel, *inner_open, &expected_doc, &expected_sel);
+ }
+ }
+ }
+
+ /// [(]) -> append " -> [("]")
+ #[test]
+ fn test_append_nested_open_inside_pair() {
+ let sel = Selection::single(0, 2);
+ let expected_sel = Selection::single(0, 3);
for (outer_open, outer_close) in differing_pairs() {
let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
@@ -385,21 +523,44 @@ mod test {
&Selection::single(1, 0),
PAIRS,
|open, _| format!("{}word", open),
- &Selection::point(1),
+ &Selection::single(2, 1),
)
}
- // [TODO] broken until it works with selections
/// [wor]d -> insert ( -> ([wor]d
#[test]
- #[ignore]
fn test_insert_open_with_selection() {
test_hooks_with_pairs(
&Rope::from("word"),
- &Selection::single(0, 4),
+ &Selection::single(3, 0),
PAIRS,
|open, _| format!("{}word", open),
- &Selection::single(1, 5),
+ &Selection::single(4, 1),
+ )
+ }
+
+ /// [wor]d -> append ) -> [wor)]d
+ #[test]
+ fn test_append_close_inside_non_pair_with_selection() {
+ let sel = Selection::single(0, 4);
+ let expected_sel = Selection::single(0, 5);
+
+ for (_, close) in PAIRS {
+ let doc = Rope::from("word");
+ let expected_doc = Rope::from(format!("wor{}d", close));
+ test_hooks(&doc, &sel, *close, &expected_doc, &expected_sel);
+ }
+ }
+
+ /// foo[ wor]d -> insert ( -> foo([) wor]d
+ #[test]
+ fn test_insert_open_trailing_word_with_selection() {
+ test_hooks_with_pairs(
+ &Rope::from("foo word"),
+ &Selection::single(7, 3),
+ differing_pairs(),
+ |open, close| format!("foo{}{} word", open, close),
+ &Selection::single(9, 4),
)
}
@@ -413,7 +574,7 @@ mod test {
fn test_insert_open_after_non_pair() {
let doc = Rope::from("word");
let sel = Selection::single(5, 4);
- let expected_sel = Selection::point(5);
+ let expected_sel = Selection::single(6, 5);
test_hooks_with_pairs(
&doc,
@@ -431,4 +592,18 @@ mod test {
&expected_sel,
);
}
+
+ /// appending with only a cursor should stay a cursor
+ ///
+ /// [] -> append to end "foo -> "foo[]"
+ #[test]
+ fn test_append_single_cursor() {
+ test_hooks_with_pairs(
+ &Rope::from("\n"),
+ &Selection::single(0, 1),
+ PAIRS,
+ |open, close| format!("{}{}\n", open, close),
+ &Selection::single(1, 2),
+ );
+ }
}
diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs
index 4fcf51c9..210ad639 100644
--- a/helix-core/src/diagnostic.rs
+++ b/helix-core/src/diagnostic.rs
@@ -1,12 +1,19 @@
//! LSP diagnostic utility types.
+use serde::{Deserialize, Serialize};
/// Describes the severity level of a [`Diagnostic`].
-#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
pub enum Severity {
- Error,
- Warning,
- Info,
Hint,
+ Info,
+ Warning,
+ Error,
+}
+
+impl Default for Severity {
+ fn default() -> Self {
+ Self::Hint
+ }
}
/// A range of `char`s within the text.
diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs
index b6f5081a..1fc2b8a5 100644
--- a/helix-core/src/indent.rs
+++ b/helix-core/src/indent.rs
@@ -1,6 +1,5 @@
use crate::{
chars::{char_is_line_ending, char_is_whitespace},
- find_first_non_whitespace_char,
syntax::{IndentQuery, LanguageConfiguration, Syntax},
tree_sitter::Node,
Rope, RopeSlice,
@@ -174,8 +173,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
/// To determine indentation of a newly inserted line, figure out the indentation at the last col
/// of the previous line.
-#[allow(dead_code)]
-fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
+pub fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
let mut len = 0;
for ch in line.chars() {
match ch {
@@ -210,10 +208,15 @@ fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Nod
Some(node)
}
-fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool) -> usize {
- // NOTE: can't use contains() on query because of comparing Vec<String> and &str
- // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.contains
-
+/// Calculate the indentation at a given treesitter node.
+/// If newline is false, then any "indent" nodes on the line are ignored ("outdent" still applies).
+/// This is because the indentation is only increased starting at the second line of the node.
+fn calculate_indentation(
+ query: &IndentQuery,
+ node: Option<Node>,
+ line: usize,
+ newline: bool,
+) -> usize {
let mut increment: isize = 0;
let mut node = match node {
@@ -221,70 +224,45 @@ fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool)
None => return 0,
};
- let mut prev_start = node.start_position().row;
-
- // if we're calculating indentation for a brand new line then the current node will become the
- // parent node. We need to take it's indentation level into account too.
- let node_kind = node.kind();
- if newline && query.indent.contains(node_kind) {
- increment += 1;
- }
-
- while let Some(parent) = node.parent() {
- let parent_kind = parent.kind();
- let start = parent.start_position().row;
-
- // detect deeply nested indents in the same line
- // .map(|a| { <-- ({ is two scopes
- // let len = 1; <-- indents one level
- // }) <-- }) is two scopes
- let starts_same_line = start == prev_start;
-
- if query.outdent.contains(node.kind()) && !starts_same_line {
- // we outdent by skipping the rules for the current level and jumping up
- // node = parent;
- increment -= 1;
- // continue;
+ let mut current_line = line;
+ let mut consider_indent = newline;
+ let mut increment_from_line: isize = 0;
+
+ loop {
+ let node_kind = node.kind();
+ let start = node.start_position().row;
+ if current_line != start {
+ // Indent/dedent by at most one per line:
+ // .map(|a| { <-- ({ is two scopes
+ // let len = 1; <-- indents one level
+ // }) <-- }) is two scopes
+ if consider_indent || increment_from_line < 0 {
+ increment += increment_from_line.signum();
+ }
+ increment_from_line = 0;
+ current_line = start;
+ consider_indent = true;
}
- if query.indent.contains(parent_kind) // && not_first_or_last_sibling
- && !starts_same_line
- {
- // println!("is_scope {}", parent_kind);
- prev_start = start;
- increment += 1
+ if query.outdent.contains(node_kind) {
+ increment_from_line -= 1;
+ }
+ if query.indent.contains(node_kind) {
+ increment_from_line += 1;
}
- // if last_scope && increment > 0 && ...{ ignore }
-
- node = parent;
+ if let Some(parent) = node.parent() {
+ node = parent;
+ } else {
+ break;
+ }
+ }
+ if consider_indent || increment_from_line < 0 {
+ increment += increment_from_line.signum();
}
-
increment.max(0) as usize
}
-#[allow(dead_code)]
-fn suggested_indent_for_line(
- language_config: &LanguageConfiguration,
- syntax: Option<&Syntax>,
- text: RopeSlice,
- line_num: usize,
- _tab_width: usize,
-) -> usize {
- if let Some(start) = find_first_non_whitespace_char(text.line(line_num)) {
- return suggested_indent_for_pos(
- Some(language_config),
- syntax,
- text,
- start + text.line_to_char(line_num),
- false,
- );
- };
-
- // if the line is blank, indent should be zero
- 0
-}
-
// TODO: two usecases: if we are triggering this for a new, blank line:
// - it should return 0 when mass indenting stuff
// - it should look up the wrapper node and count it too when we press o/O
@@ -293,23 +271,20 @@ pub fn suggested_indent_for_pos(
syntax: Option<&Syntax>,
text: RopeSlice,
pos: usize,
+ line: usize,
new_line: bool,
-) -> usize {
+) -> Option<usize> {
if let (Some(query), Some(syntax)) = (
language_config.and_then(|config| config.indent_query()),
syntax,
) {
let byte_start = text.char_to_byte(pos);
let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
-
- // let config = load indentation query config from Syntax(should contain language_config)
-
// TODO: special case for comments
// TODO: if preserve_leading_whitespace
- calculate_indentation(query, node, new_line)
+ Some(calculate_indentation(query, node, line, new_line))
} else {
- // TODO: heuristics for non-tree sitter grammars
- 0
+ None
}
}
@@ -442,6 +417,7 @@ where
);
let doc = Rope::from(doc);
+ use crate::diagnostic::Severity;
use crate::syntax::{
Configuration, IndentationConfiguration, LanguageConfiguration, Loader,
};
@@ -459,6 +435,8 @@ where
roots: vec![],
comment_token: None,
auto_format: false,
+ diagnostic_severity: Severity::Warning,
+ tree_sitter_library: None,
language_server: None,
indent: Some(IndentationConfiguration {
tab_width: 4,
@@ -482,14 +460,23 @@ where
for i in 0..doc.len_lines() {
let line = text.line(i);
- let indent = indent_level_for_line(line, tab_width);
- assert_eq!(
- suggested_indent_for_line(&language_config, Some(&syntax), text, i, tab_width),
- indent,
- "line {}: {}",
- i,
- line
- );
+ if let Some(pos) = crate::find_first_non_whitespace_char(line) {
+ let indent = indent_level_for_line(line, tab_width);
+ assert_eq!(
+ suggested_indent_for_pos(
+ Some(&language_config),
+ Some(&syntax),
+ text,
+ text.line_to_char(i) + pos,
+ i,
+ false
+ ),
+ Some(indent),
+ "line {}: \"{}\"",
+ i,
+ line
+ );
+ }
}
}
}
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index 92a59f31..7fd23b97 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -1,3 +1,5 @@
+pub use encoding_rs as encoding;
+
pub mod auto_pairs;
pub mod chars;
pub mod comment;
@@ -37,8 +39,14 @@ pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
line.chars().position(|ch| !ch.is_whitespace())
}
-/// Find `.git` root.
-pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
+/// Find project root.
+///
+/// Order of detection:
+/// * Top-most folder containing a root marker in current git repository
+/// * Git repostory root if no marker detected
+/// * Top-most folder containing a root marker if not git repository detected
+/// * Current working directory as fallback
+pub fn find_root(root: Option<&str>, root_markers: &[String]) -> Option<std::path::PathBuf> {
let current_dir = std::env::current_dir().expect("unable to determine current directory");
let root = match root {
@@ -50,16 +58,30 @@ pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
current_dir.join(root)
}
}
- None => current_dir,
+ None => current_dir.clone(),
};
+ let mut top_marker = None;
for ancestor in root.ancestors() {
- // TODO: also use defined roots if git isn't found
+ for marker in root_markers {
+ if ancestor.join(marker).exists() {
+ top_marker = Some(ancestor);
+ break;
+ }
+ }
+ // don't go higher than repo
if ancestor.join(".git").is_dir() {
- return Some(ancestor.to_path_buf());
+ // Use workspace if detected from marker
+ return Some(top_marker.unwrap_or(ancestor).to_path_buf());
}
}
- None
+
+ // In absence of git repo, use workspace if detected
+ if top_marker.is_some() {
+ top_marker.map(|a| a.to_path_buf())
+ } else {
+ Some(current_dir)
+ }
}
pub fn runtime_dir() -> std::path::PathBuf {
diff --git a/helix-core/src/match_brackets.rs b/helix-core/src/match_brackets.rs
index cd554005..0189dedd 100644
--- a/helix-core/src/match_brackets.rs
+++ b/helix-core/src/match_brackets.rs
@@ -11,7 +11,7 @@ const PAIRS: &[(char, char)] = &[
('\"', '\"'),
];
-// limit matching pairs to only ( ) { } [ ] < >
+// limit matching pairs to only ( ) { } [ ] < > ' ' " "
// Returns the position of the matching bracket under cursor.
//
diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs
index 01a8f890..47fe6827 100644
--- a/helix-core/src/movement.rs
+++ b/helix-core/src/movement.rs
@@ -307,8 +307,6 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
#[cfg(test)]
mod test {
- use std::array::{self, IntoIter};
-
use ropey::Rope;
use super::*;
@@ -360,7 +358,7 @@ mod test {
((Direction::Backward, 999usize), (0, 0)), // |This is a simple alphabetic line
];
- for ((direction, amount), coordinates) in IntoIter::new(moves_and_expected_coordinates) {
+ for ((direction, amount), coordinates) in moves_and_expected_coordinates {
range = move_horizontally(slice, range, direction, amount, Movement::Move);
assert_eq!(coords_at_pos(slice, range.head), coordinates.into())
}
@@ -374,7 +372,7 @@ mod test {
let mut range = Range::point(position);
- let moves_and_expected_coordinates = IntoIter::new([
+ let moves_and_expected_coordinates = [
((Direction::Forward, 11usize), (1, 1)), // Multiline\nt|ext sample\n...
((Direction::Backward, 1usize), (1, 0)), // Multiline\n|text sample\n...
((Direction::Backward, 5usize), (0, 5)), // Multi|line\ntext sample\n...
@@ -384,7 +382,7 @@ mod test {
((Direction::Backward, 0usize), (0, 3)), // Mul|tiline\ntext sample\n...
((Direction::Forward, 999usize), (5, 0)), // ...and whitespaced\n|
((Direction::Forward, 999usize), (5, 0)), // ...and whitespaced\n|
- ]);
+ ];
for ((direction, amount), coordinates) in moves_and_expected_coordinates {
range = move_horizontally(slice, range, direction, amount, Movement::Move);
@@ -402,11 +400,11 @@ mod test {
let mut range = Range::point(position);
let original_anchor = range.anchor;
- let moves = IntoIter::new([
+ let moves = [
(Direction::Forward, 1usize),
(Direction::Forward, 5usize),
(Direction::Backward, 3usize),
- ]);
+ ];
for (direction, amount) in moves {
range = move_horizontally(slice, range, direction, amount, Movement::Extend);
@@ -420,7 +418,7 @@ mod test {
let slice = text.slice(..);
let position = pos_at_coords(slice, (0, 0).into(), true);
let mut range = Range::point(position);
- let moves_and_expected_coordinates = IntoIter::new([
+ let moves_and_expected_coordinates = [
((Direction::Forward, 1usize), (1, 0)),
((Direction::Forward, 2usize), (3, 0)),
((Direction::Forward, 1usize), (4, 0)),
@@ -430,7 +428,7 @@ mod test {
((Direction::Backward, 0usize), (4, 0)),
((Direction::Forward, 5), (5, 0)),
((Direction::Forward, 999usize), (5, 0)),
- ]);
+ ];
for ((direction, amount), coordinates) in moves_and_expected_coordinates {
range = move_vertically(slice, range, direction, amount, Movement::Move);
@@ -450,7 +448,7 @@ mod test {
H,
V,
}
- let moves_and_expected_coordinates = IntoIter::new([
+ let moves_and_expected_coordinates = [
// Places cursor at the end of line
((Axis::H, Direction::Forward, 8usize), (0, 8)),
// First descent preserves column as the target line is wider
@@ -463,7 +461,7 @@ mod test {
((Axis::V, Direction::Backward, 999usize), (0, 8)),
((Axis::V, Direction::Forward, 4usize), (4, 8)),
((Axis::V, Direction::Forward, 999usize), (5, 0)),
- ]);
+ ];
for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
range = match axis {
@@ -489,7 +487,7 @@ mod test {
H,
V,
}
- let moves_and_expected_coordinates = IntoIter::new([
+ let moves_and_expected_coordinates = [
// Places cursor at the fourth kana.
((Axis::H, Direction::Forward, 4), (0, 4)),
// Descent places cursor at the 4th character.
@@ -498,7 +496,7 @@ mod test {
((Axis::H, Direction::Backward, 1usize), (1, 3)),
// Jumping back up 1 line.
((Axis::V, Direction::Backward, 1usize), (0, 3)),
- ]);
+ ];
for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
range = match axis {
@@ -530,7 +528,7 @@ mod test {
#[test]
fn test_behaviour_when_moving_to_start_of_next_words() {
- let tests = array::IntoIter::new([
+ let tests = [
("Basic forward motion stops at the first space",
vec![(1, Range::new(0, 0), Range::new(0, 6))]),
(" Starting from a boundary advances the anchor",
@@ -604,7 +602,7 @@ mod test {
vec![
(1, Range::new(0, 0), Range::new(0, 6)),
]),
- ]);
+ ];
for (sample, scenario) in tests {
for (count, begin, expected_end) in scenario.into_iter() {
@@ -616,7 +614,7 @@ mod test {
#[test]
fn test_behaviour_when_moving_to_start_of_next_long_words() {
- let tests = array::IntoIter::new([
+ let tests = [
("Basic forward motion stops at the first space",
vec![(1, Range::new(0, 0), Range::new(0, 6))]),
(" Starting from a boundary advances the anchor",
@@ -688,7 +686,7 @@ mod test {
vec![
(1, Range::new(0, 0), Range::new(0, 8)),
]),
- ]);
+ ];
for (sample, scenario) in tests {
for (count, begin, expected_end) in scenario.into_iter() {
@@ -700,7 +698,7 @@ mod test {
#[test]
fn test_behaviour_when_moving_to_start_of_previous_words() {
- let tests = array::IntoIter::new([
+ let tests = [
("Basic backward motion from the middle of a word",
vec![(1, Range::new(3, 3), Range::new(4, 0))]),
@@ -773,7 +771,7 @@ mod test {
vec![
(1, Range::new(0, 6), Range::new(6, 0)),
]),
- ]);
+ ];
for (sample, scenario) in tests {
for (count, begin, expected_end) in scenario.into_iter() {
@@ -785,7 +783,7 @@ mod test {
#[test]
fn test_behaviour_when_moving_to_start_of_previous_long_words() {
- let tests = array::IntoIter::new([
+ let tests = [
(
"Basic backward motion from the middle of a word",
vec![(1, Range::new(3, 3), Range::new(4, 0))],
@@ -870,7 +868,7 @@ mod test {
vec![
(1, Range::new(0, 8), Range::new(8, 0)),
]),
- ]);
+ ];
for (sample, scenario) in tests {
for (count, begin, expected_end) in scenario.into_iter() {
@@ -882,7 +880,7 @@ mod test {
#[test]
fn test_behaviour_when_moving_to_end_of_next_words() {
- let tests = array::IntoIter::new([
+ let tests = [
("Basic forward motion from the start of a word to the end of it",
vec![(1, Range::new(0, 0), Range::new(0, 5))]),
("Basic forward motion from the end of a word to the end of the next",
@@ -954,7 +952,7 @@ mod test {
vec![
(1, Range::new(0, 0), Range::new(0, 5)),
]),
- ]);
+ ];
for (sample, scenario) in tests {
for (count, begin, expected_end) in scenario.into_iter() {
@@ -966,7 +964,7 @@ mod test {
#[test]
fn test_behaviour_when_moving_to_end_of_previous_words() {
- let tests = array::IntoIter::new([
+ let tests = [
("Basic backward motion from the middle of a word",
vec![(1, Range::new(9, 9), Range::new(10, 5))]),
("Starting from after boundary retreats the anchor",
@@ -1036,7 +1034,7 @@ mod test {
vec![
(1, Range::new(0, 10), Range::new(10, 4)),
]),
- ]);
+ ];
for (sample, scenario) in tests {
for (count, begin, expected_end) in scenario.into_iter() {
@@ -1048,7 +1046,7 @@ mod test {
#[test]
fn test_behaviour_when_moving_to_end_of_next_long_words() {
- let tests = array::IntoIter::new([
+ let tests = [
("Basic forward motion from the start of a word to the end of it",
vec![(1, Range::new(0, 0), Range::new(0, 5))]),
("Basic forward motion from the end of a word to the end of the next",
@@ -1118,7 +1116,7 @@ mod test {
vec![
(1, Range::new(0, 0), Range::new(0, 7)),
]),
- ]);
+ ];
for (sample, scenario) in tests {
for (count, begin, expected_end) in scenario.into_iter() {
diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs
index 717c5994..21fa24fb 100644
--- a/helix-core/src/object.rs
+++ b/helix-core/src/object.rs
@@ -1,7 +1,5 @@
use crate::{Range, RopeSlice, Selection, Syntax};
-// TODO: to contract_selection we'd need to store the previous ranges before expand.
-// Maybe just contract to the first child node?
pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
let tree = syntax.tree();
@@ -34,3 +32,30 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection)
}
})
}
+
+pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
+ let tree = syntax.tree();
+
+ selection.clone().transform(|range| {
+ let from = text.char_to_byte(range.from());
+ let to = text.char_to_byte(range.to());
+
+ let descendant = match tree.root_node().descendant_for_byte_range(from, to) {
+ // find first child, if not possible, fallback to the node that contains selection
+ Some(descendant) => match descendant.child(0) {
+ Some(child) => child,
+ None => descendant,
+ },
+ None => return range,
+ };
+
+ let from = text.byte_to_char(descendant.start_byte());
+ let to = text.byte_to_char(descendant.end_byte());
+
+ if range.head < range.anchor {
+ Range::new(to, from)
+ } else {
+ Range::new(from, to)
+ }
+ })
+}
diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs
index 116a1c7c..1515c4fc 100644
--- a/helix-core/src/selection.rs
+++ b/helix-core/src/selection.rs
@@ -7,6 +7,7 @@ use crate::{
ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary,
prev_grapheme_boundary,
},
+ movement::Direction,
Assoc, ChangeSet, RopeSlice,
};
use smallvec::{smallvec, SmallVec};
@@ -82,6 +83,13 @@ impl Range {
std::cmp::max(self.anchor, self.head)
}
+ /// Total length of the range.
+ #[inline]
+ #[must_use]
+ pub fn len(&self) -> usize {
+ self.to() - self.from()
+ }
+
/// The (inclusive) range of lines that the range overlaps.
#[inline]
#[must_use]
@@ -102,6 +110,27 @@ impl Range {
self.anchor == self.head
}
+ /// `Direction::Backward` when head < anchor.
+ /// `Direction::Backward` otherwise.
+ #[inline]
+ #[must_use]
+ pub fn direction(&self) -> Direction {
+ if self.head < self.anchor {
+ Direction::Backward
+ } else {
+ Direction::Forward
+ }
+ }
+
+ // flips the direction of the selection
+ pub fn flip(&self) -> Self {
+ Self {
+ anchor: self.head,
+ head: self.anchor,
+ horiz: self.horiz,
+ }
+ }
+
/// Check two ranges for overlap.
#[must_use]
pub fn overlaps(&self, other: &Self) -> bool {
@@ -111,6 +140,11 @@ impl Range {
self.from() == other.from() || (self.to() > other.from() && other.to() > self.from())
}
+ #[inline]
+ pub fn contains_range(&self, other: &Self) -> bool {
+ self.from() <= other.from() && self.to() >= other.to()
+ }
+
pub fn contains(&self, pos: usize) -> bool {
self.from() <= pos && pos < self.to()
}
@@ -515,6 +549,39 @@ impl Selection {
pub fn len(&self) -> usize {
self.ranges.len()
}
+
+ // returns true if self ⊇ other
+ pub fn contains(&self, other: &Selection) -> bool {
+ // can't contain other if it is larger
+ if other.len() > self.len() {
+ return false;
+ }
+
+ let (mut iter_self, mut iter_other) = (self.iter(), other.iter());
+ let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next());
+
+ loop {
+ match (ele_self, ele_other) {
+ (Some(ra), Some(rb)) => {
+ if !ra.contains_range(rb) {
+ // `self` doesn't contain next element from `other`, advance `self`, we need to match all from `other`
+ ele_self = iter_self.next();
+ } else {
+ // matched element from `other`, advance `other`
+ ele_other = iter_other.next();
+ };
+ }
+ (None, Some(_)) => {
+ // exhausted `self`, we can't match the reminder of `other`
+ return false;
+ }
+ (_, None) => {
+ // no elements from `other` left to match, `self` contains `other`
+ return true;
+ }
+ }
+ }
+ }
}
impl<'a> IntoIterator for &'a Selection {
@@ -953,4 +1020,30 @@ mod test {
&["", "abcd", "efg", "rs", "xyz"]
);
}
+ #[test]
+ fn test_selection_contains() {
+ fn contains(a: Vec<(usize, usize)>, b: Vec<(usize, usize)>) -> bool {
+ let sela = Selection::new(a.iter().map(|a| Range::new(a.0, a.1)).collect(), 0);
+ let selb = Selection::new(b.iter().map(|b| Range::new(b.0, b.1)).collect(), 0);
+ sela.contains(&selb)
+ }
+
+ // exact match
+ assert!(contains(vec!((1, 1)), vec!((1, 1))));
+
+ // larger set contains smaller
+ assert!(contains(vec!((1, 1), (2, 2), (3, 3)), vec!((2, 2))));
+
+ // multiple matches
+ assert!(contains(vec!((1, 1), (2, 2)), vec!((1, 1), (2, 2))));
+
+ // smaller set can't contain bigger
+ assert!(!contains(vec!((1, 1)), vec!((1, 1), (2, 2))));
+
+ assert!(contains(
+ vec!((1, 1), (2, 4), (5, 6), (7, 9), (10, 13)),
+ vec!((3, 4), (7, 9))
+ ));
+ assert!(!contains(vec!((1, 1), (5, 6)), vec!((1, 6))));
+ }
}
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index ef35fc75..5d37c219 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -1,5 +1,6 @@
use crate::{
chars::char_is_line_ending,
+ diagnostic::Severity,
regex::Regex,
transaction::{ChangeSet, Operation},
Rope, RopeSlice, Tendril,
@@ -63,6 +64,10 @@ pub struct LanguageConfiguration {
#[serde(default)]
pub auto_format: bool,
+ #[serde(default)]
+ pub diagnostic_severity: Severity,
+
+ pub tree_sitter_library: Option<String>, // tree-sitter library name, defaults to language_id
// content_regex
#[serde(default, skip_serializing, deserialize_with = "deserialize_regex")]
@@ -189,9 +194,14 @@ impl LanguageConfiguration {
if highlights_query.is_empty() {
None
} else {
- let language = get_language(&crate::RUNTIME_DIR, &self.language_id)
- .map_err(|e| log::info!("{}", e))
- .ok()?;
+ let language = get_language(
+ &crate::RUNTIME_DIR,
+ self.tree_sitter_library
+ .as_deref()
+ .unwrap_or(&self.language_id),
+ )
+ .map_err(|e| log::info!("{}", e))
+ .ok()?;
let config = HighlightConfiguration::new(
language,
&highlights_query,
diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml
index 5e4619ef..ee485f57 100644
--- a/helix-lsp/Cargo.toml
+++ b/helix-lsp/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "helix-lsp"
-version = "0.5.0"
+version = "0.6.0"
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
edition = "2021"
license = "MPL-2.0"
@@ -12,7 +12,7 @@ homepage = "https://helix-editor.com"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-helix-core = { version = "0.5", path = "../helix-core" }
+helix-core = { version = "0.6", path = "../helix-core" }
anyhow = "1.0"
futures-executor = "0.3"
@@ -23,5 +23,5 @@ lsp-types = { version = "0.91", features = ["proposed"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
-tokio = { version = "1.14", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
+tokio = { version = "1.15", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
tokio-stream = "0.1.8"
diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs
index 271fd9d5..c80f70b5 100644
--- a/helix-lsp/src/client.rs
+++ b/helix-lsp/src/client.rs
@@ -31,6 +31,7 @@ pub struct Client {
pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
offset_encoding: OffsetEncoding,
config: Option<Value>,
+ root_markers: Vec<String>,
}
impl Client {
@@ -39,6 +40,7 @@ impl Client {
cmd: &str,
args: &[String],
config: Option<Value>,
+ root_markers: Vec<String>,
id: usize,
) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
let process = Command::new(cmd)
@@ -68,6 +70,7 @@ impl Client {
capabilities: OnceCell::new(),
offset_encoding: OffsetEncoding::Utf8,
config,
+ root_markers,
};
Ok((client, server_rx, initialize_notify))
@@ -202,7 +205,7 @@ impl Client {
Ok(result) => Output::Success(Success {
jsonrpc: Some(Version::V2),
id,
- result,
+ result: serde_json::to_value(result)?,
}),
Err(error) => Output::Failure(Failure {
jsonrpc: Some(Version::V2),
@@ -225,7 +228,8 @@ impl Client {
pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
// TODO: delay any requests that are triggered prior to initialize
- let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
+ let root = find_root(None, &self.root_markers)
+ .and_then(|root| lsp::Url::from_file_path(root).ok());
if self.config.is_some() {
log::info!("Using custom LSP config: {}", self.config.as_ref().unwrap());
@@ -556,6 +560,14 @@ impl Client {
self.call::<lsp::request::Completion>(params)
}
+ pub async fn resolve_completion_item(
+ &self,
+ completion_item: lsp::CompletionItem,
+ ) -> Result<lsp::CompletionItem> {
+ self.request::<lsp::request::ResolveCompletionItem>(completion_item)
+ .await
+ }
+
pub fn text_document_signature_help(
&self,
text_document: lsp::TextDocumentIdentifier,
@@ -800,4 +812,16 @@ impl Client {
let response = self.request::<lsp::request::Rename>(params).await?;
Ok(response.unwrap_or_default())
}
+
+ pub fn command(&self, command: lsp::Command) -> impl Future<Output = Result<Value>> {
+ let params = lsp::ExecuteCommandParams {
+ command: command.command,
+ arguments: command.arguments.unwrap_or_default(),
+ work_done_progress_params: lsp::WorkDoneProgressParams {
+ work_done_token: None,
+ },
+ };
+
+ self.call::<lsp::request::ExecuteCommand>(params)
+ }
}
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs
index 15cae582..109546d0 100644
--- a/helix-lsp/src/lib.rs
+++ b/helix-lsp/src/lib.rs
@@ -66,39 +66,26 @@ pub mod util {
pos: lsp::Position,
offset_encoding: OffsetEncoding,
) -> Option<usize> {
- let max_line = doc.lines().count().saturating_sub(1);
let pos_line = pos.line as usize;
- let pos_line = if pos_line > max_line {
+ if pos_line > doc.len_lines() - 1 {
return None;
- } else {
- pos_line
- };
+ }
+
match offset_encoding {
OffsetEncoding::Utf8 => {
- let max_char = doc
- .line_to_char(max_line)
- .checked_add(doc.line(max_line).len_chars())?;
let line = doc.line_to_char(pos_line);
let pos = line.checked_add(pos.character as usize)?;
- if pos <= max_char {
+ if pos <= doc.len_chars() {
Some(pos)
} else {
None
}
}
OffsetEncoding::Utf16 => {
- let max_char = doc
- .line_to_char(max_line)
- .checked_add(doc.line(max_line).len_chars())?;
- let max_cu = doc.char_to_utf16_cu(max_char);
let line = doc.line_to_char(pos_line);
let line_start = doc.char_to_utf16_cu(line);
let pos = line_start.checked_add(pos.character as usize)?;
- if pos <= max_cu {
- Some(doc.utf16_cu_to_char(pos))
- } else {
- None
- }
+ doc.try_utf16_cu_to_char(pos).ok()
}
}
}
@@ -203,6 +190,7 @@ pub mod util {
#[derive(Debug, PartialEq, Clone)]
pub enum MethodCall {
WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams),
+ ApplyWorkspaceEdit(lsp::ApplyWorkspaceEditParams),
}
impl MethodCall {
@@ -215,6 +203,12 @@ impl MethodCall {
.expect("Failed to parse WorkDoneCreate params");
Self::WorkDoneProgressCreate(params)
}
+ lsp::request::ApplyWorkspaceEdit::METHOD => {
+ let params: lsp::ApplyWorkspaceEditParams = params
+ .parse()
+ .expect("Failed to parse ApplyWorkspaceEdit params");
+ Self::ApplyWorkspaceEdit(params)
+ }
_ => {
log::warn!("unhandled lsp request: {}", method);
return None;
@@ -319,6 +313,7 @@ impl Registry {
&config.command,
&config.args,
language_config.config.clone(),
+ language_config.roots.clone(),
id,
)?;
self.incoming.push(UnboundedReceiverStream::new(incoming));
diff --git a/helix-syntax/Cargo.toml b/helix-syntax/Cargo.toml
index cceec412..855839be 100644
--- a/helix-syntax/Cargo.toml
+++ b/helix-syntax/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "helix-syntax"
-version = "0.5.0"
+version = "0.6.0"
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
edition = "2021"
license = "MPL-2.0"
diff --git a/helix-syntax/README.md b/helix-syntax/README.md
new file mode 100644
index 00000000..bba2197a
--- /dev/null
+++ b/helix-syntax/README.md
@@ -0,0 +1,13 @@
+helix-syntax
+============
+
+Syntax highlighting for helix, (shallow) submodules resides here.
+
+Differences from nvim-treesitter
+--------------------------------
+
+As the syntax are commonly ported from
+<https://github.com/nvim-treesitter/nvim-treesitter>.
+
+Note that we do not support the custom `#any-of` predicate which is
+supported by neovim so one needs to change it to `#match` with regex.
diff --git a/helix-syntax/languages/tree-sitter-comment b/helix-syntax/languages/tree-sitter-comment
new file mode 160000
+Subproject 5dd3c62f1bbe378b220fe16b317b85247898639
diff --git a/helix-syntax/languages/tree-sitter-dart b/helix-syntax/languages/tree-sitter-dart
new file mode 160000
+Subproject 6a25376685d1d47968c2cef06d4db8d84a70025
diff --git a/helix-syntax/languages/tree-sitter-dockerfile b/helix-syntax/languages/tree-sitter-dockerfile
new file mode 160000
+Subproject 7af32bc04a66ab196f5b9f92ac471f29372ae2c
diff --git a/helix-syntax/languages/tree-sitter-fish b/helix-syntax/languages/tree-sitter-fish
new file mode 160000
+Subproject 04e54ab6585dfd4fee6ddfe5849af56f101b6d4
diff --git a/helix-syntax/languages/tree-sitter-git-commit b/helix-syntax/languages/tree-sitter-git-commit
new file mode 160000
+Subproject 066e395e1107df17183cf3ae4230f1a1406cc97
diff --git a/helix-syntax/languages/tree-sitter-git-diff b/helix-syntax/languages/tree-sitter-git-diff
new file mode 160000
+Subproject c12e6ecb54485f764250556ffd7ccb18f8e2942
diff --git a/helix-syntax/languages/tree-sitter-git-rebase b/helix-syntax/languages/tree-sitter-git-rebase
new file mode 160000
+Subproject 332dc528f27044bc4427024dbb33e6941fc131f
diff --git a/helix-syntax/languages/tree-sitter-llvm b/helix-syntax/languages/tree-sitter-llvm
new file mode 160000
+Subproject 3b213925b9c4f42c1acfe2e10bfbb438d9c6834
diff --git a/helix-syntax/languages/tree-sitter-llvm-mir b/helix-syntax/languages/tree-sitter-llvm-mir
new file mode 160000
+Subproject 06fabca19454b2dc00c1b211a7cb7ad0bc2585f
diff --git a/helix-syntax/languages/tree-sitter-tablegen b/helix-syntax/languages/tree-sitter-tablegen
new file mode 160000
+Subproject 568dd8a937347175fd58db83d4c4cdaeb6069bd
diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml
index 623c5bb9..28b4fe2a 100644
--- a/helix-term/Cargo.toml
+++ b/helix-term/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "helix-term"
-version = "0.5.0"
+version = "0.6.0"
description = "A post-modern text editor."
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
edition = "2021"
@@ -22,12 +22,12 @@ name = "hx"
path = "src/main.rs"
[dependencies]
-helix-core = { version = "0.5", path = "../helix-core" }
-helix-view = { version = "0.5", path = "../helix-view" }
-helix-lsp = { version = "0.5", path = "../helix-lsp" }
+helix-core = { version = "0.6", path = "../helix-core" }
+helix-view = { version = "0.6", path = "../helix-view" }
+helix-lsp = { version = "0.6", path = "../helix-lsp" }
anyhow = "1"
-once_cell = "1.8"
+once_cell = "1.9"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
num_cpus = "1"
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 3e0b6d59..c7202feb 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -1,8 +1,12 @@
use helix_core::{merge_toml_values, syntax};
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
use helix_view::{theme, Editor};
+use serde_json::json;
-use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui};
+use crate::{
+ args::Args, commands::apply_workspace_edit, compositor::Compositor, config::Config, job::Jobs,
+ ui,
+};
use log::{error, warn};
@@ -374,6 +378,7 @@ impl Application {
let doc = self.editor.document_by_path_mut(&path);
if let Some(doc) = doc {
+ let lang_conf = doc.language_config();
let text = doc.text();
let diagnostics = params
@@ -411,19 +416,31 @@ impl Application {
return None;
};
+ let severity =
+ diagnostic.severity.map(|severity| match severity {
+ DiagnosticSeverity::ERROR => Error,
+ DiagnosticSeverity::WARNING => Warning,
+ DiagnosticSeverity::INFORMATION => Info,
+ DiagnosticSeverity::HINT => Hint,
+ severity => unreachable!(
+ "unrecognized diagnostic severity: {:?}",
+ severity
+ ),
+ });
+
+ if let Some(lang_conf) = lang_conf {
+ if let Some(severity) = severity {
+ if severity < lang_conf.diagnostic_severity {
+ return None;
+ }
+ }
+ };
+
Some(Diagnostic {
range: Range { start, end },
line: diagnostic.range.start.line as usize,
message: diagnostic.message,
- severity: diagnostic.severity.map(
- |severity| match severity {
- DiagnosticSeverity::ERROR => Error,
- DiagnosticSeverity::WARNING => Warning,
- DiagnosticSeverity::INFORMATION => Info,
- DiagnosticSeverity::HINT => Hint,
- severity => unimplemented!("{:?}", severity),
- },
- ),
+ severity,
// code
// source
})
@@ -530,14 +547,6 @@ impl Application {
Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
method, params, id, ..
}) => {
- let language_server = match self.editor.language_servers.get_by_id(server_id) {
- Some(language_server) => language_server,
- None => {
- warn!("can't find language server with id `{}`", server_id);
- return;
- }
- };
-
let call = match MethodCall::parse(&method, params) {
Some(call) => call,
None => {
@@ -567,8 +576,42 @@ impl Application {
if spinner.is_stopped() {
spinner.start();
}
+ let language_server =
+ match self.editor.language_servers.get_by_id(server_id) {
+ Some(language_server) => language_server,
+ None => {
+ warn!("can't find language server with id `{}`", server_id);
+ return;
+ }
+ };
+
tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null)));
}
+ MethodCall::ApplyWorkspaceEdit(params) => {
+ apply_workspace_edit(
+ &mut self.editor,
+ helix_lsp::OffsetEncoding::Utf8,
+ &params.edit,
+ );
+
+ let language_server =
+ match self.editor.language_servers.get_by_id(server_id) {
+ Some(language_server) => language_server,
+ None => {
+ warn!("can't find language server with id `{}`", server_id);
+ return;
+ }
+ };
+
+ tokio::spawn(language_server.reply(
+ id,
+ Ok(json!(lsp::ApplyWorkspaceEditResponse {
+ applied: true,
+ failure_reason: None,
+ failed_change: None,
+ })),
+ ));
+ }
}
}
e => unreachable!("{:?}", e),
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index cd566720..5c26a5b2 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -26,6 +26,7 @@ use helix_view::{
};
use anyhow::{anyhow, bail, ensure, Context as _};
+use fuzzy_matcher::FuzzyMatcher;
use helix_lsp::{
block_on, lsp,
util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
@@ -266,6 +267,7 @@ impl MappableCommand {
change_selection_noyank, "Change selection (delete and enter insert mode, without yanking)",
collapse_selection, "Collapse selection onto a single cursor",
flip_selections, "Flip selection cursor and anchor",
+ ensure_selections_forward, "Ensure the selection is in forward direction",
insert_mode, "Insert before selection",
append_mode, "Insert after selection (append)",
command_mode, "Enter command mode",
@@ -287,7 +289,7 @@ impl MappableCommand {
add_newline_below, "Add newline below",
goto_type_definition, "Goto type definition",
goto_implementation, "Goto implementation",
- goto_file_start, "Goto file start/line",
+ goto_file_start, "Goto line number <n> else file start",
goto_file_end, "Goto file end",
goto_file, "Goto files in selection",
goto_file_hsplit, "Goto files in selection (hsplit)",
@@ -360,6 +362,7 @@ impl MappableCommand {
rotate_selection_contents_forward, "Rotate selection contents forward",
rotate_selection_contents_backward, "Rotate selections contents backward",
expand_selection, "Expand selection to parent syntax node",
+ shrink_selection, "Shrink selection to previously expanded syntax node",
jump_forward, "Jump forward on jumplist",
jump_backward, "Jump backward on jumplist",
save_selection, "Save the current selection to the jumplist",
@@ -396,7 +399,7 @@ impl MappableCommand {
increment, "Increment",
decrement, "Decrement",
record_macro, "Record macro",
- play_macro, "Play macro",
+ replay_macro, "Replay macro",
);
}
@@ -1280,16 +1283,23 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
.max(view.offset.row + scrolloff)
.min(last_line.saturating_sub(scrolloff));
- let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
+ // If cursor needs moving, replace primary selection
+ if line != cursor.row {
+ let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
- let anchor = if doc.mode == Mode::Select {
- range.anchor
- } else {
- head
- };
+ let anchor = if doc.mode == Mode::Select {
+ range.anchor
+ } else {
+ head
+ };
- // TODO: only manipulate main selection
- doc.set_selection(view.id, Selection::single(anchor, head));
+ // replace primary selection with an empty selection at cursor pos
+ let prim_sel = Range::new(anchor, head);
+ let mut sel = doc.selection(view.id).clone();
+ let idx = sel.primary_index();
+ sel = sel.replace(idx, prim_sel);
+ doc.set_selection(view.id, sel);
+ }
}
fn page_up(cx: &mut Context) {
@@ -1543,7 +1553,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
let reg = cx.register.unwrap_or('/');
let scrolloff = cx.editor.config.scrolloff;
- let (_, doc) = current!(cx.editor);
+ let doc = doc!(cx.editor);
// TODO: could probably share with select_on_matches?
@@ -1630,7 +1640,7 @@ fn search_selection(cx: &mut Context) {
let query = doc.selection(view.id).primary().fragment(contents);
let regex = regex::escape(&query);
cx.editor.registers.get_mut('/').push(regex);
- let msg = format!("register '{}' set to '{}'", '\\', query);
+ let msg = format!("register '{}' set to '{}'", '/', query);
cx.editor.set_status(msg);
}
@@ -1904,7 +1914,21 @@ fn flip_selections(cx: &mut Context) {
let selection = doc
.selection(view.id)
.clone()
- .transform(|range| Range::new(range.head, range.anchor));
+ .transform(|range| range.flip());
+ doc.set_selection(view.id, selection);
+}
+
+fn ensure_selections_forward(cx: &mut Context) {
+ let (view, doc) = current!(cx.editor);
+
+ let selection = doc
+ .selection(view.id)
+ .clone()
+ .transform(|r| match r.direction() {
+ Direction::Forward => r,
+ Direction::Backward => r.flip(),
+ });
+
doc.set_selection(view.id, selection);
}
@@ -1938,7 +1962,7 @@ fn append_mode(cx: &mut Context) {
if !last_range.is_empty() && last_range.head == end {
let transaction = Transaction::change(
doc.text(),
- std::array::IntoIter::new([(end, end, Some(doc.line_ending.as_str().into()))]),
+ [(end, end, Some(doc.line_ending.as_str().into()))].into_iter(),
);
doc.apply(&transaction, view.id);
}
@@ -2030,7 +2054,7 @@ pub mod cmd {
fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
let jobs = &mut cx.jobs;
- let (_, doc) = current!(cx.editor);
+ let doc = doc_mut!(cx.editor);
if let Some(ref path) = path {
doc.set_path(Some(path.as_ref().as_ref()))
@@ -2083,8 +2107,7 @@ pub mod cmd {
_args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
- let (_, doc) = current!(cx.editor);
-
+ let doc = doc!(cx.editor);
if let Some(format) = doc.format() {
let callback =
make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
@@ -2307,12 +2330,7 @@ pub mod cmd {
write_all_impl(cx, args, event, true, true)
}
- fn quit_all_impl(
- editor: &mut Editor,
- _args: &[Cow<str>],
- _event: PromptEvent,
- force: bool,
- ) -> anyhow::Result<()> {
+ fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> {
if !force {
buffers_remaining_impl(editor)?;
}
@@ -2328,18 +2346,18 @@ pub mod cmd {
fn quit_all(
cx: &mut compositor::Context,
- args: &[Cow<str>],
- event: PromptEvent,
+ _args: &[Cow<str>],
+ _event: PromptEvent,
) -> anyhow::Result<()> {
- quit_all_impl(cx.editor, args, event, false)
+ quit_all_impl(cx.editor, false)
}
fn force_quit_all(
cx: &mut compositor::Context,
- args: &[Cow<str>],
- event: PromptEvent,
+ _args: &[Cow<str>],
+ _event: PromptEvent,
) -> anyhow::Result<()> {
- quit_all_impl(cx.editor, args, event, true)
+ quit_all_impl(cx.editor, true)
}
fn cquit(
@@ -2353,12 +2371,21 @@ pub mod cmd {
.unwrap_or(1);
cx.editor.exit_code = exit_code;
- let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
- for view_id in views {
- cx.editor.close(view_id);
- }
+ quit_all_impl(cx.editor, false)
+ }
- Ok(())
+ fn force_cquit(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ _event: PromptEvent,
+ ) -> anyhow::Result<()> {
+ let exit_code = args
+ .first()
+ .and_then(|code| code.parse::<i32>().ok())
+ .unwrap_or(1);
+ cx.editor.exit_code = exit_code;
+
+ quit_all_impl(cx.editor, true)
}
fn theme(
@@ -2393,7 +2420,7 @@ pub mod cmd {
args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
- let (_, doc) = current!(cx.editor);
+ let doc = doc!(cx.editor);
let default_sep = Cow::Borrowed(doc.line_ending.as_str());
let separator = args.first().unwrap_or(&default_sep);
yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
@@ -2412,7 +2439,7 @@ pub mod cmd {
args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
- let (_, doc) = current!(cx.editor);
+ let doc = doc!(cx.editor);
let default_sep = Cow::Borrowed(doc.line_ending.as_str());
let separator = args.first().unwrap_or(&default_sep);
yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
@@ -2539,7 +2566,7 @@ pub mod cmd {
args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
- let (_, doc) = current!(cx.editor);
+ let doc = doc_mut!(cx.editor);
if let Some(label) = args.first() {
doc.set_encoding(label)
} else {
@@ -2637,6 +2664,86 @@ pub mod cmd {
let (view, doc) = current!(cx.editor);
view.ensure_cursor_in_view(doc, line);
+ Ok(())
+ }
+
+ fn setting(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ _event: PromptEvent,
+ ) -> anyhow::Result<()> {
+ let runtime_config = &mut cx.editor.config;
+
+ if args.len() != 2 {
+ anyhow::bail!("Bad arguments. Usage: `:set key field`");
+ }
+
+ let (key, arg) = (&args[0].to_lowercase(), &args[1]);
+
+ match key.as_ref() {
+ "scrolloff" => runtime_config.scrolloff = arg.parse()?,
+ "scroll-lines" => runtime_config.scroll_lines = arg.parse()?,
+ "mouse" => runtime_config.mouse = arg.parse()?,
+ "line-number" => runtime_config.line_number = arg.parse()?,
+ "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?,
+ "smart-case" => runtime_config.smart_case = arg.parse()?,
+ "auto-pairs" => runtime_config.auto_pairs = arg.parse()?,
+ "auto-completion" => runtime_config.auto_completion = arg.parse()?,
+ "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?,
+ "auto-info" => runtime_config.auto_info = arg.parse()?,
+ "true-color" => runtime_config.true_color = arg.parse()?,
+ _ => anyhow::bail!("Unknown key `{}`.", args[0]),
+ }
+
+ Ok(())
+ }
+
+ fn sort(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ _event: PromptEvent,
+ ) -> anyhow::Result<()> {
+ sort_impl(cx, args, false)
+ }
+
+ fn sort_reverse(
+ cx: &mut compositor::Context,
+ args: &[Cow<str>],
+ _event: PromptEvent,
+ ) -> anyhow::Result<()> {
+ sort_impl(cx, args, true)
+ }
+
+ fn sort_impl(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ reverse: bool,
+ ) -> anyhow::Result<()> {
+ let (view, doc) = current!(cx.editor);
+ let text = doc.text().slice(..);
+
+ let selection = doc.selection(view.id);
+
+ let mut fragments: Vec<_> = selection
+ .fragments(text)
+ .map(|fragment| Tendril::from_slice(&fragment))
+ .collect();
+
+ fragments.sort_by(match reverse {
+ true => |a: &Tendril, b: &Tendril| b.cmp(a),
+ false => |a: &Tendril, b: &Tendril| a.cmp(b),
+ });
+
+ let transaction = Transaction::change(
+ doc.text(),
+ selection
+ .into_iter()
+ .zip(fragments)
+ .map(|(s, fragment)| (s.from(), s.to(), Some(fragment))),
+ );
+
+ doc.apply(&transaction, view.id);
+ doc.append_changes_to_history(view.id);
Ok(())
}
@@ -2664,18 +2771,18 @@ pub mod cmd {
completer: Some(completers::filename),
},
TypableCommand {
- name: "buffer-close",
- aliases: &["bc", "bclose"],
- doc: "Close the current buffer.",
- fun: buffer_close,
- completer: None, // FIXME: buffer completer
+ name: "buffer-close",
+ aliases: &["bc", "bclose"],
+ doc: "Close the current buffer.",
+ fun: buffer_close,
+ completer: None, // FIXME: buffer completer
},
TypableCommand {
- name: "buffer-close!",
- aliases: &["bc!", "bclose!"],
- doc: "Close the current buffer forcefully (ignoring unsaved changes).",
- fun: force_buffer_close,
- completer: None, // FIXME: buffer completer
+ name: "buffer-close!",
+ aliases: &["bc!", "bclose!"],
+ doc: "Close the current buffer forcefully (ignoring unsaved changes).",
+ fun: force_buffer_close,
+ completer: None, // FIXME: buffer completer
},
TypableCommand {
name: "write",
@@ -2783,6 +2890,13 @@ pub mod cmd {
completer: None,
},
TypableCommand {
+ name: "cquit!",
+ aliases: &["cq!"],
+ doc: "Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2).",
+ fun: force_cquit,
+ completer: None,
+ },
+ TypableCommand {
name: "theme",
aliases: &[],
doc: "Change the editor theme.",
@@ -2928,7 +3042,28 @@ pub mod cmd {
doc: "Go to line number.",
fun: goto_line_number,
completer: None,
- }
+ },
+ TypableCommand {
+ name: "set-option",
+ aliases: &["set"],
+ doc: "Set a config option at runtime",
+ fun: setting,
+ completer: Some(completers::setting),
+ },
+ TypableCommand {
+ name: "sort",
+ aliases: &[],
+ doc: "Sort ranges in selection.",
+ fun: sort,
+ completer: None,
+ },
+ TypableCommand {
+ name: "rsort",
+ aliases: &[],
+ doc: "Sort ranges in selection in reverse order.",
+ fun: sort_reverse,
+ completer: None,
+ },
];
pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
@@ -2948,17 +3083,28 @@ fn command_mode(cx: &mut Context) {
":".into(),
Some(':'),
|input: &str| {
+ static FUZZY_MATCHER: Lazy<fuzzy_matcher::skim::SkimMatcherV2> =
+ Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default);
+
// we use .this over split_whitespace() because we care about empty segments
let parts = input.split(' ').collect::<Vec<&str>>();
// simple heuristic: if there's no just one part, complete command name.
// if there's a space, per command completion kicks in.
if parts.len() <= 1 {
- let end = 0..;
- cmd::TYPABLE_COMMAND_LIST
+ let mut matches: Vec<_> = cmd::TYPABLE_COMMAND_LIST
.iter()
- .filter(|command| command.name.contains(input))
- .map(|command| (end.clone(), Cow::Borrowed(command.name)))
+ .filter_map(|command| {
+ FUZZY_MATCHER
+ .fuzzy_match(command.name, input)
+ .map(|score| (command.name, score))
+ })
+ .collect();
+
+ matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score));
+ matches
+ .into_iter()
+ .map(|(name, _)| (0.., name.into()))
.collect()
} else {
let part = parts.last().unwrap();
@@ -3002,7 +3148,16 @@ fn command_mode(cx: &mut Context) {
// Handle typable commands
if let Some(cmd) = cmd::TYPABLE_COMMAND_MAP.get(parts[0]) {
- let args = shellwords::shellwords(input);
+ let args = if cfg!(unix) {
+ shellwords::shellwords(input)
+ } else {
+ // Windows doesn't support POSIX, so fallback for now
+ parts
+ .into_iter()
+ .map(|part| part.into())
+ .collect::<Vec<_>>()
+ };
+
if let Err(e) = (cmd.fun)(cx, &args[1..], event) {
cx.editor.set_error(format!("{}", e));
}
@@ -3026,7 +3181,8 @@ fn command_mode(cx: &mut Context) {
}
fn file_picker(cx: &mut Context) {
- let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
+ // We don't specify language markers, root will be the root of the current git repo
+ let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./"));
let picker = ui::file_picker(root, &cx.editor.config);
cx.push_layer(Box::new(picker));
}
@@ -3118,7 +3274,7 @@ fn symbol_picker(cx: &mut Context) {
nested_to_flat(list, file, child);
}
}
- let (_, doc) = current!(cx.editor);
+ let doc = doc!(cx.editor);
let language_server = match doc.language_server() {
Some(language_server) => language_server,
@@ -3139,7 +3295,7 @@ fn symbol_picker(cx: &mut Context) {
let symbols = match symbols {
lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
lsp::DocumentSymbolResponse::Nested(symbols) => {
- let (_view, doc) = current!(editor);
+ let doc = doc!(editor);
let mut flat_symbols = Vec::new();
for symbol in symbols {
nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
@@ -3181,17 +3337,15 @@ fn symbol_picker(cx: &mut Context) {
}
fn workspace_symbol_picker(cx: &mut Context) {
- let (_, doc) = current!(cx.editor);
-
+ let doc = doc!(cx.editor);
+ let current_path = doc.path().cloned();
let language_server = match doc.language_server() {
Some(language_server) => language_server,
None => return,
};
let offset_encoding = language_server.offset_encoding();
-
let future = language_server.workspace_symbols("".to_string());
- let current_path = doc_mut!(cx.editor).path().cloned();
cx.callback(
future,
move |_editor: &mut Editor,
@@ -3277,12 +3431,19 @@ pub fn code_action(cx: &mut Context) {
move |editor, code_action, _action| match code_action {
lsp::CodeActionOrCommand::Command(command) => {
log::debug!("code action command: {:?}", command);
- editor.set_error(String::from("Handling code action command is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
+ execute_lsp_command(editor, command.clone());
}
lsp::CodeActionOrCommand::CodeAction(code_action) => {
log::debug!("code action: {:?}", code_action);
if let Some(ref workspace_edit) = code_action.edit {
- apply_workspace_edit(editor, offset_encoding, workspace_edit)
+ log::debug!("edit: {:?}", workspace_edit);
+ apply_workspace_edit(editor, offset_encoding, workspace_edit);
+ }
+
+ // if code action provides both edit and command first the edit
+ // should be applied and then the command
+ if let Some(command) = &code_action.command {
+ execute_lsp_command(editor, command.clone());
}
}
},
@@ -3293,6 +3454,25 @@ pub fn code_action(cx: &mut Context) {
)
}
+pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
+ let doc = doc!(editor);
+ let language_server = match doc.language_server() {
+ Some(language_server) => language_server,
+ None => return,
+ };
+
+ // the command is executed on the server and communicated back
+ // to the client asynchronously using workspace edits
+ let command_future = language_server.command(cmd);
+ tokio::spawn(async move {
+ let res = command_future.await;
+
+ if let Err(e) = res {
+ log::error!("execute LSP command: {}", e);
+ }
+ });
+}
+
pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
use lsp::ResourceOp;
use std::fs;
@@ -3346,7 +3526,7 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
}
}
-fn apply_workspace_edit(
+pub fn apply_workspace_edit(
editor: &mut Editor,
offset_encoding: OffsetEncoding,
workspace_edit: &lsp::WorkspaceEdit,
@@ -3537,22 +3717,22 @@ fn open(cx: &mut Context, open: Open) {
let mut offs = 0;
let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
- let line = range.cursor_line(text);
+ let cursor_line = range.cursor_line(text);
- let line = match open {
+ let new_line = match open {
// adjust position to the end of the line (next line - 1)
- Open::Below => line + 1,
+ Open::Below => cursor_line + 1,
// adjust position to the end of the previous line (current line - 1)
- Open::Above => line,
+ Open::Above => cursor_line,
};
// Index to insert newlines after, as well as the char width
// to use to compensate for those inserted newlines.
- let (line_end_index, line_end_offset_width) = if line == 0 {
+ let (line_end_index, line_end_offset_width) = if new_line == 0 {
(0, 0)
} else {
(
- line_end_char_index(&doc.text().slice(..), line.saturating_sub(1)),
+ line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
doc.line_ending.len_chars(),
)
};
@@ -3563,8 +3743,10 @@ fn open(cx: &mut Context, open: Open) {
doc.syntax(),
text,
line_end_index,
+ new_line.saturating_sub(1),
true,
- );
+ )
+ .unwrap_or_else(|| indent::indent_level_for_line(text.line(cursor_line), doc.tab_width()));
let indent = doc.indent_unit().repeat(indent_level);
let indent_len = indent.len();
let mut text = String::with_capacity(1 + indent_len);
@@ -3610,6 +3792,7 @@ fn normal_mode(cx: &mut Context) {
doc.mode = Mode::Normal;
+ try_restore_indent(doc, view.id);
doc.append_changes_to_history(view.id);
// if leaving append mode, move cursor back by 1
@@ -3627,6 +3810,40 @@ fn normal_mode(cx: &mut Context) {
}
}
+fn try_restore_indent(doc: &mut Document, view_id: ViewId) {
+ use helix_core::chars::char_is_whitespace;
+ use helix_core::Operation;
+
+ fn inserted_a_new_blank_line(changes: &[Operation], pos: usize, line_end_pos: usize) -> bool {
+ if let [Operation::Retain(move_pos), Operation::Insert(ref inserted_str), Operation::Retain(_)] =
+ changes
+ {
+ move_pos + inserted_str.len32() as usize == pos
+ && inserted_str.starts_with('\n')
+ && inserted_str.chars().skip(1).all(char_is_whitespace)
+ && pos == line_end_pos // ensure no characters exists after current position
+ } else {
+ false
+ }
+ }
+
+ let doc_changes = doc.changes().changes();
+ let text = doc.text().slice(..);
+ 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| {
+ let line_start_pos = text.line_to_char(range.cursor_line(text));
+ (line_start_pos, pos, None)
+ });
+ doc.apply(&transaction, view_id);
+ }
+}
+
// Store a jump on the jumplist.
fn push_jump(editor: &mut Editor) {
let (view, doc) = current!(editor);
@@ -3994,27 +4211,21 @@ fn goto_pos(editor: &mut Editor, pos: usize) {
}
fn goto_first_diag(cx: &mut Context) {
- let editor = &mut cx.editor;
- let (_, doc) = current!(editor);
-
+ let doc = doc!(cx.editor);
let pos = match doc.diagnostics().first() {
Some(diag) => diag.range.start,
None => return,
};
-
- goto_pos(editor, pos);
+ goto_pos(cx.editor, pos);
}
fn goto_last_diag(cx: &mut Context) {
- let editor = &mut cx.editor;
- let (_, doc) = current!(editor);
-
+ let doc = doc!(cx.editor);
let pos = match doc.diagnostics().last() {
Some(diag) => diag.range.start,
None => return,
};
-
- goto_pos(editor, pos);
+ goto_pos(cx.editor, pos);
}
fn goto_next_diag(cx: &mut Context) {
@@ -4270,48 +4481,48 @@ pub mod insert {
};
let curr = contents.get_char(pos).unwrap_or(' ');
- // TODO: offset range.head by 1? when calculating?
+ let current_line = text.char_to_line(pos);
let indent_level = indent::suggested_indent_for_pos(
doc.language_config(),
doc.syntax(),
text,
- pos.saturating_sub(1),
+ pos,
+ current_line,
true,
- );
- let indent = doc.indent_unit().repeat(indent_level);
- let mut text = String::with_capacity(1 + indent.len());
- text.push_str(doc.line_ending.as_str());
- text.push_str(&indent);
+ )
+ .unwrap_or_else(|| {
+ indent::indent_level_for_line(text.line(current_line), doc.tab_width())
+ });
- let head = pos + offs + text.chars().count();
+ let indent = doc.indent_unit().repeat(indent_level);
+ let mut text = String::new();
+ // If we are between pairs (such as brackets), we want to insert an additional line which is indented one level more and place the cursor there
+ let new_head_pos = if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
+ let inner_indent = doc.indent_unit().repeat(indent_level + 1);
+ text.reserve_exact(2 + indent.len() + inner_indent.len());
+ text.push_str(doc.line_ending.as_str());
+ text.push_str(&inner_indent);
+ let new_head_pos = pos + offs + text.chars().count();
+ text.push_str(doc.line_ending.as_str());
+ text.push_str(&indent);
+ new_head_pos
+ } else {
+ text.reserve_exact(1 + indent.len());
+ text.push_str(doc.line_ending.as_str());
+ text.push_str(&indent);
+ pos + offs + text.chars().count()
+ };
// TODO: range replace or extend
// range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
// can be used with cx.mode to do replace or extend on most changes
- ranges.push(Range::new(
- if range.is_empty() {
- head
- } else {
- range.anchor + offs
- },
- head,
- ));
-
- // if between a bracket pair
- if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
- // another newline, indent the end bracket one level less
- let indent = doc.indent_unit().repeat(indent_level.saturating_sub(1));
- text.push_str(doc.line_ending.as_str());
- text.push_str(&indent);
- }
-
+ ranges.push(Range::new(new_head_pos, new_head_pos));
offs += text.chars().count();
(pos, pos, Some(text.into()))
});
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
- //
doc.apply(&transaction, view.id);
}
@@ -5079,7 +5290,7 @@ pub fn completion(cx: &mut Context) {
move |editor: &mut Editor,
compositor: &mut Compositor,
response: Option<lsp::CompletionResponse>| {
- let (_, doc) = current!(editor);
+ let doc = doc!(editor);
if doc.mode() != Mode::Insert {
// we're not in insert mode anymore
return;
@@ -5257,6 +5468,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view.id);
}
+
fn rotate_selection_contents_forward(cx: &mut Context) {
rotate_selection_contents(cx, Direction::Forward)
}
@@ -5272,7 +5484,39 @@ fn expand_selection(cx: &mut Context) {
if let Some(syntax) = doc.syntax() {
let text = doc.text().slice(..);
- let selection = object::expand_selection(syntax, text, doc.selection(view.id));
+
+ let current_selection = doc.selection(view.id);
+
+ // save current selection so it can be restored using shrink_selection
+ view.object_selections.push(current_selection.clone());
+
+ let selection = object::expand_selection(syntax, text, current_selection);
+ doc.set_selection(view.id, selection);
+ }
+ };
+ motion(cx.editor);
+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
+}
+
+fn shrink_selection(cx: &mut Context) {
+ let motion = |editor: &mut Editor| {
+ let (view, doc) = current!(editor);
+ let current_selection = doc.selection(view.id);
+ // try to restore previous selection
+ if let Some(prev_selection) = view.object_selections.pop() {
+ if current_selection.contains(&prev_selection) {
+ // allow shrinking the selection only if current selection contains the previous object selection
+ doc.set_selection(view.id, prev_selection);
+ return;
+ } else {
+ // clear existing selection as they can't be shrinked to anyway
+ view.object_selections.clear();
+ }
+ }
+ // if not previous selection, shrink to first child
+ if let Some(syntax) = doc.syntax() {
+ let text = doc.text().slice(..);
+ let selection = object::shrink_selection(syntax, text, current_selection);
doc.set_selection(view.id, selection);
}
};
@@ -5920,42 +6164,42 @@ fn record_macro(cx: &mut Context) {
keys.pop();
let s = keys
.into_iter()
- .map(|key| format!("{}", key))
- .collect::<Vec<_>>()
- .join(" ");
+ .map(|key| {
+ let s = key.to_string();
+ if s.chars().count() == 1 {
+ s
+ } else {
+ format!("<{}>", s)
+ }
+ })
+ .collect::<String>();
cx.editor.registers.get_mut(reg).write(vec![s]);
cx.editor
- .set_status(format!("Recorded to register {}", reg));
+ .set_status(format!("Recorded to register [{}]", reg));
} else {
let reg = cx.register.take().unwrap_or('@');
cx.editor.macro_recording = Some((reg, Vec::new()));
cx.editor
- .set_status(format!("Recording to register {}", reg));
+ .set_status(format!("Recording to register [{}]", reg));
}
}
-fn play_macro(cx: &mut Context) {
+fn replay_macro(cx: &mut Context) {
let reg = cx.register.unwrap_or('@');
- let keys = match cx
- .editor
- .registers
- .get(reg)
- .and_then(|reg| reg.read().get(0))
- .context("Register empty")
- .and_then(|s| {
- s.split_whitespace()
- .map(str::parse::<KeyEvent>)
- .collect::<Result<Vec<_>, _>>()
- .context("Failed to parse macro")
- }) {
- Ok(keys) => keys,
- Err(e) => {
- cx.editor.set_error(format!("{}", e));
- return;
+ let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
+ match helix_view::input::parse_macro(keys_str) {
+ Ok(keys) => keys,
+ Err(err) => {
+ cx.editor.set_error(format!("Invalid macro: {}", err));
+ return;
+ }
}
+ } else {
+ cx.editor.set_error(format!("Register [{}] empty", reg));
+ return;
};
- let count = cx.count();
+ let count = cx.count();
cx.callback = Some(Box::new(
move |compositor: &mut Compositor, cx: &mut compositor::Context| {
for _ in 0..count {
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index 257d5f29..49f8469a 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -569,11 +569,13 @@ impl Default for Keymaps {
"d" => goto_prev_diag,
"D" => goto_first_diag,
"space" => add_newline_above,
+ "o" => shrink_selection,
},
"]" => { "Right bracket"
"d" => goto_next_diag,
"D" => goto_last_diag,
"space" => add_newline_below,
+ "o" => expand_selection,
},
"/" => search,
@@ -593,8 +595,8 @@ impl Default for Keymaps {
// paste_all
"P" => paste_before,
- "q" => record_macro,
- "Q" => play_macro,
+ "Q" => record_macro,
+ "q" => replay_macro,
">" => indent,
"<" => unindent,
@@ -617,6 +619,8 @@ impl Default for Keymaps {
"A-(" => rotate_selection_contents_backward,
"A-)" => rotate_selection_contents_forward,
+ "A-:" => ensure_selections_forward,
+
"esc" => normal_mode,
"C-b" | "pageup" => page_up,
"C-f" | "pagedown" => page_down,
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index a55201ff..274330c0 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -154,8 +154,19 @@ impl Completion {
);
doc.apply(&transaction, view.id);
- if let Some(additional_edits) = &item.additional_text_edits {
- // gopls uses this to add extra imports
+ // apply additional edits, mostly used to auto import unqualified types
+ let resolved_additional_text_edits = if item.additional_text_edits.is_some() {
+ None
+ } else {
+ Completion::resolve_completion_item(doc, item.clone())
+ .and_then(|item| item.additional_text_edits)
+ };
+
+ if let Some(additional_edits) = item
+ .additional_text_edits
+ .as_ref()
+ .or_else(|| resolved_additional_text_edits.as_ref())
+ {
if !additional_edits.is_empty() {
let transaction = util::generate_transaction_from_edits(
doc.text(),
@@ -181,6 +192,31 @@ impl Completion {
completion
}
+ fn resolve_completion_item(
+ doc: &Document,
+ completion_item: lsp::CompletionItem,
+ ) -> Option<CompletionItem> {
+ let language_server = doc.language_server()?;
+ let completion_resolve_provider = language_server
+ .capabilities()
+ .completion_provider
+ .as_ref()?
+ .resolve_provider;
+ if completion_resolve_provider != Some(true) {
+ return None;
+ }
+
+ let future = language_server.resolve_completion_item(completion_item);
+ let response = helix_lsp::block_on(future);
+ match response {
+ Ok(completion_item) => Some(completion_item),
+ Err(err) => {
+ log::error!("execute LSP command: {}", err);
+ None
+ }
+ }
+ }
+
pub fn recompute_filter(&mut self, editor: &Editor) {
// recompute menu based on matches
let menu = self.popup.contents_mut();
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 6b015171..5b7e9075 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -7,7 +7,7 @@ use crate::{
};
use helix_core::{
- coords_at_pos,
+ coords_at_pos, encoding,
graphemes::{ensure_grapheme_boundary_next, next_grapheme_boundary, prev_grapheme_boundary},
movement::Direction,
syntax::{self, HighlightEvent},
@@ -566,21 +566,6 @@ impl EditorView {
}
surface.set_string(viewport.x + 5, viewport.y, progress, base_style);
- let rel_path = doc.relative_path();
- let path = rel_path
- .as_ref()
- .map(|p| p.to_string_lossy())
- .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
-
- let title = format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" });
- surface.set_stringn(
- viewport.x + 8,
- viewport.y,
- title,
- viewport.width.saturating_sub(6) as usize,
- base_style,
- );
-
//-------------------------------
// Right side of the status line.
//-------------------------------
@@ -654,6 +639,13 @@ impl EditorView {
base_style,
));
+ let enc = doc.encoding();
+ if enc != encoding::UTF_8 {
+ right_side_text
+ .0
+ .push(Span::styled(format!(" {} ", enc.name()), base_style));
+ }
+
// Render to the statusline.
surface.set_spans(
viewport.x
@@ -664,6 +656,31 @@ impl EditorView {
&right_side_text,
right_side_text.width() as u16,
);
+
+ //-------------------------------
+ // Middle / File path / Title
+ //-------------------------------
+ let title = {
+ let rel_path = doc.relative_path();
+ let path = rel_path
+ .as_ref()
+ .map(|p| p.to_string_lossy())
+ .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
+ format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" })
+ };
+
+ surface.set_string_truncated(
+ viewport.x + 8, // 8: 1 space + 3 char mode string + 1 space + 1 spinner + 1 space
+ viewport.y,
+ title,
+ viewport
+ .width
+ .saturating_sub(6)
+ .saturating_sub(right_side_text.width() as u16 + 1) as usize, // "+ 1": a space between the title and the selection info
+ base_style,
+ true,
+ true,
+ );
}
/// Handle events by looking them up in `self.keymaps`. Returns None
@@ -782,8 +799,9 @@ impl EditorView {
pub fn clear_completion(&mut self, editor: &mut Editor) {
self.completion = None;
+
// Clear any savepoints
- let (_, doc) = current!(editor);
+ let doc = doc_mut!(editor);
doc.savepoint = None;
editor.clear_idle_timer(); // don't retrigger
}
@@ -941,14 +959,18 @@ impl EditorView {
}
impl Component for EditorView {
- fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
- let mut cxt = commands::Context {
- editor: cx.editor,
+ fn handle_event(
+ &mut self,
+ event: Event,
+ context: &mut crate::compositor::Context,
+ ) -> EventResult {
+ let mut cx = commands::Context {
+ editor: context.editor,
count: None,
register: None,
callback: None,
on_next_key_callback: None,
- jobs: cx.jobs,
+ jobs: context.jobs,
};
match event {
@@ -958,18 +980,19 @@ impl Component for EditorView {
EventResult::Consumed(None)
}
Event::Key(key) => {
- cxt.editor.reset_idle_timer();
+ cx.editor.reset_idle_timer();
let mut key = KeyEvent::from(key);
canonicalize_key(&mut key);
+
// clear status
- cxt.editor.status_msg = None;
+ cx.editor.status_msg = None;
- let (_, doc) = current!(cxt.editor);
+ let doc = doc!(cx.editor);
let mode = doc.mode();
if let Some(on_next_key) = self.on_next_key.take() {
// if there's a command waiting input, do that first
- on_next_key(&mut cxt, key);
+ on_next_key(&mut cx, key);
} else {
match mode {
Mode::Insert => {
@@ -981,8 +1004,8 @@ impl Component for EditorView {
if let Some(completion) = &mut self.completion {
// use a fake context here
let mut cx = Context {
- editor: cxt.editor,
- jobs: cxt.jobs,
+ editor: cx.editor,
+ jobs: cx.jobs,
scroll: None,
};
let res = completion.handle_event(event, &mut cx);
@@ -992,40 +1015,40 @@ impl Component for EditorView {
if callback.is_some() {
// assume close_fn
- self.clear_completion(cxt.editor);
+ self.clear_completion(cx.editor);
}
}
}
// if completion didn't take the event, we pass it onto commands
if !consumed {
- self.insert_mode(&mut cxt, key);
+ self.insert_mode(&mut cx, key);
// lastly we recalculate completion
if let Some(completion) = &mut self.completion {
- completion.update(&mut cxt);
+ completion.update(&mut cx);
if completion.is_empty() {
- self.clear_completion(cxt.editor);
+ self.clear_completion(cx.editor);
}
}
}
}
- mode => self.command_mode(mode, &mut cxt, key),
+ mode => self.command_mode(mode, &mut cx, key),
}
}
- self.on_next_key = cxt.on_next_key_callback.take();
+ self.on_next_key = cx.on_next_key_callback.take();
// appease borrowck
- let callback = cxt.callback.take();
+ let callback = cx.callback.take();
// if the command consumed the last view, skip the render.
// on the next loop cycle the Application will then terminate.
- if cxt.editor.should_close() {
+ if cx.editor.should_close() {
return EventResult::Ignored;
}
- let (view, doc) = current!(cxt.editor);
- view.ensure_cursor_in_view(doc, cxt.editor.config.scrolloff);
+ let (view, doc) = current!(cx.editor);
+ view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff);
// mode transitions
match (mode, doc.mode()) {
@@ -1054,7 +1077,7 @@ impl Component for EditorView {
EventResult::Consumed(callback)
}
- Event::Mouse(event) => self.handle_mouse_event(event, &mut cxt),
+ Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
}
}
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index f57e2e2b..9ff9118f 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -174,7 +174,9 @@ pub mod completers {
use crate::ui::prompt::Completion;
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
use fuzzy_matcher::FuzzyMatcher;
+ use helix_view::editor::Config;
use helix_view::theme;
+ use once_cell::sync::Lazy;
use std::borrow::Cow;
use std::cmp::Reverse;
@@ -208,6 +210,31 @@ pub mod completers {
names
}
+ pub fn setting(input: &str) -> Vec<Completion> {
+ static KEYS: Lazy<Vec<String>> = Lazy::new(|| {
+ serde_json::to_value(Config::default())
+ .unwrap()
+ .as_object()
+ .unwrap()
+ .keys()
+ .cloned()
+ .collect()
+ });
+
+ let matcher = Matcher::default();
+
+ let mut matches: Vec<_> = KEYS
+ .iter()
+ .filter_map(|name| matcher.fuzzy_match(name, input).map(|score| (name, score)))
+ .collect();
+
+ matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
+ matches
+ .into_iter()
+ .map(|(name, _)| ((0..), name.into()))
+ .collect()
+ }
+
pub fn filename(input: &str) -> Vec<Completion> {
filename_impl(input, |entry| {
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
@@ -256,7 +283,7 @@ pub mod completers {
let is_tilde = input.starts_with('~') && input.len() == 1;
let path = helix_core::path::expand_tilde(Path::new(input));
- let (dir, file_name) = if input.ends_with('/') {
+ let (dir, file_name) = if input.ends_with(std::path::MAIN_SEPARATOR) {
(path, None)
} else {
let file_name = path
diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs
index 07e1b33c..0202de23 100644
--- a/helix-term/src/ui/prompt.rs
+++ b/helix-term/src/ui/prompt.rs
@@ -127,7 +127,7 @@ impl Prompt {
let mut char_position = char_indices
.iter()
.position(|(idx, _)| *idx == self.cursor)
- .unwrap_or_else(|| char_indices.len());
+ .unwrap_or(char_indices.len());
for _ in 0..rep {
// Skip any non-whitespace characters
@@ -473,7 +473,7 @@ impl Component for Prompt {
}
}
key!(Enter) => {
- if self.selection.is_some() && self.line.ends_with('/') {
+ if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) {
self.completion = (self.completion_fn)(&self.line);
self.exit_selection();
} else {
diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml
index 6df65d36..4b8d7acb 100644
--- a/helix-tui/Cargo.toml
+++ b/helix-tui/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "helix-tui"
-version = "0.5.0"
+version = "0.6.0"
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
description = """
A library to build rich terminal user interfaces or dashboards
@@ -21,5 +21,5 @@ cassowary = "0.3"
unicode-segmentation = "1.8"
crossterm = { version = "0.22", optional = true }
serde = { version = "1", "optional" = true, features = ["derive"]}
-helix-view = { version = "0.5", path = "../helix-view", features = ["term"] }
-helix-core = { version = "0.5", path = "../helix-core" }
+helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
+helix-core = { version = "0.6", path = "../helix-core" }
diff --git a/helix-tui/README.md b/helix-tui/README.md
index 97b3d1d9..5cc80aa6 100644
--- a/helix-tui/README.md
+++ b/helix-tui/README.md
@@ -2,5 +2,5 @@
This library is a fork of the great library
[tui-rs](https://github.com/fdehau/tui-rs/). We've mainly relied on the double
-buffer implementation and render diffing, side-stepping it's widget and
+buffer implementation and render diffing, side-stepping its widget and
layouting.
diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml
index 34f55eb6..3c8866fc 100644
--- a/helix-view/Cargo.toml
+++ b/helix-view/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "helix-view"
-version = "0.5.0"
+version = "0.6.0"
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
edition = "2021"
license = "MPL-2.0"
@@ -16,12 +16,12 @@ term = ["crossterm"]
[dependencies]
bitflags = "1.3"
anyhow = "1"
-helix-core = { version = "0.5", path = "../helix-core" }
-helix-lsp = { version = "0.5", path = "../helix-lsp"}
+helix-core = { version = "0.6", path = "../helix-core" }
+helix-lsp = { version = "0.6", path = "../helix-lsp"}
crossterm = { version = "0.22", optional = true }
# Conversion traits
-once_cell = "1.8"
+once_cell = "1.9"
url = "2"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
@@ -29,7 +29,6 @@ futures-util = { version = "0.3", features = ["std", "async-await"], default-fea
slotmap = "1"
-encoding_rs = "0.8"
chardetng = "0.1"
serde = { version = "1.0", features = ["derive"] }
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index a0315bed..9185e483 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -1,5 +1,6 @@
use anyhow::{anyhow, Context, Error};
use serde::de::{self, Deserialize, Deserializer};
+use serde::Serialize;
use std::cell::Cell;
use std::collections::HashMap;
use std::fmt::Display;
@@ -9,6 +10,7 @@ use std::str::FromStr;
use std::sync::Arc;
use helix_core::{
+ encoding,
history::History,
indent::{auto_detect_indent_style, IndentStyle},
line_ending::auto_detect_line_ending,
@@ -68,13 +70,22 @@ impl<'de> Deserialize<'de> for Mode {
}
}
+impl Serialize for Mode {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ serializer.collect_str(self)
+ }
+}
+
pub struct Document {
pub(crate) id: DocumentId,
text: Rope,
pub(crate) selections: HashMap<ViewId, Selection>,
path: Option<PathBuf>,
- encoding: &'static encoding_rs::Encoding,
+ encoding: &'static encoding::Encoding,
/// Current editing mode.
pub mode: Mode,
@@ -143,8 +154,8 @@ impl fmt::Debug for Document {
/// be used to override encoding auto-detection.
pub fn from_reader<R: std::io::Read + ?Sized>(
reader: &mut R,
- encoding: Option<&'static encoding_rs::Encoding>,
-) -> Result<(Rope, &'static encoding_rs::Encoding), Error> {
+ encoding: Option<&'static encoding::Encoding>,
+) -> Result<(Rope, &'static encoding::Encoding), Error> {
// These two buffers are 8192 bytes in size each and are used as
// intermediaries during the decoding process. Text read into `buf`
// from `reader` is decoded into `buf_out` as UTF-8. Once either
@@ -212,11 +223,11 @@ pub fn from_reader<R: std::io::Read + ?Sized>(
total_read += read;
total_written += written;
match result {
- encoding_rs::CoderResult::InputEmpty => {
+ encoding::CoderResult::InputEmpty => {
debug_assert_eq!(slice.len(), total_read);
break;
}
- encoding_rs::CoderResult::OutputFull => {
+ encoding::CoderResult::OutputFull => {
debug_assert!(slice.len() > total_read);
builder.append(&buf_str[..total_written]);
total_written = 0;
@@ -251,7 +262,7 @@ pub fn from_reader<R: std::io::Read + ?Sized>(
/// replacement characters may appear in the encoded text.
pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
writer: &'a mut W,
- encoding: &'static encoding_rs::Encoding,
+ encoding: &'static encoding::Encoding,
rope: &'a Rope,
) -> Result<(), Error> {
// Text inside a `Rope` is stored as non-contiguous blocks of data called
@@ -286,12 +297,12 @@ pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
total_read += read;
total_written += written;
match result {
- encoding_rs::CoderResult::InputEmpty => {
+ encoding::CoderResult::InputEmpty => {
debug_assert_eq!(chunk.len(), total_read);
debug_assert!(buf.len() >= total_written);
break;
}
- encoding_rs::CoderResult::OutputFull => {
+ encoding::CoderResult::OutputFull => {
debug_assert!(chunk.len() > total_read);
writer.write_all(&buf[..total_written]).await?;
total_written = 0;
@@ -322,8 +333,8 @@ use helix_lsp::lsp;
use url::Url;
impl Document {
- pub fn from(text: Rope, encoding: Option<&'static encoding_rs::Encoding>) -> Self {
- let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
+ pub fn from(text: Rope, encoding: Option<&'static encoding::Encoding>) -> Self {
+ let encoding = encoding.unwrap_or(encoding::UTF_8);
let changes = ChangeSet::new(&text);
let old_state = None;
@@ -356,7 +367,7 @@ impl Document {
/// overwritten with the `encoding` parameter.
pub fn open(
path: &Path,
- encoding: Option<&'static encoding_rs::Encoding>,
+ encoding: Option<&'static encoding::Encoding>,
theme: Option<&Theme>,
config_loader: Option<&syntax::Loader>,
) -> Result<Self, Error> {
@@ -366,7 +377,7 @@ impl Document {
std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
from_reader(&mut file, encoding)?
} else {
- let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
+ let encoding = encoding.unwrap_or(encoding::UTF_8);
(Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
};
@@ -548,7 +559,7 @@ impl Document {
/// Sets the [`Document`]'s encoding with the encoding correspondent to `label`.
pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
- match encoding_rs::Encoding::for_label(label.as_bytes()) {
+ match encoding::Encoding::for_label(label.as_bytes()) {
Some(encoding) => self.encoding = encoding,
None => return Err(anyhow::anyhow!("unknown encoding")),
}
@@ -556,7 +567,7 @@ impl Document {
}
/// Returns the [`Document`]'s current encoding.
- pub fn encoding(&self) -> &'static encoding_rs::Encoding {
+ pub fn encoding(&self) -> &'static encoding::Encoding {
self.encoding
}
@@ -889,6 +900,10 @@ impl Document {
self.indent_style.as_str()
}
+ pub fn changes(&self) -> &ChangeSet {
+ &self.changes
+ }
+
#[inline]
/// File path on disk.
pub fn path(&self) -> Option<&PathBuf> {
@@ -1119,7 +1134,7 @@ mod test {
macro_rules! test_decode {
($label:expr, $label_override:expr) => {
- let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap();
+ let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
let path = base_path.join(format!("{}_in.txt", $label));
let ref_path = base_path.join(format!("{}_in_ref.txt", $label));
@@ -1138,7 +1153,7 @@ mod test {
macro_rules! test_encode {
($label:expr, $label_override:expr) => {
- let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap();
+ let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
let path = base_path.join(format!("{}_out.txt", $label));
let ref_path = base_path.join(format!("{}_out_ref.txt", $label));
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index d65c1fb2..eef7520e 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -27,7 +27,7 @@ pub use helix_core::register::Registers;
use helix_core::syntax;
use helix_core::{Position, Selection};
-use serde::{Deserialize, Deserializer};
+use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize};
fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
@@ -37,7 +37,7 @@ where
Ok(Duration::from_millis(millis))
}
-#[derive(Debug, Clone, PartialEq, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct FilePickerConfig {
/// IgnoreOptions
@@ -77,7 +77,7 @@ impl Default for FilePickerConfig {
}
}
-#[derive(Debug, Clone, PartialEq, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct Config {
/// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5.
@@ -137,6 +137,20 @@ impl<'de> Deserialize<'de> for CursorShapeConfig {
}
}
+impl Serialize for CursorShapeConfig {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ let mut map = serializer.serialize_map(Some(self.len()))?;
+ let modes = [Mode::Normal, Mode::Select, Mode::Insert];
+ for mode in modes {
+ map.serialize_entry(&mode, &self.from_mode(mode))?;
+ }
+ map.end()
+ }
+}
+
impl std::ops::Deref for CursorShapeConfig {
type Target = [CursorKind; 3];
@@ -151,7 +165,7 @@ impl Default for CursorShapeConfig {
}
}
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LineNumber {
/// Show absolute line number
@@ -160,6 +174,18 @@ pub enum LineNumber {
Relative,
}
+impl std::str::FromStr for LineNumber {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.to_lowercase().as_str() {
+ "absolute" | "abs" => Ok(Self::Absolute),
+ "relative" | "rel" => Ok(Self::Relative),
+ _ => anyhow::bail!("Line number can only be `absolute` or `relative`."),
+ }
+ }
+}
+
impl Default for Config {
fn default() -> Self {
Self {
diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs
index 892aa646..d75cde82 100644
--- a/helix-view/src/graphics.rs
+++ b/helix-view/src/graphics.rs
@@ -1,11 +1,11 @@
use bitflags::bitflags;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
use std::{
cmp::{max, min},
str::FromStr,
};
-#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
+#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
/// UNSTABLE
pub enum CursorKind {
diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs
index 92caa517..14dadc3b 100644
--- a/helix-view/src/input.rs
+++ b/helix-view/src/input.rs
@@ -254,6 +254,43 @@ impl From<KeyEvent> for crossterm::event::KeyEvent {
}
}
+pub fn parse_macro(keys_str: &str) -> anyhow::Result<Vec<KeyEvent>> {
+ use anyhow::Context;
+ let mut keys_res: anyhow::Result<_> = Ok(Vec::new());
+ let mut i = 0;
+ while let Ok(keys) = &mut keys_res {
+ if i >= keys_str.len() {
+ break;
+ }
+ if !keys_str.is_char_boundary(i) {
+ i += 1;
+ continue;
+ }
+
+ let s = &keys_str[i..];
+ let mut end_i = 1;
+ while !s.is_char_boundary(end_i) {
+ end_i += 1;
+ }
+ let c = &s[..end_i];
+ if c == ">" {
+ keys_res = Err(anyhow!("Unmatched '>'"));
+ } else if c != "<" {
+ keys.push(c);
+ i += end_i;
+ } else {
+ match s.find('>').context("'>' expected") {
+ Ok(end_i) => {
+ keys.push(&s[1..end_i]);
+ i += end_i + 1;
+ }
+ Err(err) => keys_res = Err(err),
+ }
+ }
+ }
+ keys_res.and_then(|keys| keys.into_iter().map(str::parse).collect())
+}
+
#[cfg(test)]
mod test {
use super::*;
@@ -339,4 +376,120 @@ mod test {
assert!(str::parse::<KeyEvent>("123").is_err());
assert!(str::parse::<KeyEvent>("S--").is_err());
}
+
+ #[test]
+ fn parsing_valid_macros() {
+ assert_eq!(
+ parse_macro("xdo").ok(),
+ Some(vec![
+ KeyEvent {
+ code: KeyCode::Char('x'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('d'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('o'),
+ modifiers: KeyModifiers::NONE,
+ },
+ ]),
+ );
+
+ assert_eq!(
+ parse_macro("<C-w>v<C-w>h<C-o>xx<A-s>").ok(),
+ Some(vec![
+ KeyEvent {
+ code: KeyCode::Char('w'),
+ modifiers: KeyModifiers::CONTROL,
+ },
+ KeyEvent {
+ code: KeyCode::Char('v'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('w'),
+ modifiers: KeyModifiers::CONTROL,
+ },
+ KeyEvent {
+ code: KeyCode::Char('h'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('o'),
+ modifiers: KeyModifiers::CONTROL,
+ },
+ KeyEvent {
+ code: KeyCode::Char('x'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('x'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('s'),
+ modifiers: KeyModifiers::ALT,
+ },
+ ])
+ );
+
+ assert_eq!(
+ parse_macro(":o foo.bar<ret>").ok(),
+ Some(vec![
+ KeyEvent {
+ code: KeyCode::Char(':'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('o'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char(' '),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('f'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('o'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('o'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('.'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('b'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('a'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Char('r'),
+ modifiers: KeyModifiers::NONE,
+ },
+ KeyEvent {
+ code: KeyCode::Enter,
+ modifiers: KeyModifiers::NONE,
+ },
+ ])
+ );
+ }
+
+ #[test]
+ fn parsing_invalid_macros_fails() {
+ assert!(parse_macro("abc<C-").is_err());
+ assert!(parse_macro("abc>123").is_err());
+ assert!(parse_macro("wd<foo>").is_err());
+ }
}
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index 94d67acd..89a6c196 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -80,6 +80,8 @@ pub struct View {
// uses two docs because we want to be able to swap between the
// two last modified docs which we need to manually keep track of
pub last_modified_docs: [Option<DocumentId>; 2],
+ /// used to store previous selections of tree-sitter objecs
+ pub object_selections: Vec<Selection>,
}
impl View {
@@ -92,6 +94,7 @@ impl View {
jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel
last_accessed_doc: None,
last_modified_docs: [None, None],
+ object_selections: Vec::new(),
}
}
diff --git a/languages.toml b/languages.toml
index abb94acf..e8329fe7 100644
--- a/languages.toml
+++ b/languages.toml
@@ -3,15 +3,11 @@ name = "rust"
scope = "source.rust"
injection-regex = "rust"
file-types = ["rs"]
-roots = []
+roots = ["Cargo.toml", "Cargo.lock"]
auto-format = true
comment-token = "//"
language-server = { command = "rust-analyzer" }
indent = { tab-width = 4, unit = " " }
-[language.config]
-cargo = { loadOutDirsFromCheck = true }
-procMacro = { enable = false }
-diagnostics = { disabled = ["unresolved-proc-macro"] }
[[language]]
name = "toml"
@@ -46,6 +42,17 @@ language-server = { command = "elixir-ls" }
indent = { tab-width = 2, unit = " " }
[[language]]
+name = "fish"
+scope = "source.fish"
+injection-regex = "fish"
+file-types = ["fish"]
+shebangs = ["fish"]
+roots = []
+comment-token = "#"
+
+indent = { tab-width = 4, unit = " " }
+
+[[language]]
name = "mint"
scope = "source.mint"
injection-regex = "mint"
@@ -327,6 +334,7 @@ file-types = ["yml", "yaml"]
roots = []
comment-token = "#"
indent = { tab-width = 2, unit = " " }
+injection-regex = "yml|yaml"
# [[language]]
# name = "haskell"
@@ -379,6 +387,7 @@ roots = []
comment-token = "#"
indent = { tab-width = 2, unit = " " }
language-server = { command = "cmake-language-server" }
+injection-regex = "cmake"
[[language]]
name = "glsl"
@@ -387,6 +396,7 @@ file-types = ["glsl", "vert", "tesc", "tese", "geom", "frag", "comp" ]
roots = []
comment-token = "//"
indent = { tab-width = 4, unit = " " }
+injection-regex = "glsl"
[[language]]
name = "perl"
@@ -407,6 +417,13 @@ comment-token = ";"
language-server = { command = "racket", args = ["-l", "racket-langserver"] }
[[language]]
+name = "comment"
+scope = "scope.comment"
+roots = []
+file-types = []
+injection-regex = "comment"
+
+[[language]]
name = "wgsl"
scope = "source.wgsl"
file-types = ["wgsl"]
@@ -421,6 +438,34 @@ roots = []
file-types = ["ll"]
comment-token = ";"
indent = { tab-width = 2, unit = " " }
+injection-regex = "llvm"
+
+[[language]]
+name = "llvm-mir"
+scope = "source.llvm_mir"
+roots = []
+file-types = []
+comment-token = ";"
+indent = { tab-width = 2, unit = " " }
+injection-regex = "mir"
+
+[[language]]
+name = "llvm-mir-yaml"
+tree-sitter-library = "yaml"
+scope = "source.yaml"
+roots = []
+file-types = ["mir"]
+comment-token = "#"
+indent = { tab-width = 2, unit = " " }
+
+[[language]]
+name = "tablegen"
+scope = "source.tablegen"
+roots = []
+file-types = ["td"]
+comment-token = "//"
+indent = { tab-width = 2, unit = " " }
+injection-regex = "tablegen"
[[language]]
name = "markdown"
@@ -430,3 +475,58 @@ file-types = ["md"]
roots = []
indent = { tab-width = 2, unit = " " }
+
+[[language]]
+name = "dart"
+scope = "source.dart"
+file-types = ["dart"]
+roots = ["pubspec.yaml"]
+auto-format = true
+comment-token = "//"
+language-server = { command = "dart", args = ["language-server", "--client-id=helix"] }
+indent = { tab-width = 2, unit = " " }
+
+[[language]]
+name = "scala"
+scope = "source.scala"
+roots = ["build.sbt"]
+file-types = ["scala", "sbt"]
+comment-token = "//"
+indent = { tab-width = 2, unit = " " }
+language-server = { command = "metals" }
+
+[[language]]
+name = "dockerfile"
+scope = "source.dockerfile"
+injection-regex = "docker|dockerfile"
+roots = ["Dockerfile"]
+file-types = ["Dockerfile", "dockerfile"]
+comment-token = "#"
+indent = { tab-width = 2, unit = " " }
+language-server = { command = "docker-langserver", args = ["--stdio"] }
+
+[[language]]
+name = "git-commit"
+scope = "git.commitmsg"
+roots = []
+file-types = ["COMMIT_EDITMSG"]
+comment-token = "#"
+indent = { tab-width = 2, unit = " " }
+
+[[language]]
+name = "git-diff"
+scope = "source.diff"
+roots = []
+file-types = ["diff"]
+injection-regex = "diff"
+comment-token = "#"
+indent = { tab-width = 2, unit = " " }
+
+[[language]]
+name = "git-rebase"
+scope = "source.gitrebase"
+roots = []
+file-types = ["git-rebase-todo"]
+injection-regex = "git-rebase"
+comment-token = "#"
+indent = { tab-width = 2, unit = " " }
diff --git a/runtime/queries/c/indents.toml b/runtime/queries/c/indents.toml
new file mode 100644
index 00000000..f4076e17
--- /dev/null
+++ b/runtime/queries/c/indents.toml
@@ -0,0 +1,16 @@
+indent = [
+ "compound_statement",
+ "field_declaration_list",
+ "enumerator_list",
+ "parameter_list",
+ "init_declarator",
+ "case_statement",
+ "condition_clause",
+ "expression_statement",
+]
+
+outdent = [
+ "case",
+ "}",
+ "]",
+]
diff --git a/runtime/queries/c/injections.scm b/runtime/queries/c/injections.scm
new file mode 100644
index 00000000..321c90ad
--- /dev/null
+++ b/runtime/queries/c/injections.scm
@@ -0,0 +1,2 @@
+((comment) @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/queries/c/textobjects.scm b/runtime/queries/c/textobjects.scm
new file mode 100644
index 00000000..b0f03668
--- /dev/null
+++ b/runtime/queries/c/textobjects.scm
@@ -0,0 +1,13 @@
+(function_definition
+ body: (_) @function.inside) @function.around
+
+(struct_specifier
+ body: (_) @class.inside) @class.around
+
+(enum_specifier
+ body: (_) @class.inside) @class.around
+
+(union_specifier
+ body: (_) @class.inside) @class.around
+
+(parameter_declaration) @parameter.inside
diff --git a/runtime/queries/cmake/indents.toml b/runtime/queries/cmake/indents.toml
new file mode 100644
index 00000000..8b886a4f
--- /dev/null
+++ b/runtime/queries/cmake/indents.toml
@@ -0,0 +1,12 @@
+indent = [
+ "if_condition",
+ "foreach_loop",
+ "while_loop",
+ "function_def",
+ "macro_def",
+ "normal_command",
+]
+
+outdent = [
+ ")"
+]
diff --git a/runtime/queries/cmake/injections.scm b/runtime/queries/cmake/injections.scm
new file mode 100644
index 00000000..6cb6c254
--- /dev/null
+++ b/runtime/queries/cmake/injections.scm
@@ -0,0 +1,4 @@
+((line_comment) @injection.content
+ (#set! injection.language "comment"))
+((bracket_comment) @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/queries/cmake/textobjects.scm b/runtime/queries/cmake/textobjects.scm
new file mode 100644
index 00000000..b0d1b108
--- /dev/null
+++ b/runtime/queries/cmake/textobjects.scm
@@ -0,0 +1,3 @@
+(macro_def) @function.around
+
+(argument) @parameter.inside
diff --git a/runtime/queries/comment/highlights.scm b/runtime/queries/comment/highlights.scm
new file mode 100644
index 00000000..88685d59
--- /dev/null
+++ b/runtime/queries/comment/highlights.scm
@@ -0,0 +1,30 @@
+[
+ "("
+ ")"
+] @punctuation.bracket
+
+":" @punctuation.delimiter
+
+((tag (name) @warning)
+ (#match? @warning "^(TODO|HACK|WARNING)$"))
+
+("text" @warning
+ (#match? @warning "^(TODO|HACK|WARNING)$"))
+
+((tag (name) @error)
+ (match? @error "^(FIXME|XXX|BUG)$"))
+
+("text" @error
+ (match? @error "^(FIXME|XXX|BUG)$"))
+
+(tag
+ (name) @ui.text
+ (user)? @constant)
+
+; Issue number (#123)
+("text" @constant.numeric
+ (#match? @constant.numeric "^#[0-9]+$"))
+
+; User mention (@user)
+("text" @tag
+ (#match? @tag "^[@][a-zA-Z0-9_-]+$"))
diff --git a/runtime/queries/cpp/indents.toml b/runtime/queries/cpp/indents.toml
new file mode 100644
index 00000000..0ca2ed8b
--- /dev/null
+++ b/runtime/queries/cpp/indents.toml
@@ -0,0 +1,17 @@
+indent = [
+ "compound_statement",
+ "field_declaration_list",
+ "enumerator_list",
+ "parameter_list",
+ "init_declarator",
+ "case_statement",
+ "condition_clause",
+ "expression_statement",
+]
+
+outdent = [
+ "case",
+ "access_specifier",
+ "}",
+ "]",
+]
diff --git a/runtime/queries/cpp/injections.scm b/runtime/queries/cpp/injections.scm
new file mode 100644
index 00000000..a5a5208c
--- /dev/null
+++ b/runtime/queries/cpp/injections.scm
@@ -0,0 +1 @@
+; inherits: c
diff --git a/runtime/queries/cpp/textobjects.scm b/runtime/queries/cpp/textobjects.scm
new file mode 100644
index 00000000..6e3de1a2
--- /dev/null
+++ b/runtime/queries/cpp/textobjects.scm
@@ -0,0 +1,7 @@
+; inherits: c
+
+(lambda_expression
+ body: (_) @function.inside) @function.around
+
+(class_specifier
+ body: (_) @class.inside) @class.around
diff --git a/runtime/queries/dart/highlights.scm b/runtime/queries/dart/highlights.scm
new file mode 100644
index 00000000..9f667d6b
--- /dev/null
+++ b/runtime/queries/dart/highlights.scm
@@ -0,0 +1,237 @@
+(dotted_identifier_list) @string
+
+; Methods
+; --------------------
+(super) @function.builtin
+
+(function_expression_body (identifier) @function.method)
+((identifier)(selector (argument_part)) @function.method)
+
+; Annotations
+; --------------------
+(annotation
+ name: (identifier) @attribute)
+(marker_annotation
+ name: (identifier) @attribute)
+
+; Types
+; --------------------
+(class_definition
+ name: (identifier) @type)
+
+(constructor_signature
+ name: (identifier) @function.method)
+
+(function_signature
+ name: (identifier) @function.method)
+
+(getter_signature
+ (identifier) @function.builtin)
+
+(setter_signature
+ name: (identifier) @function.builtin)
+
+(enum_declaration
+ name: (identifier) @type)
+
+(enum_constant
+ name: (identifier) @type.builtin)
+
+(void_type) @type.builtin
+
+((scoped_identifier
+ scope: (identifier) @type)
+ (#match? @type "^[a-zA-Z]"))
+
+((scoped_identifier
+ scope: (identifier) @type
+ name: (identifier) @type)
+ (#match? @type "^[a-zA-Z]"))
+
+; the DisabledDrawerButtons in : const DisabledDrawerButtons(history: true),
+(type_identifier) @type.builtin
+
+; Variables
+; --------------------
+; the "File" in var file = File();
+((identifier) @namespace
+ (#match? @namespace "^_?[A-Z].*[a-z]")) ; catch Classes or IClasses not CLASSES
+
+("Function" @type.builtin)
+(inferred_type) @type.builtin
+
+; properties
+(unconditional_assignable_selector
+ (identifier) @variable.other.member)
+
+(conditional_assignable_selector
+ (identifier) @variable.other.member)
+
+; assignments
+; --------------------
+; the "strings" in : strings = "some string"
+(assignment_expression
+ left: (assignable_expression) @variable)
+
+(this) @variable.builtin
+
+; Parameters
+; --------------------
+(formal_parameter
+ name: (identifier) @variable)
+
+(named_argument
+ (label (identifier) @variable))
+
+; Literals
+; --------------------
+[
+ (hex_integer_literal)
+ (decimal_integer_literal)
+ (decimal_floating_point_literal)
+ ;(octal_integer_literal)
+ ;(hex_floating_point_literal)
+] @constant.numeric.integer
+
+(symbol_literal) @string.special.symbol
+(string_literal) @string
+
+[
+ (const_builtin)
+ (final_builtin)
+] @variable.builtin
+
+[
+ (true)
+ (false)
+] @constant.builtin.boolean
+
+(null_literal) @constant.builtin
+
+(comment) @comment.line
+(documentation_comment) @comment.block.documentation
+
+; Tokens
+; --------------------
+(template_substitution
+ "$" @punctuation.special
+ "{" @punctuation.special
+ "}" @punctuation.special
+) @embedded
+
+(template_substitution
+ "$" @punctuation.special
+ (identifier_dollar_escaped) @variable
+) @embedded
+
+(escape_sequence) @constant.character.escape
+
+; Punctuation
+;---------------------
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+] @punctuation.bracket
+
+[
+ ";"
+ "."
+ ","
+ ":"
+] @punctuation.delimiter
+
+; Operators
+;---------------------
+[
+ "@"
+ "?"
+ "=>"
+ ".."
+ "=="
+ "&&"
+ "%"
+ "<"
+ ">"
+ "="
+ ">="
+ "<="
+ "||"
+ (multiplicative_operator)
+ (increment_operator)
+ (is_operator)
+ (prefix_operator)
+ (equality_operator)
+ (additive_operator)
+] @operator
+
+; Keywords
+; --------------------
+["import" "library" "export"] @keyword.control.import
+["do" "while" "continue" "for"] @keyword.control.repeat
+["return" "yield"] @keyword.control.return
+["as" "in" "is"] @keyword.operator
+
+[
+ "?."
+ "??"
+ "if"
+ "else"
+ "switch"
+ "default"
+ "late"
+] @keyword.control.conditional
+
+[
+ "try"
+ "throw"
+ "catch"
+ "finally"
+ (break_statement)
+] @keyword.control.exception
+
+; Reserved words (cannot be used as identifiers)
+[
+ (case_builtin)
+ "abstract"
+ "async"
+ "async*"
+ "await"
+ "class"
+ "covariant"
+ "deferred"
+ "dynamic"
+ "enum"
+ "extends"
+ "extension"
+ "external"
+ "factory"
+ "Function"
+ "get"
+ "implements"
+ "interface"
+ "mixin"
+ "new"
+ "on"
+ "operator"
+ "part"
+ "required"
+ "set"
+ "show"
+ "static"
+ "super"
+ "sync*"
+ "typedef"
+ "with"
+] @keyword
+
+; when used as an identifier:
+((identifier) @variable.builtin
+ (#match? @variable.builtin "^(abstract|as|covariant|deferred|dynamic|export|external|factory|Function|get|implements|import|interface|library|operator|mixin|part|set|static|typedef)$"))
+
+; Error
+(ERROR) @error
+
diff --git a/runtime/queries/dart/indents.toml b/runtime/queries/dart/indents.toml
new file mode 100644
index 00000000..5c11e05d
--- /dev/null
+++ b/runtime/queries/dart/indents.toml
@@ -0,0 +1,20 @@
+indent = [
+ "class_body",
+ "function_body",
+ "function_expression_body",
+ "declaration",
+ "initializers",
+ "switch_block",
+ "if_statement",
+ "formal_parameter_list",
+ "formal_parameter",
+ "list_literal",
+ "return_statement",
+ "arguments"
+]
+
+outdent = [
+ "}",
+ "]",
+ ")"
+]
diff --git a/runtime/queries/dart/locals.scm b/runtime/queries/dart/locals.scm
new file mode 100644
index 00000000..629838e5
--- /dev/null
+++ b/runtime/queries/dart/locals.scm
@@ -0,0 +1,20 @@
+; Scopes
+;-------
+
+[
+ (block)
+ (try_statement)
+ (catch_clause)
+ (finally_clause)
+] @local.scope
+
+; Definitions
+;------------
+
+(class_definition
+ body: (_) @local.definition)
+
+; References
+;------------
+
+(identifier) @local.reference
diff --git a/runtime/queries/dockerfile/highlights.scm b/runtime/queries/dockerfile/highlights.scm
new file mode 100644
index 00000000..5a945fb9
--- /dev/null
+++ b/runtime/queries/dockerfile/highlights.scm
@@ -0,0 +1,51 @@
+[
+ "FROM"
+ "AS"
+ "RUN"
+ "CMD"
+ "LABEL"
+ "EXPOSE"
+ "ENV"
+ "ADD"
+ "COPY"
+ "ENTRYPOINT"
+ "VOLUME"
+ "USER"
+ "WORKDIR"
+ "ARG"
+ "ONBUILD"
+ "STOPSIGNAL"
+ "HEALTHCHECK"
+ "SHELL"
+ "MAINTAINER"
+ "CROSS_BUILD"
+] @keyword
+
+[
+ ":"
+ "@"
+] @operator
+
+(comment) @comment
+
+
+(image_spec
+ (image_tag
+ ":" @punctuation.special)
+ (image_digest
+ "@" @punctuation.special))
+
+(double_quoted_string) @string
+
+(expansion
+ [
+ "$"
+ "{"
+ "}"
+ ] @punctuation.special
+) @none
+
+((variable) @constant
+ (#match? @constant "^[A-Z][A-Z_0-9]*$"))
+
+
diff --git a/runtime/queries/dockerfile/injections.scm b/runtime/queries/dockerfile/injections.scm
new file mode 100644
index 00000000..20396f1a
--- /dev/null
+++ b/runtime/queries/dockerfile/injections.scm
@@ -0,0 +1,6 @@
+((comment) @injection.content
+ (#set! injection.language "comment"))
+
+([(shell_command) (shell_fragment)] @injection.content
+ (#set! injection.language "bash"))
+
diff --git a/runtime/queries/elixir/injections.scm b/runtime/queries/elixir/injections.scm
new file mode 100644
index 00000000..321c90ad
--- /dev/null
+++ b/runtime/queries/elixir/injections.scm
@@ -0,0 +1,2 @@
+((comment) @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/queries/fish/highlights.scm b/runtime/queries/fish/highlights.scm
new file mode 100644
index 00000000..def53931
--- /dev/null
+++ b/runtime/queries/fish/highlights.scm
@@ -0,0 +1,156 @@
+;; Operators
+
+[
+ "&&"
+ "||"
+ "|"
+ "&"
+ "="
+ "!="
+ ".."
+ "!"
+ (direction)
+ (stream_redirect)
+ (test_option)
+] @operator
+
+[
+ "not"
+ "and"
+ "or"
+] @keyword.operator
+
+;; Conditionals
+
+(if_statement
+[
+ "if"
+ "end"
+] @keyword.control.conditional)
+
+(switch_statement
+[
+ "switch"
+ "end"
+] @keyword.control.conditional)
+
+(case_clause
+[
+ "case"
+] @keyword.control.conditional)
+
+(else_clause
+[
+ "else"
+] @keyword.control.conditional)
+
+(else_if_clause
+[
+ "else"
+ "if"
+] @keyword.control.conditional)
+
+;; Loops/Blocks
+
+(while_statement
+[
+ "while"
+ "end"
+] @keyword.control.repeat)
+
+(for_statement
+[
+ "for"
+ "end"
+] @keyword.control.repeat)
+
+(begin_statement
+[
+ "begin"
+ "end"
+] @keyword.control.repeat)
+
+;; Keywords
+
+[
+ "in"
+ (break)
+ (continue)
+] @keyword
+
+"return" @keyword.control.return
+
+;; Punctuation
+
+[
+ "["
+ "]"
+ "{"
+ "}"
+ "("
+ ")"
+] @punctuation.bracket
+
+"," @punctuation.delimiter
+
+;; Commands
+
+(command
+ argument: [
+ (word) @variable.parameter (#match? @variable.parameter "^-")
+ ]
+)
+
+; non-bultin command names
+(command name: (word) @function)
+
+; derived from builtin -n (fish 3.2.2)
+(command
+ name: [
+ (word) @function.builtin
+ (#match? @function.builtin "^(\.|:|_|alias|argparse|bg|bind|block|breakpoint|builtin|cd|command|commandline|complete|contains|count|disown|echo|emit|eval|exec|exit|fg|functions|history|isatty|jobs|math|printf|pwd|random|read|realpath|set|set_color|source|status|string|test|time|type|ulimit|wait)$")
+ ]
+)
+
+(test_command "test" @function.builtin)
+
+;; Functions
+
+(function_definition ["function" "end"] @keyword.function)
+
+(function_definition
+ name: [
+ (word) (concatenation)
+ ]
+@function)
+
+(function_definition
+ option: [
+ (word)
+ (concatenation (word))
+ ] @variable.parameter (#match? @variable.parameter "^-")
+)
+
+;; Strings
+
+[(double_quote_string) (single_quote_string)] @string
+(escape_sequence) @constant.character.escape
+
+;; Variables
+
+(variable_name) @variable
+(variable_expansion) @constant
+
+;; Nodes
+
+(integer) @constant.numeric.integer
+(float) @constant.numeric.float
+(comment) @comment
+(test_option) @string
+
+((word) @constant.builtin.boolean
+(#match? @constant.builtin.boolean "^(true|false)$"))
+
+;; Error
+
+(ERROR) @error
diff --git a/runtime/queries/fish/indents.toml b/runtime/queries/fish/indents.toml
new file mode 100644
index 00000000..6f1e563a
--- /dev/null
+++ b/runtime/queries/fish/indents.toml
@@ -0,0 +1,12 @@
+indent = [
+ "function_definition",
+ "while_statement",
+ "for_statement",
+ "if_statement",
+ "begin_statement",
+ "switch_statement",
+]
+
+outdent = [
+ "end"
+]
diff --git a/runtime/queries/fish/injections.scm b/runtime/queries/fish/injections.scm
new file mode 100644
index 00000000..321c90ad
--- /dev/null
+++ b/runtime/queries/fish/injections.scm
@@ -0,0 +1,2 @@
+((comment) @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/queries/fish/textobjects.scm b/runtime/queries/fish/textobjects.scm
new file mode 100644
index 00000000..67fd6614
--- /dev/null
+++ b/runtime/queries/fish/textobjects.scm
@@ -0,0 +1 @@
+(function_definition) @function.around
diff --git a/runtime/queries/git-commit/highlights.scm b/runtime/queries/git-commit/highlights.scm
new file mode 100644
index 00000000..0b50d419
--- /dev/null
+++ b/runtime/queries/git-commit/highlights.scm
@@ -0,0 +1,14 @@
+(subject) @markup.heading
+(path) @string.special.path
+(branch) @string.special.symbol
+(commit) @constant
+(item) @markup.link.url
+(header) @tag
+
+(change kind: "new file" @diff.plus)
+(change kind: "deleted" @diff.minus)
+(change kind: "modified" @diff.delta)
+(change kind: "renamed" @diff.delta.moved)
+
+[":" "->"] @punctuation.delimeter
+(comment) @comment
diff --git a/runtime/queries/git-commit/injections.scm b/runtime/queries/git-commit/injections.scm
new file mode 100644
index 00000000..cf0657f7
--- /dev/null
+++ b/runtime/queries/git-commit/injections.scm
@@ -0,0 +1,8 @@
+((comment (scissors))
+ (message) @injection.content
+ (#set! injection.include-children)
+ (#set! injection.language "diff"))
+
+((rebase_command) @injection.content
+ (#set! injection.include-children)
+ (#set! injection.language "git-rebase"))
diff --git a/runtime/queries/git-diff/highlights.scm b/runtime/queries/git-diff/highlights.scm
new file mode 100644
index 00000000..1c1a8829
--- /dev/null
+++ b/runtime/queries/git-diff/highlights.scm
@@ -0,0 +1,6 @@
+[(addition) (new_file)] @diff.plus
+[(deletion) (old_file)] @diff.minus
+
+(commit) @constant
+(location) @attribute
+(command) @markup.bold
diff --git a/runtime/queries/git-rebase/highlights.scm b/runtime/queries/git-rebase/highlights.scm
new file mode 100644
index 00000000..4f007037
--- /dev/null
+++ b/runtime/queries/git-rebase/highlights.scm
@@ -0,0 +1,11 @@
+(operation operator: ["p" "pick" "r" "reword" "e" "edit" "s" "squash" "m" "merge" "d" "drop" "b" "break" "x" "exec"] @keyword)
+(operation operator: ["l" "label" "t" "reset"] @function)
+(operation operator: ["f" "fixup"] @function.special)
+
+(option) @operator
+(label) @string.special.symbol
+(commit) @constant
+"#" @punctuation.delimiter
+(comment) @comment
+
+(ERROR) @error
diff --git a/runtime/queries/git-rebase/injections.scm b/runtime/queries/git-rebase/injections.scm
new file mode 100644
index 00000000..070129b6
--- /dev/null
+++ b/runtime/queries/git-rebase/injections.scm
@@ -0,0 +1,4 @@
+((operation
+ operator: ["x" "exec"]
+ (command) @injection.content)
+ (#set! injection.language "bash"))
diff --git a/runtime/queries/glsl/injections.scm b/runtime/queries/glsl/injections.scm
index 7d3323b1..6330ea3e 100644
--- a/runtime/queries/glsl/injections.scm
+++ b/runtime/queries/glsl/injections.scm
@@ -1,3 +1,4 @@
-(preproc_arg) @glsl
+; inherits: c
-(comment) @comment
+((preproc_arg) @injection.content
+ (#set! injection.language "glsl"))
diff --git a/runtime/queries/julia/injections.scm b/runtime/queries/julia/injections.scm
index be2412c0..3cf7339f 100644
--- a/runtime/queries/julia/injections.scm
+++ b/runtime/queries/julia/injections.scm
@@ -1,5 +1,7 @@
; TODO: re-add when markdown is added.
-; ((triple_string) @markdown
-; (#offset! @markdown 0 3 0 -3))
+; ((triple_string) @injection.content
+; (#offset! @injection.content 0 3 0 -3)
+; (#set! injection.language "markdown"))
-(comment) @comment
+((comment) @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/queries/latex/highlights.scm b/runtime/queries/latex/highlights.scm
index 2e308f77..0a030b31 100644
--- a/runtime/queries/latex/highlights.scm
+++ b/runtime/queries/latex/highlights.scm
@@ -371,7 +371,7 @@
((generic_command
name:(generic_command_name) @_name
.
- arg: (_) @markup.underline.link)
+ arg: (_) @markup.link.url)
(#match? @_name "^(\\\\url|\\\\href)$"))
(ERROR) @error
diff --git a/runtime/queries/ledger/injections.scm b/runtime/queries/ledger/injections.scm
index 2d948141..c1714786 100644
--- a/runtime/queries/ledger/injections.scm
+++ b/runtime/queries/ledger/injections.scm
@@ -1,2 +1,2 @@
-(comment) @comment
-(note) @comment
+([(comment) (note)] @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/queries/llvm-mir-yaml/highlights.scm b/runtime/queries/llvm-mir-yaml/highlights.scm
new file mode 100644
index 00000000..4ba254e8
--- /dev/null
+++ b/runtime/queries/llvm-mir-yaml/highlights.scm
@@ -0,0 +1 @@
+; inherits: yaml
diff --git a/runtime/queries/llvm-mir-yaml/indents.toml b/runtime/queries/llvm-mir-yaml/indents.toml
new file mode 100644
index 00000000..ddc3578b
--- /dev/null
+++ b/runtime/queries/llvm-mir-yaml/indents.toml
@@ -0,0 +1,3 @@
+indent = [
+ "block_mapping_pair",
+]
diff --git a/runtime/queries/llvm-mir-yaml/injections.scm b/runtime/queries/llvm-mir-yaml/injections.scm
new file mode 100644
index 00000000..b3243022
--- /dev/null
+++ b/runtime/queries/llvm-mir-yaml/injections.scm
@@ -0,0 +1,9 @@
+; inherits: yaml
+
+((document (block_node (block_scalar) @injection.content))
+ (#set! injection.language "llvm"))
+
+((document (block_node (block_mapping (block_mapping_pair
+ key: (flow_node (plain_scalar (string_scalar))) ; "body"
+ value: (block_node (block_scalar) @injection.content)))))
+ (#set! injection.language "mir"))
diff --git a/runtime/queries/llvm-mir/highlights.scm b/runtime/queries/llvm-mir/highlights.scm
new file mode 100644
index 00000000..79234612
--- /dev/null
+++ b/runtime/queries/llvm-mir/highlights.scm
@@ -0,0 +1,136 @@
+[
+ (label)
+ (bb_ref)
+] @label
+
+[
+ (comment)
+ (multiline_comment)
+] @comment
+
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+ "<"
+ ">"
+] @punctuation.bracket
+
+[
+ ","
+ ":"
+ "|"
+ "*"
+] @punctuation.delimiter
+
+[
+ "="
+ "x"
+] @operator
+
+[
+ "true"
+ "false"
+] @constant.builtin.boolean
+
+[
+ "null"
+ "_"
+ "unknown-address"
+] @constant.builtin
+
+[
+ (stack_object)
+ (constant_pool_index)
+ (jump_table_index)
+ (var)
+ (physical_register)
+ (ir_block)
+ (external_symbol)
+ (global_var)
+ (ir_local_var)
+ (metadata_ref)
+ (mnemonic)
+] @variable
+
+(low_level_type) @type
+
+[
+ (immediate_type)
+ (primitive_type)
+] @type.builtin
+
+(number) @constant.numeric.integer
+(float) @constant.numeric.float
+(string) @string
+
+(instruction name: _ @keyword.operator)
+
+[
+ "successors"
+ "liveins"
+ "pre-instr-symbol"
+ "post-instr-symbol"
+ "heap-alloc-marker"
+ "debug-instr-number"
+ "debug-location"
+ "mcsymbol"
+ "tied-def"
+ "target-flags"
+ "CustomRegMask"
+ "same_value"
+ "def_cfa_register"
+ "restore"
+ "undefined"
+ "offset"
+ "rel_offset"
+ "def_cfa"
+ "llvm_def_aspace_cfa"
+ "register"
+ "escape"
+ "remember_state"
+ "restore_state"
+ "window_save"
+ "negate_ra_sign_state"
+ "intpred"
+ "floatpred"
+ "shufflemask"
+ "liveout"
+ "target-index"
+ "blockaddress"
+ "intrinsic"
+ "load"
+ "store"
+ "unknown-size"
+ "on"
+ "from"
+ "into"
+ "align"
+ "basealign"
+ "addrspace"
+ "call-entry"
+ "custom"
+ "constant-pool"
+ "stack"
+ "got"
+ "jump-table"
+ "syncscope"
+ "address-taken"
+ "landing-pad"
+ "inlineasm-br-indirect-target"
+ "ehfunclet-entry"
+ "bbsections"
+
+ (intpred)
+ (floatpred)
+ (memory_operand_flag)
+ (atomic_ordering)
+ (register_flag)
+ (instruction_flag)
+ (float_keyword)
+] @keyword
+
+(ERROR) @error
diff --git a/runtime/queries/llvm-mir/indents.toml b/runtime/queries/llvm-mir/indents.toml
new file mode 100644
index 00000000..6a70e5ad
--- /dev/null
+++ b/runtime/queries/llvm-mir/indents.toml
@@ -0,0 +1,7 @@
+indent = [
+ "basic_block",
+]
+
+outdent = [
+ "label",
+]
diff --git a/runtime/queries/llvm-mir/injections.scm b/runtime/queries/llvm-mir/injections.scm
new file mode 100644
index 00000000..0b476f86
--- /dev/null
+++ b/runtime/queries/llvm-mir/injections.scm
@@ -0,0 +1,2 @@
+([ (comment) (multiline_comment)] @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/queries/llvm-mir/textobjects.scm b/runtime/queries/llvm-mir/textobjects.scm
new file mode 100644
index 00000000..73f6f772
--- /dev/null
+++ b/runtime/queries/llvm-mir/textobjects.scm
@@ -0,0 +1,3 @@
+(basic_block) @function.around
+
+(argument) @parameter.inside
diff --git a/runtime/queries/llvm/highlights.scm b/runtime/queries/llvm/highlights.scm
index 73afe85e..cb705197 100644
--- a/runtime/queries/llvm/highlights.scm
+++ b/runtime/queries/llvm/highlights.scm
@@ -1,14 +1,158 @@
(type) @type
-(statement) @keyword.operator
+(type_keyword) @type.builtin
+
+(type [
+ (local_var)
+ (global_var)
+ ] @type)
+
+(argument) @variable.parameter
+
+(_ inst_name: _ @keyword.operator)
+
+[
+ "catch"
+ "filter"
+] @keyword.operator
+
+[
+ "to"
+ "nuw"
+ "nsw"
+ "exact"
+ "unwind"
+ "from"
+ "cleanup"
+ "swifterror"
+ "volatile"
+ "inbounds"
+ "inrange"
+ (icmp_cond)
+ (fcmp_cond)
+ (fast_math)
+] @keyword.control
+
+(_ callee: _ @function)
+(function_header name: _ @function)
+
+[
+ "declare"
+ "define"
+ (calling_conv)
+] @keyword.function
+
+[
+ "target"
+ "triple"
+ "datalayout"
+ "source_filename"
+ "addrspace"
+ "blockaddress"
+ "align"
+ "syncscope"
+ "within"
+ "uselistorder"
+ "uselistorder_bb"
+ "module"
+ "asm"
+ "sideeffect"
+ "alignstack"
+ "inteldialect"
+ "unwind"
+ "type"
+ "global"
+ "constant"
+ "externally_initialized"
+ "alias"
+ "ifunc"
+ "section"
+ "comdat"
+ "thread_local"
+ "localdynamic"
+ "initialexec"
+ "localexec"
+ "any"
+ "exactmatch"
+ "largest"
+ "nodeduplicate"
+ "samesize"
+ "distinct"
+ "attributes"
+ "vscale"
+ "no_cfi"
+ (linkage_aux)
+ (dso_local)
+ (visibility)
+ (dll_storage_class)
+ (unnamed_addr)
+ (attribute_name)
+] @keyword
+
+
+(function_header [
+ (linkage)
+ (calling_conv)
+ (unnamed_addr)
+ ] @keyword.function)
+
+[
+ (string)
+ (cstring)
+] @string
+
(number) @constant.numeric.integer
(comment) @comment
-(string) @string
(label) @label
-(keyword) @keyword
-"ret" @keyword.control.return
-(boolean) @constant.builtin.boolean
+(_ inst_name: "ret" @keyword.control.return)
(float) @constant.numeric.float
-(constant) @constant
-(identifier) @variable
-(symbol) @punctuation.delimiter
-(bracket) @punctuation.bracket
+
+[
+ (local_var)
+ (global_var)
+] @variable
+
+[
+ (struct_value)
+ (array_value)
+ (vector_value)
+] @constructor
+
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+ "<"
+ ">"
+ "<{"
+ "}>"
+] @punctuation.bracket
+
+[
+ ","
+ ":"
+] @punctuation.delimiter
+
+[
+ "="
+ "|"
+ "x"
+ "..."
+] @operator
+
+[
+ "true"
+ "false"
+] @constant.builtin.boolean
+
+[
+ "undef"
+ "poison"
+ "null"
+ "none"
+ "zeroinitializer"
+] @constant.builtin
+
+(ERROR) @error
diff --git a/runtime/queries/llvm/indents.toml b/runtime/queries/llvm/indents.toml
new file mode 100644
index 00000000..8cd603c8
--- /dev/null
+++ b/runtime/queries/llvm/indents.toml
@@ -0,0 +1,8 @@
+indent = [
+ "function_body",
+ "instruction",
+]
+
+outdent = [
+ "}",
+]
diff --git a/runtime/queries/llvm/injections.scm b/runtime/queries/llvm/injections.scm
new file mode 100644
index 00000000..321c90ad
--- /dev/null
+++ b/runtime/queries/llvm/injections.scm
@@ -0,0 +1,2 @@
+((comment) @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/queries/llvm/locals.scm b/runtime/queries/llvm/locals.scm
new file mode 100644
index 00000000..1946c287
--- /dev/null
+++ b/runtime/queries/llvm/locals.scm
@@ -0,0 +1,14 @@
+; Scopes
+
+(function_body) @local.scope
+
+; Definitions
+
+(argument
+ (value (var (local_var) @local.definition)))
+
+(instruction
+ (local_var) @local.definition)
+
+; References
+(local_var) @local.reference
diff --git a/runtime/queries/llvm/textobjects.scm b/runtime/queries/llvm/textobjects.scm
new file mode 100644
index 00000000..3738a3bb
--- /dev/null
+++ b/runtime/queries/llvm/textobjects.scm
@@ -0,0 +1,16 @@
+(define
+ body: (_) @function.inside) @function.around
+
+(struct_type
+ (struct_body) @class.inside) @class.around
+
+(packed_struct_type
+ (struct_body) @class.inside) @class.around
+
+(array_type
+ (array_vector_body) @class.inside) @class.around
+
+(vector_type
+ (array_vector_body) @class.inside) @class.around
+
+(argument) @parameter.inside
diff --git a/runtime/queries/markdown/highlights.scm b/runtime/queries/markdown/highlights.scm
index a102719b..a0bd3462 100644
--- a/runtime/queries/markdown/highlights.scm
+++ b/runtime/queries/markdown/highlights.scm
@@ -10,15 +10,16 @@
(fenced_code_block)
] @markup.raw.block
+(block_quote) @markup.quote
+
(code_span) @markup.raw.inline
(emphasis) @markup.italic
(strong_emphasis) @markup.bold
-(link_destination) @markup.underline.link
-
-; (link_label) @markup.label ; TODO: rename
+(link_destination) @markup.link.url
+(link_label) @markup.link.label
[
(list_marker_plus)
diff --git a/runtime/queries/markdown/injections.scm b/runtime/queries/markdown/injections.scm
index ff3c5fe6..10dcab0b 100644
--- a/runtime/queries/markdown/injections.scm
+++ b/runtime/queries/markdown/injections.scm
@@ -1,6 +1,7 @@
(fenced_code_block
(info_string) @injection.language
- (code_fence_content) @injection.content)
+ (code_fence_content) @injection.content
+ (#set! injection.include-children))
((html_block) @injection.content
(#set! injection.language "html"))
diff --git a/runtime/queries/nix/highlights.scm b/runtime/queries/nix/highlights.scm
index 66719e87..f6682065 100644
--- a/runtime/queries/nix/highlights.scm
+++ b/runtime/queries/nix/highlights.scm
@@ -13,7 +13,7 @@
] @keyword
((identifier) @variable.builtin
- (#match? @variable.builtin "^(__currentSystem|__currentTime|__nixPath|__nixVersion|__storeDir|builtins|false|null|true)$")
+ (#match? @variable.builtin "^(__currentSystem|__currentTime|__nixPath|__nixVersion|__storeDir|builtins)$")
(#is-not? local))
((identifier) @function.builtin
@@ -33,6 +33,11 @@
(uri) @string.special.uri
+; boolean
+((identifier) @constant.builtin.boolean (#match? @constant.builtin.boolean "^(true|false)$")) @constant.builtin.boolean
+; null
+((identifier) @constant.builtin (#eq? @constant.builtin "null")) @constant.builtin
+
(integer) @constant.numeric.integer
(float) @constant.numeric.float
diff --git a/runtime/queries/ocaml/indents.toml b/runtime/queries/ocaml/indents.toml
index 9b6462d8..7586b83a 100644
--- a/runtime/queries/ocaml/indents.toml
+++ b/runtime/queries/ocaml/indents.toml
@@ -8,6 +8,6 @@ indent = [
"match_case",
]
-oudent = [
+outdent = [
"}",
]
diff --git a/runtime/queries/python/injections.scm b/runtime/queries/python/injections.scm
new file mode 100644
index 00000000..321c90ad
--- /dev/null
+++ b/runtime/queries/python/injections.scm
@@ -0,0 +1,2 @@
+((comment) @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/queries/ruby/indents.toml b/runtime/queries/ruby/indents.toml
new file mode 100644
index 00000000..b417751f
--- /dev/null
+++ b/runtime/queries/ruby/indents.toml
@@ -0,0 +1,25 @@
+indent = [
+ "argument_list",
+ "array",
+ "begin",
+ "block",
+ "call",
+ "class",
+ "case",
+ "do_block",
+ "elsif",
+ "if",
+ "hash",
+ "method",
+ "module",
+ "singleton_class",
+ "singleton_method",
+]
+
+outdent = [
+ ")",
+ "}",
+ "]",
+ "end",
+ "when",
+]
diff --git a/runtime/queries/rust/highlights.scm b/runtime/queries/rust/highlights.scm
index 539d9550..26496c66 100644
--- a/runtime/queries/rust/highlights.scm
+++ b/runtime/queries/rust/highlights.scm
@@ -127,11 +127,16 @@
"await"
] @keyword.control
+"use" @keyword.control.import
+(mod_item "mod" @keyword.control.import !body)
+(use_as_clause "as" @keyword.control.import)
+
+(type_cast_expression "as" @keyword.operator)
+
[
(crate)
(super)
"as"
- "use"
"pub"
"mod"
"extern"
@@ -242,10 +247,9 @@
; ---
; Macros
; ---
-
(meta_item
- (identifier) @attribute)
-(attribute_item) @attribute
+ (identifier) @function.macro)
+
(inner_attribute_item) @attribute
(macro_definition
@@ -259,7 +263,7 @@
"!" @function.macro)
(metavariable) @variable.parameter
-(fragment_specifier) @variable.parameter
+(fragment_specifier) @type
diff --git a/runtime/queries/rust/indents.toml b/runtime/queries/rust/indents.toml
index 3900f0b9..51a0ceea 100644
--- a/runtime/queries/rust/indents.toml
+++ b/runtime/queries/rust/indents.toml
@@ -9,6 +9,7 @@ indent = [
"field_initializer_list",
"struct_pattern",
"tuple_pattern",
+ "unit_expression",
"enum_variant_list",
"call_expression",
"binary_expression",
diff --git a/runtime/queries/rust/injections.scm b/runtime/queries/rust/injections.scm
index 6035d418..d8382e49 100644
--- a/runtime/queries/rust/injections.scm
+++ b/runtime/queries/rust/injections.scm
@@ -1,3 +1,6 @@
+([(line_comment) (block_comment)] @injection.content
+ (#set! injection.language "comment"))
+
((macro_invocation
(token_tree) @injection.content)
(#set! injection.language "rust")
diff --git a/runtime/queries/scala/highlights.scm b/runtime/queries/scala/highlights.scm
new file mode 100644
index 00000000..50a6e18a
--- /dev/null
+++ b/runtime/queries/scala/highlights.scm
@@ -0,0 +1,203 @@
+; CREDITS @stumash (stuart.mashaal@gmail.com)
+
+;; variables
+
+
+((identifier) @variable.builtin
+ (#match? @variable.builtin "^this$"))
+
+(interpolation) @none
+
+; Assume other uppercase names constants.
+; NOTE: In order to distinguish constants we highlight
+; all the identifiers that are uppercased. But this solution
+; is not suitable for all occurrences e.g. it will highlight
+; an uppercased method as a constant if used with no params.
+; Introducing highlighting for those specific cases, is probably
+; best way to resolve the issue.
+((identifier) @constant (#match? @constant "^[A-Z]"))
+
+;; types
+
+(type_identifier) @type
+
+(class_definition
+ name: (identifier) @type)
+
+(object_definition
+ name: (identifier) @type)
+
+(trait_definition
+ name: (identifier) @type)
+
+(type_definition
+ name: (type_identifier) @type)
+
+; method definition
+
+(class_definition
+ body: (template_body
+ (function_definition
+ name: (identifier) @function.method)))
+(object_definition
+ body: (template_body
+ (function_definition
+ name: (identifier) @function.method)))
+(trait_definition
+ body: (template_body
+ (function_definition
+ name: (identifier) @function.method)))
+
+; imports
+
+(import_declaration
+ path: (identifier) @namespace)
+((stable_identifier (identifier) @namespace))
+
+((import_declaration
+ path: (identifier) @type) (#match? @type "^[A-Z]"))
+((stable_identifier (identifier) @type) (#match? @type "^[A-Z]"))
+
+((import_selectors (identifier) @type) (#match? @type "^[A-Z]"))
+
+; method invocation
+
+
+(call_expression
+ function: (identifier) @function)
+
+(call_expression
+ function: (field_expression
+ field: (identifier) @function.method))
+
+((call_expression
+ function: (identifier) @variable.other.member)
+ (#match? @variable.other.member "^[A-Z]"))
+
+(generic_function
+ function: (identifier) @function)
+
+(
+ (identifier) @function.builtin
+ (#match? @function.builtin "^super$")
+)
+
+; function definitions
+
+(function_definition
+ name: (identifier) @function)
+
+(parameter
+ name: (identifier) @variable.parameter)
+
+; expressions
+
+
+(field_expression field: (identifier) @variable.other.member)
+(field_expression value: (identifier) @type
+ (#match? @type "^[A-Z]"))
+
+(infix_expression operator: (identifier) @operator)
+(infix_expression operator: (operator_identifier) @operator)
+(infix_type operator: (operator_identifier) @operator)
+(infix_type operator: (operator_identifier) @operator)
+
+; literals
+(boolean_literal) @constant.builtin.boolean
+(integer_literal) @constant.numeric.integer
+(floating_point_literal) @constant.numeric.float
+
+
+(symbol_literal) @string.special.symbol
+
+[
+(string)
+(character_literal)
+(interpolated_string_expression)
+] @string
+
+(interpolation "$" @punctuation.special)
+
+;; keywords
+
+[
+ "abstract"
+ "case"
+ "class"
+ "extends"
+ "final"
+ "finally"
+;; `forSome` existential types not implemented yet
+ "implicit"
+ "lazy"
+;; `macro` not implemented yet
+ "object"
+ "override"
+ "package"
+ "private"
+ "protected"
+ "sealed"
+ "trait"
+ "type"
+ "val"
+ "var"
+ "with"
+] @keyword
+
+(null_literal) @constant.builtin
+(wildcard) @keyword
+
+;; special keywords
+
+"new" @keyword.operator
+
+[
+ "else"
+ "if"
+ "match"
+ "try"
+ "catch"
+ "throw"
+] @keyword.control.conditional
+
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+] @punctuation.bracket
+
+[
+ "."
+ ","
+] @punctuation.delimiter
+
+[
+ "do"
+ "for"
+ "while"
+ "yield"
+] @keyword.control.repeat
+
+"def" @keyword.function
+
+[
+ "=>"
+ "<-"
+ "@"
+] @keyword.operator
+
+"import" @keyword.control.import
+
+"return" @keyword.control.return
+
+(comment) @comment
+
+;; `case` is a conditional keyword in case_block
+
+(case_block
+ (case_clause ("case") @keyword.control.conditional))
+
+(identifier) @variable \ No newline at end of file
diff --git a/runtime/queries/scala/indents.toml b/runtime/queries/scala/indents.toml
new file mode 100644
index 00000000..6de54844
--- /dev/null
+++ b/runtime/queries/scala/indents.toml
@@ -0,0 +1,23 @@
+
+indent = [
+ "block",
+ "arguments",
+ "parameter",
+ "class_definition",
+ "trait_definition",
+ "object_definition",
+ "function_definition",
+ "val_definition",
+ "import_declaration",
+ "while_expression",
+ "do_while_expression",
+ "for_expression",
+ "try_expression",
+ "match_expression"
+]
+
+outdent = [
+ "}",
+ "]",
+ ")"
+]
diff --git a/runtime/queries/scala/injections.scm b/runtime/queries/scala/injections.scm
new file mode 100644
index 00000000..4bb7d675
--- /dev/null
+++ b/runtime/queries/scala/injections.scm
@@ -0,0 +1 @@
+(comment) @comment
diff --git a/runtime/queries/svelte/highlights.scm b/runtime/queries/svelte/highlights.scm
index f9eef6b5..22b0c551 100644
--- a/runtime/queries/svelte/highlights.scm
+++ b/runtime/queries/svelte/highlights.scm
@@ -20,12 +20,12 @@
((element (start_tag (tag_name) @_tag) (text) @markup.inline)
(#match? @_tag "^(code|kbd)$"))
-((element (start_tag (tag_name) @_tag) (text) @markup.underline.link)
+((element (start_tag (tag_name) @_tag) (text) @markup.link.url)
(#eq? @_tag "a"))
((attribute
(attribute_name) @_attr
- (quoted_attribute_value (attribute_value) @markup.underline.link))
+ (quoted_attribute_value (attribute_value) @markup.link.url))
(#match? @_attr "^(href|src)$"))
(tag_name) @tag
diff --git a/runtime/queries/svelte/injections.scm b/runtime/queries/svelte/injections.scm
index 266f4701..04e860cf 100644
--- a/runtime/queries/svelte/injections.scm
+++ b/runtime/queries/svelte/injections.scm
@@ -26,5 +26,5 @@
(#set! injection.language "typescript")
)
-(comment) @comment
-
+((comment) @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/queries/tablegen/highlights.scm b/runtime/queries/tablegen/highlights.scm
new file mode 100644
index 00000000..8ade5ba9
--- /dev/null
+++ b/runtime/queries/tablegen/highlights.scm
@@ -0,0 +1,90 @@
+[
+ (comment)
+ (multiline_comment)
+] @comment
+
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+ "<"
+ ">"
+] @punctuation.bracket
+
+[
+ ","
+ ";"
+ "."
+] @punctuation.delimiter
+
+[
+ "#"
+ "-"
+ "..."
+ ":"
+] @operator
+
+[
+ "="
+ "!cond"
+ (operator_keyword)
+] @function
+
+[
+ "true"
+ "false"
+] @constant.builtin.boolean
+
+[
+ "?"
+] @constant.builtin
+
+(var) @variable
+
+(template_arg (identifier) @variable.parameter)
+
+(_ argument: (value (identifier) @variable.parameter))
+
+(type) @type
+
+"code" @type.builtin
+
+(number) @constant.numeric.integer
+[
+ (string_string)
+ (code_string)
+] @string
+
+(preprocessor) @keyword.directive
+
+[
+ "class"
+ "field"
+ "let"
+ "defvar"
+ "def"
+ "defset"
+ "defvar"
+ "assert"
+] @keyword
+
+[
+ "let"
+ "in"
+ "foreach"
+ "if"
+ "then"
+ "else"
+] @keyword.operator
+
+"include" @keyword.control.import
+
+[
+ "multiclass"
+ "defm"
+] @namespace
+
+(ERROR) @error
diff --git a/runtime/queries/tablegen/indents.toml b/runtime/queries/tablegen/indents.toml
new file mode 100644
index 00000000..43532f4d
--- /dev/null
+++ b/runtime/queries/tablegen/indents.toml
@@ -0,0 +1,7 @@
+indent = [
+ "statement",
+]
+
+outdent = [
+ "}",
+]
diff --git a/runtime/queries/tablegen/injections.scm b/runtime/queries/tablegen/injections.scm
new file mode 100644
index 00000000..0b476f86
--- /dev/null
+++ b/runtime/queries/tablegen/injections.scm
@@ -0,0 +1,2 @@
+([ (comment) (multiline_comment)] @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/queries/tablegen/textobjects.scm b/runtime/queries/tablegen/textobjects.scm
new file mode 100644
index 00000000..2cb80268
--- /dev/null
+++ b/runtime/queries/tablegen/textobjects.scm
@@ -0,0 +1,7 @@
+(class
+ body: (_) @class.inside) @class.around
+
+(multiclass
+ body: (_) @class.inside) @class.around
+
+(_ argument: _ @parameter.inside)
diff --git a/runtime/queries/tsq/injections.scm b/runtime/queries/tsq/injections.scm
new file mode 100644
index 00000000..321c90ad
--- /dev/null
+++ b/runtime/queries/tsq/injections.scm
@@ -0,0 +1,2 @@
+((comment) @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/queries/yaml/highlights.scm b/runtime/queries/yaml/highlights.scm
index a7efb5e7..e4fed27a 100644
--- a/runtime/queries/yaml/highlights.scm
+++ b/runtime/queries/yaml/highlights.scm
@@ -1,9 +1,19 @@
-(block_mapping_pair key: (_) @variable.other.member)
-(flow_mapping (_ key: (_) @variable.other.member))
+(block_mapping_pair
+ key: (flow_node [(double_quote_scalar) (single_quote_scalar)] @variable.other.member))
+(block_mapping_pair
+ key: (flow_node (plain_scalar (string_scalar) @variable.other.member)))
+
+(flow_mapping
+ (_ key: (flow_node [(double_quote_scalar) (single_quote_scalar)] @variable.other.member)))
+(flow_mapping
+ (_ key: (flow_node (plain_scalar (string_scalar) @variable.other.member))))
+
(boolean_scalar) @constant.builtin.boolean
(null_scalar) @constant.builtin
(double_quote_scalar) @string
(single_quote_scalar) @string
+(block_scalar) @string
+(string_scalar) @string
(escape_sequence) @constant.character.escape
(integer_scalar) @constant.numeric.integer
(float_scalar) @constant.numeric.float
@@ -30,4 +40,4 @@
"}"
] @punctuation.bracket
-["*" "&"] @punctuation.special
+["*" "&" "---" "..."] @punctuation.special
diff --git a/runtime/queries/yaml/injections.scm b/runtime/queries/yaml/injections.scm
new file mode 100644
index 00000000..39bda293
--- /dev/null
+++ b/runtime/queries/yaml/injections.scm
@@ -0,0 +1,2 @@
+((comment) @injection.content
+ (#set! injection.language "comment"))
diff --git a/runtime/themes/base16_default_dark.toml b/runtime/themes/base16_default_dark.toml
index fd4b0fea..33ff87fa 100644
--- a/runtime/themes/base16_default_dark.toml
+++ b/runtime/themes/base16_default_dark.toml
@@ -33,6 +33,10 @@
"namespace" = "base0E"
"ui.help" = { fg = "base06", bg = "base01" }
+"diff.plus" = "base0B"
+"diff.delta" = "base09"
+"diff.minus" = "base08"
+
"diagnostic" = { modifiers = ["underlined"] }
"ui.gutter" = { bg = "base01" }
"info" = "base0D"
diff --git a/runtime/themes/base16_default_light.toml b/runtime/themes/base16_default_light.toml
index 596990da..3f4de265 100644
--- a/runtime/themes/base16_default_light.toml
+++ b/runtime/themes/base16_default_light.toml
@@ -33,6 +33,10 @@
"namespace" = "base0E"
"ui.help" = { fg = "base06", bg = "base01" }
+"diff.plus" = "base0B"
+"diff.delta" = "base09"
+"diff.minus" = "base08"
+
"diagnostic" = { modifiers = ["underlined"] }
"ui.gutter" = { bg = "base01" }
"info" = "base0D"
diff --git a/runtime/themes/base16_terminal.toml b/runtime/themes/base16_terminal.toml
index 123ceaea..cbbfbf24 100644
--- a/runtime/themes/base16_terminal.toml
+++ b/runtime/themes/base16_terminal.toml
@@ -30,6 +30,10 @@
"namespace" = "light-magenta"
"ui.help" = { fg = "white", bg = "black" }
+"diff.plus" = "light-green"
+"diff.delta" = "yellow"
+"diff.minus" = "light-red"
+
"diagnostic" = { modifiers = ["underlined"] }
"ui.gutter" = { bg = "black" }
"info" = "light-blue"
diff --git a/runtime/themes/bogster.toml b/runtime/themes/bogster.toml
index 86a6c34b..493e5ace 100644
--- a/runtime/themes/bogster.toml
+++ b/runtime/themes/bogster.toml
@@ -28,6 +28,10 @@
"module" = "#d32c5d"
+"diff.plus" = "#59dcb7"
+"diff.delta" = "#dc7759"
+"diff.minus" = "#dc597f"
+
"ui.background" = { bg = "#161c23" }
"ui.linenr" = { fg = "#415367" }
"ui.linenr.selected" = { fg = "#e5ded6" } # TODO
@@ -49,3 +53,6 @@
"error" = "#dc597f"
"info" = "#59dcb7"
"hint" = "#59c0dc"
+
+# make diagnostic underlined, to distinguish with selection text.
+diagnostic = { modifiers = ["underlined"] }
diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml
index 0554f827..78437649 100644
--- a/runtime/themes/dark_plus.toml
+++ b/runtime/themes/dark_plus.toml
@@ -39,6 +39,10 @@
"constant.numeric" = { fg = "pale_green" }
"constant.character.escape" = { fg = "gold" }
+"diff.plus" = { fg = "pale_green" }
+"diff.delta" = { fg = "gold" }
+"diff.minus" = { fg = "red" }
+
"ui.background" = { fg = "light_gray", bg = "dark_gray2" }
"ui.window" = { bg = "widget" }
diff --git a/runtime/themes/dracula.toml b/runtime/themes/dracula.toml
new file mode 100644
index 00000000..f01c7323
--- /dev/null
+++ b/runtime/themes/dracula.toml
@@ -0,0 +1,54 @@
+# Author : Sebastian Zivota <loewenheim@mailbox.org>
+"comment" = { fg = "comment" }
+"constant" = { fg = "purple" }
+"constant.character.escape" = { fg = "pink" }
+"function" = { fg = "green" }
+"keyword" = { fg = "pink" }
+"operator" = { fg = "pink" }
+"punctuation" = { fg = "foreground" }
+"string" = { fg = "yellow" }
+"string.regexp" = { fg = "red" }
+"tag" = { fg = "pink" }
+"type" = { fg = "cyan", modifiers = ["italic"] }
+"type.enum.variant" = { fg = "foreground", modifiers = ["italic"] }
+"variable" = { fg = "foreground" }
+"variable.builtin" = { fg = "cyan", modifiers = ["italic"] }
+"variable.parameter" = { fg ="orange", modifiers = ["italic"] }
+
+"diff.plus" = { fg = "green" }
+"diff.delta" = { fg = "orange" }
+"diff.minus" = { fg = "red" }
+
+"ui.background" = { fg = "foreground", bg = "background" }
+"ui.cursor" = { fg = "background", bg = "orange", modifiers = ["dim"] }
+"ui.cursor.match" = { fg = "green", modifiers = ["underlined"] }
+"ui.cursor.primary" = { fg = "background", bg = "cyan", modifier = ["dim"] }
+"ui.help" = { fg = "foreground", bg = "background_dark" }
+"ui.linenr" = { fg = "comment" }
+"ui.linenr.selected" = { fg = "foreground" }
+"ui.menu" = { fg = "foreground", bg = "background_dark" }
+"ui.menu.selected" = { fg = "cyan", bg = "background_dark" }
+"ui.popup" = { fg = "foreground" }
+"ui.selection" = { fg = "background", bg = "purple", modifiers = ["dim"] }
+"ui.selection.primary" = { fg = "background", bg = "pink" }
+"ui.statusline" = { fg = "foreground", bg = "background_dark" }
+"ui.statusline.inactive" = { fg = "comment", bg = "background_dark" }
+"ui.text" = { fg = "foreground" }
+"ui.text.focus" = { fg = "cyan" }
+"ui.window" = { fg = "foreground" }
+
+"error" = { fg = "red" }
+"warning" = { fg = "cyan" }
+
+[palette]
+background = "#282a36"
+background_dark = "#21222c"
+foreground = "#f8f8f2"
+comment = "#6272a4"
+red = "#ff5555"
+orange = "#ffb86c"
+yellow = "#f1fa8c"
+green = "#50fa7b"
+purple = "#bd93f9"
+cyan = "#8be9fd"
+pink = "#ff79c6"
diff --git a/runtime/themes/everforest_dark.toml b/runtime/themes/everforest_dark.toml
index bbd005e6..f5a0088c 100644
--- a/runtime/themes/everforest_dark.toml
+++ b/runtime/themes/everforest_dark.toml
@@ -12,7 +12,7 @@
"type" = "yellow"
"constant" = "purple"
"constant.numeric" = "purple"
-"string" = "grey2"
+"string" = "green"
"comment" = "grey0"
"variable" = "fg"
"variable.builtin" = "blue"
@@ -34,6 +34,10 @@
"module" = "blue"
"special" = "orange"
+"diff.plus" = "green"
+"diff.delta" = "orange"
+"diff.minus" = "red"
+
"ui.background" = { bg = "bg0" }
"ui.cursor" = { fg = "bg0", bg = "fg" }
"ui.cursor.match" = { fg = "orange", bg = "bg_yellow" }
diff --git a/runtime/themes/everforest_light.toml b/runtime/themes/everforest_light.toml
new file mode 100644
index 00000000..3038ef9c
--- /dev/null
+++ b/runtime/themes/everforest_light.toml
@@ -0,0 +1,90 @@
+# Everforest (Dark Hard)
+# Author: CptPotato
+
+# Original Author:
+# URL: https://github.com/sainnhe/everforest
+# Filename: autoload/everforest.vim
+# Author: sainnhe
+# Email: sainnhe@gmail.com
+# License: MIT License
+
+"constant.character.escape" = "orange"
+"type" = "yellow"
+"constant" = "purple"
+"constant.numeric" = "purple"
+"string" = "green"
+"comment" = "grey0"
+"variable" = "fg"
+"variable.builtin" = "blue"
+"variable.parameter" = "fg"
+"variable.other.member" = "fg"
+"label" = "aqua"
+"punctuation" = "grey2"
+"punctuation.delimiter" = "grey2"
+"punctuation.bracket" = "fg"
+"keyword" = "red"
+"operator" = "orange"
+"function" = "green"
+"function.builtin" = "blue"
+"function.macro" = "aqua"
+"tag" = "yellow"
+"namespace" = "aqua"
+"attribute" = "aqua"
+"constructor" = "yellow"
+"module" = "blue"
+"special" = "orange"
+
+"diff.plus" = "green"
+"diff.delta" = "orange"
+"diff.minus" = "red"
+
+"ui.background" = { bg = "bg0" }
+"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.linenr" = "grey0"
+"ui.linenr.selected" = "fg"
+"ui.statusline" = { fg = "grey2", bg = "bg2" }
+"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.text" = "fg"
+"ui.text.focus" = "fg"
+"ui.menu" = { fg = "fg", bg = "bg2" }
+"ui.menu.selected" = { fg = "bg0", bg = "green" }
+"ui.selection" = { bg = "bg3" }
+
+"hint" = "blue"
+"info" = "aqua"
+"warning" = "yellow"
+"error" = "red"
+"diagnostic" = { modifiers = ["underlined"] }
+
+
+[palette]
+
+bg0 = "#fff9e8"
+bg1 = "#f7f4e0"
+bg2 = "#f0eed9"
+bg3 = "#e9e8d2"
+bg4 = "#e1ddcb"
+bg5 = "#bec5b2"
+bg_visual = "#edf0cd"
+bg_red = "#fce5dc"
+bg_green = "#f1f3d4"
+bg_blue = "#eaf2eb"
+bg_yellow = "#fbefd0"
+
+fg = "#5c6a72"
+red = "#f85552"
+orange = "#f57d26"
+yellow = "#dfa000"
+green = "#8da101"
+aqua = "#35a77c"
+blue = "#3a94c5"
+purple = "#df69ba"
+grey0 = "#a6b0a0"
+grey1 = "#939f91"
+grey2 = "#829181"
diff --git a/runtime/themes/gruvbox.toml b/runtime/themes/gruvbox.toml
index 0ff039ea..9eae0915 100644
--- a/runtime/themes/gruvbox.toml
+++ b/runtime/themes/gruvbox.toml
@@ -28,6 +28,10 @@
"label" = "aqua1"
"module" = "aqua1"
+"diff.plus" = "green1"
+"diff.delta" = "orange1"
+"diff.minus" = "red1"
+
"warning" = { fg = "orange1", bg = "bg1" }
"error" = { fg = "red1", bg = "bg1" }
"info" = { fg = "aqua1", bg = "bg1" }
diff --git a/runtime/themes/ingrid.toml b/runtime/themes/ingrid.toml
index 30829475..df8a922f 100644
--- a/runtime/themes/ingrid.toml
+++ b/runtime/themes/ingrid.toml
@@ -28,6 +28,10 @@
"module" = "#839A53"
+"diff.plus" = "#839A53"
+"diff.delta" = "#D4A520"
+"diff.minus" = "#D74E50"
+
"ui.background" = { bg = "#FFFCFD" }
"ui.linenr" = { fg = "#bbbbbb" }
"ui.linenr.selected" = { fg = "#F3EAE9" } # TODO
diff --git a/runtime/themes/monokai.toml b/runtime/themes/monokai.toml
index 38f9f170..c8bf8ebd 100644
--- a/runtime/themes/monokai.toml
+++ b/runtime/themes/monokai.toml
@@ -39,6 +39,10 @@
"constant.numeric" = { fg = "#ae81ff" }
"constant.character.escape" = { fg = "#ae81ff" }
+"diff.plus" = { fg = "#a6e22e" }
+"diff.delta" = { fg = "#fd971f" }
+"diff.minus" = { fg = "#f92672" }
+
"ui.background" = { fg = "text", bg = "background" }
"ui.window" = { bg = "widget" }
@@ -65,7 +69,7 @@
"warning" = { fg = "#cca700" }
"error" = { fg = "#f48771" }
"info" = { fg = "#75beff" }
-"hint" = { fg = "#eeeeeeb3" }
+"hint" = { fg = "#eeeeeb3" }
diagnostic = { modifiers = ["underlined"] }
diff --git a/runtime/themes/monokai_pro.toml b/runtime/themes/monokai_pro.toml
index bf8a4a84..5daeaf6c 100644
--- a/runtime/themes/monokai_pro.toml
+++ b/runtime/themes/monokai_pro.toml
@@ -77,6 +77,11 @@
# integer, floating point
"constant.numeric" = "purple"
+# vcs
+"diff.plus" = "green"
+"diff.delta" = "orange"
+"diff.minus" = "red"
+
# make diagnostic underlined, to distinguish with selection text.
diagnostic = { modifiers = ["underlined"] }
diff --git a/runtime/themes/monokai_pro_machine.toml b/runtime/themes/monokai_pro_machine.toml
index d8a701f1..0763a5fb 100644
--- a/runtime/themes/monokai_pro_machine.toml
+++ b/runtime/themes/monokai_pro_machine.toml
@@ -77,6 +77,11 @@
# integer, floating point
"constant.numeric" = "purple"
+# vcs
+"diff.plus" = "green"
+"diff.delta" = "orange"
+"diff.minus" = "red"
+
# make diagnostic underlined, to distinguish with selection text.
diagnostic = { modifiers = ["underlined"] }
diff --git a/runtime/themes/monokai_pro_octagon.toml b/runtime/themes/monokai_pro_octagon.toml
index 74459472..6a74a8d0 100644
--- a/runtime/themes/monokai_pro_octagon.toml
+++ b/runtime/themes/monokai_pro_octagon.toml
@@ -77,6 +77,11 @@
# integer, floating point
"constant.numeric" = "purple"
+# vcs
+"diff.plus" = "green"
+"diff.delta" = "orange"
+"diff.minus" = "red"
+
# make diagnostic underlined, to distinguish with selection text.
diagnostic = { modifiers = ["underlined"] }
diff --git a/runtime/themes/monokai_pro_ristretto.toml b/runtime/themes/monokai_pro_ristretto.toml
index a9cf4b34..1a1a32ff 100644
--- a/runtime/themes/monokai_pro_ristretto.toml
+++ b/runtime/themes/monokai_pro_ristretto.toml
@@ -77,6 +77,11 @@
# integer, floating point
"constant.numeric" = "purple"
+# vcs
+"diff.plus" = "green"
+"diff.delta" = "orange"
+"diff.minus" = "red"
+
# make diagnostic underlined, to distinguish with selection text.
diagnostic = { modifiers = ["underlined"] }
diff --git a/runtime/themes/monokai_pro_spectrum.toml b/runtime/themes/monokai_pro_spectrum.toml
index 232adfbd..36630493 100644
--- a/runtime/themes/monokai_pro_spectrum.toml
+++ b/runtime/themes/monokai_pro_spectrum.toml
@@ -77,6 +77,11 @@
# integer, floating point
"constant.numeric" = "purple"
+# vcs
+"diff.plus" = "green"
+"diff.delta" = "orange"
+"diff.minus" = "red"
+
# make diagnostic underlined, to distinguish with selection text.
diagnostic = { modifiers = ["underlined"] }
diff --git a/runtime/themes/nord.toml b/runtime/themes/nord.toml
index a619f902..ae1ea29b 100644
--- a/runtime/themes/nord.toml
+++ b/runtime/themes/nord.toml
@@ -84,6 +84,11 @@
# nord15 - integer, floating point
"constant.numeric" = "nord15"
+# vcs
+"diff.plus" = "nord14"
+"diff.delta" = "nord12"
+"diff.minus" = "nord11"
+
[palette]
nord0 = "#2e3440"
nord1 = "#3b4252"
diff --git a/runtime/themes/onedark.toml b/runtime/themes/onedark.toml
index 40ed1abe..dfeadbb2 100644
--- a/runtime/themes/onedark.toml
+++ b/runtime/themes/onedark.toml
@@ -3,27 +3,42 @@
"attribute" = { fg = "yellow" }
"comment" = { fg = "light-gray", modifiers = ["italic"] }
"constant" = { fg = "cyan" }
-"constant.builtin" = { fg = "blue" }
+"constant.numeric" = { fg = "gold" }
+"constant.builtin" = { fg = "gold" }
+"constant.character.escape" = { fg = "gold" }
"constructor" = { fg = "blue" }
-"escape" = { fg = "gold" }
"function" = { fg = "blue" }
"function.builtin" = { fg = "blue" }
"function.macro" = { fg = "purple" }
"keyword" = { fg = "red" }
"keyword.control" = { fg = "purple" }
+"keyword.control.import" = { fg = "red" }
"keyword.directive" = { fg = "purple" }
"label" = { fg = "purple" }
"namespace" = { fg = "blue" }
-"number" = { fg = "gold" }
"operator" = { fg = "purple" }
+"keyword.operator" = { fg = "purple" }
"property" = { fg = "red" }
"special" = { fg = "blue" }
"string" = { fg = "green" }
"type" = { fg = "yellow" }
-"type.builtin" = { fg = "yellow" }
# "variable" = { fg = "blue" }
"variable.builtin" = { fg = "blue" }
"variable.parameter" = { fg = "red" }
+"variable.other.member" = { fg = "red" }
+
+"markup.heading" = { fg = "red" }
+"markup.raw.inline" = { fg = "green" }
+"markup.bold" = { fg = "gold", modifiers = ["bold"] }
+"markup.italic" = { fg = "purple", modifiers = ["italic"] }
+"markup.list" = { fg = "red" }
+"markup.quote" = { fg = "yellow" }
+"markup.link.url" = { fg = "cyan", modifiers = ["underlined"]}
+"markup.link.label" = { fg = "purple" }
+
+"diff.plus" = "green"
+"diff.delta" = "gold"
+"diff.minus" = "red"
diagnostic = { modifiers = ["underlined"] }
"info" = { fg = "blue", modifiers = ["bold"] }
diff --git a/runtime/themes/rose_pine.toml b/runtime/themes/rose_pine.toml
index 256b33c8..bf3a040b 100644
--- a/runtime/themes/rose_pine.toml
+++ b/runtime/themes/rose_pine.toml
@@ -1,4 +1,5 @@
# Author: RayGervais<raygervais@hotmail.ca>
+# Author: ChrisHa<chunghha@users.noreply.github.com>
"ui.background" = { bg = "base" }
"ui.menu" = "surface"
@@ -37,6 +38,9 @@
"ui.window" = { bg = "base" }
"ui.help" = { bg = "overlay", fg = "foam" }
"text" = "text"
+"diff.plus" = "foam"
+"diff.delta" = "rose"
+"diff.minus" = "love"
"info" = "gold"
"hint" = "gold"
@@ -44,6 +48,15 @@
"diagnostic" = "rose"
"error" = "love"
+"markup.heading" = { fg = "rose" }
+"markup.raw.inline" = { fg = "foam" }
+"markup.bold" = { fg = "gold", modifiers = ["bold"] }
+"markup.italic" = { fg = "iris", modifiers = ["italic"] }
+"markup.list" = { fg = "love" }
+"markup.quote" = { fg = "rose" }
+"markup.link.url" = { fg = "pine", modifiers = ["underlined"]}
+"markup.link.label" = { fg = "foam" }
+
[palette]
base = "#191724"
surface = "#1f1d2e"
diff --git a/runtime/themes/rose_pine_dawn.toml b/runtime/themes/rose_pine_dawn.toml
index 43ba24ed..cc6d287e 100644
--- a/runtime/themes/rose_pine_dawn.toml
+++ b/runtime/themes/rose_pine_dawn.toml
@@ -1,8 +1,8 @@
-# Author: ChrisHa<chunghha@users.noreply.github.com>
# Author: RayGervais<raygervais@hotmail.ca>
+# Author: ChrisHa<chunghha@users.noreply.github.com>
-"ui.background" = { bg = "base" }
-"ui.menu" = "surface"
+"ui.background" = { bg = "surface" }
+"ui.menu" = "base"
"ui.menu.selected" = { fg = "iris", bg = "surface" }
"ui.linenr" = {fg = "subtle" }
"ui.popup" = { bg = "overlay" }
@@ -38,6 +38,9 @@
"ui.window" = { bg = "base" }
"ui.help" = { bg = "overlay", fg = "foam" }
"text" = "text"
+"diff.plus" = "foam"
+"diff.delta" = "rose"
+"diff.minus" = "love"
"info" = "gold"
"hint" = "gold"
@@ -45,6 +48,15 @@
"diagnostic" = "rose"
"error" = "love"
+"markup.heading" = { fg = "rose" }
+"markup.raw.inline" = { fg = "foam" }
+"markup.bold" = { fg = "gold", modifiers = ["bold"] }
+"markup.italic" = { fg = "iris", modifiers = ["italic"] }
+"markup.list" = { fg = "love" }
+"markup.quote" = { fg = "rose" }
+"markup.link.url" = { fg = "pine", modifiers = ["underlined"]}
+"markup.link.label" = { fg = "foam" }
+
[palette]
base = "#faf4ed"
surface = "#fffaf3"
diff --git a/runtime/themes/solarized_dark.toml b/runtime/themes/solarized_dark.toml
index 979fdaac..ab345c8d 100644
--- a/runtime/themes/solarized_dark.toml
+++ b/runtime/themes/solarized_dark.toml
@@ -22,6 +22,10 @@
"module" = { fg = "violet" }
"tag" = { fg = "magenta" }
+"diff.plus" = { fg = "green" }
+"diff.delta" = { fg = "orange" }
+"diff.minus" = { fg = "red" }
+
# 背景
"ui.background" = { bg = "base03" }
diff --git a/runtime/themes/solarized_light.toml b/runtime/themes/solarized_light.toml
index ded90cc4..6b0137fb 100644
--- a/runtime/themes/solarized_light.toml
+++ b/runtime/themes/solarized_light.toml
@@ -22,6 +22,10 @@
"module" = { fg = "violet" }
"tag" = { fg = "magenta" }
+"diff.plus" = { fg = "green" }
+"diff.delta" = { fg = "orange" }
+"diff.minus" = { fg = "red" }
+
# 背景
"ui.background" = { bg = "base03" }
diff --git a/runtime/themes/spacebones_light.toml b/runtime/themes/spacebones_light.toml
index 92f116ab..fcbe7b37 100644
--- a/runtime/themes/spacebones_light.toml
+++ b/runtime/themes/spacebones_light.toml
@@ -30,6 +30,10 @@
"label" = "#b1951d"
"module" = "#b1951d"
+"diff.plus" = "#2d9574"
+"diff.delta" = "#715ab1"
+"diff.minus" = "#ba2f59"
+
"warning" = { fg = "#da8b55" }
"error" = { fg = "#e0211d" }
"info" = { fg = "#b1951d" }
diff --git a/theme.toml b/theme.toml
index 0a79861e..ca0b2805 100644
--- a/theme.toml
+++ b/theme.toml
@@ -31,9 +31,13 @@ label = "honey"
"markup.heading" = "lilac"
"markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] }
-"markup.underline.link" = { fg = "silver", modifiers = ["underlined"] }
+"markup.link.url" = { fg = "silver", modifiers = ["underlined"] }
"markup.raw" = "almond"
+"diff.plus" = "#35bf86"
+"diff.minus" = "#f22c86"
+"diff.delta" = "#6f44f0"
+
# TODO: diferentiate doc comment
# concat (ERROR) @error.syntax and "MISSING ;" selectors for errors
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index fe5d55d4..717530d0 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -1,11 +1,11 @@
[package]
name = "xtask"
-version = "0.5.0"
+version = "0.6.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-helix-term = { version = "0.5", path = "../helix-term" }
-helix-core = { version = "0.5", path = "../helix-core" }
+helix-term = { version = "0.6", path = "../helix-term" }
+helix-core = { version = "0.6", path = "../helix-core" }
toml = "0.5"