on master help sign in
Merge branch 'master' into cursor-shape-new
commit449624965b05fd2abc9e3ba2f791f8de8b1eeb3e authorGokul Soumya <gokulps15@gmail.com> dateThu 6 Jan 2022, 11:26 (5 years ago) parentc0bbadc2e02a1d
141 files changed+3508−621
.github/workflows/release.yml+1−1 .gitmodules+37−1 CHANGELOG.md+119 Cargo.lock+40−41 base16_theme.toml+4 book/src/generated/lang-support.md+16−5 book/src/generated/typable-cmd.md+4 book/src/guides/adding_languages.md+24−13 book/src/keymap.md+36−34 book/src/languages.md−1 book/src/themes.md+10−2 flake.lock+12−12 helix-core/Cargo.toml+4−3 helix-core/src/auto_pairs.rs+255−80 helix-core/src/diagnostic.rs+11−4 helix-core/src/indent.rs+65−78 helix-core/src/lib.rs+28−6 helix-core/src/match_brackets.rs+1−1 helix-core/src/movement.rs+25−27 helix-core/src/object.rs+27−2 helix-core/src/selection.rs+93 helix-core/src/syntax.rs+13−3 helix-lsp/Cargo.toml+3−3 helix-lsp/src/client.rs+26−2 helix-lsp/src/lib.rs+13−18 helix-syntax/Cargo.toml+1−1 helix-syntax/README.md+13 helix-syntax/languages/tree-sitter-comment+1 helix-syntax/languages/tree-sitter-dart+1 helix-syntax/languages/tree-sitter-dockerfile+1 helix-syntax/languages/tree-sitter-fish+1 helix-syntax/languages/tree-sitter-git-commit+1 helix-syntax/languages/tree-sitter-git-diff+1 helix-syntax/languages/tree-sitter-git-rebase+1 helix-syntax/languages/tree-sitter-llvm+1 helix-syntax/languages/tree-sitter-llvm-mir+1 helix-syntax/languages/tree-sitter-tablegen+1 helix-term/Cargo.toml+5−5 helix-term/src/application.rs+61−18 helix-term/src/commands.rs+375−131 helix-term/src/keymap.rs+6−2 helix-term/src/ui/completion.rs+38−2 helix-term/src/ui/editor.rs+61−38 helix-term/src/ui/mod.rs+28−1 helix-term/src/ui/prompt.rs+2−2 helix-tui/Cargo.toml+3−3 helix-tui/README.md+1−1 helix-view/Cargo.toml+4−5 helix-view/src/document.rs+31−16 helix-view/src/editor.rs+30−4 helix-view/src/graphics.rs+2−2 helix-view/src/input.rs+153 helix-view/src/view.rs+3 languages.toml+105−5 runtime/queries/c/indents.toml+16 runtime/queries/c/injections.scm+2 runtime/queries/c/textobjects.scm+13 runtime/queries/cmake/indents.toml+12 runtime/queries/cmake/injections.scm+4 runtime/queries/cmake/textobjects.scm+3 runtime/queries/comment/highlights.scm+30 runtime/queries/cpp/indents.toml+17 runtime/queries/cpp/injections.scm+1 runtime/queries/cpp/textobjects.scm+7 runtime/queries/dart/highlights.scm+237 runtime/queries/dart/indents.toml+20 runtime/queries/dart/locals.scm+20 runtime/queries/dockerfile/highlights.scm+51 runtime/queries/dockerfile/injections.scm+6 runtime/queries/elixir/injections.scm+2 runtime/queries/fish/highlights.scm+156 runtime/queries/fish/indents.toml+12 runtime/queries/fish/injections.scm+2 runtime/queries/fish/textobjects.scm+1 runtime/queries/git-commit/highlights.scm+14 runtime/queries/git-commit/injections.scm+8 runtime/queries/git-diff/highlights.scm+6 runtime/queries/git-rebase/highlights.scm+11 runtime/queries/git-rebase/injections.scm+4 runtime/queries/glsl/injections.scm+3−2 runtime/queries/julia/injections.scm+5−3 runtime/queries/latex/highlights.scm+1−1 runtime/queries/ledger/injections.scm+2−2 runtime/queries/llvm-mir-yaml/highlights.scm+1 runtime/queries/llvm-mir-yaml/indents.toml+3 runtime/queries/llvm-mir-yaml/injections.scm+9 runtime/queries/llvm-mir/highlights.scm+136 runtime/queries/llvm-mir/indents.toml+7 runtime/queries/llvm-mir/injections.scm+2 runtime/queries/llvm-mir/textobjects.scm+3 runtime/queries/llvm/highlights.scm+153−9 runtime/queries/llvm/indents.toml+8 runtime/queries/llvm/injections.scm+2 runtime/queries/llvm/locals.scm+14 runtime/queries/llvm/textobjects.scm+16 runtime/queries/markdown/highlights.scm+4−3 runtime/queries/markdown/injections.scm+2−1 runtime/queries/nix/highlights.scm+6−1 runtime/queries/ocaml/indents.toml+1−1 runtime/queries/python/injections.scm+2 runtime/queries/ruby/indents.toml+25 runtime/queries/rust/highlights.scm+9−5 runtime/queries/rust/indents.toml+1 runtime/queries/rust/injections.scm+3 runtime/queries/scala/highlights.scm+203 runtime/queries/scala/indents.toml+23 runtime/queries/scala/injections.scm+1 runtime/queries/svelte/highlights.scm+2−2 runtime/queries/svelte/injections.scm+2−2 runtime/queries/tablegen/highlights.scm+90 runtime/queries/tablegen/indents.toml+7 runtime/queries/tablegen/injections.scm+2 runtime/queries/tablegen/textobjects.scm+7 runtime/queries/tsq/injections.scm+2 runtime/queries/yaml/highlights.scm+13−3 runtime/queries/yaml/injections.scm+2 runtime/themes/base16_default_dark.toml+4 runtime/themes/base16_default_light.toml+4 runtime/themes/base16_terminal.toml+4 runtime/themes/bogster.toml+7 runtime/themes/dark_plus.toml+4 runtime/themes/dracula.toml+54 runtime/themes/everforest_dark.toml+5−1 runtime/themes/everforest_light.toml+90 runtime/themes/gruvbox.toml+4 runtime/themes/ingrid.toml+4 runtime/themes/monokai.toml+5−1 runtime/themes/monokai_pro.toml+5 runtime/themes/monokai_pro_machine.toml+5 runtime/themes/monokai_pro_octagon.toml+5 runtime/themes/monokai_pro_ristretto.toml+5 runtime/themes/monokai_pro_spectrum.toml+5 runtime/themes/nord.toml+5 runtime/themes/onedark.toml+19−4 runtime/themes/rose_pine.toml+13 runtime/themes/rose_pine_dawn.toml+15−3 runtime/themes/solarized_dark.toml+4 runtime/themes/solarized_light.toml+4 runtime/themes/spacebones_light.toml+4 theme.toml+5−1 xtask/Cargo.toml+3−3
.github/workflows/release.yml+1−1
@@ -102,7 +102,7 @@ jobs:
102102 fi
103103 cp -r runtime dist
104104
105− - uses: actions/upload-artifact@v2.3.0
105+ - uses: actions/upload-artifact@v2.3.1
106106 with:
107107 name: bins-${{ matrix.build }}
108108 path: dist
.gitmodules+37−1
@@ -142,11 +142,15 @@
142142 path = helix-syntax/languages/tree-sitter-perl
143143 url = https://github.com/ganezdragon/tree-sitter-perl
144144 shallow = true
145+[submodule "helix-syntax/languages/tree-sitter-comment"]
146+ path = helix-syntax/languages/tree-sitter-comment
147+ url = https://github.com/stsewd/tree-sitter-comment
148+ shallow = true
145149[submodule "helix-syntax/languages/tree-sitter-wgsl"]
146150 path = helix-syntax/languages/tree-sitter-wgsl
147151 url = https://github.com/szebniok/tree-sitter-wgsl
148152 shallow = true
149−[submodule "helix-syntax/tree-sitter-llvm"]
153+[submodule "helix-syntax/languages/tree-sitter-llvm"]
150154 path = helix-syntax/languages/tree-sitter-llvm
151155 url = https://github.com/benwilliamgraham/tree-sitter-llvm
152156 shallow = true
@@ -154,3 +158,35 @@
154158 path = helix-syntax/languages/tree-sitter-markdown
155159 url = https://github.com/MDeiml/tree-sitter-markdown
156160 shallow = true
161+[submodule "helix-syntax/languages/tree-sitter-dart"]
162+ path = helix-syntax/languages/tree-sitter-dart
163+ url = https://github.com/UserNobody14/tree-sitter-dart.git
164+ shallow = true
165+[submodule "helix-syntax/languages/tree-sitter-dockerfile"]
166+ path = helix-syntax/languages/tree-sitter-dockerfile
167+ url = https://github.com/camdencheek/tree-sitter-dockerfile.git
168+ shallow = true
169+[submodule "helix-syntax/languages/tree-sitter-fish"]
170+ path = helix-syntax/languages/tree-sitter-fish
171+ url = https://github.com/ram02z/tree-sitter-fish
172+ shallow = true
173+[submodule "helix-syntax/languages/tree-sitter-git-commit"]
174+ path = helix-syntax/languages/tree-sitter-git-commit
175+ url = https://github.com/the-mikedavis/tree-sitter-git-commit.git
176+ shallow = true
177+[submodule "helix-syntax/languages/tree-sitter-llvm-mir"]
178+ path = helix-syntax/languages/tree-sitter-llvm-mir
179+ url = https://github.com/Flakebi/tree-sitter-llvm-mir.git
180+ shallow = true
181+[submodule "helix-syntax/languages/tree-sitter-git-diff"]
182+ path = helix-syntax/languages/tree-sitter-git-diff
183+ url = https://github.com/the-mikedavis/tree-sitter-git-diff.git
184+ shallow = true
185+[submodule "helix-syntax/languages/tree-sitter-tablegen"]
186+ path = helix-syntax/languages/tree-sitter-tablegen
187+ url = https://github.com/Flakebi/tree-sitter-tablegen
188+ shallow = true
189+[submodule "helix-syntax/languages/tree-sitter-git-rebase"]
190+ path = helix-syntax/languages/tree-sitter-git-rebase
191+ url = https://github.com/the-mikedavis/tree-sitter-git-rebase.git
192+ shallow = true
CHANGELOG.md+119
@@ -1,4 +1,123 @@
11
2+# 0.6.0 (2022-01-04)
3+
4+Happy new year and a big shout out to all the contributors! We had 55 contributors in this release.
5+
6+Helix has popped up in DPorts and Fedora Linux via COPR ([#1270](https://github.com/helix-editor/helix/pull/1270))
7+
8+As usual the following is a brief summary, refer to the git history for a full log:
9+
10+Breaking changes:
11+
12+- fix: Normalize backtab into shift-tab
13+
14+Features:
15+
16+- Macros ([#1234](https://github.com/helix-editor/helix/pull/1234))
17+- Add reverse search functionality ([#958](https://github.com/helix-editor/helix/pull/958))
18+- Allow keys to be mapped to sequences of commands ([#589](https://github.com/helix-editor/helix/pull/589))
19+- Make it possible to keybind TypableCommands ([#1169](https://github.com/helix-editor/helix/pull/1169))
20+- Detect workspace root using language markers ([#1370](https://github.com/helix-editor/helix/pull/1370))
21+- Add WORD textobject ([#991](https://github.com/helix-editor/helix/pull/991))
22+- Add LSP rename_symbol (space-r) ([#1011](https://github.com/helix-editor/helix/pull/1011))
23+- Added workspace_symbol_picker ([#1041](https://github.com/helix-editor/helix/pull/1041))
24+- Detect filetype from shebang line ([#1001](https://github.com/helix-editor/helix/pull/1001))
25+- Allow piping from stdin into a buffer on startup ([#996](https://github.com/helix-editor/helix/pull/996))
26+- Add auto pairs for same-char pairs ([#1219](https://github.com/helix-editor/helix/pull/1219))
27+- Update settings at runtime ([#798](https://github.com/helix-editor/helix/pull/798))
28+- Enable thin LTO (cccc194)
29+
30+Commands:
31+- :wonly -- window only ([#1057](https://github.com/helix-editor/helix/pull/1057))
32+- buffer-close (:bc, :bclose) ([#1035](https://github.com/helix-editor/helix/pull/1035))
33+- Add :<line> and :goto <line> commands ([#1128](https://github.com/helix-editor/helix/pull/1128))
34+- :sort command ([#1288](https://github.com/helix-editor/helix/pull/1288))
35+- Add m textobject for pair under cursor ([#961](https://github.com/helix-editor/helix/pull/961))
36+- Implement "Goto next buffer / Goto previous buffer" commands ([#950](https://github.com/helix-editor/helix/pull/950))
37+- Implement "Goto last modification" command ([#1067](https://github.com/helix-editor/helix/pull/1067))
38+- Add trim_selections command ([#1092](https://github.com/helix-editor/helix/pull/1092))
39+- Add movement shortcut for history ([#1088](https://github.com/helix-editor/helix/pull/1088))
40+- Add command to inc/dec number under cursor ([#1027](https://github.com/helix-editor/helix/pull/1027))
41+ - Add support for dates for increment/decrement
42+- Align selections (&) ([#1101](https://github.com/helix-editor/helix/pull/1101))
43+- Implement no-yank delete/change ([#1099](https://github.com/helix-editor/helix/pull/1099))
44+- Implement black hole register ([#1165](https://github.com/helix-editor/helix/pull/1165))
45+- gf as goto_file (gf) ([#1102](https://github.com/helix-editor/helix/pull/1102))
46+- Add last modified file (gm) ([#1093](https://github.com/helix-editor/helix/pull/1093))
47+- ensure_selections_forward ([#1393](https://github.com/helix-editor/helix/pull/1393))
48+- Readline style insert mode ([#1039](https://github.com/helix-editor/helix/pull/1039))
49+
50+Usability improvements and fixes:
51+
52+- Detect filetype on :write ([#1141](https://github.com/helix-editor/helix/pull/1141))
53+- Add single and double quotes to matching pairs ([#995](https://github.com/helix-editor/helix/pull/995))
54+- Launch with defaults upon invalid config/theme (rather than panicking) ([#982](https://github.com/helix-editor/helix/pull/982))
55+- If switching away from an empty scratch buffer, remove it ([#935](https://github.com/helix-editor/helix/pull/935))
56+- Truncate the starts of file paths instead of the ends in picker ([#951](https://github.com/helix-editor/helix/pull/951))
57+- Truncate the start of file paths in the StatusLine ([#1351](https://github.com/helix-editor/helix/pull/1351))
58+- Prevent picker from previewing binaries or large file ([#939](https://github.com/helix-editor/helix/pull/939))
59+- Inform when reaching undo/redo bounds ([#981](https://github.com/helix-editor/helix/pull/981))
60+- search_impl will only align cursor center when it isn't in view ([#959](https://github.com/helix-editor/helix/pull/959))
61+- Add <C-h>, <C-u>, <C-d>, Delete in prompt mode ([#1034](https://github.com/helix-editor/helix/pull/1034))
62+- Restore screen position when aborting search ([#1047](https://github.com/helix-editor/helix/pull/1047))
63+- Buffer picker: show is_modifier flag ([#1020](https://github.com/helix-editor/helix/pull/1020))
64+- Add commit hash to version info, if present ([#957](https://github.com/helix-editor/helix/pull/957))
65+- Implement indent-aware delete ([#1120](https://github.com/helix-editor/helix/pull/1120))
66+- Jump to end char of surrounding pair from any cursor pos ([#1121](https://github.com/helix-editor/helix/pull/1121))
67+- File picker configuration ([#988](https://github.com/helix-editor/helix/pull/988))
68+- Fix surround cursor position calculation ([#1183](https://github.com/helix-editor/helix/pull/1183))
69+- Accept count for goto_window ([#1033](https://github.com/helix-editor/helix/pull/1033))
70+- Make kill_to_line_end behave like emacs ([#1235](https://github.com/helix-editor/helix/pull/1235))
71+- Only use a single documentation popup ([#1241](https://github.com/helix-editor/helix/pull/1241))
72+- ui: popup: Don't allow scrolling past the end of content (3307f44c)
73+- Open files with spaces in filename, allow opening multiple files ([#1231](https://github.com/helix-editor/helix/pull/1231))
74+- Allow paste commands to take a count ([#1261](https://github.com/helix-editor/helix/pull/1261))
75+- Auto pairs selection ([#1254](https://github.com/helix-editor/helix/pull/1254))
76+- Use a fuzzy matcher for commands ([#1386](https://github.com/helix-editor/helix/pull/1386))
77+- Add c-s to pick word under doc cursor to prompt line & search completion ([#831](https://github.com/helix-editor/helix/pull/831))
78+- Fix :earlier/:later missing changeset update ([#1069](https://github.com/helix-editor/helix/pull/1069))
79+- Support extend for multiple goto ([#909](https://github.com/helix-editor/helix/pull/909))
80+- Add arrow-key bindings for window switching ([#933](https://github.com/helix-editor/helix/pull/933))
81+- Implement key ordering for info box ([#952](https://github.com/helix-editor/helix/pull/952))
82+
83+LSP:
84+- Implement MarkedString rendering (e128a8702)
85+- Don't panic if init fails (d31bef7)
86+- Configurable diagnostic severity ([#1325](https://github.com/helix-editor/helix/pull/1325))
87+- Resolve completion item ([#1315](https://github.com/helix-editor/helix/pull/1315))
88+- Code action command support ([#1304](https://github.com/helix-editor/helix/pull/1304))
89+
90+Grammars:
91+
92+- Adds mint language server ([#974](https://github.com/helix-editor/helix/pull/974))
93+- Perl ([#978](https://github.com/helix-editor/helix/pull/978)) ([#1280](https://github.com/helix-editor/helix/pull/1280))
94+- GLSL ([#993](https://github.com/helix-editor/helix/pull/993))
95+- Racket ([#1143](https://github.com/helix-editor/helix/pull/1143))
96+- WGSL ([#1166](https://github.com/helix-editor/helix/pull/1166))
97+- 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))
98+- Markdown (49e06787)
99+- Scala ([#1278](https://github.com/helix-editor/helix/pull/1278))
100+- Dart ([#1250](https://github.com/helix-editor/helix/pull/1250))
101+- Fish ([#1308](https://github.com/helix-editor/helix/pull/1308))
102+- Dockerfile ([#1303](https://github.com/helix-editor/helix/pull/1303))
103+- 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))
104+- tree-sitter-comment ([#1300](https://github.com/helix-editor/helix/pull/1300))
105+- Highlight comments in c, cpp, cmake and llvm ([#1309](https://github.com/helix-editor/helix/pull/1309))
106+- Improve yaml syntax highlighting highlighting ([#1294](https://github.com/helix-editor/helix/pull/1294))
107+- Improve rust syntax highlighting ([#1295](https://github.com/helix-editor/helix/pull/1295))
108+- Add textobjects and indents to cmake ([#1307](https://github.com/helix-editor/helix/pull/1307))
109+- Add textobjects and indents to c and cpp ([#1293](https://github.com/helix-editor/helix/pull/1293))
110+
111+New themes:
112+
113+- Solarized dark ([#999](https://github.com/helix-editor/helix/pull/999))
114+- Solarized light ([#1010](https://github.com/helix-editor/helix/pull/1010))
115+- Spacebones light ([#1131](https://github.com/helix-editor/helix/pull/1131))
116+- Monokai Pro ([#1206](https://github.com/helix-editor/helix/pull/1206))
117+- Base16 Light and Terminal ([#1078](https://github.com/helix-editor/helix/pull/1078))
118+ - and a default 16 color theme, truecolor detection
119+- Dracula ([#1258](https://github.com/helix-editor/helix/pull/1258))
120+
2121# 0.5.0 (2021-10-28)
3122
4123A big shout out to all the contributors! We had 46 contributors in this release.
Cargo.lock+40−41
@@ -13,9 +13,9 @@ dependencies = [
1313
1414[[package]]
1515name = "anyhow"
16−version = "1.0.51"
16+version = "1.0.52"
1717source = "registry+https://github.com/rust-lang/crates.io-index"
18−checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
18+checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
1919
2020[[package]]
2121name = "arc-swap"
@@ -78,9 +78,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
7878
7979[[package]]
8080name = "chardetng"
81−version = "0.1.15"
81+version = "0.1.17"
8282source = "registry+https://github.com/rust-lang/crates.io-index"
83−checksum = "83ee29c16b81c32fbc882ecc568305793338a8353952573db837f4f4a6cd5c2e"
83+checksum = "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea"
8484dependencies = [
8585 "cfg-if",
8686 "encoding_rs",
@@ -258,15 +258,15 @@ dependencies = [
258258
259259[[package]]
260260name = "futures-core"
261−version = "0.3.18"
261+version = "0.3.19"
262262source = "registry+https://github.com/rust-lang/crates.io-index"
263−checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445"
263+checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7"
264264
265265[[package]]
266266name = "futures-executor"
267−version = "0.3.18"
267+version = "0.3.19"
268268source = "registry+https://github.com/rust-lang/crates.io-index"
269−checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97"
269+checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a"
270270dependencies = [
271271 "futures-core",
272272 "futures-task",
@@ -275,15 +275,15 @@ dependencies = [
275275
276276[[package]]
277277name = "futures-task"
278−version = "0.3.18"
278+version = "0.3.19"
279279source = "registry+https://github.com/rust-lang/crates.io-index"
280−checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12"
280+checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72"
281281
282282[[package]]
283283name = "futures-util"
284−version = "0.3.18"
284+version = "0.3.19"
285285source = "registry+https://github.com/rust-lang/crates.io-index"
286−checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
286+checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164"
287287dependencies = [
288288 "futures-core",
289289 "futures-task",
@@ -366,10 +366,11 @@ dependencies = [
366366
367367[[package]]
368368name = "helix-core"
369−version = "0.5.0"
369+version = "0.6.0"
370370dependencies = [
371371 "arc-swap",
372372 "chrono",
373+ "encoding_rs",
373374 "etcetera",
374375 "helix-syntax",
375376 "log",
@@ -391,7 +392,7 @@ dependencies = [
391392
392393[[package]]
393394name = "helix-lsp"
394−version = "0.5.0"
395+version = "0.6.0"
395396dependencies = [
396397 "anyhow",
397398 "futures-executor",
@@ -409,7 +410,7 @@ dependencies = [
409410
410411[[package]]
411412name = "helix-syntax"
412−version = "0.5.0"
413+version = "0.6.0"
413414dependencies = [
414415 "anyhow",
415416 "cc",
@@ -420,7 +421,7 @@ dependencies = [
420421
421422[[package]]
422423name = "helix-term"
423−version = "0.5.0"
424+version = "0.6.0"
424425dependencies = [
425426 "anyhow",
426427 "chrono",
@@ -451,7 +452,7 @@ dependencies = [
451452
452453[[package]]
453454name = "helix-tui"
454−version = "0.5.0"
455+version = "0.6.0"
455456dependencies = [
456457 "bitflags",
457458 "cassowary",
@@ -464,14 +465,13 @@ dependencies = [
464465
465466[[package]]
466467name = "helix-view"
467−version = "0.5.0"
468+version = "0.6.0"
468469dependencies = [
469470 "anyhow",
470471 "bitflags",
471472 "chardetng",
472473 "clipboard-win",
473474 "crossterm",
474− "encoding_rs",
475475 "futures-util",
476476 "helix-core",
477477 "helix-lsp",
@@ -690,9 +690,9 @@ dependencies = [
690690
691691[[package]]
692692name = "num_cpus"
693−version = "1.13.0"
693+version = "1.13.1"
694694source = "registry+https://github.com/rust-lang/crates.io-index"
695−checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
695+checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
696696dependencies = [
697697 "hermit-abi",
698698 "libc",
@@ -700,9 +700,9 @@ dependencies = [
700700
701701[[package]]
702702name = "once_cell"
703−version = "1.8.0"
703+version = "1.9.0"
704704source = "registry+https://github.com/rust-lang/crates.io-index"
705−checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
705+checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
706706
707707[[package]]
708708name = "parking_lot"
@@ -847,9 +847,9 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
847847
848848[[package]]
849849name = "ropey"
850−version = "1.3.1"
850+version = "1.3.2"
851851source = "registry+https://github.com/rust-lang/crates.io-index"
852−checksum = "9150aff6deb25b20ed110889f070a678bcd1033e46e5e9d6fb1abeab17947f28"
852+checksum = "e6b9aa65bcd9f308d37c7158b4a1afaaa32b8450213e20c9b98e7d5b3cc2fec3"
853853dependencies = [
854854 "smallvec",
855855]
@@ -877,18 +877,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
877877
878878[[package]]
879879name = "serde"
880−version = "1.0.131"
880+version = "1.0.133"
881881source = "registry+https://github.com/rust-lang/crates.io-index"
882−checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1"
882+checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
883883dependencies = [
884884 "serde_derive",
885885]
886886
887887[[package]]
888888name = "serde_derive"
889−version = "1.0.131"
889+version = "1.0.133"
890890source = "registry+https://github.com/rust-lang/crates.io-index"
891−checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2"
891+checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
892892dependencies = [
893893 "proc-macro2",
894894 "quote",
@@ -897,9 +897,9 @@ dependencies = [
897897
898898[[package]]
899899name = "serde_json"
900−version = "1.0.73"
900+version = "1.0.74"
901901source = "registry+https://github.com/rust-lang/crates.io-index"
902−checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5"
902+checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142"
903903dependencies = [
904904 "itoa",
905905 "ryu",
@@ -919,9 +919,9 @@ dependencies = [
919919
920920[[package]]
921921name = "signal-hook"
922−version = "0.3.12"
922+version = "0.3.13"
923923source = "registry+https://github.com/rust-lang/crates.io-index"
924−checksum = "c35dfd12afb7828318348b8c408383cf5071a086c1d4ab1c0f9840ec92dbb922"
924+checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
925925dependencies = [
926926 "libc",
927927 "signal-hook-registry",
@@ -1069,11 +1069,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
10691069
10701070[[package]]
10711071name = "tokio"
1072−version = "1.14.0"
1072+version = "1.15.0"
10731073source = "registry+https://github.com/rust-lang/crates.io-index"
1074−checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
1074+checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
10751075dependencies = [
1076− "autocfg",
10771076 "bytes",
10781077 "libc",
10791078 "memchr",
@@ -1089,9 +1088,9 @@ dependencies = [
10891088
10901089[[package]]
10911090name = "tokio-macros"
1092−version = "1.6.0"
1091+version = "1.7.0"
10931092source = "registry+https://github.com/rust-lang/crates.io-index"
1094−checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e"
1093+checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
10951094dependencies = [
10961095 "proc-macro2",
10971096 "quote",
@@ -1120,9 +1119,9 @@ dependencies = [
11201119
11211120[[package]]
11221121name = "tree-sitter"
1123−version = "0.20.1"
1122+version = "0.20.2"
11241123source = "registry+https://github.com/rust-lang/crates.io-index"
1125−checksum = "9394e9dbfe967b5f3d6ab79e302e78b5fb7b530c368d634ff3b8d67ede138bf1"
1124+checksum = "c36be3222512d85a112491ae0cc280a38076022414f00b64582da1b7565ffd82"
11261125dependencies = [
11271126 "cc",
11281127 "regex",
@@ -1262,7 +1261,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
12621261
12631262[[package]]
12641263name = "xtask"
1265−version = "0.5.0"
1264+version = "0.6.0"
12661265dependencies = [
12671266 "helix-core",
12681267 "helix-term",
base16_theme.toml+4
@@ -29,6 +29,10 @@
2929"namespace" = "magenta"
3030"ui.help" = { fg = "white", bg = "black" }
3131
32+"diff.plus" = "green"
33+"diff.delta" = "yellow"
34+"diff.minus" = "red"
35+
3236"diagnostic" = { modifiers = ["underlined"] }
3337"ui.gutter" = { bg = "black" }
3438"info" = "blue"
book/src/generated/lang-support.md+16−5
@@ -1,12 +1,19 @@
11| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
22| --- | --- | --- | --- | --- |
33| bash | ✓ | | | `bash-language-server` |
4−| c | ✓ | | | `clangd` |
4+| c | ✓ | ✓ | ✓ | `clangd` |
55| c-sharp | ✓ | | | |
6−| cmake | ✓ | | | `cmake-language-server` |
7−| cpp | ✓ | | | `clangd` |
6+| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
7+| comment | ✓ | | | |
8+| cpp | ✓ | ✓ | ✓ | `clangd` |
89| css | ✓ | | | |
10+| dart | ✓ | | ✓ | `dart` |
11+| dockerfile | ✓ | | | `docker-langserver` |
912| elixir | ✓ | | | `elixir-ls` |
13+| fish | ✓ | ✓ | ✓ | |
14+| git-commit | ✓ | | | |
15+| git-diff | ✓ | | | |
16+| git-rebase | ✓ | | | |
1017| glsl | ✓ | | ✓ | |
1118| go | ✓ | ✓ | ✓ | `gopls` |
1219| html | ✓ | | | |
@@ -16,7 +23,9 @@
1623| julia | ✓ | | | `julia` |
1724| latex | ✓ | | | |
1825| ledger | ✓ | | | |
19−| llvm | ✓ | | | |
26+| llvm | ✓ | ✓ | ✓ | |
27+| llvm-mir | ✓ | ✓ | ✓ | |
28+| llvm-mir-yaml | ✓ | | ✓ | |
2029| lua | ✓ | | ✓ | |
2130| markdown | ✓ | | | |
2231| mint | | | | `mint` |
@@ -29,9 +38,11 @@
2938| protobuf | ✓ | | ✓ | |
3039| python | ✓ | ✓ | ✓ | `pylsp` |
3140| racket | | | | `racket` |
32−| ruby | ✓ | | | `solargraph` |
41+| ruby | ✓ | | ✓ | `solargraph` |
3342| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
43+| scala | ✓ | | ✓ | `metals` |
3444| svelte | ✓ | | ✓ | `svelteserver` |
45+| tablegen | ✓ | ✓ | ✓ | |
3546| toml | ✓ | | | |
3647| tsq | ✓ | | | |
3748| tsx | ✓ | | | `typescript-language-server` |
book/src/generated/typable-cmd.md+4
@@ -20,6 +20,7 @@
2020| `:quit-all`, `:qa` | Close all views. |
2121| `:quit-all!`, `:qa!` | Close all views forcefully (ignoring unsaved changes). |
2222| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
23+| `:cquit!`, `:cq!` | Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2). |
2324| `:theme` | Change the editor theme. |
2425| `:clipboard-yank` | Yank main selection into system clipboard. |
2526| `: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 @@
4142| `:hsplit`, `:hs`, `:sp` | Open the file in a horizontal split. |
4243| `:tutor` | Open the tutorial. |
4344| `:goto`, `:g` | Go to line number. |
45+| `:set-option`, `:set` | Set a config option at runtime |
46+| `:sort` | Sort ranges in selection. |
47+| `:rsort` | Sort ranges in selection in reverse order. |
book/src/guides/adding_languages.md+24−13
@@ -27,22 +27,32 @@ directory](../configuration.md).
2727
2828These are the available keys and descriptions for the file.
2929
30−| Key | Description |
31−| ---- | ----------- |
32−| name | The name of the language |
33−| 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 |
34−| 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. |
35−| file-types | The filetypes of the language, for example `["yml", "yaml"]` |
36−| shebangs | The interpreters from the shebang line, for example `["sh", "bash"]` |
37−| roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
38−| auto-format | Whether to autoformat this language when saving |
39−| comment-token | The token to use as a comment-token |
40−| indent | The indent to use. Has sub keys `tab-width` and `unit` |
41−| config | Language server configuration |
30+| Key | Description |
31+| ---- | ----------- |
32+| name | The name of the language |
33+| 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 |
34+| 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. |
35+| file-types | The filetypes of the language, for example `["yml", "yaml"]` |
36+| shebangs | The interpreters from the shebang line, for example `["sh", "bash"]` |
37+| roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
38+| auto-format | Whether to autoformat this language when saving |
39+| diagnostic-severity | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
40+| comment-token | The token to use as a comment-token |
41+| indent | The indent to use. Has sub keys `tab-width` and `unit` |
42+| config | Language server configuration |
4243
4344## Queries
4445
45−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.
46+For a language to have syntax-highlighting and indentation among
47+other things, you have to add queries. Add a directory for your
48+language with the path `runtime/queries/<name>/`. The tree-sitter
49+[website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
50+gives more info on how to write queries.
51+
52+> NOTE: When evaluating queries, the first matching query takes
53+precedence, which is different from other editors like neovim where
54+the last matching query supercedes the ones before it. See
55+[this issue][neovim-query-precedence] for an example.
4656
4757## Common Issues
4858
@@ -58,3 +68,4 @@ For a language to have syntax-highlighting and indentation among other things, y
5868
5969[treesitter-language-injection]: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
6070[languages.toml]: https://github.com/helix-editor/helix/blob/master/languages.toml
71+[neovim-query-precedence]: https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090
book/src/keymap.md+36−34
@@ -46,39 +46,39 @@
4646
4747### Changes
4848
49−| Key | Description | Command |
50−| ----- | ----------- | ------- |
51−| `r` | Replace with a character | `replace` |
52−| `R` | Replace with yanked text | `replace_with_yanked` |
53−| `~` | Switch case of the selected text | `switch_case` |
54−| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` |
55−| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` |
56−| `i` | Insert before selection | `insert_mode` |
57−| `a` | Insert after selection (append) | `append_mode` |
58−| `I` | Insert at the start of the line | `prepend_to_line` |
59−| `A` | Insert at the end of the line | `append_to_line` |
60−| `o` | Open new line below selection | `open_below` |
61−| `O` | Open new line above selection | `open_above` |
62−| `.` | Repeat last change | N/A |
63−| `u` | Undo change | `undo` |
64−| `U` | Redo change | `redo` |
65−| `Alt-u` | Move backward in history | `earlier` |
66−| `Alt-U` | Move forward in history | `later` |
67−| `y` | Yank selection | `yank` |
68−| `p` | Paste after selection | `paste_after` |
69−| `P` | Paste before selection | `paste_before` |
70−| `"` `<reg>` | Select a register to yank to or paste from | `select_register` |
71−| `>` | Indent selection | `indent` |
72−| `<` | Unindent selection | `unindent` |
73−| `=` | Format selection (currently nonfunctional/disabled) (**LSP**) | `format_selections` |
74−| `d` | Delete selection | `delete_selection` |
75−| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` |
76−| `c` | Change selection (delete and enter insert mode) | `change_selection` |
77−| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` |
78−| `Ctrl-a` | Increment object (number) under cursor | `increment` |
79−| `Ctrl-x` | Decrement object (number) under cursor | `decrement` |
80−| `q` | Start/stop macro recording to the selected register | `record_macro` |
81−| `Q` | Play back a recorded macro from the selected register | `play_macro` |
49+| Key | Description | Command |
50+| ----- | ----------- | ------- |
51+| `r` | Replace with a character | `replace` |
52+| `R` | Replace with yanked text | `replace_with_yanked` |
53+| `~` | Switch case of the selected text | `switch_case` |
54+| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` |
55+| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` |
56+| `i` | Insert before selection | `insert_mode` |
57+| `a` | Insert after selection (append) | `append_mode` |
58+| `I` | Insert at the start of the line | `prepend_to_line` |
59+| `A` | Insert at the end of the line | `append_to_line` |
60+| `o` | Open new line below selection | `open_below` |
61+| `O` | Open new line above selection | `open_above` |
62+| `.` | Repeat last change | N/A |
63+| `u` | Undo change | `undo` |
64+| `U` | Redo change | `redo` |
65+| `Alt-u` | Move backward in history | `earlier` |
66+| `Alt-U` | Move forward in history | `later` |
67+| `y` | Yank selection | `yank` |
68+| `p` | Paste after selection | `paste_after` |
69+| `P` | Paste before selection | `paste_before` |
70+| `"` `<reg>` | Select a register to yank to or paste from | `select_register` |
71+| `>` | Indent selection | `indent` |
72+| `<` | Unindent selection | `unindent` |
73+| `=` | Format selection (currently nonfunctional/disabled) (**LSP**) | `format_selections` |
74+| `d` | Delete selection | `delete_selection` |
75+| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` |
76+| `c` | Change selection (delete and enter insert mode) | `change_selection` |
77+| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` |
78+| `Ctrl-a` | Increment object (number) under cursor | `increment` |
79+| `Ctrl-x` | Decrement object (number) under cursor | `decrement` |
80+| `Q` | Start/stop macro recording to the selected register (experimental) | `record_macro` |
81+| `q` | Play back a recorded macro from the selected register (experimental) | `replay_macro` |
8282
8383#### Shell
8484
@@ -161,7 +161,7 @@ Jumps to various locations.
161161
162162| Key | Description | Command |
163163| ----- | ----------- | ------- |
164−| `g` | Go to the start of the file | `goto_file_start` |
164+| `g` | Go to line number `<n>` else start of file | `goto_file_start` |
165165| `e` | Go to the end of the file | `goto_last_line` |
166166| `f` | Go to files in the selection | `goto_file` |
167167| `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
261261| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
262262| `[space` | Add newline above | `add_newline_above` |
263263| `]space` | Add newline below | `add_newline_below` |
264+| `]o` | Expand syntax tree object selection. | `expand_selection` |
265+| `[o` | Shrink syntax tree object selection. | `shrink_selection` |
264266
265267## Insert Mode
266268
book/src/languages.md−1
@@ -11,4 +11,3 @@ Changes made to the `languages.toml` file in a user's [configuration directory](
1111name = "rust"
1212auto-format = false
1313```
14−
book/src/themes.md+10−2
@@ -105,6 +105,7 @@ We use a similar set of scopes as
105105
106106- `type` - Types
107107 - `builtin` - Primitive types provided by the language (`int`, `usize`)
108+- `constructor`
108109
109110- `constant` (TODO: constant.other.placeholder for %v)
110111 - `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
@@ -169,13 +170,20 @@ We use a similar set of scopes as
169170 - `numbered`
170171 - `bold`
171172 - `italic`
172− - `underline`
173− - `link`
173+ - `link`
174+ - `url`
175+ - `label`
174176 - `quote`
175177 - `raw`
176178 - `inline`
177179 - `block`
178180
181+- `diff` - version control changes
182+ - `plus` - additions
183+ - `minus` - deletions
184+ - `delta` - modifications
185+ - `moved` - renamed or moved files/changes
186+
179187#### Interface
180188
181189These scopes are used for theming the editor interface.
flake.lock+12−12
@@ -2,11 +2,11 @@
22 "nodes": {
33 "devshell": {
44 "locked": {
5− "lastModified": 1637575296,
6− "narHash": "sha256-ZY8YR5u8aglZPe27+AJMnPTG6645WuavB+w0xmhTarw=",
5+ "lastModified": 1639692811,
6+ "narHash": "sha256-wOOBH0fVsfNqw/5ZWRoKspyesoXBgiwEOUBH4c7JKEo=",
77 "owner": "numtide",
88 "repo": "devshell",
9− "rev": "0e56ef21ba1a717169953122c7415fa6a8cd2618",
9+ "rev": "d3a1f5bec3632b33346865b1c165bf2420bb2f52",
1010 "type": "github"
1111 },
1212 "original": {
@@ -41,11 +41,11 @@
4141 ]
4242 },
4343 "locked": {
44− "lastModified": 1638425401,
45− "narHash": "sha256-xc8ayvR3u90hSCMEy0zHHKav7lEgljAFXL4oIkWRp3M=",
44+ "lastModified": 1639807801,
45+ "narHash": "sha256-y32tMq1LTRVbMW3QN5i98iOQjQt2QSsif3ayUkD1o3g=",
4646 "owner": "yusdacra",
4747 "repo": "nix-cargo-integration",
48− "rev": "1f8b511bb30f7d7b9051dfbb4784390bc0d48d37",
48+ "rev": "b5bbaa4f5239e6f0619846f9a5380f07baa853d3",
4949 "type": "github"
5050 },
5151 "original": {
@@ -56,11 +56,11 @@
5656 },
5757 "nixpkgs": {
5858 "locked": {
59− "lastModified": 1638376152,
60− "narHash": "sha256-ucgLpVqhFnClH7YRUHBHnmiOd82RZdFR3XJt36ks5fE=",
59+ "lastModified": 1639699734,
60+ "narHash": "sha256-tlX6WebGmiHb2Hmniff+ltYp+7dRfdsBxw9YczLsP60=",
6161 "owner": "nixos",
6262 "repo": "nixpkgs",
63− "rev": "6daa4a5c045d40e6eae60a3b6e427e8700f1c07f",
63+ "rev": "03ec468b14067729a285c2c7cfa7b9434a04816c",
6464 "type": "github"
6565 },
6666 "original": {
@@ -99,11 +99,11 @@
9999 "nixpkgs": "nixpkgs_2"
100100 },
101101 "locked": {
102− "lastModified": 1638497756,
103− "narHash": "sha256-zKOvMKqGp71ZBnR+hBlPcv4TwNN82COW9EF+6ygrFs8=",
102+ "lastModified": 1639880499,
103+ "narHash": "sha256-/BibDmFwgWuuTUkNVO6YlvuTSWM9dpBvlZoTAPs7ORI=",
104104 "owner": "oxalica",
105105 "repo": "rust-overlay",
106− "rev": "783722a22ee5d762ac5c1c7b418b57b3010c827a",
106+ "rev": "c6c83589ae048af20d93d01eb07a4176012093d0",
107107 "type": "github"
108108 },
109109 "original": {
helix-core/Cargo.toml+4−3
@@ -1,6 +1,6 @@
11[package]
22name = "helix-core"
3−version = "0.5.0"
3+version = "0.6.0"
44authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
55edition = "2021"
66license = "MPL-2.0"
@@ -13,7 +13,7 @@ include = ["src/**/*", "README.md"]
1313[features]
1414
1515[dependencies]
16−helix-syntax = { version = "0.5", path = "../helix-syntax" }
16+helix-syntax = { version = "0.6", path = "../helix-syntax" }
1717
1818ropey = "1.3"
1919smallvec = "1.7"
@@ -23,7 +23,7 @@ unicode-width = "0.1"
2323unicode-general-category = "0.4"
2424# slab = "0.4.2"
2525tree-sitter = "0.20"
26−once_cell = "1.8"
26+once_cell = "1.9"
2727arc-swap = "1"
2828regex = "1"
2929
@@ -35,6 +35,7 @@ toml = "0.5"
3535similar = "2.1"
3636
3737etcetera = "0.3"
38+encoding_rs = "0.8"
3839
3940chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
4041
helix-core/src/auto_pairs.rs+255−80
@@ -1,7 +1,7 @@
11//! When typing the opening character of one of the possible pairs defined below,
22//! this module provides the functionality to insert the paired closing character.
33
4−use crate::{Range, Rope, Selection, Tendril, Transaction};
4+use crate::{movement::Direction, Range, Rope, Selection, Tendril, Transaction};
55use log::debug;
66use smallvec::SmallVec;
77
@@ -30,7 +30,6 @@ const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{20
3030
3131// [TODO]
3232// * delete implementation where it erases the whole bracket (|) -> |
33−// * do not reduce to cursors; use whole selections, and surround with pair
3433// * change to multi character pairs to handle cases like placing the cursor in the
3534// middle of triple quotes, and more exotic pairs like Jinja's {% %}
3635
@@ -38,20 +37,18 @@ const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{20
3837pub fn hook(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
3938 debug!("autopairs hook selection: {:#?}", selection);
4039
41− let cursors = selection.clone().cursors(doc.slice(..));
42−
4340 for &(open, close) in PAIRS {
4441 if open == ch {
4542 if open == close {
46− return Some(handle_same(doc, &cursors, open, CLOSE_BEFORE, OPEN_BEFORE));
43+ return Some(handle_same(doc, selection, open, CLOSE_BEFORE, OPEN_BEFORE));
4744 } else {
48− return Some(handle_open(doc, &cursors, open, close, CLOSE_BEFORE));
45+ return Some(handle_open(doc, selection, open, close, CLOSE_BEFORE));
4946 }
5047 }
5148
5249 if close == ch {
5350 // && char_at pos == close
54− return Some(handle_close(doc, &cursors, open, close));
51+ return Some(handle_close(doc, selection, open, close));
5552 }
5653 }
5754
@@ -66,6 +63,36 @@ fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
6663 doc.get_char(pos - 1)
6764}
6865
66+/// calculate what the resulting range should be for an auto pair insertion
67+fn get_next_range(
68+ start_range: &Range,
69+ offset: usize,
70+ typed_char: char,
71+ len_inserted: usize,
72+) -> Range {
73+ let end_head = start_range.head + offset + typed_char.len_utf8();
74+
75+ let end_anchor = match (start_range.len(), start_range.direction()) {
76+ // if we have a zero width cursor, it shifts to the same number
77+ (0, _) => end_head,
78+
79+ // if we are inserting for a regular one-width cursor, the anchor
80+ // moves with the head
81+ (1, Direction::Forward) => end_head - 1,
82+ (1, Direction::Backward) => end_head + 1,
83+
84+ // if we are appending, the anchor stays where it is; only offset
85+ // for multiple range insertions
86+ (_, Direction::Forward) => start_range.anchor + offset,
87+
88+ // when we are inserting in front of a selection, we need to move
89+ // the anchor over by however many characters were inserted overall
90+ (_, Direction::Backward) => start_range.anchor + offset + len_inserted,
91+ };
92+
93+ Range::new(end_anchor, end_head)
94+}
95+
6996fn handle_open(
7097 doc: &Rope,
7198 selection: &Selection,
@@ -74,36 +101,32 @@ fn handle_open(
74101 close_before: &str,
75102) -> Transaction {
76103 let mut end_ranges = SmallVec::with_capacity(selection.len());
77−
78104 let mut offs = 0;
79105
80106 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
81− let start_head = start_range.head;
82−
83− let next = doc.get_char(start_head);
84− let end_head = start_head + offs + open.len_utf8();
85−
86− let end_anchor = if start_range.is_empty() {
87− end_head
88− } else {
89− start_range.anchor + offs
90− };
91−
92− end_ranges.push(Range::new(end_anchor, end_head));
107+ let cursor = start_range.cursor(doc.slice(..));
108+ let next_char = doc.get_char(cursor);
109+ let len_inserted;
93110
94− match next {
111+ let change = match next_char {
95112 Some(ch) if !close_before.contains(ch) => {
96− offs += open.len_utf8();
97− (start_head, start_head, Some(Tendril::from_char(open)))
113+ len_inserted = open.len_utf8();
114+ (cursor, cursor, Some(Tendril::from_char(open)))
98115 }
99116 // None | Some(ch) if close_before.contains(ch) => {}
100117 _ => {
101118 // insert open & close
102119 let pair = Tendril::from_iter([open, close]);
103− offs += open.len_utf8() + close.len_utf8();
104− (start_head, start_head, Some(pair))
120+ len_inserted = open.len_utf8() + close.len_utf8();
121+ (cursor, cursor, Some(pair))
105122 }
106− }
123+ };
124+
125+ let next_range = get_next_range(start_range, offs, open, len_inserted);
126+ end_ranges.push(next_range);
127+ offs += len_inserted;
128+
129+ change
107130 });
108131
109132 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) ->
117140 let mut offs = 0;
118141
119142 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
120− let start_head = start_range.head;
121− let next = doc.get_char(start_head);
122− let end_head = start_head + offs + close.len_utf8();
143+ let cursor = start_range.cursor(doc.slice(..));
144+ let next_char = doc.get_char(cursor);
145+ let mut len_inserted = 0;
123146
124− let end_anchor = if start_range.is_empty() {
125− end_head
147+ let change = if next_char == Some(close) {
148+ // return transaction that moves past close
149+ (cursor, cursor, None) // no-op
126150 } else {
127− start_range.anchor + offs
151+ len_inserted += close.len_utf8();
152+ (cursor, cursor, Some(Tendril::from_char(close)))
128153 };
129154
130− end_ranges.push(Range::new(end_anchor, end_head));
155+ let next_range = get_next_range(start_range, offs, close, len_inserted);
156+ end_ranges.push(next_range);
157+ offs += len_inserted;
131158
132− if next == Some(close) {
133− // return transaction that moves past close
134− (start_head, start_head, None) // no-op
135− } else {
136− offs += close.len_utf8();
137− (start_head, start_head, Some(Tendril::from_char(close)))
138− }
159+ change
139160 });
140161
141− transaction.with_selection(Selection::new(end_ranges, selection.primary_index()))
162+ let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
163+ debug!("auto pair transaction: {:#?}", t);
164+ t
142165}
143166
144167/// handle cases where open and close is the same, or in triples ("""docstring""")
@@ -154,42 +177,41 @@ fn handle_same(
154177 let mut offs = 0;
155178
156179 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
157− let start_head = start_range.head;
158− let end_head = start_head + offs + token.len_utf8();
180+ let cursor = start_range.cursor(doc.slice(..));
181+ let mut len_inserted = 0;
159182
160− // if selection, retain anchor, if cursor, move over
161− let end_anchor = if start_range.is_empty() {
162− end_head
163− } else {
164− start_range.anchor + offs
165− };
183+ let next_char = doc.get_char(cursor);
184+ let prev_char = prev_char(doc, cursor);
166185
167− end_ranges.push(Range::new(end_anchor, end_head));
168−
169− let next = doc.get_char(start_head);
170− let prev = prev_char(doc, start_head);
171−
172− if next == Some(token) {
186+ let change = if next_char == Some(token) {
173187 // return transaction that moves past close
174− (start_head, start_head, None) // no-op
188+ (cursor, cursor, None) // no-op
175189 } else {
176190 let mut pair = Tendril::with_capacity(2 * token.len_utf8() as u32);
177191 pair.push_char(token);
178192
179193 // for equal pairs, don't insert both open and close if either
180194 // side has a non-pair char
181− if (next.is_none() || close_before.contains(next.unwrap()))
182− && (prev.is_none() || open_before.contains(prev.unwrap()))
195+ if (next_char.is_none() || close_before.contains(next_char.unwrap()))
196+ && (prev_char.is_none() || open_before.contains(prev_char.unwrap()))
183197 {
184198 pair.push_char(token);
185199 }
186200
187− offs += pair.len();
188− (start_head, start_head, Some(pair))
189− }
201+ len_inserted += pair.len();
202+ (cursor, cursor, Some(pair))
203+ };
204+
205+ let next_range = get_next_range(start_range, offs, token, len_inserted);
206+ end_ranges.push(next_range);
207+ offs += len_inserted;
208+
209+ change
190210 });
191211
192− transaction.with_selection(Selection::new(end_ranges, selection.primary_index()))
212+ let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
213+ debug!("auto pair transaction: {:#?}", t);
214+ t
193215}
194216
195217#[cfg(test)]
@@ -252,7 +274,20 @@ mod test {
252274 &Selection::single(1, 0),
253275 PAIRS,
254276 |open, close| format!("{}{}", open, close),
255− &Selection::single(1, 1),
277+ &Selection::single(2, 1),
278+ );
279+ }
280+
281+ /// [] -> append ( -> ([])
282+ #[test]
283+ fn test_append_blank() {
284+ test_hooks_with_pairs(
285+ // this is what happens when you have a totally blank document and then append
286+ &Rope::from("\n\n"),
287+ &Selection::single(0, 2),
288+ PAIRS,
289+ |open, close| format!("\n{}{}\n", open, close),
290+ &Selection::single(0, 3),
256291 );
257292 }
258293
@@ -276,26 +311,50 @@ mod test {
276311 )
277312 },
278313 &Selection::new(
279− smallvec!(Range::point(1), Range::point(4), Range::point(7),),
314+ smallvec!(Range::new(2, 1), Range::new(5, 4), Range::new(8, 7),),
280315 0,
281316 ),
282317 );
283318 }
284319
285− // [TODO] broken until it works with selections
286320 /// fo[o] -> append ( -> fo[o(])
287− #[ignore]
288321 #[test]
289322 fn test_append() {
290323 test_hooks_with_pairs(
291− &Rope::from("foo"),
324+ &Rope::from("foo\n"),
292325 &Selection::single(2, 4),
293− PAIRS,
294− |open, close| format!("foo{}{}", open, close),
326+ differing_pairs(),
327+ |open, close| format!("foo{}{}\n", open, close),
295328 &Selection::single(2, 5),
296329 );
297330 }
298331
332+ /// fo[o] fo[o(])
333+ /// fo[o] -> append ( -> fo[o(])
334+ /// fo[o] fo[o(])
335+ #[test]
336+ fn test_append_multi() {
337+ test_hooks_with_pairs(
338+ &Rope::from("foo\nfoo\nfoo\n"),
339+ &Selection::new(
340+ smallvec!(Range::new(2, 4), Range::new(6, 8), Range::new(10, 12)),
341+ 0,
342+ ),
343+ differing_pairs(),
344+ |open, close| {
345+ format!(
346+ "foo{open}{close}\nfoo{open}{close}\nfoo{open}{close}\n",
347+ open = open,
348+ close = close
349+ )
350+ },
351+ &Selection::new(
352+ smallvec!(Range::new(2, 5), Range::new(8, 11), Range::new(14, 17)),
353+ 0,
354+ ),
355+ );
356+ }
357+
299358 /// ([]) -> insert ) -> ()[]
300359 #[test]
301360 fn test_insert_close_inside_pair() {
@@ -307,7 +366,23 @@ mod test {
307366 &Selection::single(2, 1),
308367 *close,
309368 &doc,
310− &Selection::point(2),
369+ &Selection::single(3, 2),
370+ );
371+ }
372+ }
373+
374+ /// [(]) -> append ) -> [()]
375+ #[test]
376+ fn test_append_close_inside_pair() {
377+ for (open, close) in PAIRS {
378+ let doc = Rope::from(format!("{}{}\n", open, close));
379+
380+ test_hooks(
381+ &doc,
382+ &Selection::single(0, 2),
383+ *close,
384+ &doc,
385+ &Selection::single(0, 3),
311386 );
312387 }
313388 }
@@ -323,8 +398,33 @@ mod test {
323398 );
324399
325400 let expected_sel = Selection::new(
326− // smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),),
327− smallvec!(Range::point(2), Range::point(5), Range::point(8),),
401+ smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),),
402+ 0,
403+ );
404+
405+ for (open, close) in PAIRS {
406+ let doc = Rope::from(format!(
407+ "{open}{close}\n{open}{close}\n{open}{close}\n",
408+ open = open,
409+ close = close
410+ ));
411+
412+ test_hooks(&doc, &sel, *close, &doc, &expected_sel);
413+ }
414+ }
415+
416+ /// [(]) [()]
417+ /// [(]) -> append ) -> [()]
418+ /// [(]) [()]
419+ #[test]
420+ fn test_append_close_inside_pair_multi_cursor() {
421+ let sel = Selection::new(
422+ smallvec!(Range::new(0, 2), Range::new(3, 5), Range::new(6, 8),),
423+ 0,
424+ );
425+
426+ let expected_sel = Selection::new(
427+ smallvec!(Range::new(0, 3), Range::new(3, 6), Range::new(6, 9),),
328428 0,
329429 );
330430
@@ -343,7 +443,7 @@ mod test {
343443 #[test]
344444 fn test_insert_open_inside_pair() {
345445 let sel = Selection::single(2, 1);
346− let expected_sel = Selection::point(2);
446+ let expected_sel = Selection::single(3, 2);
347447
348448 for (open, close) in differing_pairs() {
349449 let doc = Rope::from(format!("{}{}", open, close));
@@ -357,11 +457,49 @@ mod test {
357457 }
358458 }
359459
460+ /// [word(]) -> append ( -> [word((]))
461+ #[test]
462+ fn test_append_open_inside_pair() {
463+ let sel = Selection::single(0, 6);
464+ let expected_sel = Selection::single(0, 7);
465+
466+ for (open, close) in differing_pairs() {
467+ let doc = Rope::from(format!("word{}{}", open, close));
468+ let expected_doc = Rope::from(format!(
469+ "word{open}{open}{close}{close}",
470+ open = open,
471+ close = close
472+ ));
473+
474+ test_hooks(&doc, &sel, *open, &expected_doc, &expected_sel);
475+ }
476+ }
477+
360478 /// ([]) -> insert " -> ("[]")
361479 #[test]
362480 fn test_insert_nested_open_inside_pair() {
363481 let sel = Selection::single(2, 1);
364− let expected_sel = Selection::point(2);
482+ let expected_sel = Selection::single(3, 2);
483+
484+ for (outer_open, outer_close) in differing_pairs() {
485+ let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
486+
487+ for (inner_open, inner_close) in matching_pairs() {
488+ let expected_doc = Rope::from(format!(
489+ "{}{}{}{}",
490+ outer_open, inner_open, inner_close, outer_close
491+ ));
492+
493+ test_hooks(&doc, &sel, *inner_open, &expected_doc, &expected_sel);
494+ }
495+ }
496+ }
497+
498+ /// [(]) -> append " -> [("]")
499+ #[test]
500+ fn test_append_nested_open_inside_pair() {
501+ let sel = Selection::single(0, 2);
502+ let expected_sel = Selection::single(0, 3);
365503
366504 for (outer_open, outer_close) in differing_pairs() {
367505 let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
@@ -385,21 +523,44 @@ mod test {
385523 &Selection::single(1, 0),
386524 PAIRS,
387525 |open, _| format!("{}word", open),
388− &Selection::point(1),
526+ &Selection::single(2, 1),
389527 )
390528 }
391529
392− // [TODO] broken until it works with selections
393530 /// [wor]d -> insert ( -> ([wor]d
394531 #[test]
395− #[ignore]
396532 fn test_insert_open_with_selection() {
397533 test_hooks_with_pairs(
398534 &Rope::from("word"),
399− &Selection::single(0, 4),
535+ &Selection::single(3, 0),
400536 PAIRS,
401537 |open, _| format!("{}word", open),
402− &Selection::single(1, 5),
538+ &Selection::single(4, 1),
539+ )
540+ }
541+
542+ /// [wor]d -> append ) -> [wor)]d
543+ #[test]
544+ fn test_append_close_inside_non_pair_with_selection() {
545+ let sel = Selection::single(0, 4);
546+ let expected_sel = Selection::single(0, 5);
547+
548+ for (_, close) in PAIRS {
549+ let doc = Rope::from("word");
550+ let expected_doc = Rope::from(format!("wor{}d", close));
551+ test_hooks(&doc, &sel, *close, &expected_doc, &expected_sel);
552+ }
553+ }
554+
555+ /// foo[ wor]d -> insert ( -> foo([) wor]d
556+ #[test]
557+ fn test_insert_open_trailing_word_with_selection() {
558+ test_hooks_with_pairs(
559+ &Rope::from("foo word"),
560+ &Selection::single(7, 3),
561+ differing_pairs(),
562+ |open, close| format!("foo{}{} word", open, close),
563+ &Selection::single(9, 4),
403564 )
404565 }
405566
@@ -413,7 +574,7 @@ mod test {
413574 fn test_insert_open_after_non_pair() {
414575 let doc = Rope::from("word");
415576 let sel = Selection::single(5, 4);
416− let expected_sel = Selection::point(5);
577+ let expected_sel = Selection::single(6, 5);
417578
418579 test_hooks_with_pairs(
419580 &doc,
@@ -431,4 +592,18 @@ mod test {
431592 &expected_sel,
432593 );
433594 }
595+
596+ /// appending with only a cursor should stay a cursor
597+ ///
598+ /// [] -> append to end "foo -> "foo[]"
599+ #[test]
600+ fn test_append_single_cursor() {
601+ test_hooks_with_pairs(
602+ &Rope::from("\n"),
603+ &Selection::single(0, 1),
604+ PAIRS,
605+ |open, close| format!("{}{}\n", open, close),
606+ &Selection::single(1, 2),
607+ );
608+ }
434609}
helix-core/src/diagnostic.rs+11−4
@@ -1,12 +1,19 @@
11//! LSP diagnostic utility types.
2+use serde::{Deserialize, Serialize};
23
34/// Describes the severity level of a [`Diagnostic`].
4−#[derive(Debug, Clone, Copy, Eq, PartialEq)]
5+#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
56pub enum Severity {
6− Error,
7− Warning,
8− Info,
97 Hint,
8+ Info,
9+ Warning,
10+ Error,
11+}
12+
13+impl Default for Severity {
14+ fn default() -> Self {
15+ Self::Hint
16+ }
1017}
1118
1219/// A range of `char`s within the text.
helix-core/src/indent.rs+65−78
@@ -1,6 +1,5 @@
11use crate::{
22 chars::{char_is_line_ending, char_is_whitespace},
3− find_first_non_whitespace_char,
43 syntax::{IndentQuery, LanguageConfiguration, Syntax},
54 tree_sitter::Node,
65 Rope, RopeSlice,
@@ -174,8 +173,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
174173
175174/// To determine indentation of a newly inserted line, figure out the indentation at the last col
176175/// of the previous line.
177−#[allow(dead_code)]
178−fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
176+pub fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
179177 let mut len = 0;
180178 for ch in line.chars() {
181179 match ch {
@@ -210,10 +208,15 @@ fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Nod
210208 Some(node)
211209}
212210
213−fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool) -> usize {
214− // NOTE: can't use contains() on query because of comparing Vec<String> and &str
215− // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.contains
216−
211+/// Calculate the indentation at a given treesitter node.
212+/// If newline is false, then any "indent" nodes on the line are ignored ("outdent" still applies).
213+/// This is because the indentation is only increased starting at the second line of the node.
214+fn calculate_indentation(
215+ query: &IndentQuery,
216+ node: Option<Node>,
217+ line: usize,
218+ newline: bool,
219+) -> usize {
217220 let mut increment: isize = 0;
218221
219222 let mut node = match node {
@@ -221,70 +224,45 @@ fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool)
221224 None => return 0,
222225 };
223226
224− let mut prev_start = node.start_position().row;
225−
226− // if we're calculating indentation for a brand new line then the current node will become the
227− // parent node. We need to take it's indentation level into account too.
228− let node_kind = node.kind();
229− if newline && query.indent.contains(node_kind) {
230− increment += 1;
231− }
232−
233− while let Some(parent) = node.parent() {
234− let parent_kind = parent.kind();
235− let start = parent.start_position().row;
236−
237− // detect deeply nested indents in the same line
238− // .map(|a| { <-- ({ is two scopes
239− // let len = 1; <-- indents one level
240− // }) <-- }) is two scopes
241− let starts_same_line = start == prev_start;
242−
243− if query.outdent.contains(node.kind()) && !starts_same_line {
244− // we outdent by skipping the rules for the current level and jumping up
245− // node = parent;
246− increment -= 1;
247− // continue;
227+ let mut current_line = line;
228+ let mut consider_indent = newline;
229+ let mut increment_from_line: isize = 0;
230+
231+ loop {
232+ let node_kind = node.kind();
233+ let start = node.start_position().row;
234+ if current_line != start {
235+ // Indent/dedent by at most one per line:
236+ // .map(|a| { <-- ({ is two scopes
237+ // let len = 1; <-- indents one level
238+ // }) <-- }) is two scopes
239+ if consider_indent || increment_from_line < 0 {
240+ increment += increment_from_line.signum();
241+ }
242+ increment_from_line = 0;
243+ current_line = start;
244+ consider_indent = true;
248245 }
249246
250− if query.indent.contains(parent_kind) // && not_first_or_last_sibling
251− && !starts_same_line
252− {
253− // println!("is_scope {}", parent_kind);
254− prev_start = start;
255− increment += 1
247+ if query.outdent.contains(node_kind) {
248+ increment_from_line -= 1;
249+ }
250+ if query.indent.contains(node_kind) {
251+ increment_from_line += 1;
256252 }
257253
258− // if last_scope && increment > 0 && ...{ ignore }
259−
260− node = parent;
254+ if let Some(parent) = node.parent() {
255+ node = parent;
256+ } else {
257+ break;
258+ }
259+ }
260+ if consider_indent || increment_from_line < 0 {
261+ increment += increment_from_line.signum();
261262 }
262−
263263 increment.max(0) as usize
264264}
265265
266−#[allow(dead_code)]
267−fn suggested_indent_for_line(
268− language_config: &LanguageConfiguration,
269− syntax: Option<&Syntax>,
270− text: RopeSlice,
271− line_num: usize,
272− _tab_width: usize,
273−) -> usize {
274− if let Some(start) = find_first_non_whitespace_char(text.line(line_num)) {
275− return suggested_indent_for_pos(
276− Some(language_config),
277− syntax,
278− text,
279− start + text.line_to_char(line_num),
280− false,
281− );
282− };
283−
284− // if the line is blank, indent should be zero
285− 0
286−}
287−
288266// TODO: two usecases: if we are triggering this for a new, blank line:
289267// - it should return 0 when mass indenting stuff
290268// - 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(
293271 syntax: Option<&Syntax>,
294272 text: RopeSlice,
295273 pos: usize,
274+ line: usize,
296275 new_line: bool,
297−) -> usize {
276+) -> Option<usize> {
298277 if let (Some(query), Some(syntax)) = (
299278 language_config.and_then(|config| config.indent_query()),
300279 syntax,
301280 ) {
302281 let byte_start = text.char_to_byte(pos);
303282 let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
304−
305− // let config = load indentation query config from Syntax(should contain language_config)
306−
307283 // TODO: special case for comments
308284 // TODO: if preserve_leading_whitespace
309− calculate_indentation(query, node, new_line)
285+ Some(calculate_indentation(query, node, line, new_line))
310286 } else {
311− // TODO: heuristics for non-tree sitter grammars
312− 0
287+ None
313288 }
314289}
315290
@@ -442,6 +417,7 @@ where
442417 );
443418
444419 let doc = Rope::from(doc);
420+ use crate::diagnostic::Severity;
445421 use crate::syntax::{
446422 Configuration, IndentationConfiguration, LanguageConfiguration, Loader,
447423 };
@@ -459,6 +435,8 @@ where
459435 roots: vec![],
460436 comment_token: None,
461437 auto_format: false,
438+ diagnostic_severity: Severity::Warning,
439+ tree_sitter_library: None,
462440 language_server: None,
463441 indent: Some(IndentationConfiguration {
464442 tab_width: 4,
@@ -482,14 +460,23 @@ where
482460
483461 for i in 0..doc.len_lines() {
484462 let line = text.line(i);
485− let indent = indent_level_for_line(line, tab_width);
486− assert_eq!(
487− suggested_indent_for_line(&language_config, Some(&syntax), text, i, tab_width),
488− indent,
489− "line {}: {}",
490− i,
491− line
492− );
463+ if let Some(pos) = crate::find_first_non_whitespace_char(line) {
464+ let indent = indent_level_for_line(line, tab_width);
465+ assert_eq!(
466+ suggested_indent_for_pos(
467+ Some(&language_config),
468+ Some(&syntax),
469+ text,
470+ text.line_to_char(i) + pos,
471+ i,
472+ false
473+ ),
474+ Some(indent),
475+ "line {}: \"{}\"",
476+ i,
477+ line
478+ );
479+ }
493480 }
494481 }
495482}
helix-core/src/lib.rs+28−6
@@ -1,3 +1,5 @@
1+pub use encoding_rs as encoding;
2+
13pub mod auto_pairs;
24pub mod chars;
35pub mod comment;
@@ -37,8 +39,14 @@ pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
3739 line.chars().position(|ch| !ch.is_whitespace())
3840}
3941
40−/// Find `.git` root.
41−pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
42+/// Find project root.
43+///
44+/// Order of detection:
45+/// * Top-most folder containing a root marker in current git repository
46+/// * Git repostory root if no marker detected
47+/// * Top-most folder containing a root marker if not git repository detected
48+/// * Current working directory as fallback
49+pub fn find_root(root: Option<&str>, root_markers: &[String]) -> Option<std::path::PathBuf> {
4250 let current_dir = std::env::current_dir().expect("unable to determine current directory");
4351
4452 let root = match root {
@@ -50,16 +58,30 @@ pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
5058 current_dir.join(root)
5159 }
5260 }
53− None => current_dir,
61+ None => current_dir.clone(),
5462 };
5563
64+ let mut top_marker = None;
5665 for ancestor in root.ancestors() {
57− // TODO: also use defined roots if git isn't found
66+ for marker in root_markers {
67+ if ancestor.join(marker).exists() {
68+ top_marker = Some(ancestor);
69+ break;
70+ }
71+ }
72+ // don't go higher than repo
5873 if ancestor.join(".git").is_dir() {
59− return Some(ancestor.to_path_buf());
74+ // Use workspace if detected from marker
75+ return Some(top_marker.unwrap_or(ancestor).to_path_buf());
6076 }
6177 }
62− None
78+
79+ // In absence of git repo, use workspace if detected
80+ if top_marker.is_some() {
81+ top_marker.map(|a| a.to_path_buf())
82+ } else {
83+ Some(current_dir)
84+ }
6385}
6486
6587pub fn runtime_dir() -> std::path::PathBuf {
helix-core/src/match_brackets.rs+1−1
@@ -11,7 +11,7 @@ const PAIRS: &[(char, char)] = &[
1111 ('\"', '\"'),
1212];
1313
14−// limit matching pairs to only ( ) { } [ ] < >
14+// limit matching pairs to only ( ) { } [ ] < > ' ' " "
1515
1616// Returns the position of the matching bracket under cursor.
1717//
helix-core/src/movement.rs+25−27
@@ -307,8 +307,6 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
307307
308308#[cfg(test)]
309309mod test {
310− use std::array::{self, IntoIter};
311−
312310 use ropey::Rope;
313311
314312 use super::*;
@@ -360,7 +358,7 @@ mod test {
360358 ((Direction::Backward, 999usize), (0, 0)), // |This is a simple alphabetic line
361359 ];
362360
363− for ((direction, amount), coordinates) in IntoIter::new(moves_and_expected_coordinates) {
361+ for ((direction, amount), coordinates) in moves_and_expected_coordinates {
364362 range = move_horizontally(slice, range, direction, amount, Movement::Move);
365363 assert_eq!(coords_at_pos(slice, range.head), coordinates.into())
366364 }
@@ -374,7 +372,7 @@ mod test {
374372
375373 let mut range = Range::point(position);
376374
377− let moves_and_expected_coordinates = IntoIter::new([
375+ let moves_and_expected_coordinates = [
378376 ((Direction::Forward, 11usize), (1, 1)), // Multiline\nt|ext sample\n...
379377 ((Direction::Backward, 1usize), (1, 0)), // Multiline\n|text sample\n...
380378 ((Direction::Backward, 5usize), (0, 5)), // Multi|line\ntext sample\n...
@@ -384,7 +382,7 @@ mod test {
384382 ((Direction::Backward, 0usize), (0, 3)), // Mul|tiline\ntext sample\n...
385383 ((Direction::Forward, 999usize), (5, 0)), // ...and whitespaced\n|
386384 ((Direction::Forward, 999usize), (5, 0)), // ...and whitespaced\n|
387− ]);
385+ ];
388386
389387 for ((direction, amount), coordinates) in moves_and_expected_coordinates {
390388 range = move_horizontally(slice, range, direction, amount, Movement::Move);
@@ -402,11 +400,11 @@ mod test {
402400 let mut range = Range::point(position);
403401 let original_anchor = range.anchor;
404402
405− let moves = IntoIter::new([
403+ let moves = [
406404 (Direction::Forward, 1usize),
407405 (Direction::Forward, 5usize),
408406 (Direction::Backward, 3usize),
409− ]);
407+ ];
410408
411409 for (direction, amount) in moves {
412410 range = move_horizontally(slice, range, direction, amount, Movement::Extend);
@@ -420,7 +418,7 @@ mod test {
420418 let slice = text.slice(..);
421419 let position = pos_at_coords(slice, (0, 0).into(), true);
422420 let mut range = Range::point(position);
423− let moves_and_expected_coordinates = IntoIter::new([
421+ let moves_and_expected_coordinates = [
424422 ((Direction::Forward, 1usize), (1, 0)),
425423 ((Direction::Forward, 2usize), (3, 0)),
426424 ((Direction::Forward, 1usize), (4, 0)),
@@ -430,7 +428,7 @@ mod test {
430428 ((Direction::Backward, 0usize), (4, 0)),
431429 ((Direction::Forward, 5), (5, 0)),
432430 ((Direction::Forward, 999usize), (5, 0)),
433− ]);
431+ ];
434432
435433 for ((direction, amount), coordinates) in moves_and_expected_coordinates {
436434 range = move_vertically(slice, range, direction, amount, Movement::Move);
@@ -450,7 +448,7 @@ mod test {
450448 H,
451449 V,
452450 }
453− let moves_and_expected_coordinates = IntoIter::new([
451+ let moves_and_expected_coordinates = [
454452 // Places cursor at the end of line
455453 ((Axis::H, Direction::Forward, 8usize), (0, 8)),
456454 // First descent preserves column as the target line is wider
@@ -463,7 +461,7 @@ mod test {
463461 ((Axis::V, Direction::Backward, 999usize), (0, 8)),
464462 ((Axis::V, Direction::Forward, 4usize), (4, 8)),
465463 ((Axis::V, Direction::Forward, 999usize), (5, 0)),
466− ]);
464+ ];
467465
468466 for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
469467 range = match axis {
@@ -489,7 +487,7 @@ mod test {
489487 H,
490488 V,
491489 }
492− let moves_and_expected_coordinates = IntoIter::new([
490+ let moves_and_expected_coordinates = [
493491 // Places cursor at the fourth kana.
494492 ((Axis::H, Direction::Forward, 4), (0, 4)),
495493 // Descent places cursor at the 4th character.
@@ -498,7 +496,7 @@ mod test {
498496 ((Axis::H, Direction::Backward, 1usize), (1, 3)),
499497 // Jumping back up 1 line.
500498 ((Axis::V, Direction::Backward, 1usize), (0, 3)),
501− ]);
499+ ];
502500
503501 for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
504502 range = match axis {
@@ -530,7 +528,7 @@ mod test {
530528
531529 #[test]
532530 fn test_behaviour_when_moving_to_start_of_next_words() {
533− let tests = array::IntoIter::new([
531+ let tests = [
534532 ("Basic forward motion stops at the first space",
535533 vec![(1, Range::new(0, 0), Range::new(0, 6))]),
536534 (" Starting from a boundary advances the anchor",
@@ -604,7 +602,7 @@ mod test {
604602 vec![
605603 (1, Range::new(0, 0), Range::new(0, 6)),
606604 ]),
607− ]);
605+ ];
608606
609607 for (sample, scenario) in tests {
610608 for (count, begin, expected_end) in scenario.into_iter() {
@@ -616,7 +614,7 @@ mod test {
616614
617615 #[test]
618616 fn test_behaviour_when_moving_to_start_of_next_long_words() {
619− let tests = array::IntoIter::new([
617+ let tests = [
620618 ("Basic forward motion stops at the first space",
621619 vec![(1, Range::new(0, 0), Range::new(0, 6))]),
622620 (" Starting from a boundary advances the anchor",
@@ -688,7 +686,7 @@ mod test {
688686 vec![
689687 (1, Range::new(0, 0), Range::new(0, 8)),
690688 ]),
691− ]);
689+ ];
692690
693691 for (sample, scenario) in tests {
694692 for (count, begin, expected_end) in scenario.into_iter() {
@@ -700,7 +698,7 @@ mod test {
700698
701699 #[test]
702700 fn test_behaviour_when_moving_to_start_of_previous_words() {
703− let tests = array::IntoIter::new([
701+ let tests = [
704702 ("Basic backward motion from the middle of a word",
705703 vec![(1, Range::new(3, 3), Range::new(4, 0))]),
706704
@@ -773,7 +771,7 @@ mod test {
773771 vec![
774772 (1, Range::new(0, 6), Range::new(6, 0)),
775773 ]),
776− ]);
774+ ];
777775
778776 for (sample, scenario) in tests {
779777 for (count, begin, expected_end) in scenario.into_iter() {
@@ -785,7 +783,7 @@ mod test {
785783
786784 #[test]
787785 fn test_behaviour_when_moving_to_start_of_previous_long_words() {
788− let tests = array::IntoIter::new([
786+ let tests = [
789787 (
790788 "Basic backward motion from the middle of a word",
791789 vec![(1, Range::new(3, 3), Range::new(4, 0))],
@@ -870,7 +868,7 @@ mod test {
870868 vec![
871869 (1, Range::new(0, 8), Range::new(8, 0)),
872870 ]),
873− ]);
871+ ];
874872
875873 for (sample, scenario) in tests {
876874 for (count, begin, expected_end) in scenario.into_iter() {
@@ -882,7 +880,7 @@ mod test {
882880
883881 #[test]
884882 fn test_behaviour_when_moving_to_end_of_next_words() {
885− let tests = array::IntoIter::new([
883+ let tests = [
886884 ("Basic forward motion from the start of a word to the end of it",
887885 vec![(1, Range::new(0, 0), Range::new(0, 5))]),
888886 ("Basic forward motion from the end of a word to the end of the next",
@@ -954,7 +952,7 @@ mod test {
954952 vec![
955953 (1, Range::new(0, 0), Range::new(0, 5)),
956954 ]),
957− ]);
955+ ];
958956
959957 for (sample, scenario) in tests {
960958 for (count, begin, expected_end) in scenario.into_iter() {
@@ -966,7 +964,7 @@ mod test {
966964
967965 #[test]
968966 fn test_behaviour_when_moving_to_end_of_previous_words() {
969− let tests = array::IntoIter::new([
967+ let tests = [
970968 ("Basic backward motion from the middle of a word",
971969 vec![(1, Range::new(9, 9), Range::new(10, 5))]),
972970 ("Starting from after boundary retreats the anchor",
@@ -1036,7 +1034,7 @@ mod test {
10361034 vec![
10371035 (1, Range::new(0, 10), Range::new(10, 4)),
10381036 ]),
1039− ]);
1037+ ];
10401038
10411039 for (sample, scenario) in tests {
10421040 for (count, begin, expected_end) in scenario.into_iter() {
@@ -1048,7 +1046,7 @@ mod test {
10481046
10491047 #[test]
10501048 fn test_behaviour_when_moving_to_end_of_next_long_words() {
1051− let tests = array::IntoIter::new([
1049+ let tests = [
10521050 ("Basic forward motion from the start of a word to the end of it",
10531051 vec![(1, Range::new(0, 0), Range::new(0, 5))]),
10541052 ("Basic forward motion from the end of a word to the end of the next",
@@ -1118,7 +1116,7 @@ mod test {
11181116 vec![
11191117 (1, Range::new(0, 0), Range::new(0, 7)),
11201118 ]),
1121− ]);
1119+ ];
11221120
11231121 for (sample, scenario) in tests {
11241122 for (count, begin, expected_end) in scenario.into_iter() {
helix-core/src/object.rs+27−2
@@ -1,7 +1,5 @@
11use crate::{Range, RopeSlice, Selection, Syntax};
22
3−// TODO: to contract_selection we'd need to store the previous ranges before expand.
4−// Maybe just contract to the first child node?
53pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
64 let tree = syntax.tree();
75
@@ -34,3 +32,30 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection)
3432 }
3533 })
3634}
35+
36+pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
37+ let tree = syntax.tree();
38+
39+ selection.clone().transform(|range| {
40+ let from = text.char_to_byte(range.from());
41+ let to = text.char_to_byte(range.to());
42+
43+ let descendant = match tree.root_node().descendant_for_byte_range(from, to) {
44+ // find first child, if not possible, fallback to the node that contains selection
45+ Some(descendant) => match descendant.child(0) {
46+ Some(child) => child,
47+ None => descendant,
48+ },
49+ None => return range,
50+ };
51+
52+ let from = text.byte_to_char(descendant.start_byte());
53+ let to = text.byte_to_char(descendant.end_byte());
54+
55+ if range.head < range.anchor {
56+ Range::new(to, from)
57+ } else {
58+ Range::new(from, to)
59+ }
60+ })
61+}
helix-core/src/selection.rs+93
@@ -7,6 +7,7 @@ use crate::{
77 ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary,
88 prev_grapheme_boundary,
99 },
10+ movement::Direction,
1011 Assoc, ChangeSet, RopeSlice,
1112};
1213use smallvec::{smallvec, SmallVec};
@@ -82,6 +83,13 @@ impl Range {
8283 std::cmp::max(self.anchor, self.head)
8384 }
8485
86+ /// Total length of the range.
87+ #[inline]
88+ #[must_use]
89+ pub fn len(&self) -> usize {
90+ self.to() - self.from()
91+ }
92+
8593 /// The (inclusive) range of lines that the range overlaps.
8694 #[inline]
8795 #[must_use]
@@ -102,6 +110,27 @@ impl Range {
102110 self.anchor == self.head
103111 }
104112
113+ /// `Direction::Backward` when head < anchor.
114+ /// `Direction::Backward` otherwise.
115+ #[inline]
116+ #[must_use]
117+ pub fn direction(&self) -> Direction {
118+ if self.head < self.anchor {
119+ Direction::Backward
120+ } else {
121+ Direction::Forward
122+ }
123+ }
124+
125+ // flips the direction of the selection
126+ pub fn flip(&self) -> Self {
127+ Self {
128+ anchor: self.head,
129+ head: self.anchor,
130+ horiz: self.horiz,
131+ }
132+ }
133+
105134 /// Check two ranges for overlap.
106135 #[must_use]
107136 pub fn overlaps(&self, other: &Self) -> bool {
@@ -111,6 +140,11 @@ impl Range {
111140 self.from() == other.from() || (self.to() > other.from() && other.to() > self.from())
112141 }
113142
143+ #[inline]
144+ pub fn contains_range(&self, other: &Self) -> bool {
145+ self.from() <= other.from() && self.to() >= other.to()
146+ }
147+
114148 pub fn contains(&self, pos: usize) -> bool {
115149 self.from() <= pos && pos < self.to()
116150 }
@@ -515,6 +549,39 @@ impl Selection {
515549 pub fn len(&self) -> usize {
516550 self.ranges.len()
517551 }
552+
553+ // returns true if self ⊇ other
554+ pub fn contains(&self, other: &Selection) -> bool {
555+ // can't contain other if it is larger
556+ if other.len() > self.len() {
557+ return false;
558+ }
559+
560+ let (mut iter_self, mut iter_other) = (self.iter(), other.iter());
561+ let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next());
562+
563+ loop {
564+ match (ele_self, ele_other) {
565+ (Some(ra), Some(rb)) => {
566+ if !ra.contains_range(rb) {
567+ // `self` doesn't contain next element from `other`, advance `self`, we need to match all from `other`
568+ ele_self = iter_self.next();
569+ } else {
570+ // matched element from `other`, advance `other`
571+ ele_other = iter_other.next();
572+ };
573+ }
574+ (None, Some(_)) => {
575+ // exhausted `self`, we can't match the reminder of `other`
576+ return false;
577+ }
578+ (_, None) => {
579+ // no elements from `other` left to match, `self` contains `other`
580+ return true;
581+ }
582+ }
583+ }
584+ }
518585}
519586
520587impl<'a> IntoIterator for &'a Selection {
@@ -953,4 +1020,30 @@ mod test {
9531020 &["", "abcd", "efg", "rs", "xyz"]
9541021 );
9551022 }
1023+ #[test]
1024+ fn test_selection_contains() {
1025+ fn contains(a: Vec<(usize, usize)>, b: Vec<(usize, usize)>) -> bool {
1026+ let sela = Selection::new(a.iter().map(|a| Range::new(a.0, a.1)).collect(), 0);
1027+ let selb = Selection::new(b.iter().map(|b| Range::new(b.0, b.1)).collect(), 0);
1028+ sela.contains(&selb)
1029+ }
1030+
1031+ // exact match
1032+ assert!(contains(vec!((1, 1)), vec!((1, 1))));
1033+
1034+ // larger set contains smaller
1035+ assert!(contains(vec!((1, 1), (2, 2), (3, 3)), vec!((2, 2))));
1036+
1037+ // multiple matches
1038+ assert!(contains(vec!((1, 1), (2, 2)), vec!((1, 1), (2, 2))));
1039+
1040+ // smaller set can't contain bigger
1041+ assert!(!contains(vec!((1, 1)), vec!((1, 1), (2, 2))));
1042+
1043+ assert!(contains(
1044+ vec!((1, 1), (2, 4), (5, 6), (7, 9), (10, 13)),
1045+ vec!((3, 4), (7, 9))
1046+ ));
1047+ assert!(!contains(vec!((1, 1), (5, 6)), vec!((1, 6))));
1048+ }
9561049}
helix-core/src/syntax.rs+13−3
@@ -1,5 +1,6 @@
11use crate::{
22 chars::char_is_line_ending,
3+ diagnostic::Severity,
34 regex::Regex,
45 transaction::{ChangeSet, Operation},
56 Rope, RopeSlice, Tendril,
@@ -63,6 +64,10 @@ pub struct LanguageConfiguration {
6364
6465 #[serde(default)]
6566 pub auto_format: bool,
67+ #[serde(default)]
68+ pub diagnostic_severity: Severity,
69+
70+ pub tree_sitter_library: Option<String>, // tree-sitter library name, defaults to language_id
6671
6772 // content_regex
6873 #[serde(default, skip_serializing, deserialize_with = "deserialize_regex")]
@@ -189,9 +194,14 @@ impl LanguageConfiguration {
189194 if highlights_query.is_empty() {
190195 None
191196 } else {
192− let language = get_language(&crate::RUNTIME_DIR, &self.language_id)
193− .map_err(|e| log::info!("{}", e))
194− .ok()?;
197+ let language = get_language(
198+ &crate::RUNTIME_DIR,
199+ self.tree_sitter_library
200+ .as_deref()
201+ .unwrap_or(&self.language_id),
202+ )
203+ .map_err(|e| log::info!("{}", e))
204+ .ok()?;
195205 let config = HighlightConfiguration::new(
196206 language,
197207 &highlights_query,
helix-lsp/Cargo.toml+3−3
@@ -1,6 +1,6 @@
11[package]
22name = "helix-lsp"
3−version = "0.5.0"
3+version = "0.6.0"
44authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
55edition = "2021"
66license = "MPL-2.0"
@@ -12,7 +12,7 @@ homepage = "https://helix-editor.com"
1212# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1313
1414[dependencies]
15−helix-core = { version = "0.5", path = "../helix-core" }
15+helix-core = { version = "0.6", path = "../helix-core" }
1616
1717anyhow = "1.0"
1818futures-executor = "0.3"
@@ -23,5 +23,5 @@ lsp-types = { version = "0.91", features = ["proposed"] }
2323serde = { version = "1.0", features = ["derive"] }
2424serde_json = "1.0"
2525thiserror = "1.0"
26−tokio = { version = "1.14", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
26+tokio = { version = "1.15", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
2727tokio-stream = "0.1.8"
helix-lsp/src/client.rs+26−2
@@ -31,6 +31,7 @@ pub struct Client {
3131 pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
3232 offset_encoding: OffsetEncoding,
3333 config: Option<Value>,
34+ root_markers: Vec<String>,
3435}
3536
3637impl Client {
@@ -39,6 +40,7 @@ impl Client {
3940 cmd: &str,
4041 args: &[String],
4142 config: Option<Value>,
43+ root_markers: Vec<String>,
4244 id: usize,
4345 ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
4446 let process = Command::new(cmd)
@@ -68,6 +70,7 @@ impl Client {
6870 capabilities: OnceCell::new(),
6971 offset_encoding: OffsetEncoding::Utf8,
7072 config,
73+ root_markers,
7174 };
7275
7376 Ok((client, server_rx, initialize_notify))
@@ -202,7 +205,7 @@ impl Client {
202205 Ok(result) => Output::Success(Success {
203206 jsonrpc: Some(Version::V2),
204207 id,
205− result,
208+ result: serde_json::to_value(result)?,
206209 }),
207210 Err(error) => Output::Failure(Failure {
208211 jsonrpc: Some(Version::V2),
@@ -225,7 +228,8 @@ impl Client {
225228
226229 pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
227230 // TODO: delay any requests that are triggered prior to initialize
228− let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
231+ let root = find_root(None, &self.root_markers)
232+ .and_then(|root| lsp::Url::from_file_path(root).ok());
229233
230234 if self.config.is_some() {
231235 log::info!("Using custom LSP config: {}", self.config.as_ref().unwrap());
@@ -556,6 +560,14 @@ impl Client {
556560 self.call::<lsp::request::Completion>(params)
557561 }
558562
563+ pub async fn resolve_completion_item(
564+ &self,
565+ completion_item: lsp::CompletionItem,
566+ ) -> Result<lsp::CompletionItem> {
567+ self.request::<lsp::request::ResolveCompletionItem>(completion_item)
568+ .await
569+ }
570+
559571 pub fn text_document_signature_help(
560572 &self,
561573 text_document: lsp::TextDocumentIdentifier,
@@ -800,4 +812,16 @@ impl Client {
800812 let response = self.request::<lsp::request::Rename>(params).await?;
801813 Ok(response.unwrap_or_default())
802814 }
815+
816+ pub fn command(&self, command: lsp::Command) -> impl Future<Output = Result<Value>> {
817+ let params = lsp::ExecuteCommandParams {
818+ command: command.command,
819+ arguments: command.arguments.unwrap_or_default(),
820+ work_done_progress_params: lsp::WorkDoneProgressParams {
821+ work_done_token: None,
822+ },
823+ };
824+
825+ self.call::<lsp::request::ExecuteCommand>(params)
826+ }
803827}
helix-lsp/src/lib.rs+13−18
@@ -66,39 +66,26 @@ pub mod util {
6666 pos: lsp::Position,
6767 offset_encoding: OffsetEncoding,
6868 ) -> Option<usize> {
69− let max_line = doc.lines().count().saturating_sub(1);
7069 let pos_line = pos.line as usize;
71− let pos_line = if pos_line > max_line {
70+ if pos_line > doc.len_lines() - 1 {
7271 return None;
73− } else {
74− pos_line
75− };
72+ }
73+
7674 match offset_encoding {
7775 OffsetEncoding::Utf8 => {
78− let max_char = doc
79− .line_to_char(max_line)
80− .checked_add(doc.line(max_line).len_chars())?;
8176 let line = doc.line_to_char(pos_line);
8277 let pos = line.checked_add(pos.character as usize)?;
83− if pos <= max_char {
78+ if pos <= doc.len_chars() {
8479 Some(pos)
8580 } else {
8681 None
8782 }
8883 }
8984 OffsetEncoding::Utf16 => {
90− let max_char = doc
91− .line_to_char(max_line)
92− .checked_add(doc.line(max_line).len_chars())?;
93− let max_cu = doc.char_to_utf16_cu(max_char);
9485 let line = doc.line_to_char(pos_line);
9586 let line_start = doc.char_to_utf16_cu(line);
9687 let pos = line_start.checked_add(pos.character as usize)?;
97− if pos <= max_cu {
98− Some(doc.utf16_cu_to_char(pos))
99− } else {
100− None
101− }
88+ doc.try_utf16_cu_to_char(pos).ok()
10289 }
10390 }
10491 }
@@ -203,6 +190,7 @@ pub mod util {
203190#[derive(Debug, PartialEq, Clone)]
204191pub enum MethodCall {
205192 WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams),
193+ ApplyWorkspaceEdit(lsp::ApplyWorkspaceEditParams),
206194}
207195
208196impl MethodCall {
@@ -215,6 +203,12 @@ impl MethodCall {
215203 .expect("Failed to parse WorkDoneCreate params");
216204 Self::WorkDoneProgressCreate(params)
217205 }
206+ lsp::request::ApplyWorkspaceEdit::METHOD => {
207+ let params: lsp::ApplyWorkspaceEditParams = params
208+ .parse()
209+ .expect("Failed to parse ApplyWorkspaceEdit params");
210+ Self::ApplyWorkspaceEdit(params)
211+ }
218212 _ => {
219213 log::warn!("unhandled lsp request: {}", method);
220214 return None;
@@ -319,6 +313,7 @@ impl Registry {
319313 &config.command,
320314 &config.args,
321315 language_config.config.clone(),
316+ language_config.roots.clone(),
322317 id,
323318 )?;
324319 self.incoming.push(UnboundedReceiverStream::new(incoming));
helix-syntax/Cargo.toml+1−1
@@ -1,6 +1,6 @@
11[package]
22name = "helix-syntax"
3−version = "0.5.0"
3+version = "0.6.0"
44authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
55edition = "2021"
66license = "MPL-2.0"
helix-syntax/README.mdnew file+13
@@ -0,0 +1,13 @@
1+helix-syntax
2+============
3+
4+Syntax highlighting for helix, (shallow) submodules resides here.
5+
6+Differences from nvim-treesitter
7+--------------------------------
8+
9+As the syntax are commonly ported from
10+<https://github.com/nvim-treesitter/nvim-treesitter>.
11+
12+Note that we do not support the custom `#any-of` predicate which is
13+supported by neovim so one needs to change it to `#match` with regex.
helix-syntax/languages/tree-sitter-commentnew file+1

Submodule changed.

helix-syntax/languages/tree-sitter-dartnew file+1

Submodule changed.

helix-syntax/languages/tree-sitter-dockerfilenew file+1

Submodule changed.

helix-syntax/languages/tree-sitter-fishnew file+1

Submodule changed.

helix-syntax/languages/tree-sitter-git-commitnew file+1

Submodule changed.

helix-syntax/languages/tree-sitter-git-diffnew file+1

Submodule changed.

helix-syntax/languages/tree-sitter-git-rebasenew file+1

Submodule changed.

helix-syntax/languages/tree-sitter-llvmnew file+1

Submodule changed.

helix-syntax/languages/tree-sitter-llvm-mirnew file+1

Submodule changed.

helix-syntax/languages/tree-sitter-tablegennew file+1

Submodule changed.

helix-term/Cargo.toml+5−5
@@ -1,6 +1,6 @@
11[package]
22name = "helix-term"
3−version = "0.5.0"
3+version = "0.6.0"
44description = "A post-modern text editor."
55authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
66edition = "2021"
@@ -22,12 +22,12 @@ name = "hx"
2222path = "src/main.rs"
2323
2424[dependencies]
25−helix-core = { version = "0.5", path = "../helix-core" }
26−helix-view = { version = "0.5", path = "../helix-view" }
27−helix-lsp = { version = "0.5", path = "../helix-lsp" }
25+helix-core = { version = "0.6", path = "../helix-core" }
26+helix-view = { version = "0.6", path = "../helix-view" }
27+helix-lsp = { version = "0.6", path = "../helix-lsp" }
2828
2929anyhow = "1"
30−once_cell = "1.8"
30+once_cell = "1.9"
3131
3232tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
3333num_cpus = "1"
helix-term/src/application.rs+61−18
@@ -1,8 +1,12 @@
11use helix_core::{merge_toml_values, syntax};
22use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
33use helix_view::{theme, Editor};
4+use serde_json::json;
45
5−use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui};
6+use crate::{
7+ args::Args, commands::apply_workspace_edit, compositor::Compositor, config::Config, job::Jobs,
8+ ui,
9+};
610
711use log::{error, warn};
812
@@ -374,6 +378,7 @@ impl Application {
374378 let doc = self.editor.document_by_path_mut(&path);
375379
376380 if let Some(doc) = doc {
381+ let lang_conf = doc.language_config();
377382 let text = doc.text();
378383
379384 let diagnostics = params
@@ -411,19 +416,31 @@ impl Application {
411416 return None;
412417 };
413418
419+ let severity =
420+ diagnostic.severity.map(|severity| match severity {
421+ DiagnosticSeverity::ERROR => Error,
422+ DiagnosticSeverity::WARNING => Warning,
423+ DiagnosticSeverity::INFORMATION => Info,
424+ DiagnosticSeverity::HINT => Hint,
425+ severity => unreachable!(
426+ "unrecognized diagnostic severity: {:?}",
427+ severity
428+ ),
429+ });
430+
431+ if let Some(lang_conf) = lang_conf {
432+ if let Some(severity) = severity {
433+ if severity < lang_conf.diagnostic_severity {
434+ return None;
435+ }
436+ }
437+ };
438+
414439 Some(Diagnostic {
415440 range: Range { start, end },
416441 line: diagnostic.range.start.line as usize,
417442 message: diagnostic.message,
418− severity: diagnostic.severity.map(
419− |severity| match severity {
420− DiagnosticSeverity::ERROR => Error,
421− DiagnosticSeverity::WARNING => Warning,
422− DiagnosticSeverity::INFORMATION => Info,
423− DiagnosticSeverity::HINT => Hint,
424− severity => unimplemented!("{:?}", severity),
425− },
426− ),
443+ severity,
427444 // code
428445 // source
429446 })
@@ -530,14 +547,6 @@ impl Application {
530547 Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
531548 method, params, id, ..
532549 }) => {
533− let language_server = match self.editor.language_servers.get_by_id(server_id) {
534− Some(language_server) => language_server,
535− None => {
536− warn!("can't find language server with id `{}`", server_id);
537− return;
538− }
539− };
540−
541550 let call = match MethodCall::parse(&method, params) {
542551 Some(call) => call,
543552 None => {
@@ -567,8 +576,42 @@ impl Application {
567576 if spinner.is_stopped() {
568577 spinner.start();
569578 }
579+ let language_server =
580+ match self.editor.language_servers.get_by_id(server_id) {
581+ Some(language_server) => language_server,
582+ None => {
583+ warn!("can't find language server with id `{}`", server_id);
584+ return;
585+ }
586+ };
587+
570588 tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null)));
571589 }
590+ MethodCall::ApplyWorkspaceEdit(params) => {
591+ apply_workspace_edit(
592+ &mut self.editor,
593+ helix_lsp::OffsetEncoding::Utf8,
594+ &params.edit,
595+ );
596+
597+ let language_server =
598+ match self.editor.language_servers.get_by_id(server_id) {
599+ Some(language_server) => language_server,
600+ None => {
601+ warn!("can't find language server with id `{}`", server_id);
602+ return;
603+ }
604+ };
605+
606+ tokio::spawn(language_server.reply(
607+ id,
608+ Ok(json!(lsp::ApplyWorkspaceEditResponse {
609+ applied: true,
610+ failure_reason: None,
611+ failed_change: None,
612+ })),
613+ ));
614+ }
572615 }
573616 }
574617 e => unreachable!("{:?}", e),
helix-term/src/commands.rs+375−131
@@ -26,6 +26,7 @@ use helix_view::{
2626};
2727
2828use anyhow::{anyhow, bail, ensure, Context as _};
29+use fuzzy_matcher::FuzzyMatcher;
2930use helix_lsp::{
3031 block_on, lsp,
3132 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
@@ -266,6 +267,7 @@ impl MappableCommand {
266267 change_selection_noyank, "Change selection (delete and enter insert mode, without yanking)",
267268 collapse_selection, "Collapse selection onto a single cursor",
268269 flip_selections, "Flip selection cursor and anchor",
270+ ensure_selections_forward, "Ensure the selection is in forward direction",
269271 insert_mode, "Insert before selection",
270272 append_mode, "Insert after selection (append)",
271273 command_mode, "Enter command mode",
@@ -287,7 +289,7 @@ impl MappableCommand {
287289 add_newline_below, "Add newline below",
288290 goto_type_definition, "Goto type definition",
289291 goto_implementation, "Goto implementation",
290− goto_file_start, "Goto file start/line",
292+ goto_file_start, "Goto line number <n> else file start",
291293 goto_file_end, "Goto file end",
292294 goto_file, "Goto files in selection",
293295 goto_file_hsplit, "Goto files in selection (hsplit)",
@@ -360,6 +362,7 @@ impl MappableCommand {
360362 rotate_selection_contents_forward, "Rotate selection contents forward",
361363 rotate_selection_contents_backward, "Rotate selections contents backward",
362364 expand_selection, "Expand selection to parent syntax node",
365+ shrink_selection, "Shrink selection to previously expanded syntax node",
363366 jump_forward, "Jump forward on jumplist",
364367 jump_backward, "Jump backward on jumplist",
365368 save_selection, "Save the current selection to the jumplist",
@@ -396,7 +399,7 @@ impl MappableCommand {
396399 increment, "Increment",
397400 decrement, "Decrement",
398401 record_macro, "Record macro",
399− play_macro, "Play macro",
402+ replay_macro, "Replay macro",
400403 );
401404}
402405
@@ -1280,16 +1283,23 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
12801283 .max(view.offset.row + scrolloff)
12811284 .min(last_line.saturating_sub(scrolloff));
12821285
1283− let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
1286+ // If cursor needs moving, replace primary selection
1287+ if line != cursor.row {
1288+ let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
12841289
1285− let anchor = if doc.mode == Mode::Select {
1286− range.anchor
1287− } else {
1288− head
1289− };
1290+ let anchor = if doc.mode == Mode::Select {
1291+ range.anchor
1292+ } else {
1293+ head
1294+ };
12901295
1291− // TODO: only manipulate main selection
1292− doc.set_selection(view.id, Selection::single(anchor, head));
1296+ // replace primary selection with an empty selection at cursor pos
1297+ let prim_sel = Range::new(anchor, head);
1298+ let mut sel = doc.selection(view.id).clone();
1299+ let idx = sel.primary_index();
1300+ sel = sel.replace(idx, prim_sel);
1301+ doc.set_selection(view.id, sel);
1302+ }
12931303}
12941304
12951305fn page_up(cx: &mut Context) {
@@ -1543,7 +1553,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
15431553 let reg = cx.register.unwrap_or('/');
15441554 let scrolloff = cx.editor.config.scrolloff;
15451555
1546− let (_, doc) = current!(cx.editor);
1556+ let doc = doc!(cx.editor);
15471557
15481558 // TODO: could probably share with select_on_matches?
15491559
@@ -1630,7 +1640,7 @@ fn search_selection(cx: &mut Context) {
16301640 let query = doc.selection(view.id).primary().fragment(contents);
16311641 let regex = regex::escape(&query);
16321642 cx.editor.registers.get_mut('/').push(regex);
1633− let msg = format!("register '{}' set to '{}'", '\\', query);
1643+ let msg = format!("register '{}' set to '{}'", '/', query);
16341644 cx.editor.set_status(msg);
16351645}
16361646
@@ -1904,7 +1914,21 @@ fn flip_selections(cx: &mut Context) {
19041914 let selection = doc
19051915 .selection(view.id)
19061916 .clone()
1907− .transform(|range| Range::new(range.head, range.anchor));
1917+ .transform(|range| range.flip());
1918+ doc.set_selection(view.id, selection);
1919+}
1920+
1921+fn ensure_selections_forward(cx: &mut Context) {
1922+ let (view, doc) = current!(cx.editor);
1923+
1924+ let selection = doc
1925+ .selection(view.id)
1926+ .clone()
1927+ .transform(|r| match r.direction() {
1928+ Direction::Forward => r,
1929+ Direction::Backward => r.flip(),
1930+ });
1931+
19081932 doc.set_selection(view.id, selection);
19091933}
19101934
@@ -1938,7 +1962,7 @@ fn append_mode(cx: &mut Context) {
19381962 if !last_range.is_empty() && last_range.head == end {
19391963 let transaction = Transaction::change(
19401964 doc.text(),
1941− std::array::IntoIter::new([(end, end, Some(doc.line_ending.as_str().into()))]),
1965+ [(end, end, Some(doc.line_ending.as_str().into()))].into_iter(),
19421966 );
19431967 doc.apply(&transaction, view.id);
19441968 }
@@ -2030,7 +2054,7 @@ pub mod cmd {
20302054
20312055 fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
20322056 let jobs = &mut cx.jobs;
2033− let (_, doc) = current!(cx.editor);
2057+ let doc = doc_mut!(cx.editor);
20342058
20352059 if let Some(ref path) = path {
20362060 doc.set_path(Some(path.as_ref().as_ref()))
@@ -2083,8 +2107,7 @@ pub mod cmd {
20832107 _args: &[Cow<str>],
20842108 _event: PromptEvent,
20852109 ) -> anyhow::Result<()> {
2086− let (_, doc) = current!(cx.editor);
2087−
2110+ let doc = doc!(cx.editor);
20882111 if let Some(format) = doc.format() {
20892112 let callback =
20902113 make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
@@ -2307,12 +2330,7 @@ pub mod cmd {
23072330 write_all_impl(cx, args, event, true, true)
23082331 }
23092332
2310− fn quit_all_impl(
2311− editor: &mut Editor,
2312− _args: &[Cow<str>],
2313− _event: PromptEvent,
2314− force: bool,
2315− ) -> anyhow::Result<()> {
2333+ fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> {
23162334 if !force {
23172335 buffers_remaining_impl(editor)?;
23182336 }
@@ -2328,18 +2346,18 @@ pub mod cmd {
23282346
23292347 fn quit_all(
23302348 cx: &mut compositor::Context,
2331− args: &[Cow<str>],
2332− event: PromptEvent,
2349+ _args: &[Cow<str>],
2350+ _event: PromptEvent,
23332351 ) -> anyhow::Result<()> {
2334− quit_all_impl(cx.editor, args, event, false)
2352+ quit_all_impl(cx.editor, false)
23352353 }
23362354
23372355 fn force_quit_all(
23382356 cx: &mut compositor::Context,
2339− args: &[Cow<str>],
2340− event: PromptEvent,
2357+ _args: &[Cow<str>],
2358+ _event: PromptEvent,
23412359 ) -> anyhow::Result<()> {
2342− quit_all_impl(cx.editor, args, event, true)
2360+ quit_all_impl(cx.editor, true)
23432361 }
23442362
23452363 fn cquit(
@@ -2353,12 +2371,21 @@ pub mod cmd {
23532371 .unwrap_or(1);
23542372 cx.editor.exit_code = exit_code;
23552373
2356− let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
2357− for view_id in views {
2358− cx.editor.close(view_id);
2359− }
2374+ quit_all_impl(cx.editor, false)
2375+ }
23602376
2361− Ok(())
2377+ fn force_cquit(
2378+ cx: &mut compositor::Context,
2379+ args: &[Cow<str>],
2380+ _event: PromptEvent,
2381+ ) -> anyhow::Result<()> {
2382+ let exit_code = args
2383+ .first()
2384+ .and_then(|code| code.parse::<i32>().ok())
2385+ .unwrap_or(1);
2386+ cx.editor.exit_code = exit_code;
2387+
2388+ quit_all_impl(cx.editor, true)
23622389 }
23632390
23642391 fn theme(
@@ -2393,7 +2420,7 @@ pub mod cmd {
23932420 args: &[Cow<str>],
23942421 _event: PromptEvent,
23952422 ) -> anyhow::Result<()> {
2396− let (_, doc) = current!(cx.editor);
2423+ let doc = doc!(cx.editor);
23972424 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
23982425 let separator = args.first().unwrap_or(&default_sep);
23992426 yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
@@ -2412,7 +2439,7 @@ pub mod cmd {
24122439 args: &[Cow<str>],
24132440 _event: PromptEvent,
24142441 ) -> anyhow::Result<()> {
2415− let (_, doc) = current!(cx.editor);
2442+ let doc = doc!(cx.editor);
24162443 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
24172444 let separator = args.first().unwrap_or(&default_sep);
24182445 yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
@@ -2539,7 +2566,7 @@ pub mod cmd {
25392566 args: &[Cow<str>],
25402567 _event: PromptEvent,
25412568 ) -> anyhow::Result<()> {
2542− let (_, doc) = current!(cx.editor);
2569+ let doc = doc_mut!(cx.editor);
25432570 if let Some(label) = args.first() {
25442571 doc.set_encoding(label)
25452572 } else {
@@ -2637,6 +2664,86 @@ pub mod cmd {
26372664 let (view, doc) = current!(cx.editor);
26382665
26392666 view.ensure_cursor_in_view(doc, line);
2667+ Ok(())
2668+ }
2669+
2670+ fn setting(
2671+ cx: &mut compositor::Context,
2672+ args: &[Cow<str>],
2673+ _event: PromptEvent,
2674+ ) -> anyhow::Result<()> {
2675+ let runtime_config = &mut cx.editor.config;
2676+
2677+ if args.len() != 2 {
2678+ anyhow::bail!("Bad arguments. Usage: `:set key field`");
2679+ }
2680+
2681+ let (key, arg) = (&args[0].to_lowercase(), &args[1]);
2682+
2683+ match key.as_ref() {
2684+ "scrolloff" => runtime_config.scrolloff = arg.parse()?,
2685+ "scroll-lines" => runtime_config.scroll_lines = arg.parse()?,
2686+ "mouse" => runtime_config.mouse = arg.parse()?,
2687+ "line-number" => runtime_config.line_number = arg.parse()?,
2688+ "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?,
2689+ "smart-case" => runtime_config.smart_case = arg.parse()?,
2690+ "auto-pairs" => runtime_config.auto_pairs = arg.parse()?,
2691+ "auto-completion" => runtime_config.auto_completion = arg.parse()?,
2692+ "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?,
2693+ "auto-info" => runtime_config.auto_info = arg.parse()?,
2694+ "true-color" => runtime_config.true_color = arg.parse()?,
2695+ _ => anyhow::bail!("Unknown key `{}`.", args[0]),
2696+ }
2697+
2698+ Ok(())
2699+ }
2700+
2701+ fn sort(
2702+ cx: &mut compositor::Context,
2703+ args: &[Cow<str>],
2704+ _event: PromptEvent,
2705+ ) -> anyhow::Result<()> {
2706+ sort_impl(cx, args, false)
2707+ }
2708+
2709+ fn sort_reverse(
2710+ cx: &mut compositor::Context,
2711+ args: &[Cow<str>],
2712+ _event: PromptEvent,
2713+ ) -> anyhow::Result<()> {
2714+ sort_impl(cx, args, true)
2715+ }
2716+
2717+ fn sort_impl(
2718+ cx: &mut compositor::Context,
2719+ _args: &[Cow<str>],
2720+ reverse: bool,
2721+ ) -> anyhow::Result<()> {
2722+ let (view, doc) = current!(cx.editor);
2723+ let text = doc.text().slice(..);
2724+
2725+ let selection = doc.selection(view.id);
2726+
2727+ let mut fragments: Vec<_> = selection
2728+ .fragments(text)
2729+ .map(|fragment| Tendril::from_slice(&fragment))
2730+ .collect();
2731+
2732+ fragments.sort_by(match reverse {
2733+ true => |a: &Tendril, b: &Tendril| b.cmp(a),
2734+ false => |a: &Tendril, b: &Tendril| a.cmp(b),
2735+ });
2736+
2737+ let transaction = Transaction::change(
2738+ doc.text(),
2739+ selection
2740+ .into_iter()
2741+ .zip(fragments)
2742+ .map(|(s, fragment)| (s.from(), s.to(), Some(fragment))),
2743+ );
2744+
2745+ doc.apply(&transaction, view.id);
2746+ doc.append_changes_to_history(view.id);
26402747
26412748 Ok(())
26422749 }
@@ -2664,18 +2771,18 @@ pub mod cmd {
26642771 completer: Some(completers::filename),
26652772 },
26662773 TypableCommand {
2667− name: "buffer-close",
2668− aliases: &["bc", "bclose"],
2669− doc: "Close the current buffer.",
2670− fun: buffer_close,
2671− completer: None, // FIXME: buffer completer
2774+ name: "buffer-close",
2775+ aliases: &["bc", "bclose"],
2776+ doc: "Close the current buffer.",
2777+ fun: buffer_close,
2778+ completer: None, // FIXME: buffer completer
26722779 },
26732780 TypableCommand {
2674− name: "buffer-close!",
2675− aliases: &["bc!", "bclose!"],
2676− doc: "Close the current buffer forcefully (ignoring unsaved changes).",
2677− fun: force_buffer_close,
2678− completer: None, // FIXME: buffer completer
2781+ name: "buffer-close!",
2782+ aliases: &["bc!", "bclose!"],
2783+ doc: "Close the current buffer forcefully (ignoring unsaved changes).",
2784+ fun: force_buffer_close,
2785+ completer: None, // FIXME: buffer completer
26792786 },
26802787 TypableCommand {
26812788 name: "write",
@@ -2782,6 +2889,13 @@ pub mod cmd {
27822889 fun: cquit,
27832890 completer: None,
27842891 },
2892+ TypableCommand {
2893+ name: "cquit!",
2894+ aliases: &["cq!"],
2895+ doc: "Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2).",
2896+ fun: force_cquit,
2897+ completer: None,
2898+ },
27852899 TypableCommand {
27862900 name: "theme",
27872901 aliases: &[],
@@ -2928,7 +3042,28 @@ pub mod cmd {
29283042 doc: "Go to line number.",
29293043 fun: goto_line_number,
29303044 completer: None,
2931− }
3045+ },
3046+ TypableCommand {
3047+ name: "set-option",
3048+ aliases: &["set"],
3049+ doc: "Set a config option at runtime",
3050+ fun: setting,
3051+ completer: Some(completers::setting),
3052+ },
3053+ TypableCommand {
3054+ name: "sort",
3055+ aliases: &[],
3056+ doc: "Sort ranges in selection.",
3057+ fun: sort,
3058+ completer: None,
3059+ },
3060+ TypableCommand {
3061+ name: "rsort",
3062+ aliases: &[],
3063+ doc: "Sort ranges in selection in reverse order.",
3064+ fun: sort_reverse,
3065+ completer: None,
3066+ },
29323067 ];
29333068
29343069 pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
@@ -2948,17 +3083,28 @@ fn command_mode(cx: &mut Context) {
29483083 ":".into(),
29493084 Some(':'),
29503085 |input: &str| {
3086+ static FUZZY_MATCHER: Lazy<fuzzy_matcher::skim::SkimMatcherV2> =
3087+ Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default);
3088+
29513089 // we use .this over split_whitespace() because we care about empty segments
29523090 let parts = input.split(' ').collect::<Vec<&str>>();
29533091
29543092 // simple heuristic: if there's no just one part, complete command name.
29553093 // if there's a space, per command completion kicks in.
29563094 if parts.len() <= 1 {
2957− let end = 0..;
2958− cmd::TYPABLE_COMMAND_LIST
3095+ let mut matches: Vec<_> = cmd::TYPABLE_COMMAND_LIST
29593096 .iter()
2960− .filter(|command| command.name.contains(input))
2961− .map(|command| (end.clone(), Cow::Borrowed(command.name)))
3097+ .filter_map(|command| {
3098+ FUZZY_MATCHER
3099+ .fuzzy_match(command.name, input)
3100+ .map(|score| (command.name, score))
3101+ })
3102+ .collect();
3103+
3104+ matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score));
3105+ matches
3106+ .into_iter()
3107+ .map(|(name, _)| (0.., name.into()))
29623108 .collect()
29633109 } else {
29643110 let part = parts.last().unwrap();
@@ -3002,7 +3148,16 @@ fn command_mode(cx: &mut Context) {
30023148
30033149 // Handle typable commands
30043150 if let Some(cmd) = cmd::TYPABLE_COMMAND_MAP.get(parts[0]) {
3005− let args = shellwords::shellwords(input);
3151+ let args = if cfg!(unix) {
3152+ shellwords::shellwords(input)
3153+ } else {
3154+ // Windows doesn't support POSIX, so fallback for now
3155+ parts
3156+ .into_iter()
3157+ .map(|part| part.into())
3158+ .collect::<Vec<_>>()
3159+ };
3160+
30063161 if let Err(e) = (cmd.fun)(cx, &args[1..], event) {
30073162 cx.editor.set_error(format!("{}", e));
30083163 }
@@ -3026,7 +3181,8 @@ fn command_mode(cx: &mut Context) {
30263181}
30273182
30283183fn file_picker(cx: &mut Context) {
3029− let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
3184+ // We don't specify language markers, root will be the root of the current git repo
3185+ let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./"));
30303186 let picker = ui::file_picker(root, &cx.editor.config);
30313187 cx.push_layer(Box::new(picker));
30323188}
@@ -3118,7 +3274,7 @@ fn symbol_picker(cx: &mut Context) {
31183274 nested_to_flat(list, file, child);
31193275 }
31203276 }
3121− let (_, doc) = current!(cx.editor);
3277+ let doc = doc!(cx.editor);
31223278
31233279 let language_server = match doc.language_server() {
31243280 Some(language_server) => language_server,
@@ -3139,7 +3295,7 @@ fn symbol_picker(cx: &mut Context) {
31393295 let symbols = match symbols {
31403296 lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
31413297 lsp::DocumentSymbolResponse::Nested(symbols) => {
3142− let (_view, doc) = current!(editor);
3298+ let doc = doc!(editor);
31433299 let mut flat_symbols = Vec::new();
31443300 for symbol in symbols {
31453301 nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
@@ -3181,17 +3337,15 @@ fn symbol_picker(cx: &mut Context) {
31813337}
31823338
31833339fn workspace_symbol_picker(cx: &mut Context) {
3184− let (_, doc) = current!(cx.editor);
3185−
3340+ let doc = doc!(cx.editor);
3341+ let current_path = doc.path().cloned();
31863342 let language_server = match doc.language_server() {
31873343 Some(language_server) => language_server,
31883344 None => return,
31893345 };
31903346 let offset_encoding = language_server.offset_encoding();
3191−
31923347 let future = language_server.workspace_symbols("".to_string());
31933348
3194− let current_path = doc_mut!(cx.editor).path().cloned();
31953349 cx.callback(
31963350 future,
31973351 move |_editor: &mut Editor,
@@ -3277,12 +3431,19 @@ pub fn code_action(cx: &mut Context) {
32773431 move |editor, code_action, _action| match code_action {
32783432 lsp::CodeActionOrCommand::Command(command) => {
32793433 log::debug!("code action command: {:?}", command);
3280− editor.set_error(String::from("Handling code action command is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
3434+ execute_lsp_command(editor, command.clone());
32813435 }
32823436 lsp::CodeActionOrCommand::CodeAction(code_action) => {
32833437 log::debug!("code action: {:?}", code_action);
32843438 if let Some(ref workspace_edit) = code_action.edit {
3285− apply_workspace_edit(editor, offset_encoding, workspace_edit)
3439+ log::debug!("edit: {:?}", workspace_edit);
3440+ apply_workspace_edit(editor, offset_encoding, workspace_edit);
3441+ }
3442+
3443+ // if code action provides both edit and command first the edit
3444+ // should be applied and then the command
3445+ if let Some(command) = &code_action.command {
3446+ execute_lsp_command(editor, command.clone());
32863447 }
32873448 }
32883449 },
@@ -3293,6 +3454,25 @@ pub fn code_action(cx: &mut Context) {
32933454 )
32943455}
32953456
3457+pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
3458+ let doc = doc!(editor);
3459+ let language_server = match doc.language_server() {
3460+ Some(language_server) => language_server,
3461+ None => return,
3462+ };
3463+
3464+ // the command is executed on the server and communicated back
3465+ // to the client asynchronously using workspace edits
3466+ let command_future = language_server.command(cmd);
3467+ tokio::spawn(async move {
3468+ let res = command_future.await;
3469+
3470+ if let Err(e) = res {
3471+ log::error!("execute LSP command: {}", e);
3472+ }
3473+ });
3474+}
3475+
32963476pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
32973477 use lsp::ResourceOp;
32983478 use std::fs;
@@ -3346,7 +3526,7 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
33463526 }
33473527}
33483528
3349−fn apply_workspace_edit(
3529+pub fn apply_workspace_edit(
33503530 editor: &mut Editor,
33513531 offset_encoding: OffsetEncoding,
33523532 workspace_edit: &lsp::WorkspaceEdit,
@@ -3537,22 +3717,22 @@ fn open(cx: &mut Context, open: Open) {
35373717 let mut offs = 0;
35383718
35393719 let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
3540− let line = range.cursor_line(text);
3720+ let cursor_line = range.cursor_line(text);
35413721
3542− let line = match open {
3722+ let new_line = match open {
35433723 // adjust position to the end of the line (next line - 1)
3544− Open::Below => line + 1,
3724+ Open::Below => cursor_line + 1,
35453725 // adjust position to the end of the previous line (current line - 1)
3546− Open::Above => line,
3726+ Open::Above => cursor_line,
35473727 };
35483728
35493729 // Index to insert newlines after, as well as the char width
35503730 // to use to compensate for those inserted newlines.
3551− let (line_end_index, line_end_offset_width) = if line == 0 {
3731+ let (line_end_index, line_end_offset_width) = if new_line == 0 {
35523732 (0, 0)
35533733 } else {
35543734 (
3555− line_end_char_index(&doc.text().slice(..), line.saturating_sub(1)),
3735+ line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
35563736 doc.line_ending.len_chars(),
35573737 )
35583738 };
@@ -3563,8 +3743,10 @@ fn open(cx: &mut Context, open: Open) {
35633743 doc.syntax(),
35643744 text,
35653745 line_end_index,
3746+ new_line.saturating_sub(1),
35663747 true,
3567− );
3748+ )
3749+ .unwrap_or_else(|| indent::indent_level_for_line(text.line(cursor_line), doc.tab_width()));
35683750 let indent = doc.indent_unit().repeat(indent_level);
35693751 let indent_len = indent.len();
35703752 let mut text = String::with_capacity(1 + indent_len);
@@ -3610,6 +3792,7 @@ fn normal_mode(cx: &mut Context) {
36103792
36113793 doc.mode = Mode::Normal;
36123794
3795+ try_restore_indent(doc, view.id);
36133796 doc.append_changes_to_history(view.id);
36143797
36153798 // if leaving append mode, move cursor back by 1
@@ -3627,6 +3810,40 @@ fn normal_mode(cx: &mut Context) {
36273810 }
36283811}
36293812
3813+fn try_restore_indent(doc: &mut Document, view_id: ViewId) {
3814+ use helix_core::chars::char_is_whitespace;
3815+ use helix_core::Operation;
3816+
3817+ fn inserted_a_new_blank_line(changes: &[Operation], pos: usize, line_end_pos: usize) -> bool {
3818+ if let [Operation::Retain(move_pos), Operation::Insert(ref inserted_str), Operation::Retain(_)] =
3819+ changes
3820+ {
3821+ move_pos + inserted_str.len32() as usize == pos
3822+ && inserted_str.starts_with('\n')
3823+ && inserted_str.chars().skip(1).all(char_is_whitespace)
3824+ && pos == line_end_pos // ensure no characters exists after current position
3825+ } else {
3826+ false
3827+ }
3828+ }
3829+
3830+ let doc_changes = doc.changes().changes();
3831+ let text = doc.text().slice(..);
3832+ let range = doc.selection(view_id).primary();
3833+ let pos = range.cursor(text);
3834+ let line_end_pos = line_end_char_index(&text, range.cursor_line(text));
3835+
3836+ if inserted_a_new_blank_line(doc_changes, pos, line_end_pos) {
3837+ // Removes tailing whitespaces.
3838+ let transaction =
3839+ Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
3840+ let line_start_pos = text.line_to_char(range.cursor_line(text));
3841+ (line_start_pos, pos, None)
3842+ });
3843+ doc.apply(&transaction, view_id);
3844+ }
3845+}
3846+
36303847// Store a jump on the jumplist.
36313848fn push_jump(editor: &mut Editor) {
36323849 let (view, doc) = current!(editor);
@@ -3994,27 +4211,21 @@ fn goto_pos(editor: &mut Editor, pos: usize) {
39944211}
39954212
39964213fn goto_first_diag(cx: &mut Context) {
3997− let editor = &mut cx.editor;
3998− let (_, doc) = current!(editor);
3999−
4214+ let doc = doc!(cx.editor);
40004215 let pos = match doc.diagnostics().first() {
40014216 Some(diag) => diag.range.start,
40024217 None => return,
40034218 };
4004−
4005− goto_pos(editor, pos);
4219+ goto_pos(cx.editor, pos);
40064220}
40074221
40084222fn goto_last_diag(cx: &mut Context) {
4009− let editor = &mut cx.editor;
4010− let (_, doc) = current!(editor);
4011−
4223+ let doc = doc!(cx.editor);
40124224 let pos = match doc.diagnostics().last() {
40134225 Some(diag) => diag.range.start,
40144226 None => return,
40154227 };
4016−
4017− goto_pos(editor, pos);
4228+ goto_pos(cx.editor, pos);
40184229}
40194230
40204231fn goto_next_diag(cx: &mut Context) {
@@ -4270,48 +4481,48 @@ pub mod insert {
42704481 };
42714482 let curr = contents.get_char(pos).unwrap_or(' ');
42724483
4273− // TODO: offset range.head by 1? when calculating?
4484+ let current_line = text.char_to_line(pos);
42744485 let indent_level = indent::suggested_indent_for_pos(
42754486 doc.language_config(),
42764487 doc.syntax(),
42774488 text,
4278− pos.saturating_sub(1),
4489+ pos,
4490+ current_line,
42794491 true,
4280− );
4281− let indent = doc.indent_unit().repeat(indent_level);
4282− let mut text = String::with_capacity(1 + indent.len());
4283− text.push_str(doc.line_ending.as_str());
4284− text.push_str(&indent);
4492+ )
4493+ .unwrap_or_else(|| {
4494+ indent::indent_level_for_line(text.line(current_line), doc.tab_width())
4495+ });
42854496
4286− let head = pos + offs + text.chars().count();
4497+ let indent = doc.indent_unit().repeat(indent_level);
4498+ let mut text = String::new();
4499+ // 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
4500+ let new_head_pos = if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
4501+ let inner_indent = doc.indent_unit().repeat(indent_level + 1);
4502+ text.reserve_exact(2 + indent.len() + inner_indent.len());
4503+ text.push_str(doc.line_ending.as_str());
4504+ text.push_str(&inner_indent);
4505+ let new_head_pos = pos + offs + text.chars().count();
4506+ text.push_str(doc.line_ending.as_str());
4507+ text.push_str(&indent);
4508+ new_head_pos
4509+ } else {
4510+ text.reserve_exact(1 + indent.len());
4511+ text.push_str(doc.line_ending.as_str());
4512+ text.push_str(&indent);
4513+ pos + offs + text.chars().count()
4514+ };
42874515
42884516 // TODO: range replace or extend
42894517 // range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
42904518 // can be used with cx.mode to do replace or extend on most changes
4291− ranges.push(Range::new(
4292− if range.is_empty() {
4293− head
4294− } else {
4295− range.anchor + offs
4296− },
4297− head,
4298− ));
4299−
4300− // if between a bracket pair
4301− if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
4302− // another newline, indent the end bracket one level less
4303− let indent = doc.indent_unit().repeat(indent_level.saturating_sub(1));
4304− text.push_str(doc.line_ending.as_str());
4305− text.push_str(&indent);
4306− }
4307−
4519+ ranges.push(Range::new(new_head_pos, new_head_pos));
43084520 offs += text.chars().count();
43094521
43104522 (pos, pos, Some(text.into()))
43114523 });
43124524
43134525 transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
4314− //
43154526
43164527 doc.apply(&transaction, view.id);
43174528 }
@@ -5079,7 +5290,7 @@ pub fn completion(cx: &mut Context) {
50795290 move |editor: &mut Editor,
50805291 compositor: &mut Compositor,
50815292 response: Option<lsp::CompletionResponse>| {
5082− let (_, doc) = current!(editor);
5293+ let doc = doc!(editor);
50835294 if doc.mode() != Mode::Insert {
50845295 // we're not in insert mode anymore
50855296 return;
@@ -5257,6 +5468,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
52575468 doc.apply(&transaction, view.id);
52585469 doc.append_changes_to_history(view.id);
52595470}
5471+
52605472fn rotate_selection_contents_forward(cx: &mut Context) {
52615473 rotate_selection_contents(cx, Direction::Forward)
52625474}
@@ -5272,7 +5484,39 @@ fn expand_selection(cx: &mut Context) {
52725484
52735485 if let Some(syntax) = doc.syntax() {
52745486 let text = doc.text().slice(..);
5275− let selection = object::expand_selection(syntax, text, doc.selection(view.id));
5487+
5488+ let current_selection = doc.selection(view.id);
5489+
5490+ // save current selection so it can be restored using shrink_selection
5491+ view.object_selections.push(current_selection.clone());
5492+
5493+ let selection = object::expand_selection(syntax, text, current_selection);
5494+ doc.set_selection(view.id, selection);
5495+ }
5496+ };
5497+ motion(cx.editor);
5498+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
5499+}
5500+
5501+fn shrink_selection(cx: &mut Context) {
5502+ let motion = |editor: &mut Editor| {
5503+ let (view, doc) = current!(editor);
5504+ let current_selection = doc.selection(view.id);
5505+ // try to restore previous selection
5506+ if let Some(prev_selection) = view.object_selections.pop() {
5507+ if current_selection.contains(&prev_selection) {
5508+ // allow shrinking the selection only if current selection contains the previous object selection
5509+ doc.set_selection(view.id, prev_selection);
5510+ return;
5511+ } else {
5512+ // clear existing selection as they can't be shrinked to anyway
5513+ view.object_selections.clear();
5514+ }
5515+ }
5516+ // if not previous selection, shrink to first child
5517+ if let Some(syntax) = doc.syntax() {
5518+ let text = doc.text().slice(..);
5519+ let selection = object::shrink_selection(syntax, text, current_selection);
52765520 doc.set_selection(view.id, selection);
52775521 }
52785522 };
@@ -5920,42 +6164,42 @@ fn record_macro(cx: &mut Context) {
59206164 keys.pop();
59216165 let s = keys
59226166 .into_iter()
5923− .map(|key| format!("{}", key))
5924− .collect::<Vec<_>>()
5925− .join(" ");
6167+ .map(|key| {
6168+ let s = key.to_string();
6169+ if s.chars().count() == 1 {
6170+ s
6171+ } else {
6172+ format!("<{}>", s)
6173+ }
6174+ })
6175+ .collect::<String>();
59266176 cx.editor.registers.get_mut(reg).write(vec![s]);
59276177 cx.editor
5928− .set_status(format!("Recorded to register {}", reg));
6178+ .set_status(format!("Recorded to register [{}]", reg));
59296179 } else {
59306180 let reg = cx.register.take().unwrap_or('@');
59316181 cx.editor.macro_recording = Some((reg, Vec::new()));
59326182 cx.editor
5933− .set_status(format!("Recording to register {}", reg));
6183+ .set_status(format!("Recording to register [{}]", reg));
59346184 }
59356185}
59366186
5937−fn play_macro(cx: &mut Context) {
6187+fn replay_macro(cx: &mut Context) {
59386188 let reg = cx.register.unwrap_or('@');
5939− let keys = match cx
5940− .editor
5941− .registers
5942− .get(reg)
5943− .and_then(|reg| reg.read().get(0))
5944− .context("Register empty")
5945− .and_then(|s| {
5946− s.split_whitespace()
5947− .map(str::parse::<KeyEvent>)
5948− .collect::<Result<Vec<_>, _>>()
5949− .context("Failed to parse macro")
5950− }) {
5951− Ok(keys) => keys,
5952− Err(e) => {
5953− cx.editor.set_error(format!("{}", e));
5954− return;
6189+ let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
6190+ match helix_view::input::parse_macro(keys_str) {
6191+ Ok(keys) => keys,
6192+ Err(err) => {
6193+ cx.editor.set_error(format!("Invalid macro: {}", err));
6194+ return;
6195+ }
59556196 }
6197+ } else {
6198+ cx.editor.set_error(format!("Register [{}] empty", reg));
6199+ return;
59566200 };
5957− let count = cx.count();
59586201
6202+ let count = cx.count();
59596203 cx.callback = Some(Box::new(
59606204 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
59616205 for _ in 0..count {
helix-term/src/keymap.rs+6−2
@@ -569,11 +569,13 @@ impl Default for Keymaps {
569569 "d" => goto_prev_diag,
570570 "D" => goto_first_diag,
571571 "space" => add_newline_above,
572+ "o" => shrink_selection,
572573 },
573574 "]" => { "Right bracket"
574575 "d" => goto_next_diag,
575576 "D" => goto_last_diag,
576577 "space" => add_newline_below,
578+ "o" => expand_selection,
577579 },
578580
579581 "/" => search,
@@ -593,8 +595,8 @@ impl Default for Keymaps {
593595 // paste_all
594596 "P" => paste_before,
595597
596− "q" => record_macro,
597− "Q" => play_macro,
598+ "Q" => record_macro,
599+ "q" => replay_macro,
598600
599601 ">" => indent,
600602 "<" => unindent,
@@ -617,6 +619,8 @@ impl Default for Keymaps {
617619 "A-(" => rotate_selection_contents_backward,
618620 "A-)" => rotate_selection_contents_forward,
619621
622+ "A-:" => ensure_selections_forward,
623+
620624 "esc" => normal_mode,
621625 "C-b" | "pageup" => page_up,
622626 "C-f" | "pagedown" => page_down,
helix-term/src/ui/completion.rs+38−2
@@ -154,8 +154,19 @@ impl Completion {
154154 );
155155 doc.apply(&transaction, view.id);
156156
157− if let Some(additional_edits) = &item.additional_text_edits {
158− // gopls uses this to add extra imports
157+ // apply additional edits, mostly used to auto import unqualified types
158+ let resolved_additional_text_edits = if item.additional_text_edits.is_some() {
159+ None
160+ } else {
161+ Completion::resolve_completion_item(doc, item.clone())
162+ .and_then(|item| item.additional_text_edits)
163+ };
164+
165+ if let Some(additional_edits) = item
166+ .additional_text_edits
167+ .as_ref()
168+ .or_else(|| resolved_additional_text_edits.as_ref())
169+ {
159170 if !additional_edits.is_empty() {
160171 let transaction = util::generate_transaction_from_edits(
161172 doc.text(),
@@ -181,6 +192,31 @@ impl Completion {
181192 completion
182193 }
183194
195+ fn resolve_completion_item(
196+ doc: &Document,
197+ completion_item: lsp::CompletionItem,
198+ ) -> Option<CompletionItem> {
199+ let language_server = doc.language_server()?;
200+ let completion_resolve_provider = language_server
201+ .capabilities()
202+ .completion_provider
203+ .as_ref()?
204+ .resolve_provider;
205+ if completion_resolve_provider != Some(true) {
206+ return None;
207+ }
208+
209+ let future = language_server.resolve_completion_item(completion_item);
210+ let response = helix_lsp::block_on(future);
211+ match response {
212+ Ok(completion_item) => Some(completion_item),
213+ Err(err) => {
214+ log::error!("execute LSP command: {}", err);
215+ None
216+ }
217+ }
218+ }
219+
184220 pub fn recompute_filter(&mut self, editor: &Editor) {
185221 // recompute menu based on matches
186222 let menu = self.popup.contents_mut();
helix-term/src/ui/editor.rs+61−38
@@ -7,7 +7,7 @@ use crate::{
77};
88
99use helix_core::{
10− coords_at_pos,
10+ coords_at_pos, encoding,
1111 graphemes::{ensure_grapheme_boundary_next, next_grapheme_boundary, prev_grapheme_boundary},
1212 movement::Direction,
1313 syntax::{self, HighlightEvent},
@@ -566,21 +566,6 @@ impl EditorView {
566566 }
567567 surface.set_string(viewport.x + 5, viewport.y, progress, base_style);
568568
569− let rel_path = doc.relative_path();
570− let path = rel_path
571− .as_ref()
572− .map(|p| p.to_string_lossy())
573− .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
574−
575− let title = format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" });
576− surface.set_stringn(
577− viewport.x + 8,
578− viewport.y,
579− title,
580− viewport.width.saturating_sub(6) as usize,
581− base_style,
582− );
583−
584569 //-------------------------------
585570 // Right side of the status line.
586571 //-------------------------------
@@ -654,6 +639,13 @@ impl EditorView {
654639 base_style,
655640 ));
656641
642+ let enc = doc.encoding();
643+ if enc != encoding::UTF_8 {
644+ right_side_text
645+ .0
646+ .push(Span::styled(format!(" {} ", enc.name()), base_style));
647+ }
648+
657649 // Render to the statusline.
658650 surface.set_spans(
659651 viewport.x
@@ -664,6 +656,31 @@ impl EditorView {
664656 &right_side_text,
665657 right_side_text.width() as u16,
666658 );
659+
660+ //-------------------------------
661+ // Middle / File path / Title
662+ //-------------------------------
663+ let title = {
664+ let rel_path = doc.relative_path();
665+ let path = rel_path
666+ .as_ref()
667+ .map(|p| p.to_string_lossy())
668+ .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
669+ format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" })
670+ };
671+
672+ surface.set_string_truncated(
673+ viewport.x + 8, // 8: 1 space + 3 char mode string + 1 space + 1 spinner + 1 space
674+ viewport.y,
675+ title,
676+ viewport
677+ .width
678+ .saturating_sub(6)
679+ .saturating_sub(right_side_text.width() as u16 + 1) as usize, // "+ 1": a space between the title and the selection info
680+ base_style,
681+ true,
682+ true,
683+ );
667684 }
668685
669686 /// Handle events by looking them up in `self.keymaps`. Returns None
@@ -782,8 +799,9 @@ impl EditorView {
782799
783800 pub fn clear_completion(&mut self, editor: &mut Editor) {
784801 self.completion = None;
802+
785803 // Clear any savepoints
786− let (_, doc) = current!(editor);
804+ let doc = doc_mut!(editor);
787805 doc.savepoint = None;
788806 editor.clear_idle_timer(); // don't retrigger
789807 }
@@ -941,14 +959,18 @@ impl EditorView {
941959}
942960
943961impl Component for EditorView {
944− fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
945− let mut cxt = commands::Context {
946− editor: cx.editor,
962+ fn handle_event(
963+ &mut self,
964+ event: Event,
965+ context: &mut crate::compositor::Context,
966+ ) -> EventResult {
967+ let mut cx = commands::Context {
968+ editor: context.editor,
947969 count: None,
948970 register: None,
949971 callback: None,
950972 on_next_key_callback: None,
951− jobs: cx.jobs,
973+ jobs: context.jobs,
952974 };
953975
954976 match event {
@@ -958,18 +980,19 @@ impl Component for EditorView {
958980 EventResult::Consumed(None)
959981 }
960982 Event::Key(key) => {
961− cxt.editor.reset_idle_timer();
983+ cx.editor.reset_idle_timer();
962984 let mut key = KeyEvent::from(key);
963985 canonicalize_key(&mut key);
986+
964987 // clear status
965− cxt.editor.status_msg = None;
988+ cx.editor.status_msg = None;
966989
967− let (_, doc) = current!(cxt.editor);
990+ let doc = doc!(cx.editor);
968991 let mode = doc.mode();
969992
970993 if let Some(on_next_key) = self.on_next_key.take() {
971994 // if there's a command waiting input, do that first
972− on_next_key(&mut cxt, key);
995+ on_next_key(&mut cx, key);
973996 } else {
974997 match mode {
975998 Mode::Insert => {
@@ -981,8 +1004,8 @@ impl Component for EditorView {
9811004 if let Some(completion) = &mut self.completion {
9821005 // use a fake context here
9831006 let mut cx = Context {
984− editor: cxt.editor,
985− jobs: cxt.jobs,
1007+ editor: cx.editor,
1008+ jobs: cx.jobs,
9861009 scroll: None,
9871010 };
9881011 let res = completion.handle_event(event, &mut cx);
@@ -992,40 +1015,40 @@ impl Component for EditorView {
9921015
9931016 if callback.is_some() {
9941017 // assume close_fn
995− self.clear_completion(cxt.editor);
1018+ self.clear_completion(cx.editor);
9961019 }
9971020 }
9981021 }
9991022
10001023 // if completion didn't take the event, we pass it onto commands
10011024 if !consumed {
1002− self.insert_mode(&mut cxt, key);
1025+ self.insert_mode(&mut cx, key);
10031026
10041027 // lastly we recalculate completion
10051028 if let Some(completion) = &mut self.completion {
1006− completion.update(&mut cxt);
1029+ completion.update(&mut cx);
10071030 if completion.is_empty() {
1008− self.clear_completion(cxt.editor);
1031+ self.clear_completion(cx.editor);
10091032 }
10101033 }
10111034 }
10121035 }
1013− mode => self.command_mode(mode, &mut cxt, key),
1036+ mode => self.command_mode(mode, &mut cx, key),
10141037 }
10151038 }
10161039
1017− self.on_next_key = cxt.on_next_key_callback.take();
1040+ self.on_next_key = cx.on_next_key_callback.take();
10181041 // appease borrowck
1019− let callback = cxt.callback.take();
1042+ let callback = cx.callback.take();
10201043
10211044 // if the command consumed the last view, skip the render.
10221045 // on the next loop cycle the Application will then terminate.
1023− if cxt.editor.should_close() {
1046+ if cx.editor.should_close() {
10241047 return EventResult::Ignored;
10251048 }
10261049
1027− let (view, doc) = current!(cxt.editor);
1028− view.ensure_cursor_in_view(doc, cxt.editor.config.scrolloff);
1050+ let (view, doc) = current!(cx.editor);
1051+ view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff);
10291052
10301053 // mode transitions
10311054 match (mode, doc.mode()) {
@@ -1054,7 +1077,7 @@ impl Component for EditorView {
10541077 EventResult::Consumed(callback)
10551078 }
10561079
1057− Event::Mouse(event) => self.handle_mouse_event(event, &mut cxt),
1080+ Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
10581081 }
10591082 }
10601083
helix-term/src/ui/mod.rs+28−1
@@ -174,7 +174,9 @@ pub mod completers {
174174 use crate::ui::prompt::Completion;
175175 use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
176176 use fuzzy_matcher::FuzzyMatcher;
177+ use helix_view::editor::Config;
177178 use helix_view::theme;
179+ use once_cell::sync::Lazy;
178180 use std::borrow::Cow;
179181 use std::cmp::Reverse;
180182
@@ -208,6 +210,31 @@ pub mod completers {
208210 names
209211 }
210212
213+ pub fn setting(input: &str) -> Vec<Completion> {
214+ static KEYS: Lazy<Vec<String>> = Lazy::new(|| {
215+ serde_json::to_value(Config::default())
216+ .unwrap()
217+ .as_object()
218+ .unwrap()
219+ .keys()
220+ .cloned()
221+ .collect()
222+ });
223+
224+ let matcher = Matcher::default();
225+
226+ let mut matches: Vec<_> = KEYS
227+ .iter()
228+ .filter_map(|name| matcher.fuzzy_match(name, input).map(|score| (name, score)))
229+ .collect();
230+
231+ matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
232+ matches
233+ .into_iter()
234+ .map(|(name, _)| ((0..), name.into()))
235+ .collect()
236+ }
237+
211238 pub fn filename(input: &str) -> Vec<Completion> {
212239 filename_impl(input, |entry| {
213240 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
@@ -256,7 +283,7 @@ pub mod completers {
256283 let is_tilde = input.starts_with('~') && input.len() == 1;
257284 let path = helix_core::path::expand_tilde(Path::new(input));
258285
259− let (dir, file_name) = if input.ends_with('/') {
286+ let (dir, file_name) = if input.ends_with(std::path::MAIN_SEPARATOR) {
260287 (path, None)
261288 } else {
262289 let file_name = path
helix-term/src/ui/prompt.rs+2−2
@@ -127,7 +127,7 @@ impl Prompt {
127127 let mut char_position = char_indices
128128 .iter()
129129 .position(|(idx, _)| *idx == self.cursor)
130− .unwrap_or_else(|| char_indices.len());
130+ .unwrap_or(char_indices.len());
131131
132132 for _ in 0..rep {
133133 // Skip any non-whitespace characters
@@ -473,7 +473,7 @@ impl Component for Prompt {
473473 }
474474 }
475475 key!(Enter) => {
476− if self.selection.is_some() && self.line.ends_with('/') {
476+ if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) {
477477 self.completion = (self.completion_fn)(&self.line);
478478 self.exit_selection();
479479 } else {
helix-tui/Cargo.toml+3−3
@@ -1,6 +1,6 @@
11[package]
22name = "helix-tui"
3−version = "0.5.0"
3+version = "0.6.0"
44authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
55description = """
66A library to build rich terminal user interfaces or dashboards
@@ -21,5 +21,5 @@ cassowary = "0.3"
2121unicode-segmentation = "1.8"
2222crossterm = { version = "0.22", optional = true }
2323serde = { version = "1", "optional" = true, features = ["derive"]}
24−helix-view = { version = "0.5", path = "../helix-view", features = ["term"] }
25−helix-core = { version = "0.5", path = "../helix-core" }
24+helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
25+helix-core = { version = "0.6", path = "../helix-core" }
helix-tui/README.md+1−1
@@ -2,5 +2,5 @@
22
33This library is a fork of the great library
44[tui-rs](https://github.com/fdehau/tui-rs/). We've mainly relied on the double
5−buffer implementation and render diffing, side-stepping it's widget and
5+buffer implementation and render diffing, side-stepping its widget and
66layouting.
helix-view/Cargo.toml+4−5
@@ -1,6 +1,6 @@
11[package]
22name = "helix-view"
3−version = "0.5.0"
3+version = "0.6.0"
44authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
55edition = "2021"
66license = "MPL-2.0"
@@ -16,12 +16,12 @@ term = ["crossterm"]
1616[dependencies]
1717bitflags = "1.3"
1818anyhow = "1"
19−helix-core = { version = "0.5", path = "../helix-core" }
20−helix-lsp = { version = "0.5", path = "../helix-lsp"}
19+helix-core = { version = "0.6", path = "../helix-core" }
20+helix-lsp = { version = "0.6", path = "../helix-lsp"}
2121crossterm = { version = "0.22", optional = true }
2222
2323# Conversion traits
24−once_cell = "1.8"
24+once_cell = "1.9"
2525url = "2"
2626
2727tokio = { 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
2929
3030slotmap = "1"
3131
32−encoding_rs = "0.8"
3332chardetng = "0.1"
3433
3534serde = { version = "1.0", features = ["derive"] }
helix-view/src/document.rs+31−16
@@ -1,5 +1,6 @@
11use anyhow::{anyhow, Context, Error};
22use serde::de::{self, Deserialize, Deserializer};
3+use serde::Serialize;
34use std::cell::Cell;
45use std::collections::HashMap;
56use std::fmt::Display;
@@ -9,6 +10,7 @@ use std::str::FromStr;
910use std::sync::Arc;
1011
1112use helix_core::{
13+ encoding,
1214 history::History,
1315 indent::{auto_detect_indent_style, IndentStyle},
1416 line_ending::auto_detect_line_ending,
@@ -68,13 +70,22 @@ impl<'de> Deserialize<'de> for Mode {
6870 }
6971}
7072
73+impl Serialize for Mode {
74+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
75+ where
76+ S: serde::Serializer,
77+ {
78+ serializer.collect_str(self)
79+ }
80+}
81+
7182pub struct Document {
7283 pub(crate) id: DocumentId,
7384 text: Rope,
7485 pub(crate) selections: HashMap<ViewId, Selection>,
7586
7687 path: Option<PathBuf>,
77− encoding: &'static encoding_rs::Encoding,
88+ encoding: &'static encoding::Encoding,
7889
7990 /// Current editing mode.
8091 pub mode: Mode,
@@ -143,8 +154,8 @@ impl fmt::Debug for Document {
143154/// be used to override encoding auto-detection.
144155pub fn from_reader<R: std::io::Read + ?Sized>(
145156 reader: &mut R,
146− encoding: Option<&'static encoding_rs::Encoding>,
147−) -> Result<(Rope, &'static encoding_rs::Encoding), Error> {
157+ encoding: Option<&'static encoding::Encoding>,
158+) -> Result<(Rope, &'static encoding::Encoding), Error> {
148159 // These two buffers are 8192 bytes in size each and are used as
149160 // intermediaries during the decoding process. Text read into `buf`
150161 // 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>(
212223 total_read += read;
213224 total_written += written;
214225 match result {
215− encoding_rs::CoderResult::InputEmpty => {
226+ encoding::CoderResult::InputEmpty => {
216227 debug_assert_eq!(slice.len(), total_read);
217228 break;
218229 }
219− encoding_rs::CoderResult::OutputFull => {
230+ encoding::CoderResult::OutputFull => {
220231 debug_assert!(slice.len() > total_read);
221232 builder.append(&buf_str[..total_written]);
222233 total_written = 0;
@@ -251,7 +262,7 @@ pub fn from_reader<R: std::io::Read + ?Sized>(
251262/// replacement characters may appear in the encoded text.
252263pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
253264 writer: &'a mut W,
254− encoding: &'static encoding_rs::Encoding,
265+ encoding: &'static encoding::Encoding,
255266 rope: &'a Rope,
256267) -> Result<(), Error> {
257268 // 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>(
286297 total_read += read;
287298 total_written += written;
288299 match result {
289− encoding_rs::CoderResult::InputEmpty => {
300+ encoding::CoderResult::InputEmpty => {
290301 debug_assert_eq!(chunk.len(), total_read);
291302 debug_assert!(buf.len() >= total_written);
292303 break;
293304 }
294− encoding_rs::CoderResult::OutputFull => {
305+ encoding::CoderResult::OutputFull => {
295306 debug_assert!(chunk.len() > total_read);
296307 writer.write_all(&buf[..total_written]).await?;
297308 total_written = 0;
@@ -322,8 +333,8 @@ use helix_lsp::lsp;
322333use url::Url;
323334
324335impl Document {
325− pub fn from(text: Rope, encoding: Option<&'static encoding_rs::Encoding>) -> Self {
326− let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
336+ pub fn from(text: Rope, encoding: Option<&'static encoding::Encoding>) -> Self {
337+ let encoding = encoding.unwrap_or(encoding::UTF_8);
327338 let changes = ChangeSet::new(&text);
328339 let old_state = None;
329340
@@ -356,7 +367,7 @@ impl Document {
356367 /// overwritten with the `encoding` parameter.
357368 pub fn open(
358369 path: &Path,
359− encoding: Option<&'static encoding_rs::Encoding>,
370+ encoding: Option<&'static encoding::Encoding>,
360371 theme: Option<&Theme>,
361372 config_loader: Option<&syntax::Loader>,
362373 ) -> Result<Self, Error> {
@@ -366,7 +377,7 @@ impl Document {
366377 std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
367378 from_reader(&mut file, encoding)?
368379 } else {
369− let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
380+ let encoding = encoding.unwrap_or(encoding::UTF_8);
370381 (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
371382 };
372383
@@ -548,7 +559,7 @@ impl Document {
548559
549560 /// Sets the [`Document`]'s encoding with the encoding correspondent to `label`.
550561 pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
551− match encoding_rs::Encoding::for_label(label.as_bytes()) {
562+ match encoding::Encoding::for_label(label.as_bytes()) {
552563 Some(encoding) => self.encoding = encoding,
553564 None => return Err(anyhow::anyhow!("unknown encoding")),
554565 }
@@ -556,7 +567,7 @@ impl Document {
556567 }
557568
558569 /// Returns the [`Document`]'s current encoding.
559− pub fn encoding(&self) -> &'static encoding_rs::Encoding {
570+ pub fn encoding(&self) -> &'static encoding::Encoding {
560571 self.encoding
561572 }
562573
@@ -889,6 +900,10 @@ impl Document {
889900 self.indent_style.as_str()
890901 }
891902
903+ pub fn changes(&self) -> &ChangeSet {
904+ &self.changes
905+ }
906+
892907 #[inline]
893908 /// File path on disk.
894909 pub fn path(&self) -> Option<&PathBuf> {
@@ -1119,7 +1134,7 @@ mod test {
11191134
11201135 macro_rules! test_decode {
11211136 ($label:expr, $label_override:expr) => {
1122− let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap();
1137+ let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
11231138 let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
11241139 let path = base_path.join(format!("{}_in.txt", $label));
11251140 let ref_path = base_path.join(format!("{}_in_ref.txt", $label));
@@ -1138,7 +1153,7 @@ mod test {
11381153
11391154 macro_rules! test_encode {
11401155 ($label:expr, $label_override:expr) => {
1141− let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap();
1156+ let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
11421157 let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
11431158 let path = base_path.join(format!("{}_out.txt", $label));
11441159 let ref_path = base_path.join(format!("{}_out_ref.txt", $label));
helix-view/src/editor.rs+30−4
@@ -27,7 +27,7 @@ pub use helix_core::register::Registers;
2727use helix_core::syntax;
2828use helix_core::{Position, Selection};
2929
30−use serde::{Deserialize, Deserializer};
30+use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize};
3131
3232fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
3333where
@@ -37,7 +37,7 @@ where
3737 Ok(Duration::from_millis(millis))
3838}
3939
40−#[derive(Debug, Clone, PartialEq, Deserialize)]
40+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
4141#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
4242pub struct FilePickerConfig {
4343 /// IgnoreOptions
@@ -77,7 +77,7 @@ impl Default for FilePickerConfig {
7777 }
7878}
7979
80−#[derive(Debug, Clone, PartialEq, Deserialize)]
80+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8181#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
8282pub struct Config {
8383 /// 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 {
137137 }
138138}
139139
140+impl Serialize for CursorShapeConfig {
141+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
142+ where
143+ S: serde::Serializer,
144+ {
145+ let mut map = serializer.serialize_map(Some(self.len()))?;
146+ let modes = [Mode::Normal, Mode::Select, Mode::Insert];
147+ for mode in modes {
148+ map.serialize_entry(&mode, &self.from_mode(mode))?;
149+ }
150+ map.end()
151+ }
152+}
153+
140154impl std::ops::Deref for CursorShapeConfig {
141155 type Target = [CursorKind; 3];
142156
@@ -151,7 +165,7 @@ impl Default for CursorShapeConfig {
151165 }
152166}
153167
154−#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
168+#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
155169#[serde(rename_all = "kebab-case")]
156170pub enum LineNumber {
157171 /// Show absolute line number
@@ -160,6 +174,18 @@ pub enum LineNumber {
160174 Relative,
161175}
162176
177+impl std::str::FromStr for LineNumber {
178+ type Err = anyhow::Error;
179+
180+ fn from_str(s: &str) -> Result<Self, Self::Err> {
181+ match s.to_lowercase().as_str() {
182+ "absolute" | "abs" => Ok(Self::Absolute),
183+ "relative" | "rel" => Ok(Self::Relative),
184+ _ => anyhow::bail!("Line number can only be `absolute` or `relative`."),
185+ }
186+ }
187+}
188+
163189impl Default for Config {
164190 fn default() -> Self {
165191 Self {
helix-view/src/graphics.rs+2−2
@@ -1,11 +1,11 @@
11use bitflags::bitflags;
2−use serde::Deserialize;
2+use serde::{Deserialize, Serialize};
33use std::{
44 cmp::{max, min},
55 str::FromStr,
66};
77
8−#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
8+#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
99#[serde(rename_all = "lowercase")]
1010/// UNSTABLE
1111pub enum CursorKind {
helix-view/src/input.rs+153
@@ -254,6 +254,43 @@ impl From<KeyEvent> for crossterm::event::KeyEvent {
254254 }
255255}
256256
257+pub fn parse_macro(keys_str: &str) -> anyhow::Result<Vec<KeyEvent>> {
258+ use anyhow::Context;
259+ let mut keys_res: anyhow::Result<_> = Ok(Vec::new());
260+ let mut i = 0;
261+ while let Ok(keys) = &mut keys_res {
262+ if i >= keys_str.len() {
263+ break;
264+ }
265+ if !keys_str.is_char_boundary(i) {
266+ i += 1;
267+ continue;
268+ }
269+
270+ let s = &keys_str[i..];
271+ let mut end_i = 1;
272+ while !s.is_char_boundary(end_i) {
273+ end_i += 1;
274+ }
275+ let c = &s[..end_i];
276+ if c == ">" {
277+ keys_res = Err(anyhow!("Unmatched '>'"));
278+ } else if c != "<" {
279+ keys.push(c);
280+ i += end_i;
281+ } else {
282+ match s.find('>').context("'>' expected") {
283+ Ok(end_i) => {
284+ keys.push(&s[1..end_i]);
285+ i += end_i + 1;
286+ }
287+ Err(err) => keys_res = Err(err),
288+ }
289+ }
290+ }
291+ keys_res.and_then(|keys| keys.into_iter().map(str::parse).collect())
292+}
293+
257294#[cfg(test)]
258295mod test {
259296 use super::*;
@@ -339,4 +376,120 @@ mod test {
339376 assert!(str::parse::<KeyEvent>("123").is_err());
340377 assert!(str::parse::<KeyEvent>("S--").is_err());
341378 }
379+
380+ #[test]
381+ fn parsing_valid_macros() {
382+ assert_eq!(
383+ parse_macro("xdo").ok(),
384+ Some(vec![
385+ KeyEvent {
386+ code: KeyCode::Char('x'),
387+ modifiers: KeyModifiers::NONE,
388+ },
389+ KeyEvent {
390+ code: KeyCode::Char('d'),
391+ modifiers: KeyModifiers::NONE,
392+ },
393+ KeyEvent {
394+ code: KeyCode::Char('o'),
395+ modifiers: KeyModifiers::NONE,
396+ },
397+ ]),
398+ );
399+
400+ assert_eq!(
401+ parse_macro("<C-w>v<C-w>h<C-o>xx<A-s>").ok(),
402+ Some(vec![
403+ KeyEvent {
404+ code: KeyCode::Char('w'),
405+ modifiers: KeyModifiers::CONTROL,
406+ },
407+ KeyEvent {
408+ code: KeyCode::Char('v'),
409+ modifiers: KeyModifiers::NONE,
410+ },
411+ KeyEvent {
412+ code: KeyCode::Char('w'),
413+ modifiers: KeyModifiers::CONTROL,
414+ },
415+ KeyEvent {
416+ code: KeyCode::Char('h'),
417+ modifiers: KeyModifiers::NONE,
418+ },
419+ KeyEvent {
420+ code: KeyCode::Char('o'),
421+ modifiers: KeyModifiers::CONTROL,
422+ },
423+ KeyEvent {
424+ code: KeyCode::Char('x'),
425+ modifiers: KeyModifiers::NONE,
426+ },
427+ KeyEvent {
428+ code: KeyCode::Char('x'),
429+ modifiers: KeyModifiers::NONE,
430+ },
431+ KeyEvent {
432+ code: KeyCode::Char('s'),
433+ modifiers: KeyModifiers::ALT,
434+ },
435+ ])
436+ );
437+
438+ assert_eq!(
439+ parse_macro(":o foo.bar<ret>").ok(),
440+ Some(vec![
441+ KeyEvent {
442+ code: KeyCode::Char(':'),
443+ modifiers: KeyModifiers::NONE,
444+ },
445+ KeyEvent {
446+ code: KeyCode::Char('o'),
447+ modifiers: KeyModifiers::NONE,
448+ },
449+ KeyEvent {
450+ code: KeyCode::Char(' '),
451+ modifiers: KeyModifiers::NONE,
452+ },
453+ KeyEvent {
454+ code: KeyCode::Char('f'),
455+ modifiers: KeyModifiers::NONE,
456+ },
457+ KeyEvent {
458+ code: KeyCode::Char('o'),
459+ modifiers: KeyModifiers::NONE,
460+ },
461+ KeyEvent {
462+ code: KeyCode::Char('o'),
463+ modifiers: KeyModifiers::NONE,
464+ },
465+ KeyEvent {
466+ code: KeyCode::Char('.'),
467+ modifiers: KeyModifiers::NONE,
468+ },
469+ KeyEvent {
470+ code: KeyCode::Char('b'),
471+ modifiers: KeyModifiers::NONE,
472+ },
473+ KeyEvent {
474+ code: KeyCode::Char('a'),
475+ modifiers: KeyModifiers::NONE,
476+ },
477+ KeyEvent {
478+ code: KeyCode::Char('r'),
479+ modifiers: KeyModifiers::NONE,
480+ },
481+ KeyEvent {
482+ code: KeyCode::Enter,
483+ modifiers: KeyModifiers::NONE,
484+ },
485+ ])
486+ );
487+ }
488+
489+ #[test]
490+ fn parsing_invalid_macros_fails() {
491+ assert!(parse_macro("abc<C-").is_err());
492+ assert!(parse_macro("abc>123").is_err());
493+ assert!(parse_macro("wd<foo>").is_err());
494+ }
342495}
helix-view/src/view.rs+3
@@ -80,6 +80,8 @@ pub struct View {
8080 // uses two docs because we want to be able to swap between the
8181 // two last modified docs which we need to manually keep track of
8282 pub last_modified_docs: [Option<DocumentId>; 2],
83+ /// used to store previous selections of tree-sitter objecs
84+ pub object_selections: Vec<Selection>,
8385}
8486
8587impl View {
@@ -92,6 +94,7 @@ impl View {
9294 jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel
9395 last_accessed_doc: None,
9496 last_modified_docs: [None, None],
97+ object_selections: Vec::new(),
9598 }
9699 }
97100
languages.toml+105−5
@@ -3,15 +3,11 @@ name = "rust"
33scope = "source.rust"
44injection-regex = "rust"
55file-types = ["rs"]
6−roots = []
6+roots = ["Cargo.toml", "Cargo.lock"]
77auto-format = true
88comment-token = "//"
99language-server = { command = "rust-analyzer" }
1010indent = { tab-width = 4, unit = " " }
11−[language.config]
12−cargo = { loadOutDirsFromCheck = true }
13−procMacro = { enable = false }
14−diagnostics = { disabled = ["unresolved-proc-macro"] }
1511
1612[[language]]
1713name = "toml"
@@ -45,6 +41,17 @@ comment-token = "#"
4541language-server = { command = "elixir-ls" }
4642indent = { tab-width = 2, unit = " " }
4743
44+[[language]]
45+name = "fish"
46+scope = "source.fish"
47+injection-regex = "fish"
48+file-types = ["fish"]
49+shebangs = ["fish"]
50+roots = []
51+comment-token = "#"
52+
53+indent = { tab-width = 4, unit = " " }
54+
4855[[language]]
4956name = "mint"
5057scope = "source.mint"
@@ -327,6 +334,7 @@ file-types = ["yml", "yaml"]
327334roots = []
328335comment-token = "#"
329336indent = { tab-width = 2, unit = " " }
337+injection-regex = "yml|yaml"
330338
331339# [[language]]
332340# name = "haskell"
@@ -379,6 +387,7 @@ roots = []
379387comment-token = "#"
380388indent = { tab-width = 2, unit = " " }
381389language-server = { command = "cmake-language-server" }
390+injection-regex = "cmake"
382391
383392[[language]]
384393name = "glsl"
@@ -387,6 +396,7 @@ file-types = ["glsl", "vert", "tesc", "tese", "geom", "frag", "comp" ]
387396roots = []
388397comment-token = "//"
389398indent = { tab-width = 4, unit = " " }
399+injection-regex = "glsl"
390400
391401[[language]]
392402name = "perl"
@@ -406,6 +416,13 @@ shebangs = ["racket"]
406416comment-token = ";"
407417language-server = { command = "racket", args = ["-l", "racket-langserver"] }
408418
419+[[language]]
420+name = "comment"
421+scope = "scope.comment"
422+roots = []
423+file-types = []
424+injection-regex = "comment"
425+
409426[[language]]
410427name = "wgsl"
411428scope = "source.wgsl"
@@ -421,6 +438,34 @@ roots = []
421438file-types = ["ll"]
422439comment-token = ";"
423440indent = { tab-width = 2, unit = " " }
441+injection-regex = "llvm"
442+
443+[[language]]
444+name = "llvm-mir"
445+scope = "source.llvm_mir"
446+roots = []
447+file-types = []
448+comment-token = ";"
449+indent = { tab-width = 2, unit = " " }
450+injection-regex = "mir"
451+
452+[[language]]
453+name = "llvm-mir-yaml"
454+tree-sitter-library = "yaml"
455+scope = "source.yaml"
456+roots = []
457+file-types = ["mir"]
458+comment-token = "#"
459+indent = { tab-width = 2, unit = " " }
460+
461+[[language]]
462+name = "tablegen"
463+scope = "source.tablegen"
464+roots = []
465+file-types = ["td"]
466+comment-token = "//"
467+indent = { tab-width = 2, unit = " " }
468+injection-regex = "tablegen"
424469
425470[[language]]
426471name = "markdown"
@@ -430,3 +475,58 @@ file-types = ["md"]
430475roots = []
431476
432477indent = { tab-width = 2, unit = " " }
478+
479+[[language]]
480+name = "dart"
481+scope = "source.dart"
482+file-types = ["dart"]
483+roots = ["pubspec.yaml"]
484+auto-format = true
485+comment-token = "//"
486+language-server = { command = "dart", args = ["language-server", "--client-id=helix"] }
487+indent = { tab-width = 2, unit = " " }
488+
489+[[language]]
490+name = "scala"
491+scope = "source.scala"
492+roots = ["build.sbt"]
493+file-types = ["scala", "sbt"]
494+comment-token = "//"
495+indent = { tab-width = 2, unit = " " }
496+language-server = { command = "metals" }
497+
498+[[language]]
499+name = "dockerfile"
500+scope = "source.dockerfile"
501+injection-regex = "docker|dockerfile"
502+roots = ["Dockerfile"]
503+file-types = ["Dockerfile", "dockerfile"]
504+comment-token = "#"
505+indent = { tab-width = 2, unit = " " }
506+language-server = { command = "docker-langserver", args = ["--stdio"] }
507+
508+[[language]]
509+name = "git-commit"
510+scope = "git.commitmsg"
511+roots = []
512+file-types = ["COMMIT_EDITMSG"]
513+comment-token = "#"
514+indent = { tab-width = 2, unit = " " }
515+
516+[[language]]
517+name = "git-diff"
518+scope = "source.diff"
519+roots = []
520+file-types = ["diff"]
521+injection-regex = "diff"
522+comment-token = "#"
523+indent = { tab-width = 2, unit = " " }
524+
525+[[language]]
526+name = "git-rebase"
527+scope = "source.gitrebase"
528+roots = []
529+file-types = ["git-rebase-todo"]
530+injection-regex = "git-rebase"
531+comment-token = "#"
532+indent = { tab-width = 2, unit = " " }
runtime/queries/c/indents.tomlnew file+16
@@ -0,0 +1,16 @@
1+indent = [
2+ "compound_statement",
3+ "field_declaration_list",
4+ "enumerator_list",
5+ "parameter_list",
6+ "init_declarator",
7+ "case_statement",
8+ "condition_clause",
9+ "expression_statement",
10+]
11+
12+outdent = [
13+ "case",
14+ "}",
15+ "]",
16+]
runtime/queries/c/injections.scmnew file+2
@@ -0,0 +1,2 @@
1+((comment) @injection.content
2+ (#set! injection.language "comment"))
runtime/queries/c/textobjects.scmnew file+13
@@ -0,0 +1,13 @@
1+(function_definition
2+ body: (_) @function.inside) @function.around
3+
4+(struct_specifier
5+ body: (_) @class.inside) @class.around
6+
7+(enum_specifier
8+ body: (_) @class.inside) @class.around
9+
10+(union_specifier
11+ body: (_) @class.inside) @class.around
12+
13+(parameter_declaration) @parameter.inside
runtime/queries/cmake/indents.tomlnew file+12
@@ -0,0 +1,12 @@
1+indent = [
2+ "if_condition",
3+ "foreach_loop",
4+ "while_loop",
5+ "function_def",
6+ "macro_def",
7+ "normal_command",
8+]
9+
10+outdent = [
11+ ")"
12+]
runtime/queries/cmake/injections.scmnew file+4
@@ -0,0 +1,4 @@
1+((line_comment) @injection.content
2+ (#set! injection.language "comment"))
3+((bracket_comment) @injection.content
4+ (#set! injection.language "comment"))
runtime/queries/cmake/textobjects.scmnew file+3
@@ -0,0 +1,3 @@
1+(macro_def) @function.around
2+
3+(argument) @parameter.inside
runtime/queries/comment/highlights.scmnew file+30
@@ -0,0 +1,30 @@
1+[
2+ "("
3+ ")"
4+] @punctuation.bracket
5+
6+":" @punctuation.delimiter
7+
8+((tag (name) @warning)
9+ (#match? @warning "^(TODO|HACK|WARNING)$"))
10+
11+("text" @warning
12+ (#match? @warning "^(TODO|HACK|WARNING)$"))
13+
14+((tag (name) @error)
15+ (match? @error "^(FIXME|XXX|BUG)$"))
16+
17+("text" @error
18+ (match? @error "^(FIXME|XXX|BUG)$"))
19+
20+(tag
21+ (name) @ui.text
22+ (user)? @constant)
23+
24+; Issue number (#123)
25+("text" @constant.numeric
26+ (#match? @constant.numeric "^#[0-9]+$"))
27+
28+; User mention (@user)
29+("text" @tag
30+ (#match? @tag "^[@][a-zA-Z0-9_-]+$"))
runtime/queries/cpp/indents.tomlnew file+17
@@ -0,0 +1,17 @@
1+indent = [
2+ "compound_statement",
3+ "field_declaration_list",
4+ "enumerator_list",
5+ "parameter_list",
6+ "init_declarator",
7+ "case_statement",
8+ "condition_clause",
9+ "expression_statement",
10+]
11+
12+outdent = [
13+ "case",
14+ "access_specifier",
15+ "}",
16+ "]",
17+]
runtime/queries/cpp/injections.scmnew file+1
@@ -0,0 +1 @@
1+; inherits: c
runtime/queries/cpp/textobjects.scmnew file+7
@@ -0,0 +1,7 @@
1+; inherits: c
2+
3+(lambda_expression
4+ body: (_) @function.inside) @function.around
5+
6+(class_specifier
7+ body: (_) @class.inside) @class.around
runtime/queries/dart/highlights.scmnew file+237
@@ -0,0 +1,237 @@
1+(dotted_identifier_list) @string
2+
3+; Methods
4+; --------------------
5+(super) @function.builtin
6+
7+(function_expression_body (identifier) @function.method)
8+((identifier)(selector (argument_part)) @function.method)
9+
10+; Annotations
11+; --------------------
12+(annotation
13+ name: (identifier) @attribute)
14+(marker_annotation
15+ name: (identifier) @attribute)
16+
17+; Types
18+; --------------------
19+(class_definition
20+ name: (identifier) @type)
21+
22+(constructor_signature
23+ name: (identifier) @function.method)
24+
25+(function_signature
26+ name: (identifier) @function.method)
27+
28+(getter_signature
29+ (identifier) @function.builtin)
30+
31+(setter_signature
32+ name: (identifier) @function.builtin)
33+
34+(enum_declaration
35+ name: (identifier) @type)
36+
37+(enum_constant
38+ name: (identifier) @type.builtin)
39+
40+(void_type) @type.builtin
41+
42+((scoped_identifier
43+ scope: (identifier) @type)
44+ (#match? @type "^[a-zA-Z]"))
45+
46+((scoped_identifier
47+ scope: (identifier) @type
48+ name: (identifier) @type)
49+ (#match? @type "^[a-zA-Z]"))
50+
51+; the DisabledDrawerButtons in : const DisabledDrawerButtons(history: true),
52+(type_identifier) @type.builtin
53+
54+; Variables
55+; --------------------
56+; the "File" in var file = File();
57+((identifier) @namespace
58+ (#match? @namespace "^_?[A-Z].*[a-z]")) ; catch Classes or IClasses not CLASSES
59+
60+("Function" @type.builtin)
61+(inferred_type) @type.builtin
62+
63+; properties
64+(unconditional_assignable_selector
65+ (identifier) @variable.other.member)
66+
67+(conditional_assignable_selector
68+ (identifier) @variable.other.member)
69+
70+; assignments
71+; --------------------
72+; the "strings" in : strings = "some string"
73+(assignment_expression
74+ left: (assignable_expression) @variable)
75+
76+(this) @variable.builtin
77+
78+; Parameters
79+; --------------------
80+(formal_parameter
81+ name: (identifier) @variable)
82+
83+(named_argument
84+ (label (identifier) @variable))
85+
86+; Literals
87+; --------------------
88+[
89+ (hex_integer_literal)
90+ (decimal_integer_literal)
91+ (decimal_floating_point_literal)
92+ ;(octal_integer_literal)
93+ ;(hex_floating_point_literal)
94+] @constant.numeric.integer
95+
96+(symbol_literal) @string.special.symbol
97+(string_literal) @string
98+
99+[
100+ (const_builtin)
101+ (final_builtin)
102+] @variable.builtin
103+
104+[
105+ (true)
106+ (false)
107+] @constant.builtin.boolean
108+
109+(null_literal) @constant.builtin
110+
111+(comment) @comment.line
112+(documentation_comment) @comment.block.documentation
113+
114+; Tokens
115+; --------------------
116+(template_substitution
117+ "$" @punctuation.special
118+ "{" @punctuation.special
119+ "}" @punctuation.special
120+) @embedded
121+
122+(template_substitution
123+ "$" @punctuation.special
124+ (identifier_dollar_escaped) @variable
125+) @embedded
126+
127+(escape_sequence) @constant.character.escape
128+
129+; Punctuation
130+;---------------------
131+[
132+ "("
133+ ")"
134+ "["
135+ "]"
136+ "{"
137+ "}"
138+] @punctuation.bracket
139+
140+[
141+ ";"
142+ "."
143+ ","
144+ ":"
145+] @punctuation.delimiter
146+
147+; Operators
148+;---------------------
149+[
150+ "@"
151+ "?"
152+ "=>"
153+ ".."
154+ "=="
155+ "&&"
156+ "%"
157+ "<"
158+ ">"
159+ "="
160+ ">="
161+ "<="
162+ "||"
163+ (multiplicative_operator)
164+ (increment_operator)
165+ (is_operator)
166+ (prefix_operator)
167+ (equality_operator)
168+ (additive_operator)
169+] @operator
170+
171+; Keywords
172+; --------------------
173+["import" "library" "export"] @keyword.control.import
174+["do" "while" "continue" "for"] @keyword.control.repeat
175+["return" "yield"] @keyword.control.return
176+["as" "in" "is"] @keyword.operator
177+
178+[
179+ "?."
180+ "??"
181+ "if"
182+ "else"
183+ "switch"
184+ "default"
185+ "late"
186+] @keyword.control.conditional
187+
188+[
189+ "try"
190+ "throw"
191+ "catch"
192+ "finally"
193+ (break_statement)
194+] @keyword.control.exception
195+
196+; Reserved words (cannot be used as identifiers)
197+[
198+ (case_builtin)
199+ "abstract"
200+ "async"
201+ "async*"
202+ "await"
203+ "class"
204+ "covariant"
205+ "deferred"
206+ "dynamic"
207+ "enum"
208+ "extends"
209+ "extension"
210+ "external"
211+ "factory"
212+ "Function"
213+ "get"
214+ "implements"
215+ "interface"
216+ "mixin"
217+ "new"
218+ "on"
219+ "operator"
220+ "part"
221+ "required"
222+ "set"
223+ "show"
224+ "static"
225+ "super"
226+ "sync*"
227+ "typedef"
228+ "with"
229+] @keyword
230+
231+; when used as an identifier:
232+((identifier) @variable.builtin
233+ (#match? @variable.builtin "^(abstract|as|covariant|deferred|dynamic|export|external|factory|Function|get|implements|import|interface|library|operator|mixin|part|set|static|typedef)$"))
234+
235+; Error
236+(ERROR) @error
237+
runtime/queries/dart/indents.tomlnew file+20
@@ -0,0 +1,20 @@
1+indent = [
2+ "class_body",
3+ "function_body",
4+ "function_expression_body",
5+ "declaration",
6+ "initializers",
7+ "switch_block",
8+ "if_statement",
9+ "formal_parameter_list",
10+ "formal_parameter",
11+ "list_literal",
12+ "return_statement",
13+ "arguments"
14+]
15+
16+outdent = [
17+ "}",
18+ "]",
19+ ")"
20+]
runtime/queries/dart/locals.scmnew file+20
@@ -0,0 +1,20 @@
1+; Scopes
2+;-------
3+
4+[
5+ (block)
6+ (try_statement)
7+ (catch_clause)
8+ (finally_clause)
9+] @local.scope
10+
11+; Definitions
12+;------------
13+
14+(class_definition
15+ body: (_) @local.definition)
16+
17+; References
18+;------------
19+
20+(identifier) @local.reference
runtime/queries/dockerfile/highlights.scmnew file+51
@@ -0,0 +1,51 @@
1+[
2+ "FROM"
3+ "AS"
4+ "RUN"
5+ "CMD"
6+ "LABEL"
7+ "EXPOSE"
8+ "ENV"
9+ "ADD"
10+ "COPY"
11+ "ENTRYPOINT"
12+ "VOLUME"
13+ "USER"
14+ "WORKDIR"
15+ "ARG"
16+ "ONBUILD"
17+ "STOPSIGNAL"
18+ "HEALTHCHECK"
19+ "SHELL"
20+ "MAINTAINER"
21+ "CROSS_BUILD"
22+] @keyword
23+
24+[
25+ ":"
26+ "@"
27+] @operator
28+
29+(comment) @comment
30+
31+
32+(image_spec
33+ (image_tag
34+ ":" @punctuation.special)
35+ (image_digest
36+ "@" @punctuation.special))
37+
38+(double_quoted_string) @string
39+
40+(expansion
41+ [
42+ "$"
43+ "{"
44+ "}"
45+ ] @punctuation.special
46+) @none
47+
48+((variable) @constant
49+ (#match? @constant "^[A-Z][A-Z_0-9]*$"))
50+
51+
runtime/queries/dockerfile/injections.scmnew file+6
@@ -0,0 +1,6 @@
1+((comment) @injection.content
2+ (#set! injection.language "comment"))
3+
4+([(shell_command) (shell_fragment)] @injection.content
5+ (#set! injection.language "bash"))
6+
runtime/queries/elixir/injections.scmnew file+2
@@ -0,0 +1,2 @@
1+((comment) @injection.content
2+ (#set! injection.language "comment"))
runtime/queries/fish/highlights.scmnew file+156
@@ -0,0 +1,156 @@
1+;; Operators
2+
3+[
4+ "&&"
5+ "||"
6+ "|"
7+ "&"
8+ "="
9+ "!="
10+ ".."
11+ "!"
12+ (direction)
13+ (stream_redirect)
14+ (test_option)
15+] @operator
16+
17+[
18+ "not"
19+ "and"
20+ "or"
21+] @keyword.operator
22+
23+;; Conditionals
24+
25+(if_statement
26+[
27+ "if"
28+ "end"
29+] @keyword.control.conditional)
30+
31+(switch_statement
32+[
33+ "switch"
34+ "end"
35+] @keyword.control.conditional)
36+
37+(case_clause
38+[
39+ "case"
40+] @keyword.control.conditional)
41+
42+(else_clause
43+[
44+ "else"
45+] @keyword.control.conditional)
46+
47+(else_if_clause
48+[
49+ "else"
50+ "if"
51+] @keyword.control.conditional)
52+
53+;; Loops/Blocks
54+
55+(while_statement
56+[
57+ "while"
58+ "end"
59+] @keyword.control.repeat)
60+
61+(for_statement
62+[
63+ "for"
64+ "end"
65+] @keyword.control.repeat)
66+
67+(begin_statement
68+[
69+ "begin"
70+ "end"
71+] @keyword.control.repeat)
72+
73+;; Keywords
74+
75+[
76+ "in"
77+ (break)
78+ (continue)
79+] @keyword
80+
81+"return" @keyword.control.return
82+
83+;; Punctuation
84+
85+[
86+ "["
87+ "]"
88+ "{"
89+ "}"
90+ "("
91+ ")"
92+] @punctuation.bracket
93+
94+"," @punctuation.delimiter
95+
96+;; Commands
97+
98+(command
99+ argument: [
100+ (word) @variable.parameter (#match? @variable.parameter "^-")
101+ ]
102+)
103+
104+; non-bultin command names
105+(command name: (word) @function)
106+
107+; derived from builtin -n (fish 3.2.2)
108+(command
109+ name: [
110+ (word) @function.builtin
111+ (#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)$")
112+ ]
113+)
114+
115+(test_command "test" @function.builtin)
116+
117+;; Functions
118+
119+(function_definition ["function" "end"] @keyword.function)
120+
121+(function_definition
122+ name: [
123+ (word) (concatenation)
124+ ]
125+@function)
126+
127+(function_definition
128+ option: [
129+ (word)
130+ (concatenation (word))
131+ ] @variable.parameter (#match? @variable.parameter "^-")
132+)
133+
134+;; Strings
135+
136+[(double_quote_string) (single_quote_string)] @string
137+(escape_sequence) @constant.character.escape
138+
139+;; Variables
140+
141+(variable_name) @variable
142+(variable_expansion) @constant
143+
144+;; Nodes
145+
146+(integer) @constant.numeric.integer
147+(float) @constant.numeric.float
148+(comment) @comment
149+(test_option) @string
150+
151+((word) @constant.builtin.boolean
152+(#match? @constant.builtin.boolean "^(true|false)$"))
153+
154+;; Error
155+
156+(ERROR) @error
runtime/queries/fish/indents.tomlnew file+12
@@ -0,0 +1,12 @@
1+indent = [
2+ "function_definition",
3+ "while_statement",
4+ "for_statement",
5+ "if_statement",
6+ "begin_statement",
7+ "switch_statement",
8+]
9+
10+outdent = [
11+ "end"
12+]
runtime/queries/fish/injections.scmnew file+2
@@ -0,0 +1,2 @@
1+((comment) @injection.content
2+ (#set! injection.language "comment"))
runtime/queries/fish/textobjects.scmnew file+1
@@ -0,0 +1 @@
1+(function_definition) @function.around
runtime/queries/git-commit/highlights.scmnew file+14
@@ -0,0 +1,14 @@
1+(subject) @markup.heading
2+(path) @string.special.path
3+(branch) @string.special.symbol
4+(commit) @constant
5+(item) @markup.link.url
6+(header) @tag
7+
8+(change kind: "new file" @diff.plus)
9+(change kind: "deleted" @diff.minus)
10+(change kind: "modified" @diff.delta)
11+(change kind: "renamed" @diff.delta.moved)
12+
13+[":" "->"] @punctuation.delimeter
14+(comment) @comment
runtime/queries/git-commit/injections.scmnew file+8
@@ -0,0 +1,8 @@
1+((comment (scissors))
2+ (message) @injection.content
3+ (#set! injection.include-children)
4+ (#set! injection.language "diff"))
5+
6+((rebase_command) @injection.content
7+ (#set! injection.include-children)
8+ (#set! injection.language "git-rebase"))
runtime/queries/git-diff/highlights.scmnew file+6
@@ -0,0 +1,6 @@
1+[(addition) (new_file)] @diff.plus
2+[(deletion) (old_file)] @diff.minus
3+
4+(commit) @constant
5+(location) @attribute
6+(command) @markup.bold
runtime/queries/git-rebase/highlights.scmnew file+11
@@ -0,0 +1,11 @@
1+(operation operator: ["p" "pick" "r" "reword" "e" "edit" "s" "squash" "m" "merge" "d" "drop" "b" "break" "x" "exec"] @keyword)
2+(operation operator: ["l" "label" "t" "reset"] @function)
3+(operation operator: ["f" "fixup"] @function.special)
4+
5+(option) @operator
6+(label) @string.special.symbol
7+(commit) @constant
8+"#" @punctuation.delimiter
9+(comment) @comment
10+
11+(ERROR) @error
runtime/queries/git-rebase/injections.scmnew file+4
@@ -0,0 +1,4 @@
1+((operation
2+ operator: ["x" "exec"]
3+ (command) @injection.content)
4+ (#set! injection.language "bash"))
runtime/queries/glsl/injections.scm+3−2
@@ -1,3 +1,4 @@
1−(preproc_arg) @glsl
1+; inherits: c
22
3−(comment) @comment
3+((preproc_arg) @injection.content
4+ (#set! injection.language "glsl"))
runtime/queries/julia/injections.scm+5−3
@@ -1,5 +1,7 @@
11; TODO: re-add when markdown is added.
2−; ((triple_string) @markdown
3−; (#offset! @markdown 0 3 0 -3))
2+; ((triple_string) @injection.content
3+; (#offset! @injection.content 0 3 0 -3)
4+; (#set! injection.language "markdown"))
45
5−(comment) @comment
6+((comment) @injection.content
7+ (#set! injection.language "comment"))
runtime/queries/latex/highlights.scm+1−1
@@ -371,7 +371,7 @@
371371((generic_command
372372 name:(generic_command_name) @_name
373373 .
374− arg: (_) @markup.underline.link)
374+ arg: (_) @markup.link.url)
375375 (#match? @_name "^(\\\\url|\\\\href)$"))
376376
377377(ERROR) @error
runtime/queries/ledger/injections.scm+2−2
@@ -1,2 +1,2 @@
1−(comment) @comment
2−(note) @comment
1+([(comment) (note)] @injection.content
2+ (#set! injection.language "comment"))
runtime/queries/llvm-mir-yaml/highlights.scmnew file+1
@@ -0,0 +1 @@
1+; inherits: yaml
runtime/queries/llvm-mir-yaml/indents.tomlnew file+3
@@ -0,0 +1,3 @@
1+indent = [
2+ "block_mapping_pair",
3+]
runtime/queries/llvm-mir-yaml/injections.scmnew file+9
@@ -0,0 +1,9 @@
1+; inherits: yaml
2+
3+((document (block_node (block_scalar) @injection.content))
4+ (#set! injection.language "llvm"))
5+
6+((document (block_node (block_mapping (block_mapping_pair
7+ key: (flow_node (plain_scalar (string_scalar))) ; "body"
8+ value: (block_node (block_scalar) @injection.content)))))
9+ (#set! injection.language "mir"))
runtime/queries/llvm-mir/highlights.scmnew file+136
@@ -0,0 +1,136 @@
1+[
2+ (label)
3+ (bb_ref)
4+] @label
5+
6+[
7+ (comment)
8+ (multiline_comment)
9+] @comment
10+
11+[
12+ "("
13+ ")"
14+ "["
15+ "]"
16+ "{"
17+ "}"
18+ "<"
19+ ">"
20+] @punctuation.bracket
21+
22+[
23+ ","
24+ ":"
25+ "|"
26+ "*"
27+] @punctuation.delimiter
28+
29+[
30+ "="
31+ "x"
32+] @operator
33+
34+[
35+ "true"
36+ "false"
37+] @constant.builtin.boolean
38+
39+[
40+ "null"
41+ "_"
42+ "unknown-address"
43+] @constant.builtin
44+
45+[
46+ (stack_object)
47+ (constant_pool_index)
48+ (jump_table_index)
49+ (var)
50+ (physical_register)
51+ (ir_block)
52+ (external_symbol)
53+ (global_var)
54+ (ir_local_var)
55+ (metadata_ref)
56+ (mnemonic)
57+] @variable
58+
59+(low_level_type) @type
60+
61+[
62+ (immediate_type)
63+ (primitive_type)
64+] @type.builtin
65+
66+(number) @constant.numeric.integer
67+(float) @constant.numeric.float
68+(string) @string
69+
70+(instruction name: _ @keyword.operator)
71+
72+[
73+ "successors"
74+ "liveins"
75+ "pre-instr-symbol"
76+ "post-instr-symbol"
77+ "heap-alloc-marker"
78+ "debug-instr-number"
79+ "debug-location"
80+ "mcsymbol"
81+ "tied-def"
82+ "target-flags"
83+ "CustomRegMask"
84+ "same_value"
85+ "def_cfa_register"
86+ "restore"
87+ "undefined"
88+ "offset"
89+ "rel_offset"
90+ "def_cfa"
91+ "llvm_def_aspace_cfa"
92+ "register"
93+ "escape"
94+ "remember_state"
95+ "restore_state"
96+ "window_save"
97+ "negate_ra_sign_state"
98+ "intpred"
99+ "floatpred"
100+ "shufflemask"
101+ "liveout"
102+ "target-index"
103+ "blockaddress"
104+ "intrinsic"
105+ "load"
106+ "store"
107+ "unknown-size"
108+ "on"
109+ "from"
110+ "into"
111+ "align"
112+ "basealign"
113+ "addrspace"
114+ "call-entry"
115+ "custom"
116+ "constant-pool"
117+ "stack"
118+ "got"
119+ "jump-table"
120+ "syncscope"
121+ "address-taken"
122+ "landing-pad"
123+ "inlineasm-br-indirect-target"
124+ "ehfunclet-entry"
125+ "bbsections"
126+
127+ (intpred)
128+ (floatpred)
129+ (memory_operand_flag)
130+ (atomic_ordering)
131+ (register_flag)
132+ (instruction_flag)
133+ (float_keyword)
134+] @keyword
135+
136+(ERROR) @error
runtime/queries/llvm-mir/indents.tomlnew file+7
@@ -0,0 +1,7 @@
1+indent = [
2+ "basic_block",
3+]
4+
5+outdent = [
6+ "label",
7+]
runtime/queries/llvm-mir/injections.scmnew file+2
@@ -0,0 +1,2 @@
1+([ (comment) (multiline_comment)] @injection.content
2+ (#set! injection.language "comment"))
runtime/queries/llvm-mir/textobjects.scmnew file+3
@@ -0,0 +1,3 @@
1+(basic_block) @function.around
2+
3+(argument) @parameter.inside
runtime/queries/llvm/highlights.scm+153−9
@@ -1,14 +1,158 @@
11(type) @type
2−(statement) @keyword.operator
2+(type_keyword) @type.builtin
3+
4+(type [
5+ (local_var)
6+ (global_var)
7+ ] @type)
8+
9+(argument) @variable.parameter
10+
11+(_ inst_name: _ @keyword.operator)
12+
13+[
14+ "catch"
15+ "filter"
16+] @keyword.operator
17+
18+[
19+ "to"
20+ "nuw"
21+ "nsw"
22+ "exact"
23+ "unwind"
24+ "from"
25+ "cleanup"
26+ "swifterror"
27+ "volatile"
28+ "inbounds"
29+ "inrange"
30+ (icmp_cond)
31+ (fcmp_cond)
32+ (fast_math)
33+] @keyword.control
34+
35+(_ callee: _ @function)
36+(function_header name: _ @function)
37+
38+[
39+ "declare"
40+ "define"
41+ (calling_conv)
42+] @keyword.function
43+
44+[
45+ "target"
46+ "triple"
47+ "datalayout"
48+ "source_filename"
49+ "addrspace"
50+ "blockaddress"
51+ "align"
52+ "syncscope"
53+ "within"
54+ "uselistorder"
55+ "uselistorder_bb"
56+ "module"
57+ "asm"
58+ "sideeffect"
59+ "alignstack"
60+ "inteldialect"
61+ "unwind"
62+ "type"
63+ "global"
64+ "constant"
65+ "externally_initialized"
66+ "alias"
67+ "ifunc"
68+ "section"
69+ "comdat"
70+ "thread_local"
71+ "localdynamic"
72+ "initialexec"
73+ "localexec"
74+ "any"
75+ "exactmatch"
76+ "largest"
77+ "nodeduplicate"
78+ "samesize"
79+ "distinct"
80+ "attributes"
81+ "vscale"
82+ "no_cfi"
83+ (linkage_aux)
84+ (dso_local)
85+ (visibility)
86+ (dll_storage_class)
87+ (unnamed_addr)
88+ (attribute_name)
89+] @keyword
90+
91+
92+(function_header [
93+ (linkage)
94+ (calling_conv)
95+ (unnamed_addr)
96+ ] @keyword.function)
97+
98+[
99+ (string)
100+ (cstring)
101+] @string
102+
3103(number) @constant.numeric.integer
4104(comment) @comment
5−(string) @string
6105(label) @label
7−(keyword) @keyword
8−"ret" @keyword.control.return
9−(boolean) @constant.builtin.boolean
106+(_ inst_name: "ret" @keyword.control.return)
10107(float) @constant.numeric.float
11−(constant) @constant
12−(identifier) @variable
13−(symbol) @punctuation.delimiter
14−(bracket) @punctuation.bracket
108+
109+[
110+ (local_var)
111+ (global_var)
112+] @variable
113+
114+[
115+ (struct_value)
116+ (array_value)
117+ (vector_value)
118+] @constructor
119+
120+[
121+ "("
122+ ")"
123+ "["
124+ "]"
125+ "{"
126+ "}"
127+ "<"
128+ ">"
129+ "<{"
130+ "}>"
131+] @punctuation.bracket
132+
133+[
134+ ","
135+ ":"
136+] @punctuation.delimiter
137+
138+[
139+ "="
140+ "|"
141+ "x"
142+ "..."
143+] @operator
144+
145+[
146+ "true"
147+ "false"
148+] @constant.builtin.boolean
149+
150+[
151+ "undef"
152+ "poison"
153+ "null"
154+ "none"
155+ "zeroinitializer"
156+] @constant.builtin
157+
158+(ERROR) @error
runtime/queries/llvm/indents.tomlnew file+8
@@ -0,0 +1,8 @@
1+indent = [
2+ "function_body",
3+ "instruction",
4+]
5+
6+outdent = [
7+ "}",
8+]
runtime/queries/llvm/injections.scmnew file+2
@@ -0,0 +1,2 @@
1+((comment) @injection.content
2+ (#set! injection.language "comment"))
runtime/queries/llvm/locals.scmnew file+14
@@ -0,0 +1,14 @@
1+; Scopes
2+
3+(function_body) @local.scope
4+
5+; Definitions
6+
7+(argument
8+ (value (var (local_var) @local.definition)))
9+
10+(instruction
11+ (local_var) @local.definition)
12+
13+; References
14+(local_var) @local.reference
runtime/queries/llvm/textobjects.scmnew file+16
@@ -0,0 +1,16 @@
1+(define
2+ body: (_) @function.inside) @function.around
3+
4+(struct_type
5+ (struct_body) @class.inside) @class.around
6+
7+(packed_struct_type
8+ (struct_body) @class.inside) @class.around
9+
10+(array_type
11+ (array_vector_body) @class.inside) @class.around
12+
13+(vector_type
14+ (array_vector_body) @class.inside) @class.around
15+
16+(argument) @parameter.inside
runtime/queries/markdown/highlights.scm+4−3
@@ -10,15 +10,16 @@
1010 (fenced_code_block)
1111] @markup.raw.block
1212
13+(block_quote) @markup.quote
14+
1315(code_span) @markup.raw.inline
1416
1517(emphasis) @markup.italic
1618
1719(strong_emphasis) @markup.bold
1820
19−(link_destination) @markup.underline.link
20−
21−; (link_label) @markup.label ; TODO: rename
21+(link_destination) @markup.link.url
22+(link_label) @markup.link.label
2223
2324[
2425 (list_marker_plus)
runtime/queries/markdown/injections.scm+2−1
@@ -1,6 +1,7 @@
11(fenced_code_block
22 (info_string) @injection.language
3− (code_fence_content) @injection.content)
3+ (code_fence_content) @injection.content
4+ (#set! injection.include-children))
45
56((html_block) @injection.content
67 (#set! injection.language "html"))
runtime/queries/nix/highlights.scm+6−1
@@ -13,7 +13,7 @@
1313] @keyword
1414
1515((identifier) @variable.builtin
16− (#match? @variable.builtin "^(__currentSystem|__currentTime|__nixPath|__nixVersion|__storeDir|builtins|false|null|true)$")
16+ (#match? @variable.builtin "^(__currentSystem|__currentTime|__nixPath|__nixVersion|__storeDir|builtins)$")
1717 (#is-not? local))
1818
1919((identifier) @function.builtin
@@ -33,6 +33,11 @@
3333
3434(uri) @string.special.uri
3535
36+; boolean
37+((identifier) @constant.builtin.boolean (#match? @constant.builtin.boolean "^(true|false)$")) @constant.builtin.boolean
38+; null
39+((identifier) @constant.builtin (#eq? @constant.builtin "null")) @constant.builtin
40+
3641(integer) @constant.numeric.integer
3742(float) @constant.numeric.float
3843
runtime/queries/ocaml/indents.toml+1−1
@@ -8,6 +8,6 @@ indent = [
88 "match_case",
99]
1010
11−oudent = [
11+outdent = [
1212 "}",
1313]
runtime/queries/python/injections.scmnew file+2
@@ -0,0 +1,2 @@
1+((comment) @injection.content
2+ (#set! injection.language "comment"))
runtime/queries/ruby/indents.tomlnew file+25
@@ -0,0 +1,25 @@
1+indent = [
2+ "argument_list",
3+ "array",
4+ "begin",
5+ "block",
6+ "call",
7+ "class",
8+ "case",
9+ "do_block",
10+ "elsif",
11+ "if",
12+ "hash",
13+ "method",
14+ "module",
15+ "singleton_class",
16+ "singleton_method",
17+]
18+
19+outdent = [
20+ ")",
21+ "}",
22+ "]",
23+ "end",
24+ "when",
25+]
runtime/queries/rust/highlights.scm+9−5
@@ -127,11 +127,16 @@
127127 "await"
128128] @keyword.control
129129
130+"use" @keyword.control.import
131+(mod_item "mod" @keyword.control.import !body)
132+(use_as_clause "as" @keyword.control.import)
133+
134+(type_cast_expression "as" @keyword.operator)
135+
130136[
131137 (crate)
132138 (super)
133139 "as"
134− "use"
135140 "pub"
136141 "mod"
137142 "extern"
@@ -242,10 +247,9 @@
242247; ---
243248; Macros
244249; ---
245−
246250(meta_item
247− (identifier) @attribute)
248−(attribute_item) @attribute
251+ (identifier) @function.macro)
252+
249253(inner_attribute_item) @attribute
250254
251255(macro_definition
@@ -259,7 +263,7 @@
259263 "!" @function.macro)
260264
261265(metavariable) @variable.parameter
262−(fragment_specifier) @variable.parameter
266+(fragment_specifier) @type
263267
264268
265269
runtime/queries/rust/indents.toml+1
@@ -9,6 +9,7 @@ indent = [
99 "field_initializer_list",
1010 "struct_pattern",
1111 "tuple_pattern",
12+ "unit_expression",
1213 "enum_variant_list",
1314 "call_expression",
1415 "binary_expression",
runtime/queries/rust/injections.scm+3
@@ -1,3 +1,6 @@
1+([(line_comment) (block_comment)] @injection.content
2+ (#set! injection.language "comment"))
3+
14((macro_invocation
25 (token_tree) @injection.content)
36 (#set! injection.language "rust")
runtime/queries/scala/highlights.scmnew file+203
@@ -0,0 +1,203 @@
1+; CREDITS @stumash (stuart.mashaal@gmail.com)
2+
3+;; variables
4+
5+
6+((identifier) @variable.builtin
7+ (#match? @variable.builtin "^this$"))
8+
9+(interpolation) @none
10+
11+; Assume other uppercase names constants.
12+; NOTE: In order to distinguish constants we highlight
13+; all the identifiers that are uppercased. But this solution
14+; is not suitable for all occurrences e.g. it will highlight
15+; an uppercased method as a constant if used with no params.
16+; Introducing highlighting for those specific cases, is probably
17+; best way to resolve the issue.
18+((identifier) @constant (#match? @constant "^[A-Z]"))
19+
20+;; types
21+
22+(type_identifier) @type
23+
24+(class_definition
25+ name: (identifier) @type)
26+
27+(object_definition
28+ name: (identifier) @type)
29+
30+(trait_definition
31+ name: (identifier) @type)
32+
33+(type_definition
34+ name: (type_identifier) @type)
35+
36+; method definition
37+
38+(class_definition
39+ body: (template_body
40+ (function_definition
41+ name: (identifier) @function.method)))
42+(object_definition
43+ body: (template_body
44+ (function_definition
45+ name: (identifier) @function.method)))
46+(trait_definition
47+ body: (template_body
48+ (function_definition
49+ name: (identifier) @function.method)))
50+
51+; imports
52+
53+(import_declaration
54+ path: (identifier) @namespace)
55+((stable_identifier (identifier) @namespace))
56+
57+((import_declaration
58+ path: (identifier) @type) (#match? @type "^[A-Z]"))
59+((stable_identifier (identifier) @type) (#match? @type "^[A-Z]"))
60+
61+((import_selectors (identifier) @type) (#match? @type "^[A-Z]"))
62+
63+; method invocation
64+
65+
66+(call_expression
67+ function: (identifier) @function)
68+
69+(call_expression
70+ function: (field_expression
71+ field: (identifier) @function.method))
72+
73+((call_expression
74+ function: (identifier) @variable.other.member)
75+ (#match? @variable.other.member "^[A-Z]"))
76+
77+(generic_function
78+ function: (identifier) @function)
79+
80+(
81+ (identifier) @function.builtin
82+ (#match? @function.builtin "^super$")
83+)
84+
85+; function definitions
86+
87+(function_definition
88+ name: (identifier) @function)
89+
90+(parameter
91+ name: (identifier) @variable.parameter)
92+
93+; expressions
94+
95+
96+(field_expression field: (identifier) @variable.other.member)
97+(field_expression value: (identifier) @type
98+ (#match? @type "^[A-Z]"))
99+
100+(infix_expression operator: (identifier) @operator)
101+(infix_expression operator: (operator_identifier) @operator)
102+(infix_type operator: (operator_identifier) @operator)
103+(infix_type operator: (operator_identifier) @operator)
104+
105+; literals
106+(boolean_literal) @constant.builtin.boolean
107+(integer_literal) @constant.numeric.integer
108+(floating_point_literal) @constant.numeric.float
109+
110+
111+(symbol_literal) @string.special.symbol
112+
113+[
114+(string)
115+(character_literal)
116+(interpolated_string_expression)
117+] @string
118+
119+(interpolation "$" @punctuation.special)
120+
121+;; keywords
122+
123+[
124+ "abstract"
125+ "case"
126+ "class"
127+ "extends"
128+ "final"
129+ "finally"
130+;; `forSome` existential types not implemented yet
131+ "implicit"
132+ "lazy"
133+;; `macro` not implemented yet
134+ "object"
135+ "override"
136+ "package"
137+ "private"
138+ "protected"
139+ "sealed"
140+ "trait"
141+ "type"
142+ "val"
143+ "var"
144+ "with"
145+] @keyword
146+
147+(null_literal) @constant.builtin
148+(wildcard) @keyword
149+
150+;; special keywords
151+
152+"new" @keyword.operator
153+
154+[
155+ "else"
156+ "if"
157+ "match"
158+ "try"
159+ "catch"
160+ "throw"
161+] @keyword.control.conditional
162+
163+[
164+ "("
165+ ")"
166+ "["
167+ "]"
168+ "{"
169+ "}"
170+] @punctuation.bracket
171+
172+[
173+ "."
174+ ","
175+] @punctuation.delimiter
176+
177+[
178+ "do"
179+ "for"
180+ "while"
181+ "yield"
182+] @keyword.control.repeat
183+
184+"def" @keyword.function
185+
186+[
187+ "=>"
188+ "<-"
189+ "@"
190+] @keyword.operator
191+
192+"import" @keyword.control.import
193+
194+"return" @keyword.control.return
195+
196+(comment) @comment
197+
198+;; `case` is a conditional keyword in case_block
199+
200+(case_block
201+ (case_clause ("case") @keyword.control.conditional))
202+
203+(identifier) @variable
runtime/queries/scala/indents.tomlnew file+23
@@ -0,0 +1,23 @@
1+
2+indent = [
3+ "block",
4+ "arguments",
5+ "parameter",
6+ "class_definition",
7+ "trait_definition",
8+ "object_definition",
9+ "function_definition",
10+ "val_definition",
11+ "import_declaration",
12+ "while_expression",
13+ "do_while_expression",
14+ "for_expression",
15+ "try_expression",
16+ "match_expression"
17+]
18+
19+outdent = [
20+ "}",
21+ "]",
22+ ")"
23+]
runtime/queries/scala/injections.scmnew file+1
@@ -0,0 +1 @@
1+(comment) @comment
runtime/queries/svelte/highlights.scm+2−2
@@ -20,12 +20,12 @@
2020((element (start_tag (tag_name) @_tag) (text) @markup.inline)
2121 (#match? @_tag "^(code|kbd)$"))
2222
23−((element (start_tag (tag_name) @_tag) (text) @markup.underline.link)
23+((element (start_tag (tag_name) @_tag) (text) @markup.link.url)
2424 (#eq? @_tag "a"))
2525
2626((attribute
2727 (attribute_name) @_attr
28− (quoted_attribute_value (attribute_value) @markup.underline.link))
28+ (quoted_attribute_value (attribute_value) @markup.link.url))
2929 (#match? @_attr "^(href|src)$"))
3030
3131(tag_name) @tag
runtime/queries/svelte/injections.scm+2−2
@@ -26,5 +26,5 @@
2626 (#set! injection.language "typescript")
2727)
2828
29−(comment) @comment
30−
29+((comment) @injection.content
30+ (#set! injection.language "comment"))
runtime/queries/tablegen/highlights.scmnew file+90
@@ -0,0 +1,90 @@
1+[
2+ (comment)
3+ (multiline_comment)
4+] @comment
5+
6+[
7+ "("
8+ ")"
9+ "["
10+ "]"
11+ "{"
12+ "}"
13+ "<"
14+ ">"
15+] @punctuation.bracket
16+
17+[
18+ ","
19+ ";"
20+ "."
21+] @punctuation.delimiter
22+
23+[
24+ "#"
25+ "-"
26+ "..."
27+ ":"
28+] @operator
29+
30+[
31+ "="
32+ "!cond"
33+ (operator_keyword)
34+] @function
35+
36+[
37+ "true"
38+ "false"
39+] @constant.builtin.boolean
40+
41+[
42+ "?"
43+] @constant.builtin
44+
45+(var) @variable
46+
47+(template_arg (identifier) @variable.parameter)
48+
49+(_ argument: (value (identifier) @variable.parameter))
50+
51+(type) @type
52+
53+"code" @type.builtin
54+
55+(number) @constant.numeric.integer
56+[
57+ (string_string)
58+ (code_string)
59+] @string
60+
61+(preprocessor) @keyword.directive
62+
63+[
64+ "class"
65+ "field"
66+ "let"
67+ "defvar"
68+ "def"
69+ "defset"
70+ "defvar"
71+ "assert"
72+] @keyword
73+
74+[
75+ "let"
76+ "in"
77+ "foreach"
78+ "if"
79+ "then"
80+ "else"
81+] @keyword.operator
82+
83+"include" @keyword.control.import
84+
85+[
86+ "multiclass"
87+ "defm"
88+] @namespace
89+
90+(ERROR) @error
runtime/queries/tablegen/indents.tomlnew file+7
@@ -0,0 +1,7 @@
1+indent = [
2+ "statement",
3+]
4+
5+outdent = [
6+ "}",
7+]
runtime/queries/tablegen/injections.scmnew file+2
@@ -0,0 +1,2 @@
1+([ (comment) (multiline_comment)] @injection.content
2+ (#set! injection.language "comment"))
runtime/queries/tablegen/textobjects.scmnew file+7
@@ -0,0 +1,7 @@
1+(class
2+ body: (_) @class.inside) @class.around
3+
4+(multiclass
5+ body: (_) @class.inside) @class.around
6+
7+(_ argument: _ @parameter.inside)
runtime/queries/tsq/injections.scmnew file+2
@@ -0,0 +1,2 @@
1+((comment) @injection.content
2+ (#set! injection.language "comment"))
runtime/queries/yaml/highlights.scm+13−3
@@ -1,9 +1,19 @@
1−(block_mapping_pair key: (_) @variable.other.member)
2−(flow_mapping (_ key: (_) @variable.other.member))
1+(block_mapping_pair
2+ key: (flow_node [(double_quote_scalar) (single_quote_scalar)] @variable.other.member))
3+(block_mapping_pair
4+ key: (flow_node (plain_scalar (string_scalar) @variable.other.member)))
5+
6+(flow_mapping
7+ (_ key: (flow_node [(double_quote_scalar) (single_quote_scalar)] @variable.other.member)))
8+(flow_mapping
9+ (_ key: (flow_node (plain_scalar (string_scalar) @variable.other.member))))
10+
311(boolean_scalar) @constant.builtin.boolean
412(null_scalar) @constant.builtin
513(double_quote_scalar) @string
614(single_quote_scalar) @string
15+(block_scalar) @string
16+(string_scalar) @string
717(escape_sequence) @constant.character.escape
818(integer_scalar) @constant.numeric.integer
919(float_scalar) @constant.numeric.float
@@ -30,4 +40,4 @@
3040"}"
3141] @punctuation.bracket
3242
33−["*" "&"] @punctuation.special
43+["*" "&" "---" "..."] @punctuation.special
runtime/queries/yaml/injections.scmnew file+2
@@ -0,0 +1,2 @@
1+((comment) @injection.content
2+ (#set! injection.language "comment"))
runtime/themes/base16_default_dark.toml+4
@@ -33,6 +33,10 @@
3333"namespace" = "base0E"
3434"ui.help" = { fg = "base06", bg = "base01" }
3535
36+"diff.plus" = "base0B"
37+"diff.delta" = "base09"
38+"diff.minus" = "base08"
39+
3640"diagnostic" = { modifiers = ["underlined"] }
3741"ui.gutter" = { bg = "base01" }
3842"info" = "base0D"
runtime/themes/base16_default_light.toml+4
@@ -33,6 +33,10 @@
3333"namespace" = "base0E"
3434"ui.help" = { fg = "base06", bg = "base01" }
3535
36+"diff.plus" = "base0B"
37+"diff.delta" = "base09"
38+"diff.minus" = "base08"
39+
3640"diagnostic" = { modifiers = ["underlined"] }
3741"ui.gutter" = { bg = "base01" }
3842"info" = "base0D"
runtime/themes/base16_terminal.toml+4
@@ -30,6 +30,10 @@
3030"namespace" = "light-magenta"
3131"ui.help" = { fg = "white", bg = "black" }
3232
33+"diff.plus" = "light-green"
34+"diff.delta" = "yellow"
35+"diff.minus" = "light-red"
36+
3337"diagnostic" = { modifiers = ["underlined"] }
3438"ui.gutter" = { bg = "black" }
3539"info" = "light-blue"
runtime/themes/bogster.toml+7
@@ -28,6 +28,10 @@
2828
2929"module" = "#d32c5d"
3030
31+"diff.plus" = "#59dcb7"
32+"diff.delta" = "#dc7759"
33+"diff.minus" = "#dc597f"
34+
3135"ui.background" = { bg = "#161c23" }
3236"ui.linenr" = { fg = "#415367" }
3337"ui.linenr.selected" = { fg = "#e5ded6" } # TODO
@@ -49,3 +53,6 @@
4953"error" = "#dc597f"
5054"info" = "#59dcb7"
5155"hint" = "#59c0dc"
56+
57+# make diagnostic underlined, to distinguish with selection text.
58+diagnostic = { modifiers = ["underlined"] }
runtime/themes/dark_plus.toml+4
@@ -39,6 +39,10 @@
3939"constant.numeric" = { fg = "pale_green" }
4040"constant.character.escape" = { fg = "gold" }
4141
42+"diff.plus" = { fg = "pale_green" }
43+"diff.delta" = { fg = "gold" }
44+"diff.minus" = { fg = "red" }
45+
4246"ui.background" = { fg = "light_gray", bg = "dark_gray2" }
4347
4448"ui.window" = { bg = "widget" }
runtime/themes/dracula.tomlnew file+54
@@ -0,0 +1,54 @@
1+# Author : Sebastian Zivota <loewenheim@mailbox.org>
2+"comment" = { fg = "comment" }
3+"constant" = { fg = "purple" }
4+"constant.character.escape" = { fg = "pink" }
5+"function" = { fg = "green" }
6+"keyword" = { fg = "pink" }
7+"operator" = { fg = "pink" }
8+"punctuation" = { fg = "foreground" }
9+"string" = { fg = "yellow" }
10+"string.regexp" = { fg = "red" }
11+"tag" = { fg = "pink" }
12+"type" = { fg = "cyan", modifiers = ["italic"] }
13+"type.enum.variant" = { fg = "foreground", modifiers = ["italic"] }
14+"variable" = { fg = "foreground" }
15+"variable.builtin" = { fg = "cyan", modifiers = ["italic"] }
16+"variable.parameter" = { fg ="orange", modifiers = ["italic"] }
17+
18+"diff.plus" = { fg = "green" }
19+"diff.delta" = { fg = "orange" }
20+"diff.minus" = { fg = "red" }
21+
22+"ui.background" = { fg = "foreground", bg = "background" }
23+"ui.cursor" = { fg = "background", bg = "orange", modifiers = ["dim"] }
24+"ui.cursor.match" = { fg = "green", modifiers = ["underlined"] }
25+"ui.cursor.primary" = { fg = "background", bg = "cyan", modifier = ["dim"] }
26+"ui.help" = { fg = "foreground", bg = "background_dark" }
27+"ui.linenr" = { fg = "comment" }
28+"ui.linenr.selected" = { fg = "foreground" }
29+"ui.menu" = { fg = "foreground", bg = "background_dark" }
30+"ui.menu.selected" = { fg = "cyan", bg = "background_dark" }
31+"ui.popup" = { fg = "foreground" }
32+"ui.selection" = { fg = "background", bg = "purple", modifiers = ["dim"] }
33+"ui.selection.primary" = { fg = "background", bg = "pink" }
34+"ui.statusline" = { fg = "foreground", bg = "background_dark" }
35+"ui.statusline.inactive" = { fg = "comment", bg = "background_dark" }
36+"ui.text" = { fg = "foreground" }
37+"ui.text.focus" = { fg = "cyan" }
38+"ui.window" = { fg = "foreground" }
39+
40+"error" = { fg = "red" }
41+"warning" = { fg = "cyan" }
42+
43+[palette]
44+background = "#282a36"
45+background_dark = "#21222c"
46+foreground = "#f8f8f2"
47+comment = "#6272a4"
48+red = "#ff5555"
49+orange = "#ffb86c"
50+yellow = "#f1fa8c"
51+green = "#50fa7b"
52+purple = "#bd93f9"
53+cyan = "#8be9fd"
54+pink = "#ff79c6"
runtime/themes/everforest_dark.toml+5−1
@@ -12,7 +12,7 @@
1212"type" = "yellow"
1313"constant" = "purple"
1414"constant.numeric" = "purple"
15−"string" = "grey2"
15+"string" = "green"
1616"comment" = "grey0"
1717"variable" = "fg"
1818"variable.builtin" = "blue"
@@ -34,6 +34,10 @@
3434"module" = "blue"
3535"special" = "orange"
3636
37+"diff.plus" = "green"
38+"diff.delta" = "orange"
39+"diff.minus" = "red"
40+
3741"ui.background" = { bg = "bg0" }
3842"ui.cursor" = { fg = "bg0", bg = "fg" }
3943"ui.cursor.match" = { fg = "orange", bg = "bg_yellow" }
runtime/themes/everforest_light.tomlnew file+90
@@ -0,0 +1,90 @@
1+# Everforest (Dark Hard)
2+# Author: CptPotato
3+
4+# Original Author:
5+# URL: https://github.com/sainnhe/everforest
6+# Filename: autoload/everforest.vim
7+# Author: sainnhe
8+# Email: sainnhe@gmail.com
9+# License: MIT License
10+
11+"constant.character.escape" = "orange"
12+"type" = "yellow"
13+"constant" = "purple"
14+"constant.numeric" = "purple"
15+"string" = "green"
16+"comment" = "grey0"
17+"variable" = "fg"
18+"variable.builtin" = "blue"
19+"variable.parameter" = "fg"
20+"variable.other.member" = "fg"
21+"label" = "aqua"
22+"punctuation" = "grey2"
23+"punctuation.delimiter" = "grey2"
24+"punctuation.bracket" = "fg"
25+"keyword" = "red"
26+"operator" = "orange"
27+"function" = "green"
28+"function.builtin" = "blue"
29+"function.macro" = "aqua"
30+"tag" = "yellow"
31+"namespace" = "aqua"
32+"attribute" = "aqua"
33+"constructor" = "yellow"
34+"module" = "blue"
35+"special" = "orange"
36+
37+"diff.plus" = "green"
38+"diff.delta" = "orange"
39+"diff.minus" = "red"
40+
41+"ui.background" = { bg = "bg0" }
42+"ui.cursor" = { fg = "bg0", bg = "fg" }
43+"ui.cursor.match" = { fg = "orange", bg = "bg_yellow" }
44+"ui.cursor.insert" = { fg = "bg0", bg = "grey1" }
45+"ui.cursor.select" = { fg = "bg0", bg = "blue" }
46+"ui.linenr" = "grey0"
47+"ui.linenr.selected" = "fg"
48+"ui.statusline" = { fg = "grey2", bg = "bg2" }
49+"ui.statusline.inactive" = { fg = "grey0", bg = "bg1" }
50+"ui.popup" = { fg = "grey2", bg = "bg1" }
51+"ui.window" = { fg = "grey2", bg = "bg1" }
52+"ui.help" = { fg = "fg", bg = "bg1" }
53+"ui.text" = "fg"
54+"ui.text.focus" = "fg"
55+"ui.menu" = { fg = "fg", bg = "bg2" }
56+"ui.menu.selected" = { fg = "bg0", bg = "green" }
57+"ui.selection" = { bg = "bg3" }
58+
59+"hint" = "blue"
60+"info" = "aqua"
61+"warning" = "yellow"
62+"error" = "red"
63+"diagnostic" = { modifiers = ["underlined"] }
64+
65+
66+[palette]
67+
68+bg0 = "#fff9e8"
69+bg1 = "#f7f4e0"
70+bg2 = "#f0eed9"
71+bg3 = "#e9e8d2"
72+bg4 = "#e1ddcb"
73+bg5 = "#bec5b2"
74+bg_visual = "#edf0cd"
75+bg_red = "#fce5dc"
76+bg_green = "#f1f3d4"
77+bg_blue = "#eaf2eb"
78+bg_yellow = "#fbefd0"
79+
80+fg = "#5c6a72"
81+red = "#f85552"
82+orange = "#f57d26"
83+yellow = "#dfa000"
84+green = "#8da101"
85+aqua = "#35a77c"
86+blue = "#3a94c5"
87+purple = "#df69ba"
88+grey0 = "#a6b0a0"
89+grey1 = "#939f91"
90+grey2 = "#829181"
runtime/themes/gruvbox.toml+4
@@ -28,6 +28,10 @@
2828"label" = "aqua1"
2929"module" = "aqua1"
3030
31+"diff.plus" = "green1"
32+"diff.delta" = "orange1"
33+"diff.minus" = "red1"
34+
3135"warning" = { fg = "orange1", bg = "bg1" }
3236"error" = { fg = "red1", bg = "bg1" }
3337"info" = { fg = "aqua1", bg = "bg1" }
runtime/themes/ingrid.toml+4
@@ -28,6 +28,10 @@
2828
2929"module" = "#839A53"
3030
31+"diff.plus" = "#839A53"
32+"diff.delta" = "#D4A520"
33+"diff.minus" = "#D74E50"
34+
3135"ui.background" = { bg = "#FFFCFD" }
3236"ui.linenr" = { fg = "#bbbbbb" }
3337"ui.linenr.selected" = { fg = "#F3EAE9" } # TODO
runtime/themes/monokai.toml+5−1
@@ -39,6 +39,10 @@
3939"constant.numeric" = { fg = "#ae81ff" }
4040"constant.character.escape" = { fg = "#ae81ff" }
4141
42+"diff.plus" = { fg = "#a6e22e" }
43+"diff.delta" = { fg = "#fd971f" }
44+"diff.minus" = { fg = "#f92672" }
45+
4246"ui.background" = { fg = "text", bg = "background" }
4347
4448"ui.window" = { bg = "widget" }
@@ -65,7 +69,7 @@
6569"warning" = { fg = "#cca700" }
6670"error" = { fg = "#f48771" }
6771"info" = { fg = "#75beff" }
68−"hint" = { fg = "#eeeeeeb3" }
72+"hint" = { fg = "#eeeeeb3" }
6973
7074diagnostic = { modifiers = ["underlined"] }
7175
runtime/themes/monokai_pro.toml+5
@@ -77,6 +77,11 @@
7777# integer, floating point
7878"constant.numeric" = "purple"
7979
80+# vcs
81+"diff.plus" = "green"
82+"diff.delta" = "orange"
83+"diff.minus" = "red"
84+
8085# make diagnostic underlined, to distinguish with selection text.
8186diagnostic = { modifiers = ["underlined"] }
8287
runtime/themes/monokai_pro_machine.toml+5
@@ -77,6 +77,11 @@
7777# integer, floating point
7878"constant.numeric" = "purple"
7979
80+# vcs
81+"diff.plus" = "green"
82+"diff.delta" = "orange"
83+"diff.minus" = "red"
84+
8085# make diagnostic underlined, to distinguish with selection text.
8186diagnostic = { modifiers = ["underlined"] }
8287
runtime/themes/monokai_pro_octagon.toml+5
@@ -77,6 +77,11 @@
7777# integer, floating point
7878"constant.numeric" = "purple"
7979
80+# vcs
81+"diff.plus" = "green"
82+"diff.delta" = "orange"
83+"diff.minus" = "red"
84+
8085# make diagnostic underlined, to distinguish with selection text.
8186diagnostic = { modifiers = ["underlined"] }
8287
runtime/themes/monokai_pro_ristretto.toml+5
@@ -77,6 +77,11 @@
7777# integer, floating point
7878"constant.numeric" = "purple"
7979
80+# vcs
81+"diff.plus" = "green"
82+"diff.delta" = "orange"
83+"diff.minus" = "red"
84+
8085# make diagnostic underlined, to distinguish with selection text.
8186diagnostic = { modifiers = ["underlined"] }
8287
runtime/themes/monokai_pro_spectrum.toml+5
@@ -77,6 +77,11 @@
7777# integer, floating point
7878"constant.numeric" = "purple"
7979
80+# vcs
81+"diff.plus" = "green"
82+"diff.delta" = "orange"
83+"diff.minus" = "red"
84+
8085# make diagnostic underlined, to distinguish with selection text.
8186diagnostic = { modifiers = ["underlined"] }
8287
runtime/themes/nord.toml+5
@@ -84,6 +84,11 @@
8484# nord15 - integer, floating point
8585"constant.numeric" = "nord15"
8686
87+# vcs
88+"diff.plus" = "nord14"
89+"diff.delta" = "nord12"
90+"diff.minus" = "nord11"
91+
8792[palette]
8893nord0 = "#2e3440"
8994nord1 = "#3b4252"
runtime/themes/onedark.toml+19−4
@@ -3,27 +3,42 @@
33"attribute" = { fg = "yellow" }
44"comment" = { fg = "light-gray", modifiers = ["italic"] }
55"constant" = { fg = "cyan" }
6−"constant.builtin" = { fg = "blue" }
6+"constant.numeric" = { fg = "gold" }
7+"constant.builtin" = { fg = "gold" }
8+"constant.character.escape" = { fg = "gold" }
79"constructor" = { fg = "blue" }
8−"escape" = { fg = "gold" }
910"function" = { fg = "blue" }
1011"function.builtin" = { fg = "blue" }
1112"function.macro" = { fg = "purple" }
1213"keyword" = { fg = "red" }
1314"keyword.control" = { fg = "purple" }
15+"keyword.control.import" = { fg = "red" }
1416"keyword.directive" = { fg = "purple" }
1517"label" = { fg = "purple" }
1618"namespace" = { fg = "blue" }
17−"number" = { fg = "gold" }
1819"operator" = { fg = "purple" }
20+"keyword.operator" = { fg = "purple" }
1921"property" = { fg = "red" }
2022"special" = { fg = "blue" }
2123"string" = { fg = "green" }
2224"type" = { fg = "yellow" }
23−"type.builtin" = { fg = "yellow" }
2425# "variable" = { fg = "blue" }
2526"variable.builtin" = { fg = "blue" }
2627"variable.parameter" = { fg = "red" }
28+"variable.other.member" = { fg = "red" }
29+
30+"markup.heading" = { fg = "red" }
31+"markup.raw.inline" = { fg = "green" }
32+"markup.bold" = { fg = "gold", modifiers = ["bold"] }
33+"markup.italic" = { fg = "purple", modifiers = ["italic"] }
34+"markup.list" = { fg = "red" }
35+"markup.quote" = { fg = "yellow" }
36+"markup.link.url" = { fg = "cyan", modifiers = ["underlined"]}
37+"markup.link.label" = { fg = "purple" }
38+
39+"diff.plus" = "green"
40+"diff.delta" = "gold"
41+"diff.minus" = "red"
2742
2843diagnostic = { modifiers = ["underlined"] }
2944"info" = { fg = "blue", modifiers = ["bold"] }
runtime/themes/rose_pine.toml+13
@@ -1,4 +1,5 @@
11# Author: RayGervais<raygervais@hotmail.ca>
2+# Author: ChrisHa<chunghha@users.noreply.github.com>
23
34"ui.background" = { bg = "base" }
45"ui.menu" = "surface"
@@ -37,6 +38,9 @@
3738"ui.window" = { bg = "base" }
3839"ui.help" = { bg = "overlay", fg = "foam" }
3940"text" = "text"
41+"diff.plus" = "foam"
42+"diff.delta" = "rose"
43+"diff.minus" = "love"
4044
4145"info" = "gold"
4246"hint" = "gold"
@@ -44,6 +48,15 @@
4448"diagnostic" = "rose"
4549"error" = "love"
4650
51+"markup.heading" = { fg = "rose" }
52+"markup.raw.inline" = { fg = "foam" }
53+"markup.bold" = { fg = "gold", modifiers = ["bold"] }
54+"markup.italic" = { fg = "iris", modifiers = ["italic"] }
55+"markup.list" = { fg = "love" }
56+"markup.quote" = { fg = "rose" }
57+"markup.link.url" = { fg = "pine", modifiers = ["underlined"]}
58+"markup.link.label" = { fg = "foam" }
59+
4760[palette]
4861base = "#191724"
4962surface = "#1f1d2e"
runtime/themes/rose_pine_dawn.toml+15−3
@@ -1,8 +1,8 @@
1−# Author: ChrisHa<chunghha@users.noreply.github.com>
21# Author: RayGervais<raygervais@hotmail.ca>
2+# Author: ChrisHa<chunghha@users.noreply.github.com>
33
4−"ui.background" = { bg = "base" }
5−"ui.menu" = "surface"
4+"ui.background" = { bg = "surface" }
5+"ui.menu" = "base"
66"ui.menu.selected" = { fg = "iris", bg = "surface" }
77"ui.linenr" = {fg = "subtle" }
88"ui.popup" = { bg = "overlay" }
@@ -38,6 +38,9 @@
3838"ui.window" = { bg = "base" }
3939"ui.help" = { bg = "overlay", fg = "foam" }
4040"text" = "text"
41+"diff.plus" = "foam"
42+"diff.delta" = "rose"
43+"diff.minus" = "love"
4144
4245"info" = "gold"
4346"hint" = "gold"
@@ -45,6 +48,15 @@
4548"diagnostic" = "rose"
4649"error" = "love"
4750
51+"markup.heading" = { fg = "rose" }
52+"markup.raw.inline" = { fg = "foam" }
53+"markup.bold" = { fg = "gold", modifiers = ["bold"] }
54+"markup.italic" = { fg = "iris", modifiers = ["italic"] }
55+"markup.list" = { fg = "love" }
56+"markup.quote" = { fg = "rose" }
57+"markup.link.url" = { fg = "pine", modifiers = ["underlined"]}
58+"markup.link.label" = { fg = "foam" }
59+
4860[palette]
4961base = "#faf4ed"
5062surface = "#fffaf3"
runtime/themes/solarized_dark.toml+4
@@ -22,6 +22,10 @@
2222"module" = { fg = "violet" }
2323"tag" = { fg = "magenta" }
2424
25+"diff.plus" = { fg = "green" }
26+"diff.delta" = { fg = "orange" }
27+"diff.minus" = { fg = "red" }
28+
2529# 背景
2630"ui.background" = { bg = "base03" }
2731
runtime/themes/solarized_light.toml+4
@@ -22,6 +22,10 @@
2222"module" = { fg = "violet" }
2323"tag" = { fg = "magenta" }
2424
25+"diff.plus" = { fg = "green" }
26+"diff.delta" = { fg = "orange" }
27+"diff.minus" = { fg = "red" }
28+
2529# 背景
2630"ui.background" = { bg = "base03" }
2731
runtime/themes/spacebones_light.toml+4
@@ -30,6 +30,10 @@
3030"label" = "#b1951d"
3131"module" = "#b1951d"
3232
33+"diff.plus" = "#2d9574"
34+"diff.delta" = "#715ab1"
35+"diff.minus" = "#ba2f59"
36+
3337"warning" = { fg = "#da8b55" }
3438"error" = { fg = "#e0211d" }
3539"info" = { fg = "#b1951d" }
theme.toml+5−1
@@ -31,9 +31,13 @@ label = "honey"
3131"markup.heading" = "lilac"
3232"markup.bold" = { modifiers = ["bold"] }
3333"markup.italic" = { modifiers = ["italic"] }
34−"markup.underline.link" = { fg = "silver", modifiers = ["underlined"] }
34+"markup.link.url" = { fg = "silver", modifiers = ["underlined"] }
3535"markup.raw" = "almond"
3636
37+"diff.plus" = "#35bf86"
38+"diff.minus" = "#f22c86"
39+"diff.delta" = "#6f44f0"
40+
3741# TODO: diferentiate doc comment
3842# concat (ERROR) @error.syntax and "MISSING ;" selectors for errors
3943
xtask/Cargo.toml+3−3
@@ -1,11 +1,11 @@
11[package]
22name = "xtask"
3−version = "0.5.0"
3+version = "0.6.0"
44edition = "2021"
55
66# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77
88[dependencies]
9−helix-term = { version = "0.5", path = "../helix-term" }
10−helix-core = { version = "0.5", path = "../helix-core" }
9+helix-term = { version = "0.6", path = "../helix-term" }
10+helix-core = { version = "0.6", path = "../helix-core" }
1111toml = "0.5"