mirror of
https://github.com/helix-editor/helix.git
synced 2025-01-19 13:37:06 +04:00
Merge branch 'master' into cursor-shape-new
This commit is contained in:
commit
449624965b
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -102,7 +102,7 @@ jobs:
|
||||
fi
|
||||
cp -r runtime dist
|
||||
|
||||
- uses: actions/upload-artifact@v2.3.0
|
||||
- uses: actions/upload-artifact@v2.3.1
|
||||
with:
|
||||
name: bins-${{ matrix.build }}
|
||||
path: dist
|
||||
|
38
.gitmodules
vendored
38
.gitmodules
vendored
@ -142,11 +142,15 @@
|
||||
path = helix-syntax/languages/tree-sitter-perl
|
||||
url = https://github.com/ganezdragon/tree-sitter-perl
|
||||
shallow = true
|
||||
[submodule "helix-syntax/languages/tree-sitter-comment"]
|
||||
path = helix-syntax/languages/tree-sitter-comment
|
||||
url = https://github.com/stsewd/tree-sitter-comment
|
||||
shallow = true
|
||||
[submodule "helix-syntax/languages/tree-sitter-wgsl"]
|
||||
path = helix-syntax/languages/tree-sitter-wgsl
|
||||
url = https://github.com/szebniok/tree-sitter-wgsl
|
||||
shallow = true
|
||||
[submodule "helix-syntax/tree-sitter-llvm"]
|
||||
[submodule "helix-syntax/languages/tree-sitter-llvm"]
|
||||
path = helix-syntax/languages/tree-sitter-llvm
|
||||
url = https://github.com/benwilliamgraham/tree-sitter-llvm
|
||||
shallow = true
|
||||
@ -154,3 +158,35 @@
|
||||
path = helix-syntax/languages/tree-sitter-markdown
|
||||
url = https://github.com/MDeiml/tree-sitter-markdown
|
||||
shallow = true
|
||||
[submodule "helix-syntax/languages/tree-sitter-dart"]
|
||||
path = helix-syntax/languages/tree-sitter-dart
|
||||
url = https://github.com/UserNobody14/tree-sitter-dart.git
|
||||
shallow = true
|
||||
[submodule "helix-syntax/languages/tree-sitter-dockerfile"]
|
||||
path = helix-syntax/languages/tree-sitter-dockerfile
|
||||
url = https://github.com/camdencheek/tree-sitter-dockerfile.git
|
||||
shallow = true
|
||||
[submodule "helix-syntax/languages/tree-sitter-fish"]
|
||||
path = helix-syntax/languages/tree-sitter-fish
|
||||
url = https://github.com/ram02z/tree-sitter-fish
|
||||
shallow = true
|
||||
[submodule "helix-syntax/languages/tree-sitter-git-commit"]
|
||||
path = helix-syntax/languages/tree-sitter-git-commit
|
||||
url = https://github.com/the-mikedavis/tree-sitter-git-commit.git
|
||||
shallow = true
|
||||
[submodule "helix-syntax/languages/tree-sitter-llvm-mir"]
|
||||
path = helix-syntax/languages/tree-sitter-llvm-mir
|
||||
url = https://github.com/Flakebi/tree-sitter-llvm-mir.git
|
||||
shallow = true
|
||||
[submodule "helix-syntax/languages/tree-sitter-git-diff"]
|
||||
path = helix-syntax/languages/tree-sitter-git-diff
|
||||
url = https://github.com/the-mikedavis/tree-sitter-git-diff.git
|
||||
shallow = true
|
||||
[submodule "helix-syntax/languages/tree-sitter-tablegen"]
|
||||
path = helix-syntax/languages/tree-sitter-tablegen
|
||||
url = https://github.com/Flakebi/tree-sitter-tablegen
|
||||
shallow = true
|
||||
[submodule "helix-syntax/languages/tree-sitter-git-rebase"]
|
||||
path = helix-syntax/languages/tree-sitter-git-rebase
|
||||
url = https://github.com/the-mikedavis/tree-sitter-git-rebase.git
|
||||
shallow = true
|
||||
|
119
CHANGELOG.md
119
CHANGELOG.md
@ -1,4 +1,123 @@
|
||||
|
||||
# 0.6.0 (2022-01-04)
|
||||
|
||||
Happy new year and a big shout out to all the contributors! We had 55 contributors in this release.
|
||||
|
||||
Helix has popped up in DPorts and Fedora Linux via COPR ([#1270](https://github.com/helix-editor/helix/pull/1270))
|
||||
|
||||
As usual the following is a brief summary, refer to the git history for a full log:
|
||||
|
||||
Breaking changes:
|
||||
|
||||
- fix: Normalize backtab into shift-tab
|
||||
|
||||
Features:
|
||||
|
||||
- Macros ([#1234](https://github.com/helix-editor/helix/pull/1234))
|
||||
- Add reverse search functionality ([#958](https://github.com/helix-editor/helix/pull/958))
|
||||
- Allow keys to be mapped to sequences of commands ([#589](https://github.com/helix-editor/helix/pull/589))
|
||||
- Make it possible to keybind TypableCommands ([#1169](https://github.com/helix-editor/helix/pull/1169))
|
||||
- Detect workspace root using language markers ([#1370](https://github.com/helix-editor/helix/pull/1370))
|
||||
- Add WORD textobject ([#991](https://github.com/helix-editor/helix/pull/991))
|
||||
- Add LSP rename_symbol (space-r) ([#1011](https://github.com/helix-editor/helix/pull/1011))
|
||||
- Added workspace_symbol_picker ([#1041](https://github.com/helix-editor/helix/pull/1041))
|
||||
- Detect filetype from shebang line ([#1001](https://github.com/helix-editor/helix/pull/1001))
|
||||
- Allow piping from stdin into a buffer on startup ([#996](https://github.com/helix-editor/helix/pull/996))
|
||||
- Add auto pairs for same-char pairs ([#1219](https://github.com/helix-editor/helix/pull/1219))
|
||||
- Update settings at runtime ([#798](https://github.com/helix-editor/helix/pull/798))
|
||||
- Enable thin LTO (cccc194)
|
||||
|
||||
Commands:
|
||||
- :wonly -- window only ([#1057](https://github.com/helix-editor/helix/pull/1057))
|
||||
- buffer-close (:bc, :bclose) ([#1035](https://github.com/helix-editor/helix/pull/1035))
|
||||
- Add :<line> and :goto <line> commands ([#1128](https://github.com/helix-editor/helix/pull/1128))
|
||||
- :sort command ([#1288](https://github.com/helix-editor/helix/pull/1288))
|
||||
- Add m textobject for pair under cursor ([#961](https://github.com/helix-editor/helix/pull/961))
|
||||
- Implement "Goto next buffer / Goto previous buffer" commands ([#950](https://github.com/helix-editor/helix/pull/950))
|
||||
- Implement "Goto last modification" command ([#1067](https://github.com/helix-editor/helix/pull/1067))
|
||||
- Add trim_selections command ([#1092](https://github.com/helix-editor/helix/pull/1092))
|
||||
- Add movement shortcut for history ([#1088](https://github.com/helix-editor/helix/pull/1088))
|
||||
- Add command to inc/dec number under cursor ([#1027](https://github.com/helix-editor/helix/pull/1027))
|
||||
- Add support for dates for increment/decrement
|
||||
- Align selections (&) ([#1101](https://github.com/helix-editor/helix/pull/1101))
|
||||
- Implement no-yank delete/change ([#1099](https://github.com/helix-editor/helix/pull/1099))
|
||||
- Implement black hole register ([#1165](https://github.com/helix-editor/helix/pull/1165))
|
||||
- gf as goto_file (gf) ([#1102](https://github.com/helix-editor/helix/pull/1102))
|
||||
- Add last modified file (gm) ([#1093](https://github.com/helix-editor/helix/pull/1093))
|
||||
- ensure_selections_forward ([#1393](https://github.com/helix-editor/helix/pull/1393))
|
||||
- Readline style insert mode ([#1039](https://github.com/helix-editor/helix/pull/1039))
|
||||
|
||||
Usability improvements and fixes:
|
||||
|
||||
- Detect filetype on :write ([#1141](https://github.com/helix-editor/helix/pull/1141))
|
||||
- Add single and double quotes to matching pairs ([#995](https://github.com/helix-editor/helix/pull/995))
|
||||
- Launch with defaults upon invalid config/theme (rather than panicking) ([#982](https://github.com/helix-editor/helix/pull/982))
|
||||
- If switching away from an empty scratch buffer, remove it ([#935](https://github.com/helix-editor/helix/pull/935))
|
||||
- Truncate the starts of file paths instead of the ends in picker ([#951](https://github.com/helix-editor/helix/pull/951))
|
||||
- Truncate the start of file paths in the StatusLine ([#1351](https://github.com/helix-editor/helix/pull/1351))
|
||||
- Prevent picker from previewing binaries or large file ([#939](https://github.com/helix-editor/helix/pull/939))
|
||||
- Inform when reaching undo/redo bounds ([#981](https://github.com/helix-editor/helix/pull/981))
|
||||
- search_impl will only align cursor center when it isn't in view ([#959](https://github.com/helix-editor/helix/pull/959))
|
||||
- Add <C-h>, <C-u>, <C-d>, Delete in prompt mode ([#1034](https://github.com/helix-editor/helix/pull/1034))
|
||||
- Restore screen position when aborting search ([#1047](https://github.com/helix-editor/helix/pull/1047))
|
||||
- Buffer picker: show is_modifier flag ([#1020](https://github.com/helix-editor/helix/pull/1020))
|
||||
- Add commit hash to version info, if present ([#957](https://github.com/helix-editor/helix/pull/957))
|
||||
- Implement indent-aware delete ([#1120](https://github.com/helix-editor/helix/pull/1120))
|
||||
- Jump to end char of surrounding pair from any cursor pos ([#1121](https://github.com/helix-editor/helix/pull/1121))
|
||||
- File picker configuration ([#988](https://github.com/helix-editor/helix/pull/988))
|
||||
- Fix surround cursor position calculation ([#1183](https://github.com/helix-editor/helix/pull/1183))
|
||||
- Accept count for goto_window ([#1033](https://github.com/helix-editor/helix/pull/1033))
|
||||
- Make kill_to_line_end behave like emacs ([#1235](https://github.com/helix-editor/helix/pull/1235))
|
||||
- Only use a single documentation popup ([#1241](https://github.com/helix-editor/helix/pull/1241))
|
||||
- ui: popup: Don't allow scrolling past the end of content (3307f44c)
|
||||
- Open files with spaces in filename, allow opening multiple files ([#1231](https://github.com/helix-editor/helix/pull/1231))
|
||||
- Allow paste commands to take a count ([#1261](https://github.com/helix-editor/helix/pull/1261))
|
||||
- Auto pairs selection ([#1254](https://github.com/helix-editor/helix/pull/1254))
|
||||
- Use a fuzzy matcher for commands ([#1386](https://github.com/helix-editor/helix/pull/1386))
|
||||
- Add c-s to pick word under doc cursor to prompt line & search completion ([#831](https://github.com/helix-editor/helix/pull/831))
|
||||
- Fix :earlier/:later missing changeset update ([#1069](https://github.com/helix-editor/helix/pull/1069))
|
||||
- Support extend for multiple goto ([#909](https://github.com/helix-editor/helix/pull/909))
|
||||
- Add arrow-key bindings for window switching ([#933](https://github.com/helix-editor/helix/pull/933))
|
||||
- Implement key ordering for info box ([#952](https://github.com/helix-editor/helix/pull/952))
|
||||
|
||||
LSP:
|
||||
- Implement MarkedString rendering (e128a8702)
|
||||
- Don't panic if init fails (d31bef7)
|
||||
- Configurable diagnostic severity ([#1325](https://github.com/helix-editor/helix/pull/1325))
|
||||
- Resolve completion item ([#1315](https://github.com/helix-editor/helix/pull/1315))
|
||||
- Code action command support ([#1304](https://github.com/helix-editor/helix/pull/1304))
|
||||
|
||||
Grammars:
|
||||
|
||||
- Adds mint language server ([#974](https://github.com/helix-editor/helix/pull/974))
|
||||
- Perl ([#978](https://github.com/helix-editor/helix/pull/978)) ([#1280](https://github.com/helix-editor/helix/pull/1280))
|
||||
- GLSL ([#993](https://github.com/helix-editor/helix/pull/993))
|
||||
- Racket ([#1143](https://github.com/helix-editor/helix/pull/1143))
|
||||
- WGSL ([#1166](https://github.com/helix-editor/helix/pull/1166))
|
||||
- LLVM ([#1167](https://github.com/helix-editor/helix/pull/1167)) ([#1388](https://github.com/helix-editor/helix/pull/1388)) ([#1409](https://github.com/helix-editor/helix/pull/1409)) ([#1398](https://github.com/helix-editor/helix/pull/1398))
|
||||
- Markdown (49e06787)
|
||||
- Scala ([#1278](https://github.com/helix-editor/helix/pull/1278))
|
||||
- Dart ([#1250](https://github.com/helix-editor/helix/pull/1250))
|
||||
- Fish ([#1308](https://github.com/helix-editor/helix/pull/1308))
|
||||
- Dockerfile ([#1303](https://github.com/helix-editor/helix/pull/1303))
|
||||
- Git (commit, rebase, diff) ([#1338](https://github.com/helix-editor/helix/pull/1338)) ([#1402](https://github.com/helix-editor/helix/pull/1402)) ([#1373](https://github.com/helix-editor/helix/pull/1373))
|
||||
- tree-sitter-comment ([#1300](https://github.com/helix-editor/helix/pull/1300))
|
||||
- Highlight comments in c, cpp, cmake and llvm ([#1309](https://github.com/helix-editor/helix/pull/1309))
|
||||
- Improve yaml syntax highlighting highlighting ([#1294](https://github.com/helix-editor/helix/pull/1294))
|
||||
- Improve rust syntax highlighting ([#1295](https://github.com/helix-editor/helix/pull/1295))
|
||||
- Add textobjects and indents to cmake ([#1307](https://github.com/helix-editor/helix/pull/1307))
|
||||
- Add textobjects and indents to c and cpp ([#1293](https://github.com/helix-editor/helix/pull/1293))
|
||||
|
||||
New themes:
|
||||
|
||||
- Solarized dark ([#999](https://github.com/helix-editor/helix/pull/999))
|
||||
- Solarized light ([#1010](https://github.com/helix-editor/helix/pull/1010))
|
||||
- Spacebones light ([#1131](https://github.com/helix-editor/helix/pull/1131))
|
||||
- Monokai Pro ([#1206](https://github.com/helix-editor/helix/pull/1206))
|
||||
- Base16 Light and Terminal ([#1078](https://github.com/helix-editor/helix/pull/1078))
|
||||
- and a default 16 color theme, truecolor detection
|
||||
- Dracula ([#1258](https://github.com/helix-editor/helix/pull/1258))
|
||||
|
||||
# 0.5.0 (2021-10-28)
|
||||
|
||||
A big shout out to all the contributors! We had 46 contributors in this release.
|
||||
|
81
Cargo.lock
generated
81
Cargo.lock
generated
@ -13,9 +13,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.51"
|
||||
version = "1.0.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
|
||||
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
@ -78,9 +78,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chardetng"
|
||||
version = "0.1.15"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83ee29c16b81c32fbc882ecc568305793338a8353952573db837f4f4a6cd5c2e"
|
||||
checksum = "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"encoding_rs",
|
||||
@ -258,15 +258,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.18"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445"
|
||||
checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.18"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97"
|
||||
checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@ -275,15 +275,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.18"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12"
|
||||
checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.18"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
|
||||
checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@ -366,10 +366,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "helix-core"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"chrono",
|
||||
"encoding_rs",
|
||||
"etcetera",
|
||||
"helix-syntax",
|
||||
"log",
|
||||
@ -391,7 +392,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "helix-lsp"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-executor",
|
||||
@ -409,7 +410,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "helix-syntax"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cc",
|
||||
@ -420,7 +421,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "helix-term"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -451,7 +452,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "helix-tui"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cassowary",
|
||||
@ -464,14 +465,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "helix-view"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"chardetng",
|
||||
"clipboard-win",
|
||||
"crossterm",
|
||||
"encoding_rs",
|
||||
"futures-util",
|
||||
"helix-core",
|
||||
"helix-lsp",
|
||||
@ -690,9 +690,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
@ -700,9 +700,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.8.0"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
@ -847,9 +847,9 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "ropey"
|
||||
version = "1.3.1"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9150aff6deb25b20ed110889f070a678bcd1033e46e5e9d6fb1abeab17947f28"
|
||||
checksum = "e6b9aa65bcd9f308d37c7158b4a1afaaa32b8450213e20c9b98e7d5b3cc2fec3"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
@ -877,18 +877,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.131"
|
||||
version = "1.0.133"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1"
|
||||
checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.131"
|
||||
version = "1.0.133"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2"
|
||||
checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -897,9 +897,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.73"
|
||||
version = "1.0.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5"
|
||||
checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@ -919,9 +919,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.12"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c35dfd12afb7828318348b8c408383cf5071a086c1d4ab1c0f9840ec92dbb922"
|
||||
checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
@ -1069,11 +1069,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.14.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
|
||||
checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
@ -1089,9 +1088,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e"
|
||||
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1120,9 +1119,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter"
|
||||
version = "0.20.1"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9394e9dbfe967b5f3d6ab79e302e78b5fb7b530c368d634ff3b8d67ede138bf1"
|
||||
checksum = "c36be3222512d85a112491ae0cc280a38076022414f00b64582da1b7565ffd82"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"regex",
|
||||
@ -1262,7 +1261,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"helix-core",
|
||||
"helix-term",
|
||||
|
@ -29,6 +29,10 @@
|
||||
"namespace" = "magenta"
|
||||
"ui.help" = { fg = "white", bg = "black" }
|
||||
|
||||
"diff.plus" = "green"
|
||||
"diff.delta" = "yellow"
|
||||
"diff.minus" = "red"
|
||||
|
||||
"diagnostic" = { modifiers = ["underlined"] }
|
||||
"ui.gutter" = { bg = "black" }
|
||||
"info" = "blue"
|
||||
|
@ -1,12 +1,19 @@
|
||||
| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| bash | ✓ | | | `bash-language-server` |
|
||||
| c | ✓ | | | `clangd` |
|
||||
| c | ✓ | ✓ | ✓ | `clangd` |
|
||||
| c-sharp | ✓ | | | |
|
||||
| cmake | ✓ | | | `cmake-language-server` |
|
||||
| cpp | ✓ | | | `clangd` |
|
||||
| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
|
||||
| comment | ✓ | | | |
|
||||
| cpp | ✓ | ✓ | ✓ | `clangd` |
|
||||
| css | ✓ | | | |
|
||||
| dart | ✓ | | ✓ | `dart` |
|
||||
| dockerfile | ✓ | | | `docker-langserver` |
|
||||
| elixir | ✓ | | | `elixir-ls` |
|
||||
| fish | ✓ | ✓ | ✓ | |
|
||||
| git-commit | ✓ | | | |
|
||||
| git-diff | ✓ | | | |
|
||||
| git-rebase | ✓ | | | |
|
||||
| glsl | ✓ | | ✓ | |
|
||||
| go | ✓ | ✓ | ✓ | `gopls` |
|
||||
| html | ✓ | | | |
|
||||
@ -16,7 +23,9 @@
|
||||
| julia | ✓ | | | `julia` |
|
||||
| latex | ✓ | | | |
|
||||
| ledger | ✓ | | | |
|
||||
| llvm | ✓ | | | |
|
||||
| llvm | ✓ | ✓ | ✓ | |
|
||||
| llvm-mir | ✓ | ✓ | ✓ | |
|
||||
| llvm-mir-yaml | ✓ | | ✓ | |
|
||||
| lua | ✓ | | ✓ | |
|
||||
| markdown | ✓ | | | |
|
||||
| mint | | | | `mint` |
|
||||
@ -29,9 +38,11 @@
|
||||
| protobuf | ✓ | | ✓ | |
|
||||
| python | ✓ | ✓ | ✓ | `pylsp` |
|
||||
| racket | | | | `racket` |
|
||||
| ruby | ✓ | | | `solargraph` |
|
||||
| ruby | ✓ | | ✓ | `solargraph` |
|
||||
| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
|
||||
| scala | ✓ | | ✓ | `metals` |
|
||||
| svelte | ✓ | | ✓ | `svelteserver` |
|
||||
| tablegen | ✓ | ✓ | ✓ | |
|
||||
| toml | ✓ | | | |
|
||||
| tsq | ✓ | | | |
|
||||
| tsx | ✓ | | | `typescript-language-server` |
|
||||
|
@ -20,6 +20,7 @@
|
||||
| `:quit-all`, `:qa` | Close all views. |
|
||||
| `:quit-all!`, `:qa!` | Close all views forcefully (ignoring unsaved changes). |
|
||||
| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
|
||||
| `:cquit!`, `:cq!` | Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2). |
|
||||
| `:theme` | Change the editor theme. |
|
||||
| `:clipboard-yank` | Yank main selection into system clipboard. |
|
||||
| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
|
||||
@ -41,3 +42,6 @@
|
||||
| `:hsplit`, `:hs`, `:sp` | Open the file in a horizontal split. |
|
||||
| `:tutor` | Open the tutorial. |
|
||||
| `:goto`, `:g` | Go to line number. |
|
||||
| `:set-option`, `:set` | Set a config option at runtime |
|
||||
| `:sort` | Sort ranges in selection. |
|
||||
| `:rsort` | Sort ranges in selection in reverse order. |
|
||||
|
@ -27,22 +27,32 @@ ## languages.toml
|
||||
|
||||
These are the available keys and descriptions for the file.
|
||||
|
||||
| Key | Description |
|
||||
| ---- | ----------- |
|
||||
| name | The name of the language |
|
||||
| scope | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.<name>` or `text.<name>` in case of markup languages |
|
||||
| injection-regex | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. |
|
||||
| file-types | The filetypes of the language, for example `["yml", "yaml"]` |
|
||||
| shebangs | The interpreters from the shebang line, for example `["sh", "bash"]` |
|
||||
| roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
|
||||
| auto-format | Whether to autoformat this language when saving |
|
||||
| comment-token | The token to use as a comment-token |
|
||||
| indent | The indent to use. Has sub keys `tab-width` and `unit` |
|
||||
| config | Language server configuration |
|
||||
| Key | Description |
|
||||
| ---- | ----------- |
|
||||
| name | The name of the language |
|
||||
| scope | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.<name>` or `text.<name>` in case of markup languages |
|
||||
| injection-regex | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. |
|
||||
| file-types | The filetypes of the language, for example `["yml", "yaml"]` |
|
||||
| shebangs | The interpreters from the shebang line, for example `["sh", "bash"]` |
|
||||
| roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` |
|
||||
| auto-format | Whether to autoformat this language when saving |
|
||||
| diagnostic-severity | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
|
||||
| comment-token | The token to use as a comment-token |
|
||||
| indent | The indent to use. Has sub keys `tab-width` and `unit` |
|
||||
| config | Language server configuration |
|
||||
|
||||
## Queries
|
||||
|
||||
For a language to have syntax-highlighting and indentation among other things, you have to add queries. Add a directory for your language with the path `runtime/queries/<name>/`. The tree-sitter [website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries) gives more info on how to write queries.
|
||||
For a language to have syntax-highlighting and indentation among
|
||||
other things, you have to add queries. Add a directory for your
|
||||
language with the path `runtime/queries/<name>/`. The tree-sitter
|
||||
[website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
|
||||
gives more info on how to write queries.
|
||||
|
||||
> NOTE: When evaluating queries, the first matching query takes
|
||||
precedence, which is different from other editors like neovim where
|
||||
the last matching query supercedes the ones before it. See
|
||||
[this issue][neovim-query-precedence] for an example.
|
||||
|
||||
## Common Issues
|
||||
|
||||
@ -58,3 +68,4 @@ ## Common Issues
|
||||
|
||||
[treesitter-language-injection]: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
|
||||
[languages.toml]: https://github.com/helix-editor/helix/blob/master/languages.toml
|
||||
[neovim-query-precedence]: https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090
|
||||
|
@ -46,39 +46,39 @@ ### Movement
|
||||
|
||||
### Changes
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `r` | Replace with a character | `replace` |
|
||||
| `R` | Replace with yanked text | `replace_with_yanked` |
|
||||
| `~` | Switch case of the selected text | `switch_case` |
|
||||
| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` |
|
||||
| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` |
|
||||
| `i` | Insert before selection | `insert_mode` |
|
||||
| `a` | Insert after selection (append) | `append_mode` |
|
||||
| `I` | Insert at the start of the line | `prepend_to_line` |
|
||||
| `A` | Insert at the end of the line | `append_to_line` |
|
||||
| `o` | Open new line below selection | `open_below` |
|
||||
| `O` | Open new line above selection | `open_above` |
|
||||
| `.` | Repeat last change | N/A |
|
||||
| `u` | Undo change | `undo` |
|
||||
| `U` | Redo change | `redo` |
|
||||
| `Alt-u` | Move backward in history | `earlier` |
|
||||
| `Alt-U` | Move forward in history | `later` |
|
||||
| `y` | Yank selection | `yank` |
|
||||
| `p` | Paste after selection | `paste_after` |
|
||||
| `P` | Paste before selection | `paste_before` |
|
||||
| `"` `<reg>` | Select a register to yank to or paste from | `select_register` |
|
||||
| `>` | Indent selection | `indent` |
|
||||
| `<` | Unindent selection | `unindent` |
|
||||
| `=` | Format selection (currently nonfunctional/disabled) (**LSP**) | `format_selections` |
|
||||
| `d` | Delete selection | `delete_selection` |
|
||||
| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` |
|
||||
| `c` | Change selection (delete and enter insert mode) | `change_selection` |
|
||||
| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` |
|
||||
| `Ctrl-a` | Increment object (number) under cursor | `increment` |
|
||||
| `Ctrl-x` | Decrement object (number) under cursor | `decrement` |
|
||||
| `q` | Start/stop macro recording to the selected register | `record_macro` |
|
||||
| `Q` | Play back a recorded macro from the selected register | `play_macro` |
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `r` | Replace with a character | `replace` |
|
||||
| `R` | Replace with yanked text | `replace_with_yanked` |
|
||||
| `~` | Switch case of the selected text | `switch_case` |
|
||||
| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` |
|
||||
| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` |
|
||||
| `i` | Insert before selection | `insert_mode` |
|
||||
| `a` | Insert after selection (append) | `append_mode` |
|
||||
| `I` | Insert at the start of the line | `prepend_to_line` |
|
||||
| `A` | Insert at the end of the line | `append_to_line` |
|
||||
| `o` | Open new line below selection | `open_below` |
|
||||
| `O` | Open new line above selection | `open_above` |
|
||||
| `.` | Repeat last change | N/A |
|
||||
| `u` | Undo change | `undo` |
|
||||
| `U` | Redo change | `redo` |
|
||||
| `Alt-u` | Move backward in history | `earlier` |
|
||||
| `Alt-U` | Move forward in history | `later` |
|
||||
| `y` | Yank selection | `yank` |
|
||||
| `p` | Paste after selection | `paste_after` |
|
||||
| `P` | Paste before selection | `paste_before` |
|
||||
| `"` `<reg>` | Select a register to yank to or paste from | `select_register` |
|
||||
| `>` | Indent selection | `indent` |
|
||||
| `<` | Unindent selection | `unindent` |
|
||||
| `=` | Format selection (currently nonfunctional/disabled) (**LSP**) | `format_selections` |
|
||||
| `d` | Delete selection | `delete_selection` |
|
||||
| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` |
|
||||
| `c` | Change selection (delete and enter insert mode) | `change_selection` |
|
||||
| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` |
|
||||
| `Ctrl-a` | Increment object (number) under cursor | `increment` |
|
||||
| `Ctrl-x` | Decrement object (number) under cursor | `decrement` |
|
||||
| `Q` | Start/stop macro recording to the selected register (experimental) | `record_macro` |
|
||||
| `q` | Play back a recorded macro from the selected register (experimental) | `replay_macro` |
|
||||
|
||||
#### Shell
|
||||
|
||||
@ -161,7 +161,7 @@ #### Goto mode
|
||||
|
||||
| Key | Description | Command |
|
||||
| ----- | ----------- | ------- |
|
||||
| `g` | Go to the start of the file | `goto_file_start` |
|
||||
| `g` | Go to line number `<n>` else start of file | `goto_file_start` |
|
||||
| `e` | Go to the end of the file | `goto_last_line` |
|
||||
| `f` | Go to files in the selection | `goto_file` |
|
||||
| `h` | Go to the start of the line | `goto_line_start` |
|
||||
@ -261,6 +261,8 @@ #### Unimpaired
|
||||
| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
|
||||
| `[space` | Add newline above | `add_newline_above` |
|
||||
| `]space` | Add newline below | `add_newline_below` |
|
||||
| `]o` | Expand syntax tree object selection. | `expand_selection` |
|
||||
| `[o` | Shrink syntax tree object selection. | `shrink_selection` |
|
||||
|
||||
## Insert Mode
|
||||
|
||||
|
@ -11,4 +11,3 @@ # in <config_dir>/helix/languages.toml
|
||||
name = "rust"
|
||||
auto-format = false
|
||||
```
|
||||
|
||||
|
@ -105,6 +105,7 @@ #### Syntax highlighting
|
||||
|
||||
- `type` - Types
|
||||
- `builtin` - Primitive types provided by the language (`int`, `usize`)
|
||||
- `constructor`
|
||||
|
||||
- `constant` (TODO: constant.other.placeholder for %v)
|
||||
- `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
|
||||
@ -169,13 +170,20 @@ #### Syntax highlighting
|
||||
- `numbered`
|
||||
- `bold`
|
||||
- `italic`
|
||||
- `underline`
|
||||
- `link`
|
||||
- `link`
|
||||
- `url`
|
||||
- `label`
|
||||
- `quote`
|
||||
- `raw`
|
||||
- `inline`
|
||||
- `block`
|
||||
|
||||
- `diff` - version control changes
|
||||
- `plus` - additions
|
||||
- `minus` - deletions
|
||||
- `delta` - modifications
|
||||
- `moved` - renamed or moved files/changes
|
||||
|
||||
#### Interface
|
||||
|
||||
These scopes are used for theming the editor interface.
|
||||
|
24
flake.lock
24
flake.lock
@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"devshell": {
|
||||
"locked": {
|
||||
"lastModified": 1637575296,
|
||||
"narHash": "sha256-ZY8YR5u8aglZPe27+AJMnPTG6645WuavB+w0xmhTarw=",
|
||||
"lastModified": 1639692811,
|
||||
"narHash": "sha256-wOOBH0fVsfNqw/5ZWRoKspyesoXBgiwEOUBH4c7JKEo=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "0e56ef21ba1a717169953122c7415fa6a8cd2618",
|
||||
"rev": "d3a1f5bec3632b33346865b1c165bf2420bb2f52",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -41,11 +41,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1638425401,
|
||||
"narHash": "sha256-xc8ayvR3u90hSCMEy0zHHKav7lEgljAFXL4oIkWRp3M=",
|
||||
"lastModified": 1639807801,
|
||||
"narHash": "sha256-y32tMq1LTRVbMW3QN5i98iOQjQt2QSsif3ayUkD1o3g=",
|
||||
"owner": "yusdacra",
|
||||
"repo": "nix-cargo-integration",
|
||||
"rev": "1f8b511bb30f7d7b9051dfbb4784390bc0d48d37",
|
||||
"rev": "b5bbaa4f5239e6f0619846f9a5380f07baa853d3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -56,11 +56,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1638376152,
|
||||
"narHash": "sha256-ucgLpVqhFnClH7YRUHBHnmiOd82RZdFR3XJt36ks5fE=",
|
||||
"lastModified": 1639699734,
|
||||
"narHash": "sha256-tlX6WebGmiHb2Hmniff+ltYp+7dRfdsBxw9YczLsP60=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6daa4a5c045d40e6eae60a3b6e427e8700f1c07f",
|
||||
"rev": "03ec468b14067729a285c2c7cfa7b9434a04816c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -99,11 +99,11 @@
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1638497756,
|
||||
"narHash": "sha256-zKOvMKqGp71ZBnR+hBlPcv4TwNN82COW9EF+6ygrFs8=",
|
||||
"lastModified": 1639880499,
|
||||
"narHash": "sha256-/BibDmFwgWuuTUkNVO6YlvuTSWM9dpBvlZoTAPs7ORI=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "783722a22ee5d762ac5c1c7b418b57b3010c827a",
|
||||
"rev": "c6c83589ae048af20d93d01eb07a4176012093d0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "helix-core"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
@ -13,7 +13,7 @@ include = ["src/**/*", "README.md"]
|
||||
[features]
|
||||
|
||||
[dependencies]
|
||||
helix-syntax = { version = "0.5", path = "../helix-syntax" }
|
||||
helix-syntax = { version = "0.6", path = "../helix-syntax" }
|
||||
|
||||
ropey = "1.3"
|
||||
smallvec = "1.7"
|
||||
@ -23,7 +23,7 @@ unicode-width = "0.1"
|
||||
unicode-general-category = "0.4"
|
||||
# slab = "0.4.2"
|
||||
tree-sitter = "0.20"
|
||||
once_cell = "1.8"
|
||||
once_cell = "1.9"
|
||||
arc-swap = "1"
|
||||
regex = "1"
|
||||
|
||||
@ -35,6 +35,7 @@ toml = "0.5"
|
||||
similar = "2.1"
|
||||
|
||||
etcetera = "0.3"
|
||||
encoding_rs = "0.8"
|
||||
|
||||
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! When typing the opening character of one of the possible pairs defined below,
|
||||
//! this module provides the functionality to insert the paired closing character.
|
||||
|
||||
use crate::{Range, Rope, Selection, Tendril, Transaction};
|
||||
use crate::{movement::Direction, Range, Rope, Selection, Tendril, Transaction};
|
||||
use log::debug;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@ -30,7 +30,6 @@
|
||||
|
||||
// [TODO]
|
||||
// * delete implementation where it erases the whole bracket (|) -> |
|
||||
// * do not reduce to cursors; use whole selections, and surround with pair
|
||||
// * change to multi character pairs to handle cases like placing the cursor in the
|
||||
// middle of triple quotes, and more exotic pairs like Jinja's {% %}
|
||||
|
||||
@ -38,20 +37,18 @@
|
||||
pub fn hook(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
|
||||
debug!("autopairs hook selection: {:#?}", selection);
|
||||
|
||||
let cursors = selection.clone().cursors(doc.slice(..));
|
||||
|
||||
for &(open, close) in PAIRS {
|
||||
if open == ch {
|
||||
if open == close {
|
||||
return Some(handle_same(doc, &cursors, open, CLOSE_BEFORE, OPEN_BEFORE));
|
||||
return Some(handle_same(doc, selection, open, CLOSE_BEFORE, OPEN_BEFORE));
|
||||
} else {
|
||||
return Some(handle_open(doc, &cursors, open, close, CLOSE_BEFORE));
|
||||
return Some(handle_open(doc, selection, open, close, CLOSE_BEFORE));
|
||||
}
|
||||
}
|
||||
|
||||
if close == ch {
|
||||
// && char_at pos == close
|
||||
return Some(handle_close(doc, &cursors, open, close));
|
||||
return Some(handle_close(doc, selection, open, close));
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,6 +63,36 @@ fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
|
||||
doc.get_char(pos - 1)
|
||||
}
|
||||
|
||||
/// calculate what the resulting range should be for an auto pair insertion
|
||||
fn get_next_range(
|
||||
start_range: &Range,
|
||||
offset: usize,
|
||||
typed_char: char,
|
||||
len_inserted: usize,
|
||||
) -> Range {
|
||||
let end_head = start_range.head + offset + typed_char.len_utf8();
|
||||
|
||||
let end_anchor = match (start_range.len(), start_range.direction()) {
|
||||
// if we have a zero width cursor, it shifts to the same number
|
||||
(0, _) => end_head,
|
||||
|
||||
// if we are inserting for a regular one-width cursor, the anchor
|
||||
// moves with the head
|
||||
(1, Direction::Forward) => end_head - 1,
|
||||
(1, Direction::Backward) => end_head + 1,
|
||||
|
||||
// if we are appending, the anchor stays where it is; only offset
|
||||
// for multiple range insertions
|
||||
(_, Direction::Forward) => start_range.anchor + offset,
|
||||
|
||||
// when we are inserting in front of a selection, we need to move
|
||||
// the anchor over by however many characters were inserted overall
|
||||
(_, Direction::Backward) => start_range.anchor + offset + len_inserted,
|
||||
};
|
||||
|
||||
Range::new(end_anchor, end_head)
|
||||
}
|
||||
|
||||
fn handle_open(
|
||||
doc: &Rope,
|
||||
selection: &Selection,
|
||||
@ -74,36 +101,32 @@ fn handle_open(
|
||||
close_before: &str,
|
||||
) -> Transaction {
|
||||
let mut end_ranges = SmallVec::with_capacity(selection.len());
|
||||
|
||||
let mut offs = 0;
|
||||
|
||||
let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
|
||||
let start_head = start_range.head;
|
||||
let cursor = start_range.cursor(doc.slice(..));
|
||||
let next_char = doc.get_char(cursor);
|
||||
let len_inserted;
|
||||
|
||||
let next = doc.get_char(start_head);
|
||||
let end_head = start_head + offs + open.len_utf8();
|
||||
|
||||
let end_anchor = if start_range.is_empty() {
|
||||
end_head
|
||||
} else {
|
||||
start_range.anchor + offs
|
||||
};
|
||||
|
||||
end_ranges.push(Range::new(end_anchor, end_head));
|
||||
|
||||
match next {
|
||||
let change = match next_char {
|
||||
Some(ch) if !close_before.contains(ch) => {
|
||||
offs += open.len_utf8();
|
||||
(start_head, start_head, Some(Tendril::from_char(open)))
|
||||
len_inserted = open.len_utf8();
|
||||
(cursor, cursor, Some(Tendril::from_char(open)))
|
||||
}
|
||||
// None | Some(ch) if close_before.contains(ch) => {}
|
||||
_ => {
|
||||
// insert open & close
|
||||
let pair = Tendril::from_iter([open, close]);
|
||||
offs += open.len_utf8() + close.len_utf8();
|
||||
(start_head, start_head, Some(pair))
|
||||
len_inserted = open.len_utf8() + close.len_utf8();
|
||||
(cursor, cursor, Some(pair))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let next_range = get_next_range(start_range, offs, open, len_inserted);
|
||||
end_ranges.push(next_range);
|
||||
offs += len_inserted;
|
||||
|
||||
change
|
||||
});
|
||||
|
||||
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
|
||||
@ -117,28 +140,28 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) ->
|
||||
let mut offs = 0;
|
||||
|
||||
let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
|
||||
let start_head = start_range.head;
|
||||
let next = doc.get_char(start_head);
|
||||
let end_head = start_head + offs + close.len_utf8();
|
||||
let cursor = start_range.cursor(doc.slice(..));
|
||||
let next_char = doc.get_char(cursor);
|
||||
let mut len_inserted = 0;
|
||||
|
||||
let end_anchor = if start_range.is_empty() {
|
||||
end_head
|
||||
let change = if next_char == Some(close) {
|
||||
// return transaction that moves past close
|
||||
(cursor, cursor, None) // no-op
|
||||
} else {
|
||||
start_range.anchor + offs
|
||||
len_inserted += close.len_utf8();
|
||||
(cursor, cursor, Some(Tendril::from_char(close)))
|
||||
};
|
||||
|
||||
end_ranges.push(Range::new(end_anchor, end_head));
|
||||
let next_range = get_next_range(start_range, offs, close, len_inserted);
|
||||
end_ranges.push(next_range);
|
||||
offs += len_inserted;
|
||||
|
||||
if next == Some(close) {
|
||||
// return transaction that moves past close
|
||||
(start_head, start_head, None) // no-op
|
||||
} else {
|
||||
offs += close.len_utf8();
|
||||
(start_head, start_head, Some(Tendril::from_char(close)))
|
||||
}
|
||||
change
|
||||
});
|
||||
|
||||
transaction.with_selection(Selection::new(end_ranges, selection.primary_index()))
|
||||
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
|
||||
debug!("auto pair transaction: {:#?}", t);
|
||||
t
|
||||
}
|
||||
|
||||
/// handle cases where open and close is the same, or in triples ("""docstring""")
|
||||
@ -154,42 +177,41 @@ fn handle_same(
|
||||
let mut offs = 0;
|
||||
|
||||
let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
|
||||
let start_head = start_range.head;
|
||||
let end_head = start_head + offs + token.len_utf8();
|
||||
let cursor = start_range.cursor(doc.slice(..));
|
||||
let mut len_inserted = 0;
|
||||
|
||||
// if selection, retain anchor, if cursor, move over
|
||||
let end_anchor = if start_range.is_empty() {
|
||||
end_head
|
||||
} else {
|
||||
start_range.anchor + offs
|
||||
};
|
||||
let next_char = doc.get_char(cursor);
|
||||
let prev_char = prev_char(doc, cursor);
|
||||
|
||||
end_ranges.push(Range::new(end_anchor, end_head));
|
||||
|
||||
let next = doc.get_char(start_head);
|
||||
let prev = prev_char(doc, start_head);
|
||||
|
||||
if next == Some(token) {
|
||||
let change = if next_char == Some(token) {
|
||||
// return transaction that moves past close
|
||||
(start_head, start_head, None) // no-op
|
||||
(cursor, cursor, None) // no-op
|
||||
} else {
|
||||
let mut pair = Tendril::with_capacity(2 * token.len_utf8() as u32);
|
||||
pair.push_char(token);
|
||||
|
||||
// for equal pairs, don't insert both open and close if either
|
||||
// side has a non-pair char
|
||||
if (next.is_none() || close_before.contains(next.unwrap()))
|
||||
&& (prev.is_none() || open_before.contains(prev.unwrap()))
|
||||
if (next_char.is_none() || close_before.contains(next_char.unwrap()))
|
||||
&& (prev_char.is_none() || open_before.contains(prev_char.unwrap()))
|
||||
{
|
||||
pair.push_char(token);
|
||||
}
|
||||
|
||||
offs += pair.len();
|
||||
(start_head, start_head, Some(pair))
|
||||
}
|
||||
len_inserted += pair.len();
|
||||
(cursor, cursor, Some(pair))
|
||||
};
|
||||
|
||||
let next_range = get_next_range(start_range, offs, token, len_inserted);
|
||||
end_ranges.push(next_range);
|
||||
offs += len_inserted;
|
||||
|
||||
change
|
||||
});
|
||||
|
||||
transaction.with_selection(Selection::new(end_ranges, selection.primary_index()))
|
||||
let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
|
||||
debug!("auto pair transaction: {:#?}", t);
|
||||
t
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -252,7 +274,20 @@ fn test_insert_blank() {
|
||||
&Selection::single(1, 0),
|
||||
PAIRS,
|
||||
|open, close| format!("{}{}", open, close),
|
||||
&Selection::single(1, 1),
|
||||
&Selection::single(2, 1),
|
||||
);
|
||||
}
|
||||
|
||||
/// [] -> append ( -> ([])
|
||||
#[test]
|
||||
fn test_append_blank() {
|
||||
test_hooks_with_pairs(
|
||||
// this is what happens when you have a totally blank document and then append
|
||||
&Rope::from("\n\n"),
|
||||
&Selection::single(0, 2),
|
||||
PAIRS,
|
||||
|open, close| format!("\n{}{}\n", open, close),
|
||||
&Selection::single(0, 3),
|
||||
);
|
||||
}
|
||||
|
||||
@ -276,26 +311,50 @@ fn test_insert_blank_multi_cursor() {
|
||||
)
|
||||
},
|
||||
&Selection::new(
|
||||
smallvec!(Range::point(1), Range::point(4), Range::point(7),),
|
||||
smallvec!(Range::new(2, 1), Range::new(5, 4), Range::new(8, 7),),
|
||||
0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// [TODO] broken until it works with selections
|
||||
/// fo[o] -> append ( -> fo[o(])
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_append() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from("foo"),
|
||||
&Rope::from("foo\n"),
|
||||
&Selection::single(2, 4),
|
||||
PAIRS,
|
||||
|open, close| format!("foo{}{}", open, close),
|
||||
differing_pairs(),
|
||||
|open, close| format!("foo{}{}\n", open, close),
|
||||
&Selection::single(2, 5),
|
||||
);
|
||||
}
|
||||
|
||||
/// fo[o] fo[o(])
|
||||
/// fo[o] -> append ( -> fo[o(])
|
||||
/// fo[o] fo[o(])
|
||||
#[test]
|
||||
fn test_append_multi() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from("foo\nfoo\nfoo\n"),
|
||||
&Selection::new(
|
||||
smallvec!(Range::new(2, 4), Range::new(6, 8), Range::new(10, 12)),
|
||||
0,
|
||||
),
|
||||
differing_pairs(),
|
||||
|open, close| {
|
||||
format!(
|
||||
"foo{open}{close}\nfoo{open}{close}\nfoo{open}{close}\n",
|
||||
open = open,
|
||||
close = close
|
||||
)
|
||||
},
|
||||
&Selection::new(
|
||||
smallvec!(Range::new(2, 5), Range::new(8, 11), Range::new(14, 17)),
|
||||
0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// ([]) -> insert ) -> ()[]
|
||||
#[test]
|
||||
fn test_insert_close_inside_pair() {
|
||||
@ -307,7 +366,23 @@ fn test_insert_close_inside_pair() {
|
||||
&Selection::single(2, 1),
|
||||
*close,
|
||||
&doc,
|
||||
&Selection::point(2),
|
||||
&Selection::single(3, 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// [(]) -> append ) -> [()]
|
||||
#[test]
|
||||
fn test_append_close_inside_pair() {
|
||||
for (open, close) in PAIRS {
|
||||
let doc = Rope::from(format!("{}{}\n", open, close));
|
||||
|
||||
test_hooks(
|
||||
&doc,
|
||||
&Selection::single(0, 2),
|
||||
*close,
|
||||
&doc,
|
||||
&Selection::single(0, 3),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -323,8 +398,33 @@ fn test_insert_close_inside_pair_multi_cursor() {
|
||||
);
|
||||
|
||||
let expected_sel = Selection::new(
|
||||
// smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),),
|
||||
smallvec!(Range::point(2), Range::point(5), Range::point(8),),
|
||||
smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),),
|
||||
0,
|
||||
);
|
||||
|
||||
for (open, close) in PAIRS {
|
||||
let doc = Rope::from(format!(
|
||||
"{open}{close}\n{open}{close}\n{open}{close}\n",
|
||||
open = open,
|
||||
close = close
|
||||
));
|
||||
|
||||
test_hooks(&doc, &sel, *close, &doc, &expected_sel);
|
||||
}
|
||||
}
|
||||
|
||||
/// [(]) [()]
|
||||
/// [(]) -> append ) -> [()]
|
||||
/// [(]) [()]
|
||||
#[test]
|
||||
fn test_append_close_inside_pair_multi_cursor() {
|
||||
let sel = Selection::new(
|
||||
smallvec!(Range::new(0, 2), Range::new(3, 5), Range::new(6, 8),),
|
||||
0,
|
||||
);
|
||||
|
||||
let expected_sel = Selection::new(
|
||||
smallvec!(Range::new(0, 3), Range::new(3, 6), Range::new(6, 9),),
|
||||
0,
|
||||
);
|
||||
|
||||
@ -343,7 +443,7 @@ fn test_insert_close_inside_pair_multi_cursor() {
|
||||
#[test]
|
||||
fn test_insert_open_inside_pair() {
|
||||
let sel = Selection::single(2, 1);
|
||||
let expected_sel = Selection::point(2);
|
||||
let expected_sel = Selection::single(3, 2);
|
||||
|
||||
for (open, close) in differing_pairs() {
|
||||
let doc = Rope::from(format!("{}{}", open, close));
|
||||
@ -357,11 +457,49 @@ fn test_insert_open_inside_pair() {
|
||||
}
|
||||
}
|
||||
|
||||
/// [word(]) -> append ( -> [word((]))
|
||||
#[test]
|
||||
fn test_append_open_inside_pair() {
|
||||
let sel = Selection::single(0, 6);
|
||||
let expected_sel = Selection::single(0, 7);
|
||||
|
||||
for (open, close) in differing_pairs() {
|
||||
let doc = Rope::from(format!("word{}{}", open, close));
|
||||
let expected_doc = Rope::from(format!(
|
||||
"word{open}{open}{close}{close}",
|
||||
open = open,
|
||||
close = close
|
||||
));
|
||||
|
||||
test_hooks(&doc, &sel, *open, &expected_doc, &expected_sel);
|
||||
}
|
||||
}
|
||||
|
||||
/// ([]) -> insert " -> ("[]")
|
||||
#[test]
|
||||
fn test_insert_nested_open_inside_pair() {
|
||||
let sel = Selection::single(2, 1);
|
||||
let expected_sel = Selection::point(2);
|
||||
let expected_sel = Selection::single(3, 2);
|
||||
|
||||
for (outer_open, outer_close) in differing_pairs() {
|
||||
let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
|
||||
|
||||
for (inner_open, inner_close) in matching_pairs() {
|
||||
let expected_doc = Rope::from(format!(
|
||||
"{}{}{}{}",
|
||||
outer_open, inner_open, inner_close, outer_close
|
||||
));
|
||||
|
||||
test_hooks(&doc, &sel, *inner_open, &expected_doc, &expected_sel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [(]) -> append " -> [("]")
|
||||
#[test]
|
||||
fn test_append_nested_open_inside_pair() {
|
||||
let sel = Selection::single(0, 2);
|
||||
let expected_sel = Selection::single(0, 3);
|
||||
|
||||
for (outer_open, outer_close) in differing_pairs() {
|
||||
let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
|
||||
@ -385,21 +523,44 @@ fn test_insert_open_before_non_pair() {
|
||||
&Selection::single(1, 0),
|
||||
PAIRS,
|
||||
|open, _| format!("{}word", open),
|
||||
&Selection::point(1),
|
||||
&Selection::single(2, 1),
|
||||
)
|
||||
}
|
||||
|
||||
// [TODO] broken until it works with selections
|
||||
/// [wor]d -> insert ( -> ([wor]d
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_insert_open_with_selection() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from("word"),
|
||||
&Selection::single(0, 4),
|
||||
&Selection::single(3, 0),
|
||||
PAIRS,
|
||||
|open, _| format!("{}word", open),
|
||||
&Selection::single(1, 5),
|
||||
&Selection::single(4, 1),
|
||||
)
|
||||
}
|
||||
|
||||
/// [wor]d -> append ) -> [wor)]d
|
||||
#[test]
|
||||
fn test_append_close_inside_non_pair_with_selection() {
|
||||
let sel = Selection::single(0, 4);
|
||||
let expected_sel = Selection::single(0, 5);
|
||||
|
||||
for (_, close) in PAIRS {
|
||||
let doc = Rope::from("word");
|
||||
let expected_doc = Rope::from(format!("wor{}d", close));
|
||||
test_hooks(&doc, &sel, *close, &expected_doc, &expected_sel);
|
||||
}
|
||||
}
|
||||
|
||||
/// foo[ wor]d -> insert ( -> foo([) wor]d
|
||||
#[test]
|
||||
fn test_insert_open_trailing_word_with_selection() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from("foo word"),
|
||||
&Selection::single(7, 3),
|
||||
differing_pairs(),
|
||||
|open, close| format!("foo{}{} word", open, close),
|
||||
&Selection::single(9, 4),
|
||||
)
|
||||
}
|
||||
|
||||
@ -413,7 +574,7 @@ fn test_insert_open_with_selection() {
|
||||
fn test_insert_open_after_non_pair() {
|
||||
let doc = Rope::from("word");
|
||||
let sel = Selection::single(5, 4);
|
||||
let expected_sel = Selection::point(5);
|
||||
let expected_sel = Selection::single(6, 5);
|
||||
|
||||
test_hooks_with_pairs(
|
||||
&doc,
|
||||
@ -431,4 +592,18 @@ fn test_insert_open_after_non_pair() {
|
||||
&expected_sel,
|
||||
);
|
||||
}
|
||||
|
||||
/// appending with only a cursor should stay a cursor
|
||||
///
|
||||
/// [] -> append to end "foo -> "foo[]"
|
||||
#[test]
|
||||
fn test_append_single_cursor() {
|
||||
test_hooks_with_pairs(
|
||||
&Rope::from("\n"),
|
||||
&Selection::single(0, 1),
|
||||
PAIRS,
|
||||
|open, close| format!("{}{}\n", open, close),
|
||||
&Selection::single(1, 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,19 @@
|
||||
//! LSP diagnostic utility types.
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Describes the severity level of a [`Diagnostic`].
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)]
|
||||
pub enum Severity {
|
||||
Error,
|
||||
Warning,
|
||||
Info,
|
||||
Hint,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl Default for Severity {
|
||||
fn default() -> Self {
|
||||
Self::Hint
|
||||
}
|
||||
}
|
||||
|
||||
/// A range of `char`s within the text.
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::{
|
||||
chars::{char_is_line_ending, char_is_whitespace},
|
||||
find_first_non_whitespace_char,
|
||||
syntax::{IndentQuery, LanguageConfiguration, Syntax},
|
||||
tree_sitter::Node,
|
||||
Rope, RopeSlice,
|
||||
@ -174,8 +173,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
|
||||
|
||||
/// To determine indentation of a newly inserted line, figure out the indentation at the last col
|
||||
/// of the previous line.
|
||||
#[allow(dead_code)]
|
||||
fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
|
||||
pub fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
|
||||
let mut len = 0;
|
||||
for ch in line.chars() {
|
||||
match ch {
|
||||
@ -210,10 +208,15 @@ fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Nod
|
||||
Some(node)
|
||||
}
|
||||
|
||||
fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool) -> usize {
|
||||
// NOTE: can't use contains() on query because of comparing Vec<String> and &str
|
||||
// https://doc.rust-lang.org/std/vec/struct.Vec.html#method.contains
|
||||
|
||||
/// Calculate the indentation at a given treesitter node.
|
||||
/// If newline is false, then any "indent" nodes on the line are ignored ("outdent" still applies).
|
||||
/// This is because the indentation is only increased starting at the second line of the node.
|
||||
fn calculate_indentation(
|
||||
query: &IndentQuery,
|
||||
node: Option<Node>,
|
||||
line: usize,
|
||||
newline: bool,
|
||||
) -> usize {
|
||||
let mut increment: isize = 0;
|
||||
|
||||
let mut node = match node {
|
||||
@ -221,70 +224,45 @@ fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool)
|
||||
None => return 0,
|
||||
};
|
||||
|
||||
let mut prev_start = node.start_position().row;
|
||||
let mut current_line = line;
|
||||
let mut consider_indent = newline;
|
||||
let mut increment_from_line: isize = 0;
|
||||
|
||||
// if we're calculating indentation for a brand new line then the current node will become the
|
||||
// parent node. We need to take it's indentation level into account too.
|
||||
let node_kind = node.kind();
|
||||
if newline && query.indent.contains(node_kind) {
|
||||
increment += 1;
|
||||
}
|
||||
|
||||
while let Some(parent) = node.parent() {
|
||||
let parent_kind = parent.kind();
|
||||
let start = parent.start_position().row;
|
||||
|
||||
// detect deeply nested indents in the same line
|
||||
// .map(|a| { <-- ({ is two scopes
|
||||
// let len = 1; <-- indents one level
|
||||
// }) <-- }) is two scopes
|
||||
let starts_same_line = start == prev_start;
|
||||
|
||||
if query.outdent.contains(node.kind()) && !starts_same_line {
|
||||
// we outdent by skipping the rules for the current level and jumping up
|
||||
// node = parent;
|
||||
increment -= 1;
|
||||
// continue;
|
||||
loop {
|
||||
let node_kind = node.kind();
|
||||
let start = node.start_position().row;
|
||||
if current_line != start {
|
||||
// Indent/dedent by at most one per line:
|
||||
// .map(|a| { <-- ({ is two scopes
|
||||
// let len = 1; <-- indents one level
|
||||
// }) <-- }) is two scopes
|
||||
if consider_indent || increment_from_line < 0 {
|
||||
increment += increment_from_line.signum();
|
||||
}
|
||||
increment_from_line = 0;
|
||||
current_line = start;
|
||||
consider_indent = true;
|
||||
}
|
||||
|
||||
if query.indent.contains(parent_kind) // && not_first_or_last_sibling
|
||||
&& !starts_same_line
|
||||
{
|
||||
// println!("is_scope {}", parent_kind);
|
||||
prev_start = start;
|
||||
increment += 1
|
||||
if query.outdent.contains(node_kind) {
|
||||
increment_from_line -= 1;
|
||||
}
|
||||
if query.indent.contains(node_kind) {
|
||||
increment_from_line += 1;
|
||||
}
|
||||
|
||||
// if last_scope && increment > 0 && ...{ ignore }
|
||||
|
||||
node = parent;
|
||||
if let Some(parent) = node.parent() {
|
||||
node = parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if consider_indent || increment_from_line < 0 {
|
||||
increment += increment_from_line.signum();
|
||||
}
|
||||
|
||||
increment.max(0) as usize
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn suggested_indent_for_line(
|
||||
language_config: &LanguageConfiguration,
|
||||
syntax: Option<&Syntax>,
|
||||
text: RopeSlice,
|
||||
line_num: usize,
|
||||
_tab_width: usize,
|
||||
) -> usize {
|
||||
if let Some(start) = find_first_non_whitespace_char(text.line(line_num)) {
|
||||
return suggested_indent_for_pos(
|
||||
Some(language_config),
|
||||
syntax,
|
||||
text,
|
||||
start + text.line_to_char(line_num),
|
||||
false,
|
||||
);
|
||||
};
|
||||
|
||||
// if the line is blank, indent should be zero
|
||||
0
|
||||
}
|
||||
|
||||
// TODO: two usecases: if we are triggering this for a new, blank line:
|
||||
// - it should return 0 when mass indenting stuff
|
||||
// - it should look up the wrapper node and count it too when we press o/O
|
||||
@ -293,23 +271,20 @@ pub fn suggested_indent_for_pos(
|
||||
syntax: Option<&Syntax>,
|
||||
text: RopeSlice,
|
||||
pos: usize,
|
||||
line: usize,
|
||||
new_line: bool,
|
||||
) -> usize {
|
||||
) -> Option<usize> {
|
||||
if let (Some(query), Some(syntax)) = (
|
||||
language_config.and_then(|config| config.indent_query()),
|
||||
syntax,
|
||||
) {
|
||||
let byte_start = text.char_to_byte(pos);
|
||||
let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
|
||||
|
||||
// let config = load indentation query config from Syntax(should contain language_config)
|
||||
|
||||
// TODO: special case for comments
|
||||
// TODO: if preserve_leading_whitespace
|
||||
calculate_indentation(query, node, new_line)
|
||||
Some(calculate_indentation(query, node, line, new_line))
|
||||
} else {
|
||||
// TODO: heuristics for non-tree sitter grammars
|
||||
0
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@ -442,6 +417,7 @@ pub fn change<I>(document: &Document, changes: I) -> Self
|
||||
);
|
||||
|
||||
let doc = Rope::from(doc);
|
||||
use crate::diagnostic::Severity;
|
||||
use crate::syntax::{
|
||||
Configuration, IndentationConfiguration, LanguageConfiguration, Loader,
|
||||
};
|
||||
@ -459,6 +435,8 @@ pub fn change<I>(document: &Document, changes: I) -> Self
|
||||
roots: vec![],
|
||||
comment_token: None,
|
||||
auto_format: false,
|
||||
diagnostic_severity: Severity::Warning,
|
||||
tree_sitter_library: None,
|
||||
language_server: None,
|
||||
indent: Some(IndentationConfiguration {
|
||||
tab_width: 4,
|
||||
@ -482,14 +460,23 @@ pub fn change<I>(document: &Document, changes: I) -> Self
|
||||
|
||||
for i in 0..doc.len_lines() {
|
||||
let line = text.line(i);
|
||||
let indent = indent_level_for_line(line, tab_width);
|
||||
assert_eq!(
|
||||
suggested_indent_for_line(&language_config, Some(&syntax), text, i, tab_width),
|
||||
indent,
|
||||
"line {}: {}",
|
||||
i,
|
||||
line
|
||||
);
|
||||
if let Some(pos) = crate::find_first_non_whitespace_char(line) {
|
||||
let indent = indent_level_for_line(line, tab_width);
|
||||
assert_eq!(
|
||||
suggested_indent_for_pos(
|
||||
Some(&language_config),
|
||||
Some(&syntax),
|
||||
text,
|
||||
text.line_to_char(i) + pos,
|
||||
i,
|
||||
false
|
||||
),
|
||||
Some(indent),
|
||||
"line {}: \"{}\"",
|
||||
i,
|
||||
line
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
pub use encoding_rs as encoding;
|
||||
|
||||
pub mod auto_pairs;
|
||||
pub mod chars;
|
||||
pub mod comment;
|
||||
@ -37,8 +39,14 @@ pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
|
||||
line.chars().position(|ch| !ch.is_whitespace())
|
||||
}
|
||||
|
||||
/// Find `.git` root.
|
||||
pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
|
||||
/// Find project root.
|
||||
///
|
||||
/// Order of detection:
|
||||
/// * Top-most folder containing a root marker in current git repository
|
||||
/// * Git repostory root if no marker detected
|
||||
/// * Top-most folder containing a root marker if not git repository detected
|
||||
/// * Current working directory as fallback
|
||||
pub fn find_root(root: Option<&str>, root_markers: &[String]) -> Option<std::path::PathBuf> {
|
||||
let current_dir = std::env::current_dir().expect("unable to determine current directory");
|
||||
|
||||
let root = match root {
|
||||
@ -50,16 +58,30 @@ pub fn find_root(root: Option<&str>) -> Option<std::path::PathBuf> {
|
||||
current_dir.join(root)
|
||||
}
|
||||
}
|
||||
None => current_dir,
|
||||
None => current_dir.clone(),
|
||||
};
|
||||
|
||||
let mut top_marker = None;
|
||||
for ancestor in root.ancestors() {
|
||||
// TODO: also use defined roots if git isn't found
|
||||
for marker in root_markers {
|
||||
if ancestor.join(marker).exists() {
|
||||
top_marker = Some(ancestor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// don't go higher than repo
|
||||
if ancestor.join(".git").is_dir() {
|
||||
return Some(ancestor.to_path_buf());
|
||||
// Use workspace if detected from marker
|
||||
return Some(top_marker.unwrap_or(ancestor).to_path_buf());
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
// In absence of git repo, use workspace if detected
|
||||
if top_marker.is_some() {
|
||||
top_marker.map(|a| a.to_path_buf())
|
||||
} else {
|
||||
Some(current_dir)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runtime_dir() -> std::path::PathBuf {
|
||||
|
@ -11,7 +11,7 @@
|
||||
('\"', '\"'),
|
||||
];
|
||||
|
||||
// limit matching pairs to only ( ) { } [ ] < >
|
||||
// limit matching pairs to only ( ) { } [ ] < > ' ' " "
|
||||
|
||||
// Returns the position of the matching bracket under cursor.
|
||||
//
|
||||
|
@ -307,8 +307,6 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::array::{self, IntoIter};
|
||||
|
||||
use ropey::Rope;
|
||||
|
||||
use super::*;
|
||||
@ -360,7 +358,7 @@ fn horizontal_moves_through_single_line_text() {
|
||||
((Direction::Backward, 999usize), (0, 0)), // |This is a simple alphabetic line
|
||||
];
|
||||
|
||||
for ((direction, amount), coordinates) in IntoIter::new(moves_and_expected_coordinates) {
|
||||
for ((direction, amount), coordinates) in moves_and_expected_coordinates {
|
||||
range = move_horizontally(slice, range, direction, amount, Movement::Move);
|
||||
assert_eq!(coords_at_pos(slice, range.head), coordinates.into())
|
||||
}
|
||||
@ -374,7 +372,7 @@ fn horizontal_moves_through_multiline_text() {
|
||||
|
||||
let mut range = Range::point(position);
|
||||
|
||||
let moves_and_expected_coordinates = IntoIter::new([
|
||||
let moves_and_expected_coordinates = [
|
||||
((Direction::Forward, 11usize), (1, 1)), // Multiline\nt|ext sample\n...
|
||||
((Direction::Backward, 1usize), (1, 0)), // Multiline\n|text sample\n...
|
||||
((Direction::Backward, 5usize), (0, 5)), // Multi|line\ntext sample\n...
|
||||
@ -384,7 +382,7 @@ fn horizontal_moves_through_multiline_text() {
|
||||
((Direction::Backward, 0usize), (0, 3)), // Mul|tiline\ntext sample\n...
|
||||
((Direction::Forward, 999usize), (5, 0)), // ...and whitespaced\n|
|
||||
((Direction::Forward, 999usize), (5, 0)), // ...and whitespaced\n|
|
||||
]);
|
||||
];
|
||||
|
||||
for ((direction, amount), coordinates) in moves_and_expected_coordinates {
|
||||
range = move_horizontally(slice, range, direction, amount, Movement::Move);
|
||||
@ -402,11 +400,11 @@ fn selection_extending_moves_in_single_line_text() {
|
||||
let mut range = Range::point(position);
|
||||
let original_anchor = range.anchor;
|
||||
|
||||
let moves = IntoIter::new([
|
||||
let moves = [
|
||||
(Direction::Forward, 1usize),
|
||||
(Direction::Forward, 5usize),
|
||||
(Direction::Backward, 3usize),
|
||||
]);
|
||||
];
|
||||
|
||||
for (direction, amount) in moves {
|
||||
range = move_horizontally(slice, range, direction, amount, Movement::Extend);
|
||||
@ -420,7 +418,7 @@ fn vertical_moves_in_single_column() {
|
||||
let slice = text.slice(..);
|
||||
let position = pos_at_coords(slice, (0, 0).into(), true);
|
||||
let mut range = Range::point(position);
|
||||
let moves_and_expected_coordinates = IntoIter::new([
|
||||
let moves_and_expected_coordinates = [
|
||||
((Direction::Forward, 1usize), (1, 0)),
|
||||
((Direction::Forward, 2usize), (3, 0)),
|
||||
((Direction::Forward, 1usize), (4, 0)),
|
||||
@ -430,7 +428,7 @@ fn vertical_moves_in_single_column() {
|
||||
((Direction::Backward, 0usize), (4, 0)),
|
||||
((Direction::Forward, 5), (5, 0)),
|
||||
((Direction::Forward, 999usize), (5, 0)),
|
||||
]);
|
||||
];
|
||||
|
||||
for ((direction, amount), coordinates) in moves_and_expected_coordinates {
|
||||
range = move_vertically(slice, range, direction, amount, Movement::Move);
|
||||
@ -450,7 +448,7 @@ enum Axis {
|
||||
H,
|
||||
V,
|
||||
}
|
||||
let moves_and_expected_coordinates = IntoIter::new([
|
||||
let moves_and_expected_coordinates = [
|
||||
// Places cursor at the end of line
|
||||
((Axis::H, Direction::Forward, 8usize), (0, 8)),
|
||||
// First descent preserves column as the target line is wider
|
||||
@ -463,7 +461,7 @@ enum Axis {
|
||||
((Axis::V, Direction::Backward, 999usize), (0, 8)),
|
||||
((Axis::V, Direction::Forward, 4usize), (4, 8)),
|
||||
((Axis::V, Direction::Forward, 999usize), (5, 0)),
|
||||
]);
|
||||
];
|
||||
|
||||
for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
|
||||
range = match axis {
|
||||
@ -489,7 +487,7 @@ enum Axis {
|
||||
H,
|
||||
V,
|
||||
}
|
||||
let moves_and_expected_coordinates = IntoIter::new([
|
||||
let moves_and_expected_coordinates = [
|
||||
// Places cursor at the fourth kana.
|
||||
((Axis::H, Direction::Forward, 4), (0, 4)),
|
||||
// Descent places cursor at the 4th character.
|
||||
@ -498,7 +496,7 @@ enum Axis {
|
||||
((Axis::H, Direction::Backward, 1usize), (1, 3)),
|
||||
// Jumping back up 1 line.
|
||||
((Axis::V, Direction::Backward, 1usize), (0, 3)),
|
||||
]);
|
||||
];
|
||||
|
||||
for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
|
||||
range = match axis {
|
||||
@ -530,7 +528,7 @@ fn nonsensical_ranges_panic_on_backwards_movement_attempt_in_debug_mode() {
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_start_of_next_words() {
|
||||
let tests = array::IntoIter::new([
|
||||
let tests = [
|
||||
("Basic forward motion stops at the first space",
|
||||
vec![(1, Range::new(0, 0), Range::new(0, 6))]),
|
||||
(" Starting from a boundary advances the anchor",
|
||||
@ -604,7 +602,7 @@ fn test_behaviour_when_moving_to_start_of_next_words() {
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 6)),
|
||||
]),
|
||||
]);
|
||||
];
|
||||
|
||||
for (sample, scenario) in tests {
|
||||
for (count, begin, expected_end) in scenario.into_iter() {
|
||||
@ -616,7 +614,7 @@ fn test_behaviour_when_moving_to_start_of_next_words() {
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_start_of_next_long_words() {
|
||||
let tests = array::IntoIter::new([
|
||||
let tests = [
|
||||
("Basic forward motion stops at the first space",
|
||||
vec![(1, Range::new(0, 0), Range::new(0, 6))]),
|
||||
(" Starting from a boundary advances the anchor",
|
||||
@ -688,7 +686,7 @@ fn test_behaviour_when_moving_to_start_of_next_long_words() {
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 8)),
|
||||
]),
|
||||
]);
|
||||
];
|
||||
|
||||
for (sample, scenario) in tests {
|
||||
for (count, begin, expected_end) in scenario.into_iter() {
|
||||
@ -700,7 +698,7 @@ fn test_behaviour_when_moving_to_start_of_next_long_words() {
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_start_of_previous_words() {
|
||||
let tests = array::IntoIter::new([
|
||||
let tests = [
|
||||
("Basic backward motion from the middle of a word",
|
||||
vec![(1, Range::new(3, 3), Range::new(4, 0))]),
|
||||
|
||||
@ -773,7 +771,7 @@ fn test_behaviour_when_moving_to_start_of_previous_words() {
|
||||
vec![
|
||||
(1, Range::new(0, 6), Range::new(6, 0)),
|
||||
]),
|
||||
]);
|
||||
];
|
||||
|
||||
for (sample, scenario) in tests {
|
||||
for (count, begin, expected_end) in scenario.into_iter() {
|
||||
@ -785,7 +783,7 @@ fn test_behaviour_when_moving_to_start_of_previous_words() {
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_start_of_previous_long_words() {
|
||||
let tests = array::IntoIter::new([
|
||||
let tests = [
|
||||
(
|
||||
"Basic backward motion from the middle of a word",
|
||||
vec![(1, Range::new(3, 3), Range::new(4, 0))],
|
||||
@ -870,7 +868,7 @@ fn test_behaviour_when_moving_to_start_of_previous_long_words() {
|
||||
vec![
|
||||
(1, Range::new(0, 8), Range::new(8, 0)),
|
||||
]),
|
||||
]);
|
||||
];
|
||||
|
||||
for (sample, scenario) in tests {
|
||||
for (count, begin, expected_end) in scenario.into_iter() {
|
||||
@ -882,7 +880,7 @@ fn test_behaviour_when_moving_to_start_of_previous_long_words() {
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_end_of_next_words() {
|
||||
let tests = array::IntoIter::new([
|
||||
let tests = [
|
||||
("Basic forward motion from the start of a word to the end of it",
|
||||
vec![(1, Range::new(0, 0), Range::new(0, 5))]),
|
||||
("Basic forward motion from the end of a word to the end of the next",
|
||||
@ -954,7 +952,7 @@ fn test_behaviour_when_moving_to_end_of_next_words() {
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 5)),
|
||||
]),
|
||||
]);
|
||||
];
|
||||
|
||||
for (sample, scenario) in tests {
|
||||
for (count, begin, expected_end) in scenario.into_iter() {
|
||||
@ -966,7 +964,7 @@ fn test_behaviour_when_moving_to_end_of_next_words() {
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_end_of_previous_words() {
|
||||
let tests = array::IntoIter::new([
|
||||
let tests = [
|
||||
("Basic backward motion from the middle of a word",
|
||||
vec![(1, Range::new(9, 9), Range::new(10, 5))]),
|
||||
("Starting from after boundary retreats the anchor",
|
||||
@ -1036,7 +1034,7 @@ fn test_behaviour_when_moving_to_end_of_previous_words() {
|
||||
vec![
|
||||
(1, Range::new(0, 10), Range::new(10, 4)),
|
||||
]),
|
||||
]);
|
||||
];
|
||||
|
||||
for (sample, scenario) in tests {
|
||||
for (count, begin, expected_end) in scenario.into_iter() {
|
||||
@ -1048,7 +1046,7 @@ fn test_behaviour_when_moving_to_end_of_previous_words() {
|
||||
|
||||
#[test]
|
||||
fn test_behaviour_when_moving_to_end_of_next_long_words() {
|
||||
let tests = array::IntoIter::new([
|
||||
let tests = [
|
||||
("Basic forward motion from the start of a word to the end of it",
|
||||
vec![(1, Range::new(0, 0), Range::new(0, 5))]),
|
||||
("Basic forward motion from the end of a word to the end of the next",
|
||||
@ -1118,7 +1116,7 @@ fn test_behaviour_when_moving_to_end_of_next_long_words() {
|
||||
vec![
|
||||
(1, Range::new(0, 0), Range::new(0, 7)),
|
||||
]),
|
||||
]);
|
||||
];
|
||||
|
||||
for (sample, scenario) in tests {
|
||||
for (count, begin, expected_end) in scenario.into_iter() {
|
||||
|
@ -1,7 +1,5 @@
|
||||
use crate::{Range, RopeSlice, Selection, Syntax};
|
||||
|
||||
// TODO: to contract_selection we'd need to store the previous ranges before expand.
|
||||
// Maybe just contract to the first child node?
|
||||
pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
|
||||
let tree = syntax.tree();
|
||||
|
||||
@ -34,3 +32,30 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn shrink_selection(syntax: &Syntax, text: RopeSlice, selection: &Selection) -> Selection {
|
||||
let tree = syntax.tree();
|
||||
|
||||
selection.clone().transform(|range| {
|
||||
let from = text.char_to_byte(range.from());
|
||||
let to = text.char_to_byte(range.to());
|
||||
|
||||
let descendant = match tree.root_node().descendant_for_byte_range(from, to) {
|
||||
// find first child, if not possible, fallback to the node that contains selection
|
||||
Some(descendant) => match descendant.child(0) {
|
||||
Some(child) => child,
|
||||
None => descendant,
|
||||
},
|
||||
None => return range,
|
||||
};
|
||||
|
||||
let from = text.byte_to_char(descendant.start_byte());
|
||||
let to = text.byte_to_char(descendant.end_byte());
|
||||
|
||||
if range.head < range.anchor {
|
||||
Range::new(to, from)
|
||||
} else {
|
||||
Range::new(from, to)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary,
|
||||
prev_grapheme_boundary,
|
||||
},
|
||||
movement::Direction,
|
||||
Assoc, ChangeSet, RopeSlice,
|
||||
};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
@ -82,6 +83,13 @@ pub fn to(&self) -> usize {
|
||||
std::cmp::max(self.anchor, self.head)
|
||||
}
|
||||
|
||||
/// Total length of the range.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.to() - self.from()
|
||||
}
|
||||
|
||||
/// The (inclusive) range of lines that the range overlaps.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -102,6 +110,27 @@ pub fn is_empty(&self) -> bool {
|
||||
self.anchor == self.head
|
||||
}
|
||||
|
||||
/// `Direction::Backward` when head < anchor.
|
||||
/// `Direction::Backward` otherwise.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn direction(&self) -> Direction {
|
||||
if self.head < self.anchor {
|
||||
Direction::Backward
|
||||
} else {
|
||||
Direction::Forward
|
||||
}
|
||||
}
|
||||
|
||||
// flips the direction of the selection
|
||||
pub fn flip(&self) -> Self {
|
||||
Self {
|
||||
anchor: self.head,
|
||||
head: self.anchor,
|
||||
horiz: self.horiz,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check two ranges for overlap.
|
||||
#[must_use]
|
||||
pub fn overlaps(&self, other: &Self) -> bool {
|
||||
@ -111,6 +140,11 @@ pub fn overlaps(&self, other: &Self) -> bool {
|
||||
self.from() == other.from() || (self.to() > other.from() && other.to() > self.from())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains_range(&self, other: &Self) -> bool {
|
||||
self.from() <= other.from() && self.to() >= other.to()
|
||||
}
|
||||
|
||||
pub fn contains(&self, pos: usize) -> bool {
|
||||
self.from() <= pos && pos < self.to()
|
||||
}
|
||||
@ -515,6 +549,39 @@ pub fn iter(&self) -> std::slice::Iter<'_, Range> {
|
||||
pub fn len(&self) -> usize {
|
||||
self.ranges.len()
|
||||
}
|
||||
|
||||
// returns true if self ⊇ other
|
||||
pub fn contains(&self, other: &Selection) -> bool {
|
||||
// can't contain other if it is larger
|
||||
if other.len() > self.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let (mut iter_self, mut iter_other) = (self.iter(), other.iter());
|
||||
let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next());
|
||||
|
||||
loop {
|
||||
match (ele_self, ele_other) {
|
||||
(Some(ra), Some(rb)) => {
|
||||
if !ra.contains_range(rb) {
|
||||
// `self` doesn't contain next element from `other`, advance `self`, we need to match all from `other`
|
||||
ele_self = iter_self.next();
|
||||
} else {
|
||||
// matched element from `other`, advance `other`
|
||||
ele_other = iter_other.next();
|
||||
};
|
||||
}
|
||||
(None, Some(_)) => {
|
||||
// exhausted `self`, we can't match the reminder of `other`
|
||||
return false;
|
||||
}
|
||||
(_, None) => {
|
||||
// no elements from `other` left to match, `self` contains `other`
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Selection {
|
||||
@ -953,4 +1020,30 @@ fn test_split_on_matches() {
|
||||
&["", "abcd", "efg", "rs", "xyz"]
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_selection_contains() {
|
||||
fn contains(a: Vec<(usize, usize)>, b: Vec<(usize, usize)>) -> bool {
|
||||
let sela = Selection::new(a.iter().map(|a| Range::new(a.0, a.1)).collect(), 0);
|
||||
let selb = Selection::new(b.iter().map(|b| Range::new(b.0, b.1)).collect(), 0);
|
||||
sela.contains(&selb)
|
||||
}
|
||||
|
||||
// exact match
|
||||
assert!(contains(vec!((1, 1)), vec!((1, 1))));
|
||||
|
||||
// larger set contains smaller
|
||||
assert!(contains(vec!((1, 1), (2, 2), (3, 3)), vec!((2, 2))));
|
||||
|
||||
// multiple matches
|
||||
assert!(contains(vec!((1, 1), (2, 2)), vec!((1, 1), (2, 2))));
|
||||
|
||||
// smaller set can't contain bigger
|
||||
assert!(!contains(vec!((1, 1)), vec!((1, 1), (2, 2))));
|
||||
|
||||
assert!(contains(
|
||||
vec!((1, 1), (2, 4), (5, 6), (7, 9), (10, 13)),
|
||||
vec!((3, 4), (7, 9))
|
||||
));
|
||||
assert!(!contains(vec!((1, 1), (5, 6)), vec!((1, 6))));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
chars::char_is_line_ending,
|
||||
diagnostic::Severity,
|
||||
regex::Regex,
|
||||
transaction::{ChangeSet, Operation},
|
||||
Rope, RopeSlice, Tendril,
|
||||
@ -63,6 +64,10 @@ pub struct LanguageConfiguration {
|
||||
|
||||
#[serde(default)]
|
||||
pub auto_format: bool,
|
||||
#[serde(default)]
|
||||
pub diagnostic_severity: Severity,
|
||||
|
||||
pub tree_sitter_library: Option<String>, // tree-sitter library name, defaults to language_id
|
||||
|
||||
// content_regex
|
||||
#[serde(default, skip_serializing, deserialize_with = "deserialize_regex")]
|
||||
@ -189,9 +194,14 @@ fn initialize_highlight(&self, scopes: &[String]) -> Option<Arc<HighlightConfigu
|
||||
if highlights_query.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let language = get_language(&crate::RUNTIME_DIR, &self.language_id)
|
||||
.map_err(|e| log::info!("{}", e))
|
||||
.ok()?;
|
||||
let language = get_language(
|
||||
&crate::RUNTIME_DIR,
|
||||
self.tree_sitter_library
|
||||
.as_deref()
|
||||
.unwrap_or(&self.language_id),
|
||||
)
|
||||
.map_err(|e| log::info!("{}", e))
|
||||
.ok()?;
|
||||
let config = HighlightConfiguration::new(
|
||||
language,
|
||||
&highlights_query,
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "helix-lsp"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
@ -12,7 +12,7 @@ homepage = "https://helix-editor.com"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
helix-core = { version = "0.5", path = "../helix-core" }
|
||||
helix-core = { version = "0.6", path = "../helix-core" }
|
||||
|
||||
anyhow = "1.0"
|
||||
futures-executor = "0.3"
|
||||
@ -23,5 +23,5 @@ lsp-types = { version = "0.91", features = ["proposed"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.14", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
|
||||
tokio = { version = "1.15", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
|
||||
tokio-stream = "0.1.8"
|
||||
|
@ -31,6 +31,7 @@ pub struct Client {
|
||||
pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
|
||||
offset_encoding: OffsetEncoding,
|
||||
config: Option<Value>,
|
||||
root_markers: Vec<String>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@ -39,6 +40,7 @@ pub fn start(
|
||||
cmd: &str,
|
||||
args: &[String],
|
||||
config: Option<Value>,
|
||||
root_markers: Vec<String>,
|
||||
id: usize,
|
||||
) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
|
||||
let process = Command::new(cmd)
|
||||
@ -68,6 +70,7 @@ pub fn start(
|
||||
capabilities: OnceCell::new(),
|
||||
offset_encoding: OffsetEncoding::Utf8,
|
||||
config,
|
||||
root_markers,
|
||||
};
|
||||
|
||||
Ok((client, server_rx, initialize_notify))
|
||||
@ -202,7 +205,7 @@ pub fn reply(
|
||||
Ok(result) => Output::Success(Success {
|
||||
jsonrpc: Some(Version::V2),
|
||||
id,
|
||||
result,
|
||||
result: serde_json::to_value(result)?,
|
||||
}),
|
||||
Err(error) => Output::Failure(Failure {
|
||||
jsonrpc: Some(Version::V2),
|
||||
@ -225,7 +228,8 @@ pub fn reply(
|
||||
|
||||
pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
|
||||
// TODO: delay any requests that are triggered prior to initialize
|
||||
let root = find_root(None).and_then(|root| lsp::Url::from_file_path(root).ok());
|
||||
let root = find_root(None, &self.root_markers)
|
||||
.and_then(|root| lsp::Url::from_file_path(root).ok());
|
||||
|
||||
if self.config.is_some() {
|
||||
log::info!("Using custom LSP config: {}", self.config.as_ref().unwrap());
|
||||
@ -556,6 +560,14 @@ pub fn completion(
|
||||
self.call::<lsp::request::Completion>(params)
|
||||
}
|
||||
|
||||
pub async fn resolve_completion_item(
|
||||
&self,
|
||||
completion_item: lsp::CompletionItem,
|
||||
) -> Result<lsp::CompletionItem> {
|
||||
self.request::<lsp::request::ResolveCompletionItem>(completion_item)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn text_document_signature_help(
|
||||
&self,
|
||||
text_document: lsp::TextDocumentIdentifier,
|
||||
@ -800,4 +812,16 @@ pub async fn rename_symbol(
|
||||
let response = self.request::<lsp::request::Rename>(params).await?;
|
||||
Ok(response.unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn command(&self, command: lsp::Command) -> impl Future<Output = Result<Value>> {
|
||||
let params = lsp::ExecuteCommandParams {
|
||||
command: command.command,
|
||||
arguments: command.arguments.unwrap_or_default(),
|
||||
work_done_progress_params: lsp::WorkDoneProgressParams {
|
||||
work_done_token: None,
|
||||
},
|
||||
};
|
||||
|
||||
self.call::<lsp::request::ExecuteCommand>(params)
|
||||
}
|
||||
}
|
||||
|
@ -66,39 +66,26 @@ pub fn lsp_pos_to_pos(
|
||||
pos: lsp::Position,
|
||||
offset_encoding: OffsetEncoding,
|
||||
) -> Option<usize> {
|
||||
let max_line = doc.lines().count().saturating_sub(1);
|
||||
let pos_line = pos.line as usize;
|
||||
let pos_line = if pos_line > max_line {
|
||||
if pos_line > doc.len_lines() - 1 {
|
||||
return None;
|
||||
} else {
|
||||
pos_line
|
||||
};
|
||||
}
|
||||
|
||||
match offset_encoding {
|
||||
OffsetEncoding::Utf8 => {
|
||||
let max_char = doc
|
||||
.line_to_char(max_line)
|
||||
.checked_add(doc.line(max_line).len_chars())?;
|
||||
let line = doc.line_to_char(pos_line);
|
||||
let pos = line.checked_add(pos.character as usize)?;
|
||||
if pos <= max_char {
|
||||
if pos <= doc.len_chars() {
|
||||
Some(pos)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
OffsetEncoding::Utf16 => {
|
||||
let max_char = doc
|
||||
.line_to_char(max_line)
|
||||
.checked_add(doc.line(max_line).len_chars())?;
|
||||
let max_cu = doc.char_to_utf16_cu(max_char);
|
||||
let line = doc.line_to_char(pos_line);
|
||||
let line_start = doc.char_to_utf16_cu(line);
|
||||
let pos = line_start.checked_add(pos.character as usize)?;
|
||||
if pos <= max_cu {
|
||||
Some(doc.utf16_cu_to_char(pos))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
doc.try_utf16_cu_to_char(pos).ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -203,6 +190,7 @@ fn from(fmt: LspFormatting) -> Transaction {
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum MethodCall {
|
||||
WorkDoneProgressCreate(lsp::WorkDoneProgressCreateParams),
|
||||
ApplyWorkspaceEdit(lsp::ApplyWorkspaceEditParams),
|
||||
}
|
||||
|
||||
impl MethodCall {
|
||||
@ -215,6 +203,12 @@ pub fn parse(method: &str, params: jsonrpc::Params) -> Option<MethodCall> {
|
||||
.expect("Failed to parse WorkDoneCreate params");
|
||||
Self::WorkDoneProgressCreate(params)
|
||||
}
|
||||
lsp::request::ApplyWorkspaceEdit::METHOD => {
|
||||
let params: lsp::ApplyWorkspaceEditParams = params
|
||||
.parse()
|
||||
.expect("Failed to parse ApplyWorkspaceEdit params");
|
||||
Self::ApplyWorkspaceEdit(params)
|
||||
}
|
||||
_ => {
|
||||
log::warn!("unhandled lsp request: {}", method);
|
||||
return None;
|
||||
@ -319,6 +313,7 @@ pub fn get(&mut self, language_config: &LanguageConfiguration) -> Result<Arc<Cli
|
||||
&config.command,
|
||||
&config.args,
|
||||
language_config.config.clone(),
|
||||
language_config.roots.clone(),
|
||||
id,
|
||||
)?;
|
||||
self.incoming.push(UnboundedReceiverStream::new(incoming));
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "helix-syntax"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
|
13
helix-syntax/README.md
Normal file
13
helix-syntax/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
helix-syntax
|
||||
============
|
||||
|
||||
Syntax highlighting for helix, (shallow) submodules resides here.
|
||||
|
||||
Differences from nvim-treesitter
|
||||
--------------------------------
|
||||
|
||||
As the syntax are commonly ported from
|
||||
<https://github.com/nvim-treesitter/nvim-treesitter>.
|
||||
|
||||
Note that we do not support the custom `#any-of` predicate which is
|
||||
supported by neovim so one needs to change it to `#match` with regex.
|
1
helix-syntax/languages/tree-sitter-comment
Submodule
1
helix-syntax/languages/tree-sitter-comment
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 5dd3c62f1bbe378b220fe16b317b85247898639e
|
1
helix-syntax/languages/tree-sitter-dart
Submodule
1
helix-syntax/languages/tree-sitter-dart
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 6a25376685d1d47968c2cef06d4db8d84a70025e
|
1
helix-syntax/languages/tree-sitter-dockerfile
Submodule
1
helix-syntax/languages/tree-sitter-dockerfile
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 7af32bc04a66ab196f5b9f92ac471f29372ae2ce
|
1
helix-syntax/languages/tree-sitter-fish
Submodule
1
helix-syntax/languages/tree-sitter-fish
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 04e54ab6585dfd4fee6ddfe5849af56f101b6d4f
|
1
helix-syntax/languages/tree-sitter-git-commit
Submodule
1
helix-syntax/languages/tree-sitter-git-commit
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 066e395e1107df17183cf3ae4230f1a1406cc972
|
1
helix-syntax/languages/tree-sitter-git-diff
Submodule
1
helix-syntax/languages/tree-sitter-git-diff
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit c12e6ecb54485f764250556ffd7ccb18f8e2942b
|
1
helix-syntax/languages/tree-sitter-git-rebase
Submodule
1
helix-syntax/languages/tree-sitter-git-rebase
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 332dc528f27044bc4427024dbb33e6941fc131f2
|
1
helix-syntax/languages/tree-sitter-llvm
Submodule
1
helix-syntax/languages/tree-sitter-llvm
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 3b213925b9c4f42c1acfe2e10bfbb438d9c6834d
|
1
helix-syntax/languages/tree-sitter-llvm-mir
Submodule
1
helix-syntax/languages/tree-sitter-llvm-mir
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 06fabca19454b2dc00c1b211a7cb7ad0bc2585f1
|
1
helix-syntax/languages/tree-sitter-tablegen
Submodule
1
helix-syntax/languages/tree-sitter-tablegen
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 568dd8a937347175fd58db83d4c4cdaeb6069bd2
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "helix-term"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
description = "A post-modern text editor."
|
||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||
edition = "2021"
|
||||
@ -22,12 +22,12 @@ name = "hx"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
helix-core = { version = "0.5", path = "../helix-core" }
|
||||
helix-view = { version = "0.5", path = "../helix-view" }
|
||||
helix-lsp = { version = "0.5", path = "../helix-lsp" }
|
||||
helix-core = { version = "0.6", path = "../helix-core" }
|
||||
helix-view = { version = "0.6", path = "../helix-view" }
|
||||
helix-lsp = { version = "0.6", path = "../helix-lsp" }
|
||||
|
||||
anyhow = "1"
|
||||
once_cell = "1.8"
|
||||
once_cell = "1.9"
|
||||
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
|
||||
num_cpus = "1"
|
||||
|
@ -1,8 +1,12 @@
|
||||
use helix_core::{merge_toml_values, syntax};
|
||||
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
|
||||
use helix_view::{theme, Editor};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{args::Args, compositor::Compositor, config::Config, job::Jobs, ui};
|
||||
use crate::{
|
||||
args::Args, commands::apply_workspace_edit, compositor::Compositor, config::Config, job::Jobs,
|
||||
ui,
|
||||
};
|
||||
|
||||
use log::{error, warn};
|
||||
|
||||
@ -374,6 +378,7 @@ pub async fn handle_language_server_message(
|
||||
let doc = self.editor.document_by_path_mut(&path);
|
||||
|
||||
if let Some(doc) = doc {
|
||||
let lang_conf = doc.language_config();
|
||||
let text = doc.text();
|
||||
|
||||
let diagnostics = params
|
||||
@ -411,19 +416,31 @@ pub async fn handle_language_server_message(
|
||||
return None;
|
||||
};
|
||||
|
||||
let severity =
|
||||
diagnostic.severity.map(|severity| match severity {
|
||||
DiagnosticSeverity::ERROR => Error,
|
||||
DiagnosticSeverity::WARNING => Warning,
|
||||
DiagnosticSeverity::INFORMATION => Info,
|
||||
DiagnosticSeverity::HINT => Hint,
|
||||
severity => unreachable!(
|
||||
"unrecognized diagnostic severity: {:?}",
|
||||
severity
|
||||
),
|
||||
});
|
||||
|
||||
if let Some(lang_conf) = lang_conf {
|
||||
if let Some(severity) = severity {
|
||||
if severity < lang_conf.diagnostic_severity {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Some(Diagnostic {
|
||||
range: Range { start, end },
|
||||
line: diagnostic.range.start.line as usize,
|
||||
message: diagnostic.message,
|
||||
severity: diagnostic.severity.map(
|
||||
|severity| match severity {
|
||||
DiagnosticSeverity::ERROR => Error,
|
||||
DiagnosticSeverity::WARNING => Warning,
|
||||
DiagnosticSeverity::INFORMATION => Info,
|
||||
DiagnosticSeverity::HINT => Hint,
|
||||
severity => unimplemented!("{:?}", severity),
|
||||
},
|
||||
),
|
||||
severity,
|
||||
// code
|
||||
// source
|
||||
})
|
||||
@ -530,14 +547,6 @@ pub async fn handle_language_server_message(
|
||||
Call::MethodCall(helix_lsp::jsonrpc::MethodCall {
|
||||
method, params, id, ..
|
||||
}) => {
|
||||
let language_server = match self.editor.language_servers.get_by_id(server_id) {
|
||||
Some(language_server) => language_server,
|
||||
None => {
|
||||
warn!("can't find language server with id `{}`", server_id);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let call = match MethodCall::parse(&method, params) {
|
||||
Some(call) => call,
|
||||
None => {
|
||||
@ -567,8 +576,42 @@ pub async fn handle_language_server_message(
|
||||
if spinner.is_stopped() {
|
||||
spinner.start();
|
||||
}
|
||||
let language_server =
|
||||
match self.editor.language_servers.get_by_id(server_id) {
|
||||
Some(language_server) => language_server,
|
||||
None => {
|
||||
warn!("can't find language server with id `{}`", server_id);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
tokio::spawn(language_server.reply(id, Ok(serde_json::Value::Null)));
|
||||
}
|
||||
MethodCall::ApplyWorkspaceEdit(params) => {
|
||||
apply_workspace_edit(
|
||||
&mut self.editor,
|
||||
helix_lsp::OffsetEncoding::Utf8,
|
||||
¶ms.edit,
|
||||
);
|
||||
|
||||
let language_server =
|
||||
match self.editor.language_servers.get_by_id(server_id) {
|
||||
Some(language_server) => language_server,
|
||||
None => {
|
||||
warn!("can't find language server with id `{}`", server_id);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
tokio::spawn(language_server.reply(
|
||||
id,
|
||||
Ok(json!(lsp::ApplyWorkspaceEditResponse {
|
||||
applied: true,
|
||||
failure_reason: None,
|
||||
failed_change: None,
|
||||
})),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
e => unreachable!("{:?}", e),
|
||||
|
@ -26,6 +26,7 @@
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, bail, ensure, Context as _};
|
||||
use fuzzy_matcher::FuzzyMatcher;
|
||||
use helix_lsp::{
|
||||
block_on, lsp,
|
||||
util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
|
||||
@ -266,6 +267,7 @@ pub fn doc(&self) -> &str {
|
||||
change_selection_noyank, "Change selection (delete and enter insert mode, without yanking)",
|
||||
collapse_selection, "Collapse selection onto a single cursor",
|
||||
flip_selections, "Flip selection cursor and anchor",
|
||||
ensure_selections_forward, "Ensure the selection is in forward direction",
|
||||
insert_mode, "Insert before selection",
|
||||
append_mode, "Insert after selection (append)",
|
||||
command_mode, "Enter command mode",
|
||||
@ -287,7 +289,7 @@ pub fn doc(&self) -> &str {
|
||||
add_newline_below, "Add newline below",
|
||||
goto_type_definition, "Goto type definition",
|
||||
goto_implementation, "Goto implementation",
|
||||
goto_file_start, "Goto file start/line",
|
||||
goto_file_start, "Goto line number <n> else file start",
|
||||
goto_file_end, "Goto file end",
|
||||
goto_file, "Goto files in selection",
|
||||
goto_file_hsplit, "Goto files in selection (hsplit)",
|
||||
@ -360,6 +362,7 @@ pub fn doc(&self) -> &str {
|
||||
rotate_selection_contents_forward, "Rotate selection contents forward",
|
||||
rotate_selection_contents_backward, "Rotate selections contents backward",
|
||||
expand_selection, "Expand selection to parent syntax node",
|
||||
shrink_selection, "Shrink selection to previously expanded syntax node",
|
||||
jump_forward, "Jump forward on jumplist",
|
||||
jump_backward, "Jump backward on jumplist",
|
||||
save_selection, "Save the current selection to the jumplist",
|
||||
@ -396,7 +399,7 @@ pub fn doc(&self) -> &str {
|
||||
increment, "Increment",
|
||||
decrement, "Decrement",
|
||||
record_macro, "Record macro",
|
||||
play_macro, "Play macro",
|
||||
replay_macro, "Replay macro",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1280,16 +1283,23 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
|
||||
.max(view.offset.row + scrolloff)
|
||||
.min(last_line.saturating_sub(scrolloff));
|
||||
|
||||
let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
|
||||
// If cursor needs moving, replace primary selection
|
||||
if line != cursor.row {
|
||||
let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
|
||||
|
||||
let anchor = if doc.mode == Mode::Select {
|
||||
range.anchor
|
||||
} else {
|
||||
head
|
||||
};
|
||||
let anchor = if doc.mode == Mode::Select {
|
||||
range.anchor
|
||||
} else {
|
||||
head
|
||||
};
|
||||
|
||||
// TODO: only manipulate main selection
|
||||
doc.set_selection(view.id, Selection::single(anchor, head));
|
||||
// replace primary selection with an empty selection at cursor pos
|
||||
let prim_sel = Range::new(anchor, head);
|
||||
let mut sel = doc.selection(view.id).clone();
|
||||
let idx = sel.primary_index();
|
||||
sel = sel.replace(idx, prim_sel);
|
||||
doc.set_selection(view.id, sel);
|
||||
}
|
||||
}
|
||||
|
||||
fn page_up(cx: &mut Context) {
|
||||
@ -1543,7 +1553,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
|
||||
let reg = cx.register.unwrap_or('/');
|
||||
let scrolloff = cx.editor.config.scrolloff;
|
||||
|
||||
let (_, doc) = current!(cx.editor);
|
||||
let doc = doc!(cx.editor);
|
||||
|
||||
// TODO: could probably share with select_on_matches?
|
||||
|
||||
@ -1630,7 +1640,7 @@ fn search_selection(cx: &mut Context) {
|
||||
let query = doc.selection(view.id).primary().fragment(contents);
|
||||
let regex = regex::escape(&query);
|
||||
cx.editor.registers.get_mut('/').push(regex);
|
||||
let msg = format!("register '{}' set to '{}'", '\\', query);
|
||||
let msg = format!("register '{}' set to '{}'", '/', query);
|
||||
cx.editor.set_status(msg);
|
||||
}
|
||||
|
||||
@ -1904,7 +1914,21 @@ fn flip_selections(cx: &mut Context) {
|
||||
let selection = doc
|
||||
.selection(view.id)
|
||||
.clone()
|
||||
.transform(|range| Range::new(range.head, range.anchor));
|
||||
.transform(|range| range.flip());
|
||||
doc.set_selection(view.id, selection);
|
||||
}
|
||||
|
||||
fn ensure_selections_forward(cx: &mut Context) {
|
||||
let (view, doc) = current!(cx.editor);
|
||||
|
||||
let selection = doc
|
||||
.selection(view.id)
|
||||
.clone()
|
||||
.transform(|r| match r.direction() {
|
||||
Direction::Forward => r,
|
||||
Direction::Backward => r.flip(),
|
||||
});
|
||||
|
||||
doc.set_selection(view.id, selection);
|
||||
}
|
||||
|
||||
@ -1938,7 +1962,7 @@ fn append_mode(cx: &mut Context) {
|
||||
if !last_range.is_empty() && last_range.head == end {
|
||||
let transaction = Transaction::change(
|
||||
doc.text(),
|
||||
std::array::IntoIter::new([(end, end, Some(doc.line_ending.as_str().into()))]),
|
||||
[(end, end, Some(doc.line_ending.as_str().into()))].into_iter(),
|
||||
);
|
||||
doc.apply(&transaction, view.id);
|
||||
}
|
||||
@ -2030,7 +2054,7 @@ fn force_buffer_close(
|
||||
|
||||
fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
|
||||
let jobs = &mut cx.jobs;
|
||||
let (_, doc) = current!(cx.editor);
|
||||
let doc = doc_mut!(cx.editor);
|
||||
|
||||
if let Some(ref path) = path {
|
||||
doc.set_path(Some(path.as_ref().as_ref()))
|
||||
@ -2083,8 +2107,7 @@ fn format(
|
||||
_args: &[Cow<str>],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
let (_, doc) = current!(cx.editor);
|
||||
|
||||
let doc = doc!(cx.editor);
|
||||
if let Some(format) = doc.format() {
|
||||
let callback =
|
||||
make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
|
||||
@ -2307,12 +2330,7 @@ fn force_write_all_quit(
|
||||
write_all_impl(cx, args, event, true, true)
|
||||
}
|
||||
|
||||
fn quit_all_impl(
|
||||
editor: &mut Editor,
|
||||
_args: &[Cow<str>],
|
||||
_event: PromptEvent,
|
||||
force: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> {
|
||||
if !force {
|
||||
buffers_remaining_impl(editor)?;
|
||||
}
|
||||
@ -2328,18 +2346,18 @@ fn quit_all_impl(
|
||||
|
||||
fn quit_all(
|
||||
cx: &mut compositor::Context,
|
||||
args: &[Cow<str>],
|
||||
event: PromptEvent,
|
||||
_args: &[Cow<str>],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
quit_all_impl(cx.editor, args, event, false)
|
||||
quit_all_impl(cx.editor, false)
|
||||
}
|
||||
|
||||
fn force_quit_all(
|
||||
cx: &mut compositor::Context,
|
||||
args: &[Cow<str>],
|
||||
event: PromptEvent,
|
||||
_args: &[Cow<str>],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
quit_all_impl(cx.editor, args, event, true)
|
||||
quit_all_impl(cx.editor, true)
|
||||
}
|
||||
|
||||
fn cquit(
|
||||
@ -2353,12 +2371,21 @@ fn cquit(
|
||||
.unwrap_or(1);
|
||||
cx.editor.exit_code = exit_code;
|
||||
|
||||
let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
|
||||
for view_id in views {
|
||||
cx.editor.close(view_id);
|
||||
}
|
||||
quit_all_impl(cx.editor, false)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn force_cquit(
|
||||
cx: &mut compositor::Context,
|
||||
args: &[Cow<str>],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
let exit_code = args
|
||||
.first()
|
||||
.and_then(|code| code.parse::<i32>().ok())
|
||||
.unwrap_or(1);
|
||||
cx.editor.exit_code = exit_code;
|
||||
|
||||
quit_all_impl(cx.editor, true)
|
||||
}
|
||||
|
||||
fn theme(
|
||||
@ -2393,7 +2420,7 @@ fn yank_joined_to_clipboard(
|
||||
args: &[Cow<str>],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
let (_, doc) = current!(cx.editor);
|
||||
let doc = doc!(cx.editor);
|
||||
let default_sep = Cow::Borrowed(doc.line_ending.as_str());
|
||||
let separator = args.first().unwrap_or(&default_sep);
|
||||
yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
|
||||
@ -2412,7 +2439,7 @@ fn yank_joined_to_primary_clipboard(
|
||||
args: &[Cow<str>],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
let (_, doc) = current!(cx.editor);
|
||||
let doc = doc!(cx.editor);
|
||||
let default_sep = Cow::Borrowed(doc.line_ending.as_str());
|
||||
let separator = args.first().unwrap_or(&default_sep);
|
||||
yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
|
||||
@ -2539,7 +2566,7 @@ fn set_encoding(
|
||||
args: &[Cow<str>],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
let (_, doc) = current!(cx.editor);
|
||||
let doc = doc_mut!(cx.editor);
|
||||
if let Some(label) = args.first() {
|
||||
doc.set_encoding(label)
|
||||
} else {
|
||||
@ -2637,6 +2664,86 @@ pub(super) fn goto_line_number(
|
||||
let (view, doc) = current!(cx.editor);
|
||||
|
||||
view.ensure_cursor_in_view(doc, line);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setting(
|
||||
cx: &mut compositor::Context,
|
||||
args: &[Cow<str>],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
let runtime_config = &mut cx.editor.config;
|
||||
|
||||
if args.len() != 2 {
|
||||
anyhow::bail!("Bad arguments. Usage: `:set key field`");
|
||||
}
|
||||
|
||||
let (key, arg) = (&args[0].to_lowercase(), &args[1]);
|
||||
|
||||
match key.as_ref() {
|
||||
"scrolloff" => runtime_config.scrolloff = arg.parse()?,
|
||||
"scroll-lines" => runtime_config.scroll_lines = arg.parse()?,
|
||||
"mouse" => runtime_config.mouse = arg.parse()?,
|
||||
"line-number" => runtime_config.line_number = arg.parse()?,
|
||||
"middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?,
|
||||
"smart-case" => runtime_config.smart_case = arg.parse()?,
|
||||
"auto-pairs" => runtime_config.auto_pairs = arg.parse()?,
|
||||
"auto-completion" => runtime_config.auto_completion = arg.parse()?,
|
||||
"completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?,
|
||||
"auto-info" => runtime_config.auto_info = arg.parse()?,
|
||||
"true-color" => runtime_config.true_color = arg.parse()?,
|
||||
_ => anyhow::bail!("Unknown key `{}`.", args[0]),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sort(
|
||||
cx: &mut compositor::Context,
|
||||
args: &[Cow<str>],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
sort_impl(cx, args, false)
|
||||
}
|
||||
|
||||
fn sort_reverse(
|
||||
cx: &mut compositor::Context,
|
||||
args: &[Cow<str>],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
sort_impl(cx, args, true)
|
||||
}
|
||||
|
||||
fn sort_impl(
|
||||
cx: &mut compositor::Context,
|
||||
_args: &[Cow<str>],
|
||||
reverse: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let (view, doc) = current!(cx.editor);
|
||||
let text = doc.text().slice(..);
|
||||
|
||||
let selection = doc.selection(view.id);
|
||||
|
||||
let mut fragments: Vec<_> = selection
|
||||
.fragments(text)
|
||||
.map(|fragment| Tendril::from_slice(&fragment))
|
||||
.collect();
|
||||
|
||||
fragments.sort_by(match reverse {
|
||||
true => |a: &Tendril, b: &Tendril| b.cmp(a),
|
||||
false => |a: &Tendril, b: &Tendril| a.cmp(b),
|
||||
});
|
||||
|
||||
let transaction = Transaction::change(
|
||||
doc.text(),
|
||||
selection
|
||||
.into_iter()
|
||||
.zip(fragments)
|
||||
.map(|(s, fragment)| (s.from(), s.to(), Some(fragment))),
|
||||
);
|
||||
|
||||
doc.apply(&transaction, view.id);
|
||||
doc.append_changes_to_history(view.id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -2664,18 +2771,18 @@ pub(super) fn goto_line_number(
|
||||
completer: Some(completers::filename),
|
||||
},
|
||||
TypableCommand {
|
||||
name: "buffer-close",
|
||||
aliases: &["bc", "bclose"],
|
||||
doc: "Close the current buffer.",
|
||||
fun: buffer_close,
|
||||
completer: None, // FIXME: buffer completer
|
||||
name: "buffer-close",
|
||||
aliases: &["bc", "bclose"],
|
||||
doc: "Close the current buffer.",
|
||||
fun: buffer_close,
|
||||
completer: None, // FIXME: buffer completer
|
||||
},
|
||||
TypableCommand {
|
||||
name: "buffer-close!",
|
||||
aliases: &["bc!", "bclose!"],
|
||||
doc: "Close the current buffer forcefully (ignoring unsaved changes).",
|
||||
fun: force_buffer_close,
|
||||
completer: None, // FIXME: buffer completer
|
||||
name: "buffer-close!",
|
||||
aliases: &["bc!", "bclose!"],
|
||||
doc: "Close the current buffer forcefully (ignoring unsaved changes).",
|
||||
fun: force_buffer_close,
|
||||
completer: None, // FIXME: buffer completer
|
||||
},
|
||||
TypableCommand {
|
||||
name: "write",
|
||||
@ -2782,6 +2889,13 @@ pub(super) fn goto_line_number(
|
||||
fun: cquit,
|
||||
completer: None,
|
||||
},
|
||||
TypableCommand {
|
||||
name: "cquit!",
|
||||
aliases: &["cq!"],
|
||||
doc: "Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2).",
|
||||
fun: force_cquit,
|
||||
completer: None,
|
||||
},
|
||||
TypableCommand {
|
||||
name: "theme",
|
||||
aliases: &[],
|
||||
@ -2928,7 +3042,28 @@ pub(super) fn goto_line_number(
|
||||
doc: "Go to line number.",
|
||||
fun: goto_line_number,
|
||||
completer: None,
|
||||
}
|
||||
},
|
||||
TypableCommand {
|
||||
name: "set-option",
|
||||
aliases: &["set"],
|
||||
doc: "Set a config option at runtime",
|
||||
fun: setting,
|
||||
completer: Some(completers::setting),
|
||||
},
|
||||
TypableCommand {
|
||||
name: "sort",
|
||||
aliases: &[],
|
||||
doc: "Sort ranges in selection.",
|
||||
fun: sort,
|
||||
completer: None,
|
||||
},
|
||||
TypableCommand {
|
||||
name: "rsort",
|
||||
aliases: &[],
|
||||
doc: "Sort ranges in selection in reverse order.",
|
||||
fun: sort_reverse,
|
||||
completer: None,
|
||||
},
|
||||
];
|
||||
|
||||
pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
|
||||
@ -2948,17 +3083,28 @@ fn command_mode(cx: &mut Context) {
|
||||
":".into(),
|
||||
Some(':'),
|
||||
|input: &str| {
|
||||
static FUZZY_MATCHER: Lazy<fuzzy_matcher::skim::SkimMatcherV2> =
|
||||
Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default);
|
||||
|
||||
// we use .this over split_whitespace() because we care about empty segments
|
||||
let parts = input.split(' ').collect::<Vec<&str>>();
|
||||
|
||||
// simple heuristic: if there's no just one part, complete command name.
|
||||
// if there's a space, per command completion kicks in.
|
||||
if parts.len() <= 1 {
|
||||
let end = 0..;
|
||||
cmd::TYPABLE_COMMAND_LIST
|
||||
let mut matches: Vec<_> = cmd::TYPABLE_COMMAND_LIST
|
||||
.iter()
|
||||
.filter(|command| command.name.contains(input))
|
||||
.map(|command| (end.clone(), Cow::Borrowed(command.name)))
|
||||
.filter_map(|command| {
|
||||
FUZZY_MATCHER
|
||||
.fuzzy_match(command.name, input)
|
||||
.map(|score| (command.name, score))
|
||||
})
|
||||
.collect();
|
||||
|
||||
matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score));
|
||||
matches
|
||||
.into_iter()
|
||||
.map(|(name, _)| (0.., name.into()))
|
||||
.collect()
|
||||
} else {
|
||||
let part = parts.last().unwrap();
|
||||
@ -3002,7 +3148,16 @@ fn command_mode(cx: &mut Context) {
|
||||
|
||||
// Handle typable commands
|
||||
if let Some(cmd) = cmd::TYPABLE_COMMAND_MAP.get(parts[0]) {
|
||||
let args = shellwords::shellwords(input);
|
||||
let args = if cfg!(unix) {
|
||||
shellwords::shellwords(input)
|
||||
} else {
|
||||
// Windows doesn't support POSIX, so fallback for now
|
||||
parts
|
||||
.into_iter()
|
||||
.map(|part| part.into())
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
if let Err(e) = (cmd.fun)(cx, &args[1..], event) {
|
||||
cx.editor.set_error(format!("{}", e));
|
||||
}
|
||||
@ -3026,7 +3181,8 @@ fn command_mode(cx: &mut Context) {
|
||||
}
|
||||
|
||||
fn file_picker(cx: &mut Context) {
|
||||
let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
|
||||
// We don't specify language markers, root will be the root of the current git repo
|
||||
let root = find_root(None, &[]).unwrap_or_else(|| PathBuf::from("./"));
|
||||
let picker = ui::file_picker(root, &cx.editor.config);
|
||||
cx.push_layer(Box::new(picker));
|
||||
}
|
||||
@ -3118,7 +3274,7 @@ fn nested_to_flat(
|
||||
nested_to_flat(list, file, child);
|
||||
}
|
||||
}
|
||||
let (_, doc) = current!(cx.editor);
|
||||
let doc = doc!(cx.editor);
|
||||
|
||||
let language_server = match doc.language_server() {
|
||||
Some(language_server) => language_server,
|
||||
@ -3139,7 +3295,7 @@ fn nested_to_flat(
|
||||
let symbols = match symbols {
|
||||
lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
|
||||
lsp::DocumentSymbolResponse::Nested(symbols) => {
|
||||
let (_view, doc) = current!(editor);
|
||||
let doc = doc!(editor);
|
||||
let mut flat_symbols = Vec::new();
|
||||
for symbol in symbols {
|
||||
nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
|
||||
@ -3181,17 +3337,15 @@ fn nested_to_flat(
|
||||
}
|
||||
|
||||
fn workspace_symbol_picker(cx: &mut Context) {
|
||||
let (_, doc) = current!(cx.editor);
|
||||
|
||||
let doc = doc!(cx.editor);
|
||||
let current_path = doc.path().cloned();
|
||||
let language_server = match doc.language_server() {
|
||||
Some(language_server) => language_server,
|
||||
None => return,
|
||||
};
|
||||
let offset_encoding = language_server.offset_encoding();
|
||||
|
||||
let future = language_server.workspace_symbols("".to_string());
|
||||
|
||||
let current_path = doc_mut!(cx.editor).path().cloned();
|
||||
cx.callback(
|
||||
future,
|
||||
move |_editor: &mut Editor,
|
||||
@ -3277,12 +3431,19 @@ pub fn code_action(cx: &mut Context) {
|
||||
move |editor, code_action, _action| match code_action {
|
||||
lsp::CodeActionOrCommand::Command(command) => {
|
||||
log::debug!("code action command: {:?}", command);
|
||||
editor.set_error(String::from("Handling code action command is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
|
||||
execute_lsp_command(editor, command.clone());
|
||||
}
|
||||
lsp::CodeActionOrCommand::CodeAction(code_action) => {
|
||||
log::debug!("code action: {:?}", code_action);
|
||||
if let Some(ref workspace_edit) = code_action.edit {
|
||||
apply_workspace_edit(editor, offset_encoding, workspace_edit)
|
||||
log::debug!("edit: {:?}", workspace_edit);
|
||||
apply_workspace_edit(editor, offset_encoding, workspace_edit);
|
||||
}
|
||||
|
||||
// if code action provides both edit and command first the edit
|
||||
// should be applied and then the command
|
||||
if let Some(command) = &code_action.command {
|
||||
execute_lsp_command(editor, command.clone());
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -3293,6 +3454,25 @@ pub fn code_action(cx: &mut Context) {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
|
||||
let doc = doc!(editor);
|
||||
let language_server = match doc.language_server() {
|
||||
Some(language_server) => language_server,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// the command is executed on the server and communicated back
|
||||
// to the client asynchronously using workspace edits
|
||||
let command_future = language_server.command(cmd);
|
||||
tokio::spawn(async move {
|
||||
let res = command_future.await;
|
||||
|
||||
if let Err(e) = res {
|
||||
log::error!("execute LSP command: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
|
||||
use lsp::ResourceOp;
|
||||
use std::fs;
|
||||
@ -3346,7 +3526,7 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_workspace_edit(
|
||||
pub fn apply_workspace_edit(
|
||||
editor: &mut Editor,
|
||||
offset_encoding: OffsetEncoding,
|
||||
workspace_edit: &lsp::WorkspaceEdit,
|
||||
@ -3537,22 +3717,22 @@ fn open(cx: &mut Context, open: Open) {
|
||||
let mut offs = 0;
|
||||
|
||||
let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
|
||||
let line = range.cursor_line(text);
|
||||
let cursor_line = range.cursor_line(text);
|
||||
|
||||
let line = match open {
|
||||
let new_line = match open {
|
||||
// adjust position to the end of the line (next line - 1)
|
||||
Open::Below => line + 1,
|
||||
Open::Below => cursor_line + 1,
|
||||
// adjust position to the end of the previous line (current line - 1)
|
||||
Open::Above => line,
|
||||
Open::Above => cursor_line,
|
||||
};
|
||||
|
||||
// Index to insert newlines after, as well as the char width
|
||||
// to use to compensate for those inserted newlines.
|
||||
let (line_end_index, line_end_offset_width) = if line == 0 {
|
||||
let (line_end_index, line_end_offset_width) = if new_line == 0 {
|
||||
(0, 0)
|
||||
} else {
|
||||
(
|
||||
line_end_char_index(&doc.text().slice(..), line.saturating_sub(1)),
|
||||
line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
|
||||
doc.line_ending.len_chars(),
|
||||
)
|
||||
};
|
||||
@ -3563,8 +3743,10 @@ fn open(cx: &mut Context, open: Open) {
|
||||
doc.syntax(),
|
||||
text,
|
||||
line_end_index,
|
||||
new_line.saturating_sub(1),
|
||||
true,
|
||||
);
|
||||
)
|
||||
.unwrap_or_else(|| indent::indent_level_for_line(text.line(cursor_line), doc.tab_width()));
|
||||
let indent = doc.indent_unit().repeat(indent_level);
|
||||
let indent_len = indent.len();
|
||||
let mut text = String::with_capacity(1 + indent_len);
|
||||
@ -3610,6 +3792,7 @@ fn normal_mode(cx: &mut Context) {
|
||||
|
||||
doc.mode = Mode::Normal;
|
||||
|
||||
try_restore_indent(doc, view.id);
|
||||
doc.append_changes_to_history(view.id);
|
||||
|
||||
// if leaving append mode, move cursor back by 1
|
||||
@ -3627,6 +3810,40 @@ fn normal_mode(cx: &mut Context) {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_restore_indent(doc: &mut Document, view_id: ViewId) {
|
||||
use helix_core::chars::char_is_whitespace;
|
||||
use helix_core::Operation;
|
||||
|
||||
fn inserted_a_new_blank_line(changes: &[Operation], pos: usize, line_end_pos: usize) -> bool {
|
||||
if let [Operation::Retain(move_pos), Operation::Insert(ref inserted_str), Operation::Retain(_)] =
|
||||
changes
|
||||
{
|
||||
move_pos + inserted_str.len32() as usize == pos
|
||||
&& inserted_str.starts_with('\n')
|
||||
&& inserted_str.chars().skip(1).all(char_is_whitespace)
|
||||
&& pos == line_end_pos // ensure no characters exists after current position
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
let doc_changes = doc.changes().changes();
|
||||
let text = doc.text().slice(..);
|
||||
let range = doc.selection(view_id).primary();
|
||||
let pos = range.cursor(text);
|
||||
let line_end_pos = line_end_char_index(&text, range.cursor_line(text));
|
||||
|
||||
if inserted_a_new_blank_line(doc_changes, pos, line_end_pos) {
|
||||
// Removes tailing whitespaces.
|
||||
let transaction =
|
||||
Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
|
||||
let line_start_pos = text.line_to_char(range.cursor_line(text));
|
||||
(line_start_pos, pos, None)
|
||||
});
|
||||
doc.apply(&transaction, view_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Store a jump on the jumplist.
|
||||
fn push_jump(editor: &mut Editor) {
|
||||
let (view, doc) = current!(editor);
|
||||
@ -3994,27 +4211,21 @@ fn goto_pos(editor: &mut Editor, pos: usize) {
|
||||
}
|
||||
|
||||
fn goto_first_diag(cx: &mut Context) {
|
||||
let editor = &mut cx.editor;
|
||||
let (_, doc) = current!(editor);
|
||||
|
||||
let doc = doc!(cx.editor);
|
||||
let pos = match doc.diagnostics().first() {
|
||||
Some(diag) => diag.range.start,
|
||||
None => return,
|
||||
};
|
||||
|
||||
goto_pos(editor, pos);
|
||||
goto_pos(cx.editor, pos);
|
||||
}
|
||||
|
||||
fn goto_last_diag(cx: &mut Context) {
|
||||
let editor = &mut cx.editor;
|
||||
let (_, doc) = current!(editor);
|
||||
|
||||
let doc = doc!(cx.editor);
|
||||
let pos = match doc.diagnostics().last() {
|
||||
Some(diag) => diag.range.start,
|
||||
None => return,
|
||||
};
|
||||
|
||||
goto_pos(editor, pos);
|
||||
goto_pos(cx.editor, pos);
|
||||
}
|
||||
|
||||
fn goto_next_diag(cx: &mut Context) {
|
||||
@ -4270,48 +4481,48 @@ pub fn insert_newline(cx: &mut Context) {
|
||||
};
|
||||
let curr = contents.get_char(pos).unwrap_or(' ');
|
||||
|
||||
// TODO: offset range.head by 1? when calculating?
|
||||
let current_line = text.char_to_line(pos);
|
||||
let indent_level = indent::suggested_indent_for_pos(
|
||||
doc.language_config(),
|
||||
doc.syntax(),
|
||||
text,
|
||||
pos.saturating_sub(1),
|
||||
pos,
|
||||
current_line,
|
||||
true,
|
||||
);
|
||||
let indent = doc.indent_unit().repeat(indent_level);
|
||||
let mut text = String::with_capacity(1 + indent.len());
|
||||
text.push_str(doc.line_ending.as_str());
|
||||
text.push_str(&indent);
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
indent::indent_level_for_line(text.line(current_line), doc.tab_width())
|
||||
});
|
||||
|
||||
let head = pos + offs + text.chars().count();
|
||||
let indent = doc.indent_unit().repeat(indent_level);
|
||||
let mut text = String::new();
|
||||
// If we are between pairs (such as brackets), we want to insert an additional line which is indented one level more and place the cursor there
|
||||
let new_head_pos = if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
|
||||
let inner_indent = doc.indent_unit().repeat(indent_level + 1);
|
||||
text.reserve_exact(2 + indent.len() + inner_indent.len());
|
||||
text.push_str(doc.line_ending.as_str());
|
||||
text.push_str(&inner_indent);
|
||||
let new_head_pos = pos + offs + text.chars().count();
|
||||
text.push_str(doc.line_ending.as_str());
|
||||
text.push_str(&indent);
|
||||
new_head_pos
|
||||
} else {
|
||||
text.reserve_exact(1 + indent.len());
|
||||
text.push_str(doc.line_ending.as_str());
|
||||
text.push_str(&indent);
|
||||
pos + offs + text.chars().count()
|
||||
};
|
||||
|
||||
// TODO: range replace or extend
|
||||
// range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
|
||||
// can be used with cx.mode to do replace or extend on most changes
|
||||
ranges.push(Range::new(
|
||||
if range.is_empty() {
|
||||
head
|
||||
} else {
|
||||
range.anchor + offs
|
||||
},
|
||||
head,
|
||||
));
|
||||
|
||||
// if between a bracket pair
|
||||
if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
|
||||
// another newline, indent the end bracket one level less
|
||||
let indent = doc.indent_unit().repeat(indent_level.saturating_sub(1));
|
||||
text.push_str(doc.line_ending.as_str());
|
||||
text.push_str(&indent);
|
||||
}
|
||||
|
||||
ranges.push(Range::new(new_head_pos, new_head_pos));
|
||||
offs += text.chars().count();
|
||||
|
||||
(pos, pos, Some(text.into()))
|
||||
});
|
||||
|
||||
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
|
||||
//
|
||||
|
||||
doc.apply(&transaction, view.id);
|
||||
}
|
||||
@ -5079,7 +5290,7 @@ pub fn completion(cx: &mut Context) {
|
||||
move |editor: &mut Editor,
|
||||
compositor: &mut Compositor,
|
||||
response: Option<lsp::CompletionResponse>| {
|
||||
let (_, doc) = current!(editor);
|
||||
let doc = doc!(editor);
|
||||
if doc.mode() != Mode::Insert {
|
||||
// we're not in insert mode anymore
|
||||
return;
|
||||
@ -5257,6 +5468,7 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
|
||||
doc.apply(&transaction, view.id);
|
||||
doc.append_changes_to_history(view.id);
|
||||
}
|
||||
|
||||
fn rotate_selection_contents_forward(cx: &mut Context) {
|
||||
rotate_selection_contents(cx, Direction::Forward)
|
||||
}
|
||||
@ -5272,7 +5484,39 @@ fn expand_selection(cx: &mut Context) {
|
||||
|
||||
if let Some(syntax) = doc.syntax() {
|
||||
let text = doc.text().slice(..);
|
||||
let selection = object::expand_selection(syntax, text, doc.selection(view.id));
|
||||
|
||||
let current_selection = doc.selection(view.id);
|
||||
|
||||
// save current selection so it can be restored using shrink_selection
|
||||
view.object_selections.push(current_selection.clone());
|
||||
|
||||
let selection = object::expand_selection(syntax, text, current_selection);
|
||||
doc.set_selection(view.id, selection);
|
||||
}
|
||||
};
|
||||
motion(cx.editor);
|
||||
cx.editor.last_motion = Some(Motion(Box::new(motion)));
|
||||
}
|
||||
|
||||
fn shrink_selection(cx: &mut Context) {
|
||||
let motion = |editor: &mut Editor| {
|
||||
let (view, doc) = current!(editor);
|
||||
let current_selection = doc.selection(view.id);
|
||||
// try to restore previous selection
|
||||
if let Some(prev_selection) = view.object_selections.pop() {
|
||||
if current_selection.contains(&prev_selection) {
|
||||
// allow shrinking the selection only if current selection contains the previous object selection
|
||||
doc.set_selection(view.id, prev_selection);
|
||||
return;
|
||||
} else {
|
||||
// clear existing selection as they can't be shrinked to anyway
|
||||
view.object_selections.clear();
|
||||
}
|
||||
}
|
||||
// if not previous selection, shrink to first child
|
||||
if let Some(syntax) = doc.syntax() {
|
||||
let text = doc.text().slice(..);
|
||||
let selection = object::shrink_selection(syntax, text, current_selection);
|
||||
doc.set_selection(view.id, selection);
|
||||
}
|
||||
};
|
||||
@ -5920,42 +6164,42 @@ fn record_macro(cx: &mut Context) {
|
||||
keys.pop();
|
||||
let s = keys
|
||||
.into_iter()
|
||||
.map(|key| format!("{}", key))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
.map(|key| {
|
||||
let s = key.to_string();
|
||||
if s.chars().count() == 1 {
|
||||
s
|
||||
} else {
|
||||
format!("<{}>", s)
|
||||
}
|
||||
})
|
||||
.collect::<String>();
|
||||
cx.editor.registers.get_mut(reg).write(vec![s]);
|
||||
cx.editor
|
||||
.set_status(format!("Recorded to register {}", reg));
|
||||
.set_status(format!("Recorded to register [{}]", reg));
|
||||
} else {
|
||||
let reg = cx.register.take().unwrap_or('@');
|
||||
cx.editor.macro_recording = Some((reg, Vec::new()));
|
||||
cx.editor
|
||||
.set_status(format!("Recording to register {}", reg));
|
||||
.set_status(format!("Recording to register [{}]", reg));
|
||||
}
|
||||
}
|
||||
|
||||
fn play_macro(cx: &mut Context) {
|
||||
fn replay_macro(cx: &mut Context) {
|
||||
let reg = cx.register.unwrap_or('@');
|
||||
let keys = match cx
|
||||
.editor
|
||||
.registers
|
||||
.get(reg)
|
||||
.and_then(|reg| reg.read().get(0))
|
||||
.context("Register empty")
|
||||
.and_then(|s| {
|
||||
s.split_whitespace()
|
||||
.map(str::parse::<KeyEvent>)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.context("Failed to parse macro")
|
||||
}) {
|
||||
Ok(keys) => keys,
|
||||
Err(e) => {
|
||||
cx.editor.set_error(format!("{}", e));
|
||||
return;
|
||||
let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
|
||||
match helix_view::input::parse_macro(keys_str) {
|
||||
Ok(keys) => keys,
|
||||
Err(err) => {
|
||||
cx.editor.set_error(format!("Invalid macro: {}", err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cx.editor.set_error(format!("Register [{}] empty", reg));
|
||||
return;
|
||||
};
|
||||
let count = cx.count();
|
||||
|
||||
let count = cx.count();
|
||||
cx.callback = Some(Box::new(
|
||||
move |compositor: &mut Compositor, cx: &mut compositor::Context| {
|
||||
for _ in 0..count {
|
||||
|
@ -569,11 +569,13 @@ fn default() -> Keymaps {
|
||||
"d" => goto_prev_diag,
|
||||
"D" => goto_first_diag,
|
||||
"space" => add_newline_above,
|
||||
"o" => shrink_selection,
|
||||
},
|
||||
"]" => { "Right bracket"
|
||||
"d" => goto_next_diag,
|
||||
"D" => goto_last_diag,
|
||||
"space" => add_newline_below,
|
||||
"o" => expand_selection,
|
||||
},
|
||||
|
||||
"/" => search,
|
||||
@ -593,8 +595,8 @@ fn default() -> Keymaps {
|
||||
// paste_all
|
||||
"P" => paste_before,
|
||||
|
||||
"q" => record_macro,
|
||||
"Q" => play_macro,
|
||||
"Q" => record_macro,
|
||||
"q" => replay_macro,
|
||||
|
||||
">" => indent,
|
||||
"<" => unindent,
|
||||
@ -617,6 +619,8 @@ fn default() -> Keymaps {
|
||||
"A-(" => rotate_selection_contents_backward,
|
||||
"A-)" => rotate_selection_contents_forward,
|
||||
|
||||
"A-:" => ensure_selections_forward,
|
||||
|
||||
"esc" => normal_mode,
|
||||
"C-b" | "pageup" => page_up,
|
||||
"C-f" | "pagedown" => page_down,
|
||||
|
@ -154,8 +154,19 @@ fn item_to_transaction(
|
||||
);
|
||||
doc.apply(&transaction, view.id);
|
||||
|
||||
if let Some(additional_edits) = &item.additional_text_edits {
|
||||
// gopls uses this to add extra imports
|
||||
// apply additional edits, mostly used to auto import unqualified types
|
||||
let resolved_additional_text_edits = if item.additional_text_edits.is_some() {
|
||||
None
|
||||
} else {
|
||||
Completion::resolve_completion_item(doc, item.clone())
|
||||
.and_then(|item| item.additional_text_edits)
|
||||
};
|
||||
|
||||
if let Some(additional_edits) = item
|
||||
.additional_text_edits
|
||||
.as_ref()
|
||||
.or_else(|| resolved_additional_text_edits.as_ref())
|
||||
{
|
||||
if !additional_edits.is_empty() {
|
||||
let transaction = util::generate_transaction_from_edits(
|
||||
doc.text(),
|
||||
@ -181,6 +192,31 @@ fn item_to_transaction(
|
||||
completion
|
||||
}
|
||||
|
||||
fn resolve_completion_item(
|
||||
doc: &Document,
|
||||
completion_item: lsp::CompletionItem,
|
||||
) -> Option<CompletionItem> {
|
||||
let language_server = doc.language_server()?;
|
||||
let completion_resolve_provider = language_server
|
||||
.capabilities()
|
||||
.completion_provider
|
||||
.as_ref()?
|
||||
.resolve_provider;
|
||||
if completion_resolve_provider != Some(true) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let future = language_server.resolve_completion_item(completion_item);
|
||||
let response = helix_lsp::block_on(future);
|
||||
match response {
|
||||
Ok(completion_item) => Some(completion_item),
|
||||
Err(err) => {
|
||||
log::error!("execute LSP command: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recompute_filter(&mut self, editor: &Editor) {
|
||||
// recompute menu based on matches
|
||||
let menu = self.popup.contents_mut();
|
||||
|
@ -7,7 +7,7 @@
|
||||
};
|
||||
|
||||
use helix_core::{
|
||||
coords_at_pos,
|
||||
coords_at_pos, encoding,
|
||||
graphemes::{ensure_grapheme_boundary_next, next_grapheme_boundary, prev_grapheme_boundary},
|
||||
movement::Direction,
|
||||
syntax::{self, HighlightEvent},
|
||||
@ -566,21 +566,6 @@ pub fn render_statusline(
|
||||
}
|
||||
surface.set_string(viewport.x + 5, viewport.y, progress, base_style);
|
||||
|
||||
let rel_path = doc.relative_path();
|
||||
let path = rel_path
|
||||
.as_ref()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
|
||||
|
||||
let title = format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" });
|
||||
surface.set_stringn(
|
||||
viewport.x + 8,
|
||||
viewport.y,
|
||||
title,
|
||||
viewport.width.saturating_sub(6) as usize,
|
||||
base_style,
|
||||
);
|
||||
|
||||
//-------------------------------
|
||||
// Right side of the status line.
|
||||
//-------------------------------
|
||||
@ -654,6 +639,13 @@ pub fn render_statusline(
|
||||
base_style,
|
||||
));
|
||||
|
||||
let enc = doc.encoding();
|
||||
if enc != encoding::UTF_8 {
|
||||
right_side_text
|
||||
.0
|
||||
.push(Span::styled(format!(" {} ", enc.name()), base_style));
|
||||
}
|
||||
|
||||
// Render to the statusline.
|
||||
surface.set_spans(
|
||||
viewport.x
|
||||
@ -664,6 +656,31 @@ pub fn render_statusline(
|
||||
&right_side_text,
|
||||
right_side_text.width() as u16,
|
||||
);
|
||||
|
||||
//-------------------------------
|
||||
// Middle / File path / Title
|
||||
//-------------------------------
|
||||
let title = {
|
||||
let rel_path = doc.relative_path();
|
||||
let path = rel_path
|
||||
.as_ref()
|
||||
.map(|p| p.to_string_lossy())
|
||||
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
|
||||
format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" })
|
||||
};
|
||||
|
||||
surface.set_string_truncated(
|
||||
viewport.x + 8, // 8: 1 space + 3 char mode string + 1 space + 1 spinner + 1 space
|
||||
viewport.y,
|
||||
title,
|
||||
viewport
|
||||
.width
|
||||
.saturating_sub(6)
|
||||
.saturating_sub(right_side_text.width() as u16 + 1) as usize, // "+ 1": a space between the title and the selection info
|
||||
base_style,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
/// Handle events by looking them up in `self.keymaps`. Returns None
|
||||
@ -782,8 +799,9 @@ pub fn set_completion(
|
||||
|
||||
pub fn clear_completion(&mut self, editor: &mut Editor) {
|
||||
self.completion = None;
|
||||
|
||||
// Clear any savepoints
|
||||
let (_, doc) = current!(editor);
|
||||
let doc = doc_mut!(editor);
|
||||
doc.savepoint = None;
|
||||
editor.clear_idle_timer(); // don't retrigger
|
||||
}
|
||||
@ -941,14 +959,18 @@ fn handle_mouse_event(
|
||||
}
|
||||
|
||||
impl Component for EditorView {
|
||||
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||
let mut cxt = commands::Context {
|
||||
editor: cx.editor,
|
||||
fn handle_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
context: &mut crate::compositor::Context,
|
||||
) -> EventResult {
|
||||
let mut cx = commands::Context {
|
||||
editor: context.editor,
|
||||
count: None,
|
||||
register: None,
|
||||
callback: None,
|
||||
on_next_key_callback: None,
|
||||
jobs: cx.jobs,
|
||||
jobs: context.jobs,
|
||||
};
|
||||
|
||||
match event {
|
||||
@ -958,18 +980,19 @@ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
Event::Key(key) => {
|
||||
cxt.editor.reset_idle_timer();
|
||||
cx.editor.reset_idle_timer();
|
||||
let mut key = KeyEvent::from(key);
|
||||
canonicalize_key(&mut key);
|
||||
// clear status
|
||||
cxt.editor.status_msg = None;
|
||||
|
||||
let (_, doc) = current!(cxt.editor);
|
||||
// clear status
|
||||
cx.editor.status_msg = None;
|
||||
|
||||
let doc = doc!(cx.editor);
|
||||
let mode = doc.mode();
|
||||
|
||||
if let Some(on_next_key) = self.on_next_key.take() {
|
||||
// if there's a command waiting input, do that first
|
||||
on_next_key(&mut cxt, key);
|
||||
on_next_key(&mut cx, key);
|
||||
} else {
|
||||
match mode {
|
||||
Mode::Insert => {
|
||||
@ -981,8 +1004,8 @@ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||
if let Some(completion) = &mut self.completion {
|
||||
// use a fake context here
|
||||
let mut cx = Context {
|
||||
editor: cxt.editor,
|
||||
jobs: cxt.jobs,
|
||||
editor: cx.editor,
|
||||
jobs: cx.jobs,
|
||||
scroll: None,
|
||||
};
|
||||
let res = completion.handle_event(event, &mut cx);
|
||||
@ -992,40 +1015,40 @@ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||
|
||||
if callback.is_some() {
|
||||
// assume close_fn
|
||||
self.clear_completion(cxt.editor);
|
||||
self.clear_completion(cx.editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if completion didn't take the event, we pass it onto commands
|
||||
if !consumed {
|
||||
self.insert_mode(&mut cxt, key);
|
||||
self.insert_mode(&mut cx, key);
|
||||
|
||||
// lastly we recalculate completion
|
||||
if let Some(completion) = &mut self.completion {
|
||||
completion.update(&mut cxt);
|
||||
completion.update(&mut cx);
|
||||
if completion.is_empty() {
|
||||
self.clear_completion(cxt.editor);
|
||||
self.clear_completion(cx.editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mode => self.command_mode(mode, &mut cxt, key),
|
||||
mode => self.command_mode(mode, &mut cx, key),
|
||||
}
|
||||
}
|
||||
|
||||
self.on_next_key = cxt.on_next_key_callback.take();
|
||||
self.on_next_key = cx.on_next_key_callback.take();
|
||||
// appease borrowck
|
||||
let callback = cxt.callback.take();
|
||||
let callback = cx.callback.take();
|
||||
|
||||
// if the command consumed the last view, skip the render.
|
||||
// on the next loop cycle the Application will then terminate.
|
||||
if cxt.editor.should_close() {
|
||||
if cx.editor.should_close() {
|
||||
return EventResult::Ignored;
|
||||
}
|
||||
|
||||
let (view, doc) = current!(cxt.editor);
|
||||
view.ensure_cursor_in_view(doc, cxt.editor.config.scrolloff);
|
||||
let (view, doc) = current!(cx.editor);
|
||||
view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff);
|
||||
|
||||
// mode transitions
|
||||
match (mode, doc.mode()) {
|
||||
@ -1054,7 +1077,7 @@ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||
EventResult::Consumed(callback)
|
||||
}
|
||||
|
||||
Event::Mouse(event) => self.handle_mouse_event(event, &mut cxt),
|
||||
Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,7 +174,9 @@ pub mod completers {
|
||||
use crate::ui::prompt::Completion;
|
||||
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
|
||||
use fuzzy_matcher::FuzzyMatcher;
|
||||
use helix_view::editor::Config;
|
||||
use helix_view::theme;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::Reverse;
|
||||
|
||||
@ -208,6 +210,31 @@ pub fn theme(input: &str) -> Vec<Completion> {
|
||||
names
|
||||
}
|
||||
|
||||
pub fn setting(input: &str) -> Vec<Completion> {
|
||||
static KEYS: Lazy<Vec<String>> = Lazy::new(|| {
|
||||
serde_json::to_value(Config::default())
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect()
|
||||
});
|
||||
|
||||
let matcher = Matcher::default();
|
||||
|
||||
let mut matches: Vec<_> = KEYS
|
||||
.iter()
|
||||
.filter_map(|name| matcher.fuzzy_match(name, input).map(|score| (name, score)))
|
||||
.collect();
|
||||
|
||||
matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
|
||||
matches
|
||||
.into_iter()
|
||||
.map(|(name, _)| ((0..), name.into()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn filename(input: &str) -> Vec<Completion> {
|
||||
filename_impl(input, |entry| {
|
||||
let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
|
||||
@ -256,7 +283,7 @@ fn filename_impl<F>(input: &str, filter_fn: F) -> Vec<Completion>
|
||||
let is_tilde = input.starts_with('~') && input.len() == 1;
|
||||
let path = helix_core::path::expand_tilde(Path::new(input));
|
||||
|
||||
let (dir, file_name) = if input.ends_with('/') {
|
||||
let (dir, file_name) = if input.ends_with(std::path::MAIN_SEPARATOR) {
|
||||
(path, None)
|
||||
} else {
|
||||
let file_name = path
|
||||
|
@ -127,7 +127,7 @@ fn eval_movement(&self, movement: Movement) -> usize {
|
||||
let mut char_position = char_indices
|
||||
.iter()
|
||||
.position(|(idx, _)| *idx == self.cursor)
|
||||
.unwrap_or_else(|| char_indices.len());
|
||||
.unwrap_or(char_indices.len());
|
||||
|
||||
for _ in 0..rep {
|
||||
// Skip any non-whitespace characters
|
||||
@ -473,7 +473,7 @@ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||
}
|
||||
}
|
||||
key!(Enter) => {
|
||||
if self.selection.is_some() && self.line.ends_with('/') {
|
||||
if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) {
|
||||
self.completion = (self.completion_fn)(&self.line);
|
||||
self.exit_selection();
|
||||
} else {
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "helix-tui"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||
description = """
|
||||
A library to build rich terminal user interfaces or dashboards
|
||||
@ -21,5 +21,5 @@ cassowary = "0.3"
|
||||
unicode-segmentation = "1.8"
|
||||
crossterm = { version = "0.22", optional = true }
|
||||
serde = { version = "1", "optional" = true, features = ["derive"]}
|
||||
helix-view = { version = "0.5", path = "../helix-view", features = ["term"] }
|
||||
helix-core = { version = "0.5", path = "../helix-core" }
|
||||
helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
|
||||
helix-core = { version = "0.6", path = "../helix-core" }
|
||||
|
@ -2,5 +2,5 @@ # helix-tui
|
||||
|
||||
This library is a fork of the great library
|
||||
[tui-rs](https://github.com/fdehau/tui-rs/). We've mainly relied on the double
|
||||
buffer implementation and render diffing, side-stepping it's widget and
|
||||
buffer implementation and render diffing, side-stepping its widget and
|
||||
layouting.
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "helix-view"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
@ -16,12 +16,12 @@ term = ["crossterm"]
|
||||
[dependencies]
|
||||
bitflags = "1.3"
|
||||
anyhow = "1"
|
||||
helix-core = { version = "0.5", path = "../helix-core" }
|
||||
helix-lsp = { version = "0.5", path = "../helix-lsp"}
|
||||
helix-core = { version = "0.6", path = "../helix-core" }
|
||||
helix-lsp = { version = "0.6", path = "../helix-lsp"}
|
||||
crossterm = { version = "0.22", optional = true }
|
||||
|
||||
# Conversion traits
|
||||
once_cell = "1.8"
|
||||
once_cell = "1.9"
|
||||
url = "2"
|
||||
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
|
||||
@ -29,7 +29,6 @@ futures-util = { version = "0.3", features = ["std", "async-await"], default-fea
|
||||
|
||||
slotmap = "1"
|
||||
|
||||
encoding_rs = "0.8"
|
||||
chardetng = "0.1"
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
@ -1,5 +1,6 @@
|
||||
use anyhow::{anyhow, Context, Error};
|
||||
use serde::de::{self, Deserialize, Deserializer};
|
||||
use serde::Serialize;
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
@ -9,6 +10,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use helix_core::{
|
||||
encoding,
|
||||
history::History,
|
||||
indent::{auto_detect_indent_style, IndentStyle},
|
||||
line_ending::auto_detect_line_ending,
|
||||
@ -68,13 +70,22 @@ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Mode {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.collect_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Document {
|
||||
pub(crate) id: DocumentId,
|
||||
text: Rope,
|
||||
pub(crate) selections: HashMap<ViewId, Selection>,
|
||||
|
||||
path: Option<PathBuf>,
|
||||
encoding: &'static encoding_rs::Encoding,
|
||||
encoding: &'static encoding::Encoding,
|
||||
|
||||
/// Current editing mode.
|
||||
pub mode: Mode,
|
||||
@ -143,8 +154,8 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// be used to override encoding auto-detection.
|
||||
pub fn from_reader<R: std::io::Read + ?Sized>(
|
||||
reader: &mut R,
|
||||
encoding: Option<&'static encoding_rs::Encoding>,
|
||||
) -> Result<(Rope, &'static encoding_rs::Encoding), Error> {
|
||||
encoding: Option<&'static encoding::Encoding>,
|
||||
) -> Result<(Rope, &'static encoding::Encoding), Error> {
|
||||
// These two buffers are 8192 bytes in size each and are used as
|
||||
// intermediaries during the decoding process. Text read into `buf`
|
||||
// from `reader` is decoded into `buf_out` as UTF-8. Once either
|
||||
@ -212,11 +223,11 @@ pub fn from_reader<R: std::io::Read + ?Sized>(
|
||||
total_read += read;
|
||||
total_written += written;
|
||||
match result {
|
||||
encoding_rs::CoderResult::InputEmpty => {
|
||||
encoding::CoderResult::InputEmpty => {
|
||||
debug_assert_eq!(slice.len(), total_read);
|
||||
break;
|
||||
}
|
||||
encoding_rs::CoderResult::OutputFull => {
|
||||
encoding::CoderResult::OutputFull => {
|
||||
debug_assert!(slice.len() > total_read);
|
||||
builder.append(&buf_str[..total_written]);
|
||||
total_written = 0;
|
||||
@ -251,7 +262,7 @@ pub fn from_reader<R: std::io::Read + ?Sized>(
|
||||
/// replacement characters may appear in the encoded text.
|
||||
pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
|
||||
writer: &'a mut W,
|
||||
encoding: &'static encoding_rs::Encoding,
|
||||
encoding: &'static encoding::Encoding,
|
||||
rope: &'a Rope,
|
||||
) -> Result<(), Error> {
|
||||
// Text inside a `Rope` is stored as non-contiguous blocks of data called
|
||||
@ -286,12 +297,12 @@ pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>(
|
||||
total_read += read;
|
||||
total_written += written;
|
||||
match result {
|
||||
encoding_rs::CoderResult::InputEmpty => {
|
||||
encoding::CoderResult::InputEmpty => {
|
||||
debug_assert_eq!(chunk.len(), total_read);
|
||||
debug_assert!(buf.len() >= total_written);
|
||||
break;
|
||||
}
|
||||
encoding_rs::CoderResult::OutputFull => {
|
||||
encoding::CoderResult::OutputFull => {
|
||||
debug_assert!(chunk.len() > total_read);
|
||||
writer.write_all(&buf[..total_written]).await?;
|
||||
total_written = 0;
|
||||
@ -322,8 +333,8 @@ fn take_with<T, F>(mut_ref: &mut T, f: F)
|
||||
use url::Url;
|
||||
|
||||
impl Document {
|
||||
pub fn from(text: Rope, encoding: Option<&'static encoding_rs::Encoding>) -> Self {
|
||||
let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
|
||||
pub fn from(text: Rope, encoding: Option<&'static encoding::Encoding>) -> Self {
|
||||
let encoding = encoding.unwrap_or(encoding::UTF_8);
|
||||
let changes = ChangeSet::new(&text);
|
||||
let old_state = None;
|
||||
|
||||
@ -356,7 +367,7 @@ pub fn from(text: Rope, encoding: Option<&'static encoding_rs::Encoding>) -> Sel
|
||||
/// overwritten with the `encoding` parameter.
|
||||
pub fn open(
|
||||
path: &Path,
|
||||
encoding: Option<&'static encoding_rs::Encoding>,
|
||||
encoding: Option<&'static encoding::Encoding>,
|
||||
theme: Option<&Theme>,
|
||||
config_loader: Option<&syntax::Loader>,
|
||||
) -> Result<Self, Error> {
|
||||
@ -366,7 +377,7 @@ pub fn open(
|
||||
std::fs::File::open(path).context(format!("unable to open {:?}", path))?;
|
||||
from_reader(&mut file, encoding)?
|
||||
} else {
|
||||
let encoding = encoding.unwrap_or(encoding_rs::UTF_8);
|
||||
let encoding = encoding.unwrap_or(encoding::UTF_8);
|
||||
(Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
|
||||
};
|
||||
|
||||
@ -548,7 +559,7 @@ pub fn reload(&mut self, view_id: ViewId) -> Result<(), Error> {
|
||||
|
||||
/// Sets the [`Document`]'s encoding with the encoding correspondent to `label`.
|
||||
pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
|
||||
match encoding_rs::Encoding::for_label(label.as_bytes()) {
|
||||
match encoding::Encoding::for_label(label.as_bytes()) {
|
||||
Some(encoding) => self.encoding = encoding,
|
||||
None => return Err(anyhow::anyhow!("unknown encoding")),
|
||||
}
|
||||
@ -556,7 +567,7 @@ pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> {
|
||||
}
|
||||
|
||||
/// Returns the [`Document`]'s current encoding.
|
||||
pub fn encoding(&self) -> &'static encoding_rs::Encoding {
|
||||
pub fn encoding(&self) -> &'static encoding::Encoding {
|
||||
self.encoding
|
||||
}
|
||||
|
||||
@ -889,6 +900,10 @@ pub fn indent_unit(&self) -> &'static str {
|
||||
self.indent_style.as_str()
|
||||
}
|
||||
|
||||
pub fn changes(&self) -> &ChangeSet {
|
||||
&self.changes
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// File path on disk.
|
||||
pub fn path(&self) -> Option<&PathBuf> {
|
||||
@ -1119,7 +1134,7 @@ fn test_line_ending() {
|
||||
|
||||
macro_rules! test_decode {
|
||||
($label:expr, $label_override:expr) => {
|
||||
let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap();
|
||||
let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
|
||||
let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
|
||||
let path = base_path.join(format!("{}_in.txt", $label));
|
||||
let ref_path = base_path.join(format!("{}_in_ref.txt", $label));
|
||||
@ -1138,7 +1153,7 @@ macro_rules! test_decode {
|
||||
|
||||
macro_rules! test_encode {
|
||||
($label:expr, $label_override:expr) => {
|
||||
let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap();
|
||||
let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap();
|
||||
let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding");
|
||||
let path = base_path.join(format!("{}_out.txt", $label));
|
||||
let ref_path = base_path.join(format!("{}_out_ref.txt", $label));
|
||||
|
@ -27,7 +27,7 @@
|
||||
use helix_core::syntax;
|
||||
use helix_core::{Position, Selection};
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize};
|
||||
|
||||
fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
|
||||
where
|
||||
@ -37,7 +37,7 @@ fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::E
|
||||
Ok(Duration::from_millis(millis))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
||||
pub struct FilePickerConfig {
|
||||
/// IgnoreOptions
|
||||
@ -77,7 +77,7 @@ fn default() -> Self {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
/// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5.
|
||||
@ -137,6 +137,20 @@ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for CursorShapeConfig {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut map = serializer.serialize_map(Some(self.len()))?;
|
||||
let modes = [Mode::Normal, Mode::Select, Mode::Insert];
|
||||
for mode in modes {
|
||||
map.serialize_entry(&mode, &self.from_mode(mode))?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for CursorShapeConfig {
|
||||
type Target = [CursorKind; 3];
|
||||
|
||||
@ -151,7 +165,7 @@ fn default() -> Self {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum LineNumber {
|
||||
/// Show absolute line number
|
||||
@ -160,6 +174,18 @@ pub enum LineNumber {
|
||||
Relative,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for LineNumber {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"absolute" | "abs" => Ok(Self::Absolute),
|
||||
"relative" | "rel" => Ok(Self::Relative),
|
||||
_ => anyhow::bail!("Line number can only be `absolute` or `relative`."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
@ -1,11 +1,11 @@
|
||||
use bitflags::bitflags;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
cmp::{max, min},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
/// UNSTABLE
|
||||
pub enum CursorKind {
|
||||
|
@ -254,6 +254,43 @@ fn from(KeyEvent { code, modifiers }: KeyEvent) -> Self {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_macro(keys_str: &str) -> anyhow::Result<Vec<KeyEvent>> {
|
||||
use anyhow::Context;
|
||||
let mut keys_res: anyhow::Result<_> = Ok(Vec::new());
|
||||
let mut i = 0;
|
||||
while let Ok(keys) = &mut keys_res {
|
||||
if i >= keys_str.len() {
|
||||
break;
|
||||
}
|
||||
if !keys_str.is_char_boundary(i) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let s = &keys_str[i..];
|
||||
let mut end_i = 1;
|
||||
while !s.is_char_boundary(end_i) {
|
||||
end_i += 1;
|
||||
}
|
||||
let c = &s[..end_i];
|
||||
if c == ">" {
|
||||
keys_res = Err(anyhow!("Unmatched '>'"));
|
||||
} else if c != "<" {
|
||||
keys.push(c);
|
||||
i += end_i;
|
||||
} else {
|
||||
match s.find('>').context("'>' expected") {
|
||||
Ok(end_i) => {
|
||||
keys.push(&s[1..end_i]);
|
||||
i += end_i + 1;
|
||||
}
|
||||
Err(err) => keys_res = Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
keys_res.and_then(|keys| keys.into_iter().map(str::parse).collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@ -339,4 +376,120 @@ fn parsing_nonsensical_keys_fails() {
|
||||
assert!(str::parse::<KeyEvent>("123").is_err());
|
||||
assert!(str::parse::<KeyEvent>("S--").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_valid_macros() {
|
||||
assert_eq!(
|
||||
parse_macro("xdo").ok(),
|
||||
Some(vec![
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('x'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('d'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('o'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_macro("<C-w>v<C-w>h<C-o>xx<A-s>").ok(),
|
||||
Some(vec![
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('w'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('v'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('w'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('h'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('o'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('x'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('x'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('s'),
|
||||
modifiers: KeyModifiers::ALT,
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_macro(":o foo.bar<ret>").ok(),
|
||||
Some(vec![
|
||||
KeyEvent {
|
||||
code: KeyCode::Char(':'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('o'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char(' '),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('f'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('o'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('o'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('.'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('b'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('a'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Char('r'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
KeyEvent {
|
||||
code: KeyCode::Enter,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_invalid_macros_fails() {
|
||||
assert!(parse_macro("abc<C-").is_err());
|
||||
assert!(parse_macro("abc>123").is_err());
|
||||
assert!(parse_macro("wd<foo>").is_err());
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +80,8 @@ pub struct View {
|
||||
// uses two docs because we want to be able to swap between the
|
||||
// two last modified docs which we need to manually keep track of
|
||||
pub last_modified_docs: [Option<DocumentId>; 2],
|
||||
/// used to store previous selections of tree-sitter objecs
|
||||
pub object_selections: Vec<Selection>,
|
||||
}
|
||||
|
||||
impl View {
|
||||
@ -92,6 +94,7 @@ pub fn new(doc: DocumentId) -> Self {
|
||||
jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel
|
||||
last_accessed_doc: None,
|
||||
last_modified_docs: [None, None],
|
||||
object_selections: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
110
languages.toml
110
languages.toml
@ -3,15 +3,11 @@ name = "rust"
|
||||
scope = "source.rust"
|
||||
injection-regex = "rust"
|
||||
file-types = ["rs"]
|
||||
roots = []
|
||||
roots = ["Cargo.toml", "Cargo.lock"]
|
||||
auto-format = true
|
||||
comment-token = "//"
|
||||
language-server = { command = "rust-analyzer" }
|
||||
indent = { tab-width = 4, unit = " " }
|
||||
[language.config]
|
||||
cargo = { loadOutDirsFromCheck = true }
|
||||
procMacro = { enable = false }
|
||||
diagnostics = { disabled = ["unresolved-proc-macro"] }
|
||||
|
||||
[[language]]
|
||||
name = "toml"
|
||||
@ -45,6 +41,17 @@ comment-token = "#"
|
||||
language-server = { command = "elixir-ls" }
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
|
||||
[[language]]
|
||||
name = "fish"
|
||||
scope = "source.fish"
|
||||
injection-regex = "fish"
|
||||
file-types = ["fish"]
|
||||
shebangs = ["fish"]
|
||||
roots = []
|
||||
comment-token = "#"
|
||||
|
||||
indent = { tab-width = 4, unit = " " }
|
||||
|
||||
[[language]]
|
||||
name = "mint"
|
||||
scope = "source.mint"
|
||||
@ -327,6 +334,7 @@ file-types = ["yml", "yaml"]
|
||||
roots = []
|
||||
comment-token = "#"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
injection-regex = "yml|yaml"
|
||||
|
||||
# [[language]]
|
||||
# name = "haskell"
|
||||
@ -379,6 +387,7 @@ roots = []
|
||||
comment-token = "#"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
language-server = { command = "cmake-language-server" }
|
||||
injection-regex = "cmake"
|
||||
|
||||
[[language]]
|
||||
name = "glsl"
|
||||
@ -387,6 +396,7 @@ file-types = ["glsl", "vert", "tesc", "tese", "geom", "frag", "comp" ]
|
||||
roots = []
|
||||
comment-token = "//"
|
||||
indent = { tab-width = 4, unit = " " }
|
||||
injection-regex = "glsl"
|
||||
|
||||
[[language]]
|
||||
name = "perl"
|
||||
@ -406,6 +416,13 @@ shebangs = ["racket"]
|
||||
comment-token = ";"
|
||||
language-server = { command = "racket", args = ["-l", "racket-langserver"] }
|
||||
|
||||
[[language]]
|
||||
name = "comment"
|
||||
scope = "scope.comment"
|
||||
roots = []
|
||||
file-types = []
|
||||
injection-regex = "comment"
|
||||
|
||||
[[language]]
|
||||
name = "wgsl"
|
||||
scope = "source.wgsl"
|
||||
@ -421,6 +438,34 @@ roots = []
|
||||
file-types = ["ll"]
|
||||
comment-token = ";"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
injection-regex = "llvm"
|
||||
|
||||
[[language]]
|
||||
name = "llvm-mir"
|
||||
scope = "source.llvm_mir"
|
||||
roots = []
|
||||
file-types = []
|
||||
comment-token = ";"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
injection-regex = "mir"
|
||||
|
||||
[[language]]
|
||||
name = "llvm-mir-yaml"
|
||||
tree-sitter-library = "yaml"
|
||||
scope = "source.yaml"
|
||||
roots = []
|
||||
file-types = ["mir"]
|
||||
comment-token = "#"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
|
||||
[[language]]
|
||||
name = "tablegen"
|
||||
scope = "source.tablegen"
|
||||
roots = []
|
||||
file-types = ["td"]
|
||||
comment-token = "//"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
injection-regex = "tablegen"
|
||||
|
||||
[[language]]
|
||||
name = "markdown"
|
||||
@ -430,3 +475,58 @@ file-types = ["md"]
|
||||
roots = []
|
||||
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
|
||||
[[language]]
|
||||
name = "dart"
|
||||
scope = "source.dart"
|
||||
file-types = ["dart"]
|
||||
roots = ["pubspec.yaml"]
|
||||
auto-format = true
|
||||
comment-token = "//"
|
||||
language-server = { command = "dart", args = ["language-server", "--client-id=helix"] }
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
|
||||
[[language]]
|
||||
name = "scala"
|
||||
scope = "source.scala"
|
||||
roots = ["build.sbt"]
|
||||
file-types = ["scala", "sbt"]
|
||||
comment-token = "//"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
language-server = { command = "metals" }
|
||||
|
||||
[[language]]
|
||||
name = "dockerfile"
|
||||
scope = "source.dockerfile"
|
||||
injection-regex = "docker|dockerfile"
|
||||
roots = ["Dockerfile"]
|
||||
file-types = ["Dockerfile", "dockerfile"]
|
||||
comment-token = "#"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
language-server = { command = "docker-langserver", args = ["--stdio"] }
|
||||
|
||||
[[language]]
|
||||
name = "git-commit"
|
||||
scope = "git.commitmsg"
|
||||
roots = []
|
||||
file-types = ["COMMIT_EDITMSG"]
|
||||
comment-token = "#"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
|
||||
[[language]]
|
||||
name = "git-diff"
|
||||
scope = "source.diff"
|
||||
roots = []
|
||||
file-types = ["diff"]
|
||||
injection-regex = "diff"
|
||||
comment-token = "#"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
|
||||
[[language]]
|
||||
name = "git-rebase"
|
||||
scope = "source.gitrebase"
|
||||
roots = []
|
||||
file-types = ["git-rebase-todo"]
|
||||
injection-regex = "git-rebase"
|
||||
comment-token = "#"
|
||||
indent = { tab-width = 2, unit = " " }
|
||||
|
16
runtime/queries/c/indents.toml
Normal file
16
runtime/queries/c/indents.toml
Normal file
@ -0,0 +1,16 @@
|
||||
indent = [
|
||||
"compound_statement",
|
||||
"field_declaration_list",
|
||||
"enumerator_list",
|
||||
"parameter_list",
|
||||
"init_declarator",
|
||||
"case_statement",
|
||||
"condition_clause",
|
||||
"expression_statement",
|
||||
]
|
||||
|
||||
outdent = [
|
||||
"case",
|
||||
"}",
|
||||
"]",
|
||||
]
|
2
runtime/queries/c/injections.scm
Normal file
2
runtime/queries/c/injections.scm
Normal file
@ -0,0 +1,2 @@
|
||||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
13
runtime/queries/c/textobjects.scm
Normal file
13
runtime/queries/c/textobjects.scm
Normal file
@ -0,0 +1,13 @@
|
||||
(function_definition
|
||||
body: (_) @function.inside) @function.around
|
||||
|
||||
(struct_specifier
|
||||
body: (_) @class.inside) @class.around
|
||||
|
||||
(enum_specifier
|
||||
body: (_) @class.inside) @class.around
|
||||
|
||||
(union_specifier
|
||||
body: (_) @class.inside) @class.around
|
||||
|
||||
(parameter_declaration) @parameter.inside
|
12
runtime/queries/cmake/indents.toml
Normal file
12
runtime/queries/cmake/indents.toml
Normal file
@ -0,0 +1,12 @@
|
||||
indent = [
|
||||
"if_condition",
|
||||
"foreach_loop",
|
||||
"while_loop",
|
||||
"function_def",
|
||||
"macro_def",
|
||||
"normal_command",
|
||||
]
|
||||
|
||||
outdent = [
|
||||
")"
|
||||
]
|
4
runtime/queries/cmake/injections.scm
Normal file
4
runtime/queries/cmake/injections.scm
Normal file
@ -0,0 +1,4 @@
|
||||
((line_comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
||||
((bracket_comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
3
runtime/queries/cmake/textobjects.scm
Normal file
3
runtime/queries/cmake/textobjects.scm
Normal file
@ -0,0 +1,3 @@
|
||||
(macro_def) @function.around
|
||||
|
||||
(argument) @parameter.inside
|
30
runtime/queries/comment/highlights.scm
Normal file
30
runtime/queries/comment/highlights.scm
Normal file
@ -0,0 +1,30 @@
|
||||
[
|
||||
"("
|
||||
")"
|
||||
] @punctuation.bracket
|
||||
|
||||
":" @punctuation.delimiter
|
||||
|
||||
((tag (name) @warning)
|
||||
(#match? @warning "^(TODO|HACK|WARNING)$"))
|
||||
|
||||
("text" @warning
|
||||
(#match? @warning "^(TODO|HACK|WARNING)$"))
|
||||
|
||||
((tag (name) @error)
|
||||
(match? @error "^(FIXME|XXX|BUG)$"))
|
||||
|
||||
("text" @error
|
||||
(match? @error "^(FIXME|XXX|BUG)$"))
|
||||
|
||||
(tag
|
||||
(name) @ui.text
|
||||
(user)? @constant)
|
||||
|
||||
; Issue number (#123)
|
||||
("text" @constant.numeric
|
||||
(#match? @constant.numeric "^#[0-9]+$"))
|
||||
|
||||
; User mention (@user)
|
||||
("text" @tag
|
||||
(#match? @tag "^[@][a-zA-Z0-9_-]+$"))
|
17
runtime/queries/cpp/indents.toml
Normal file
17
runtime/queries/cpp/indents.toml
Normal file
@ -0,0 +1,17 @@
|
||||
indent = [
|
||||
"compound_statement",
|
||||
"field_declaration_list",
|
||||
"enumerator_list",
|
||||
"parameter_list",
|
||||
"init_declarator",
|
||||
"case_statement",
|
||||
"condition_clause",
|
||||
"expression_statement",
|
||||
]
|
||||
|
||||
outdent = [
|
||||
"case",
|
||||
"access_specifier",
|
||||
"}",
|
||||
"]",
|
||||
]
|
1
runtime/queries/cpp/injections.scm
Normal file
1
runtime/queries/cpp/injections.scm
Normal file
@ -0,0 +1 @@
|
||||
; inherits: c
|
7
runtime/queries/cpp/textobjects.scm
Normal file
7
runtime/queries/cpp/textobjects.scm
Normal file
@ -0,0 +1,7 @@
|
||||
; inherits: c
|
||||
|
||||
(lambda_expression
|
||||
body: (_) @function.inside) @function.around
|
||||
|
||||
(class_specifier
|
||||
body: (_) @class.inside) @class.around
|
237
runtime/queries/dart/highlights.scm
Normal file
237
runtime/queries/dart/highlights.scm
Normal file
@ -0,0 +1,237 @@
|
||||
(dotted_identifier_list) @string
|
||||
|
||||
; Methods
|
||||
; --------------------
|
||||
(super) @function.builtin
|
||||
|
||||
(function_expression_body (identifier) @function.method)
|
||||
((identifier)(selector (argument_part)) @function.method)
|
||||
|
||||
; Annotations
|
||||
; --------------------
|
||||
(annotation
|
||||
name: (identifier) @attribute)
|
||||
(marker_annotation
|
||||
name: (identifier) @attribute)
|
||||
|
||||
; Types
|
||||
; --------------------
|
||||
(class_definition
|
||||
name: (identifier) @type)
|
||||
|
||||
(constructor_signature
|
||||
name: (identifier) @function.method)
|
||||
|
||||
(function_signature
|
||||
name: (identifier) @function.method)
|
||||
|
||||
(getter_signature
|
||||
(identifier) @function.builtin)
|
||||
|
||||
(setter_signature
|
||||
name: (identifier) @function.builtin)
|
||||
|
||||
(enum_declaration
|
||||
name: (identifier) @type)
|
||||
|
||||
(enum_constant
|
||||
name: (identifier) @type.builtin)
|
||||
|
||||
(void_type) @type.builtin
|
||||
|
||||
((scoped_identifier
|
||||
scope: (identifier) @type)
|
||||
(#match? @type "^[a-zA-Z]"))
|
||||
|
||||
((scoped_identifier
|
||||
scope: (identifier) @type
|
||||
name: (identifier) @type)
|
||||
(#match? @type "^[a-zA-Z]"))
|
||||
|
||||
; the DisabledDrawerButtons in : const DisabledDrawerButtons(history: true),
|
||||
(type_identifier) @type.builtin
|
||||
|
||||
; Variables
|
||||
; --------------------
|
||||
; the "File" in var file = File();
|
||||
((identifier) @namespace
|
||||
(#match? @namespace "^_?[A-Z].*[a-z]")) ; catch Classes or IClasses not CLASSES
|
||||
|
||||
("Function" @type.builtin)
|
||||
(inferred_type) @type.builtin
|
||||
|
||||
; properties
|
||||
(unconditional_assignable_selector
|
||||
(identifier) @variable.other.member)
|
||||
|
||||
(conditional_assignable_selector
|
||||
(identifier) @variable.other.member)
|
||||
|
||||
; assignments
|
||||
; --------------------
|
||||
; the "strings" in : strings = "some string"
|
||||
(assignment_expression
|
||||
left: (assignable_expression) @variable)
|
||||
|
||||
(this) @variable.builtin
|
||||
|
||||
; Parameters
|
||||
; --------------------
|
||||
(formal_parameter
|
||||
name: (identifier) @variable)
|
||||
|
||||
(named_argument
|
||||
(label (identifier) @variable))
|
||||
|
||||
; Literals
|
||||
; --------------------
|
||||
[
|
||||
(hex_integer_literal)
|
||||
(decimal_integer_literal)
|
||||
(decimal_floating_point_literal)
|
||||
;(octal_integer_literal)
|
||||
;(hex_floating_point_literal)
|
||||
] @constant.numeric.integer
|
||||
|
||||
(symbol_literal) @string.special.symbol
|
||||
(string_literal) @string
|
||||
|
||||
[
|
||||
(const_builtin)
|
||||
(final_builtin)
|
||||
] @variable.builtin
|
||||
|
||||
[
|
||||
(true)
|
||||
(false)
|
||||
] @constant.builtin.boolean
|
||||
|
||||
(null_literal) @constant.builtin
|
||||
|
||||
(comment) @comment.line
|
||||
(documentation_comment) @comment.block.documentation
|
||||
|
||||
; Tokens
|
||||
; --------------------
|
||||
(template_substitution
|
||||
"$" @punctuation.special
|
||||
"{" @punctuation.special
|
||||
"}" @punctuation.special
|
||||
) @embedded
|
||||
|
||||
(template_substitution
|
||||
"$" @punctuation.special
|
||||
(identifier_dollar_escaped) @variable
|
||||
) @embedded
|
||||
|
||||
(escape_sequence) @constant.character.escape
|
||||
|
||||
; Punctuation
|
||||
;---------------------
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
";"
|
||||
"."
|
||||
","
|
||||
":"
|
||||
] @punctuation.delimiter
|
||||
|
||||
; Operators
|
||||
;---------------------
|
||||
[
|
||||
"@"
|
||||
"?"
|
||||
"=>"
|
||||
".."
|
||||
"=="
|
||||
"&&"
|
||||
"%"
|
||||
"<"
|
||||
">"
|
||||
"="
|
||||
">="
|
||||
"<="
|
||||
"||"
|
||||
(multiplicative_operator)
|
||||
(increment_operator)
|
||||
(is_operator)
|
||||
(prefix_operator)
|
||||
(equality_operator)
|
||||
(additive_operator)
|
||||
] @operator
|
||||
|
||||
; Keywords
|
||||
; --------------------
|
||||
["import" "library" "export"] @keyword.control.import
|
||||
["do" "while" "continue" "for"] @keyword.control.repeat
|
||||
["return" "yield"] @keyword.control.return
|
||||
["as" "in" "is"] @keyword.operator
|
||||
|
||||
[
|
||||
"?."
|
||||
"??"
|
||||
"if"
|
||||
"else"
|
||||
"switch"
|
||||
"default"
|
||||
"late"
|
||||
] @keyword.control.conditional
|
||||
|
||||
[
|
||||
"try"
|
||||
"throw"
|
||||
"catch"
|
||||
"finally"
|
||||
(break_statement)
|
||||
] @keyword.control.exception
|
||||
|
||||
; Reserved words (cannot be used as identifiers)
|
||||
[
|
||||
(case_builtin)
|
||||
"abstract"
|
||||
"async"
|
||||
"async*"
|
||||
"await"
|
||||
"class"
|
||||
"covariant"
|
||||
"deferred"
|
||||
"dynamic"
|
||||
"enum"
|
||||
"extends"
|
||||
"extension"
|
||||
"external"
|
||||
"factory"
|
||||
"Function"
|
||||
"get"
|
||||
"implements"
|
||||
"interface"
|
||||
"mixin"
|
||||
"new"
|
||||
"on"
|
||||
"operator"
|
||||
"part"
|
||||
"required"
|
||||
"set"
|
||||
"show"
|
||||
"static"
|
||||
"super"
|
||||
"sync*"
|
||||
"typedef"
|
||||
"with"
|
||||
] @keyword
|
||||
|
||||
; when used as an identifier:
|
||||
((identifier) @variable.builtin
|
||||
(#match? @variable.builtin "^(abstract|as|covariant|deferred|dynamic|export|external|factory|Function|get|implements|import|interface|library|operator|mixin|part|set|static|typedef)$"))
|
||||
|
||||
; Error
|
||||
(ERROR) @error
|
||||
|
20
runtime/queries/dart/indents.toml
Normal file
20
runtime/queries/dart/indents.toml
Normal file
@ -0,0 +1,20 @@
|
||||
indent = [
|
||||
"class_body",
|
||||
"function_body",
|
||||
"function_expression_body",
|
||||
"declaration",
|
||||
"initializers",
|
||||
"switch_block",
|
||||
"if_statement",
|
||||
"formal_parameter_list",
|
||||
"formal_parameter",
|
||||
"list_literal",
|
||||
"return_statement",
|
||||
"arguments"
|
||||
]
|
||||
|
||||
outdent = [
|
||||
"}",
|
||||
"]",
|
||||
")"
|
||||
]
|
20
runtime/queries/dart/locals.scm
Normal file
20
runtime/queries/dart/locals.scm
Normal file
@ -0,0 +1,20 @@
|
||||
; Scopes
|
||||
;-------
|
||||
|
||||
[
|
||||
(block)
|
||||
(try_statement)
|
||||
(catch_clause)
|
||||
(finally_clause)
|
||||
] @local.scope
|
||||
|
||||
; Definitions
|
||||
;------------
|
||||
|
||||
(class_definition
|
||||
body: (_) @local.definition)
|
||||
|
||||
; References
|
||||
;------------
|
||||
|
||||
(identifier) @local.reference
|
51
runtime/queries/dockerfile/highlights.scm
Normal file
51
runtime/queries/dockerfile/highlights.scm
Normal file
@ -0,0 +1,51 @@
|
||||
[
|
||||
"FROM"
|
||||
"AS"
|
||||
"RUN"
|
||||
"CMD"
|
||||
"LABEL"
|
||||
"EXPOSE"
|
||||
"ENV"
|
||||
"ADD"
|
||||
"COPY"
|
||||
"ENTRYPOINT"
|
||||
"VOLUME"
|
||||
"USER"
|
||||
"WORKDIR"
|
||||
"ARG"
|
||||
"ONBUILD"
|
||||
"STOPSIGNAL"
|
||||
"HEALTHCHECK"
|
||||
"SHELL"
|
||||
"MAINTAINER"
|
||||
"CROSS_BUILD"
|
||||
] @keyword
|
||||
|
||||
[
|
||||
":"
|
||||
"@"
|
||||
] @operator
|
||||
|
||||
(comment) @comment
|
||||
|
||||
|
||||
(image_spec
|
||||
(image_tag
|
||||
":" @punctuation.special)
|
||||
(image_digest
|
||||
"@" @punctuation.special))
|
||||
|
||||
(double_quoted_string) @string
|
||||
|
||||
(expansion
|
||||
[
|
||||
"$"
|
||||
"{"
|
||||
"}"
|
||||
] @punctuation.special
|
||||
) @none
|
||||
|
||||
((variable) @constant
|
||||
(#match? @constant "^[A-Z][A-Z_0-9]*$"))
|
||||
|
||||
|
6
runtime/queries/dockerfile/injections.scm
Normal file
6
runtime/queries/dockerfile/injections.scm
Normal file
@ -0,0 +1,6 @@
|
||||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
||||
|
||||
([(shell_command) (shell_fragment)] @injection.content
|
||||
(#set! injection.language "bash"))
|
||||
|
2
runtime/queries/elixir/injections.scm
Normal file
2
runtime/queries/elixir/injections.scm
Normal file
@ -0,0 +1,2 @@
|
||||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
156
runtime/queries/fish/highlights.scm
Normal file
156
runtime/queries/fish/highlights.scm
Normal file
@ -0,0 +1,156 @@
|
||||
;; Operators
|
||||
|
||||
[
|
||||
"&&"
|
||||
"||"
|
||||
"|"
|
||||
"&"
|
||||
"="
|
||||
"!="
|
||||
".."
|
||||
"!"
|
||||
(direction)
|
||||
(stream_redirect)
|
||||
(test_option)
|
||||
] @operator
|
||||
|
||||
[
|
||||
"not"
|
||||
"and"
|
||||
"or"
|
||||
] @keyword.operator
|
||||
|
||||
;; Conditionals
|
||||
|
||||
(if_statement
|
||||
[
|
||||
"if"
|
||||
"end"
|
||||
] @keyword.control.conditional)
|
||||
|
||||
(switch_statement
|
||||
[
|
||||
"switch"
|
||||
"end"
|
||||
] @keyword.control.conditional)
|
||||
|
||||
(case_clause
|
||||
[
|
||||
"case"
|
||||
] @keyword.control.conditional)
|
||||
|
||||
(else_clause
|
||||
[
|
||||
"else"
|
||||
] @keyword.control.conditional)
|
||||
|
||||
(else_if_clause
|
||||
[
|
||||
"else"
|
||||
"if"
|
||||
] @keyword.control.conditional)
|
||||
|
||||
;; Loops/Blocks
|
||||
|
||||
(while_statement
|
||||
[
|
||||
"while"
|
||||
"end"
|
||||
] @keyword.control.repeat)
|
||||
|
||||
(for_statement
|
||||
[
|
||||
"for"
|
||||
"end"
|
||||
] @keyword.control.repeat)
|
||||
|
||||
(begin_statement
|
||||
[
|
||||
"begin"
|
||||
"end"
|
||||
] @keyword.control.repeat)
|
||||
|
||||
;; Keywords
|
||||
|
||||
[
|
||||
"in"
|
||||
(break)
|
||||
(continue)
|
||||
] @keyword
|
||||
|
||||
"return" @keyword.control.return
|
||||
|
||||
;; Punctuation
|
||||
|
||||
[
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
"("
|
||||
")"
|
||||
] @punctuation.bracket
|
||||
|
||||
"," @punctuation.delimiter
|
||||
|
||||
;; Commands
|
||||
|
||||
(command
|
||||
argument: [
|
||||
(word) @variable.parameter (#match? @variable.parameter "^-")
|
||||
]
|
||||
)
|
||||
|
||||
; non-bultin command names
|
||||
(command name: (word) @function)
|
||||
|
||||
; derived from builtin -n (fish 3.2.2)
|
||||
(command
|
||||
name: [
|
||||
(word) @function.builtin
|
||||
(#match? @function.builtin "^(\.|:|_|alias|argparse|bg|bind|block|breakpoint|builtin|cd|command|commandline|complete|contains|count|disown|echo|emit|eval|exec|exit|fg|functions|history|isatty|jobs|math|printf|pwd|random|read|realpath|set|set_color|source|status|string|test|time|type|ulimit|wait)$")
|
||||
]
|
||||
)
|
||||
|
||||
(test_command "test" @function.builtin)
|
||||
|
||||
;; Functions
|
||||
|
||||
(function_definition ["function" "end"] @keyword.function)
|
||||
|
||||
(function_definition
|
||||
name: [
|
||||
(word) (concatenation)
|
||||
]
|
||||
@function)
|
||||
|
||||
(function_definition
|
||||
option: [
|
||||
(word)
|
||||
(concatenation (word))
|
||||
] @variable.parameter (#match? @variable.parameter "^-")
|
||||
)
|
||||
|
||||
;; Strings
|
||||
|
||||
[(double_quote_string) (single_quote_string)] @string
|
||||
(escape_sequence) @constant.character.escape
|
||||
|
||||
;; Variables
|
||||
|
||||
(variable_name) @variable
|
||||
(variable_expansion) @constant
|
||||
|
||||
;; Nodes
|
||||
|
||||
(integer) @constant.numeric.integer
|
||||
(float) @constant.numeric.float
|
||||
(comment) @comment
|
||||
(test_option) @string
|
||||
|
||||
((word) @constant.builtin.boolean
|
||||
(#match? @constant.builtin.boolean "^(true|false)$"))
|
||||
|
||||
;; Error
|
||||
|
||||
(ERROR) @error
|
12
runtime/queries/fish/indents.toml
Normal file
12
runtime/queries/fish/indents.toml
Normal file
@ -0,0 +1,12 @@
|
||||
indent = [
|
||||
"function_definition",
|
||||
"while_statement",
|
||||
"for_statement",
|
||||
"if_statement",
|
||||
"begin_statement",
|
||||
"switch_statement",
|
||||
]
|
||||
|
||||
outdent = [
|
||||
"end"
|
||||
]
|
2
runtime/queries/fish/injections.scm
Normal file
2
runtime/queries/fish/injections.scm
Normal file
@ -0,0 +1,2 @@
|
||||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
1
runtime/queries/fish/textobjects.scm
Normal file
1
runtime/queries/fish/textobjects.scm
Normal file
@ -0,0 +1 @@
|
||||
(function_definition) @function.around
|
14
runtime/queries/git-commit/highlights.scm
Normal file
14
runtime/queries/git-commit/highlights.scm
Normal file
@ -0,0 +1,14 @@
|
||||
(subject) @markup.heading
|
||||
(path) @string.special.path
|
||||
(branch) @string.special.symbol
|
||||
(commit) @constant
|
||||
(item) @markup.link.url
|
||||
(header) @tag
|
||||
|
||||
(change kind: "new file" @diff.plus)
|
||||
(change kind: "deleted" @diff.minus)
|
||||
(change kind: "modified" @diff.delta)
|
||||
(change kind: "renamed" @diff.delta.moved)
|
||||
|
||||
[":" "->"] @punctuation.delimeter
|
||||
(comment) @comment
|
8
runtime/queries/git-commit/injections.scm
Normal file
8
runtime/queries/git-commit/injections.scm
Normal file
@ -0,0 +1,8 @@
|
||||
((comment (scissors))
|
||||
(message) @injection.content
|
||||
(#set! injection.include-children)
|
||||
(#set! injection.language "diff"))
|
||||
|
||||
((rebase_command) @injection.content
|
||||
(#set! injection.include-children)
|
||||
(#set! injection.language "git-rebase"))
|
6
runtime/queries/git-diff/highlights.scm
Normal file
6
runtime/queries/git-diff/highlights.scm
Normal file
@ -0,0 +1,6 @@
|
||||
[(addition) (new_file)] @diff.plus
|
||||
[(deletion) (old_file)] @diff.minus
|
||||
|
||||
(commit) @constant
|
||||
(location) @attribute
|
||||
(command) @markup.bold
|
11
runtime/queries/git-rebase/highlights.scm
Normal file
11
runtime/queries/git-rebase/highlights.scm
Normal file
@ -0,0 +1,11 @@
|
||||
(operation operator: ["p" "pick" "r" "reword" "e" "edit" "s" "squash" "m" "merge" "d" "drop" "b" "break" "x" "exec"] @keyword)
|
||||
(operation operator: ["l" "label" "t" "reset"] @function)
|
||||
(operation operator: ["f" "fixup"] @function.special)
|
||||
|
||||
(option) @operator
|
||||
(label) @string.special.symbol
|
||||
(commit) @constant
|
||||
"#" @punctuation.delimiter
|
||||
(comment) @comment
|
||||
|
||||
(ERROR) @error
|
4
runtime/queries/git-rebase/injections.scm
Normal file
4
runtime/queries/git-rebase/injections.scm
Normal file
@ -0,0 +1,4 @@
|
||||
((operation
|
||||
operator: ["x" "exec"]
|
||||
(command) @injection.content)
|
||||
(#set! injection.language "bash"))
|
@ -1,3 +1,4 @@
|
||||
(preproc_arg) @glsl
|
||||
; inherits: c
|
||||
|
||||
(comment) @comment
|
||||
((preproc_arg) @injection.content
|
||||
(#set! injection.language "glsl"))
|
||||
|
@ -1,5 +1,7 @@
|
||||
; TODO: re-add when markdown is added.
|
||||
; ((triple_string) @markdown
|
||||
; (#offset! @markdown 0 3 0 -3))
|
||||
; ((triple_string) @injection.content
|
||||
; (#offset! @injection.content 0 3 0 -3)
|
||||
; (#set! injection.language "markdown"))
|
||||
|
||||
(comment) @comment
|
||||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
||||
|
@ -371,7 +371,7 @@
|
||||
((generic_command
|
||||
name:(generic_command_name) @_name
|
||||
.
|
||||
arg: (_) @markup.underline.link)
|
||||
arg: (_) @markup.link.url)
|
||||
(#match? @_name "^(\\\\url|\\\\href)$"))
|
||||
|
||||
(ERROR) @error
|
||||
|
@ -1,2 +1,2 @@
|
||||
(comment) @comment
|
||||
(note) @comment
|
||||
([(comment) (note)] @injection.content
|
||||
(#set! injection.language "comment"))
|
||||
|
1
runtime/queries/llvm-mir-yaml/highlights.scm
Normal file
1
runtime/queries/llvm-mir-yaml/highlights.scm
Normal file
@ -0,0 +1 @@
|
||||
; inherits: yaml
|
3
runtime/queries/llvm-mir-yaml/indents.toml
Normal file
3
runtime/queries/llvm-mir-yaml/indents.toml
Normal file
@ -0,0 +1,3 @@
|
||||
indent = [
|
||||
"block_mapping_pair",
|
||||
]
|
9
runtime/queries/llvm-mir-yaml/injections.scm
Normal file
9
runtime/queries/llvm-mir-yaml/injections.scm
Normal file
@ -0,0 +1,9 @@
|
||||
; inherits: yaml
|
||||
|
||||
((document (block_node (block_scalar) @injection.content))
|
||||
(#set! injection.language "llvm"))
|
||||
|
||||
((document (block_node (block_mapping (block_mapping_pair
|
||||
key: (flow_node (plain_scalar (string_scalar))) ; "body"
|
||||
value: (block_node (block_scalar) @injection.content)))))
|
||||
(#set! injection.language "mir"))
|
136
runtime/queries/llvm-mir/highlights.scm
Normal file
136
runtime/queries/llvm-mir/highlights.scm
Normal file
@ -0,0 +1,136 @@
|
||||
[
|
||||
(label)
|
||||
(bb_ref)
|
||||
] @label
|
||||
|
||||
[
|
||||
(comment)
|
||||
(multiline_comment)
|
||||
] @comment
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
"<"
|
||||
">"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
","
|
||||
":"
|
||||
"|"
|
||||
"*"
|
||||
] @punctuation.delimiter
|
||||
|
||||
[
|
||||
"="
|
||||
"x"
|
||||
] @operator
|
||||
|
||||
[
|
||||
"true"
|
||||
"false"
|
||||
] @constant.builtin.boolean
|
||||
|
||||
[
|
||||
"null"
|
||||
"_"
|
||||
"unknown-address"
|
||||
] @constant.builtin
|
||||
|
||||
[
|
||||
(stack_object)
|
||||
(constant_pool_index)
|
||||
(jump_table_index)
|
||||
(var)
|
||||
(physical_register)
|
||||
(ir_block)
|
||||
(external_symbol)
|
||||
(global_var)
|
||||
(ir_local_var)
|
||||
(metadata_ref)
|
||||
(mnemonic)
|
||||
] @variable
|
||||
|
||||
(low_level_type) @type
|
||||
|
||||
[
|
||||
(immediate_type)
|
||||
(primitive_type)
|
||||
] @type.builtin
|
||||
|
||||
(number) @constant.numeric.integer
|
||||
(float) @constant.numeric.float
|
||||
(string) @string
|
||||
|
||||
(instruction name: _ @keyword.operator)
|
||||
|
||||
[
|
||||
"successors"
|
||||
"liveins"
|
||||
"pre-instr-symbol"
|
||||
"post-instr-symbol"
|
||||
"heap-alloc-marker"
|
||||
"debug-instr-number"
|
||||
"debug-location"
|
||||
"mcsymbol"
|
||||
"tied-def"
|
||||
"target-flags"
|
||||
"CustomRegMask"
|
||||
"same_value"
|
||||
"def_cfa_register"
|
||||
"restore"
|
||||
"undefined"
|
||||
"offset"
|
||||
"rel_offset"
|
||||
"def_cfa"
|
||||
"llvm_def_aspace_cfa"
|
||||
"register"
|
||||
"escape"
|
||||
"remember_state"
|
||||
"restore_state"
|
||||
"window_save"
|
||||
"negate_ra_sign_state"
|
||||
"intpred"
|
||||
"floatpred"
|
||||
"shufflemask"
|
||||
"liveout"
|
||||
"target-index"
|
||||
"blockaddress"
|
||||
"intrinsic"
|
||||
"load"
|
||||
"store"
|
||||
"unknown-size"
|
||||
"on"
|
||||
"from"
|
||||
"into"
|
||||
"align"
|
||||
"basealign"
|
||||
"addrspace"
|
||||
"call-entry"
|
||||
"custom"
|
||||
"constant-pool"
|
||||
"stack"
|
||||
"got"
|
||||
"jump-table"
|
||||
"syncscope"
|
||||
"address-taken"
|
||||
"landing-pad"
|
||||
"inlineasm-br-indirect-target"
|
||||
"ehfunclet-entry"
|
||||
"bbsections"
|
||||
|
||||
(intpred)
|
||||
(floatpred)
|
||||
(memory_operand_flag)
|
||||
(atomic_ordering)
|
||||
(register_flag)
|
||||
(instruction_flag)
|
||||
(float_keyword)
|
||||
] @keyword
|
||||
|
||||
(ERROR) @error
|
7
runtime/queries/llvm-mir/indents.toml
Normal file
7
runtime/queries/llvm-mir/indents.toml
Normal file
@ -0,0 +1,7 @@
|
||||
indent = [
|
||||
"basic_block",
|
||||
]
|
||||
|
||||
outdent = [
|
||||
"label",
|
||||
]
|
2
runtime/queries/llvm-mir/injections.scm
Normal file
2
runtime/queries/llvm-mir/injections.scm
Normal file
@ -0,0 +1,2 @@
|
||||
([ (comment) (multiline_comment)] @injection.content
|
||||
(#set! injection.language "comment"))
|
3
runtime/queries/llvm-mir/textobjects.scm
Normal file
3
runtime/queries/llvm-mir/textobjects.scm
Normal file
@ -0,0 +1,3 @@
|
||||
(basic_block) @function.around
|
||||
|
||||
(argument) @parameter.inside
|
@ -1,14 +1,158 @@
|
||||
(type) @type
|
||||
(statement) @keyword.operator
|
||||
(type_keyword) @type.builtin
|
||||
|
||||
(type [
|
||||
(local_var)
|
||||
(global_var)
|
||||
] @type)
|
||||
|
||||
(argument) @variable.parameter
|
||||
|
||||
(_ inst_name: _ @keyword.operator)
|
||||
|
||||
[
|
||||
"catch"
|
||||
"filter"
|
||||
] @keyword.operator
|
||||
|
||||
[
|
||||
"to"
|
||||
"nuw"
|
||||
"nsw"
|
||||
"exact"
|
||||
"unwind"
|
||||
"from"
|
||||
"cleanup"
|
||||
"swifterror"
|
||||
"volatile"
|
||||
"inbounds"
|
||||
"inrange"
|
||||
(icmp_cond)
|
||||
(fcmp_cond)
|
||||
(fast_math)
|
||||
] @keyword.control
|
||||
|
||||
(_ callee: _ @function)
|
||||
(function_header name: _ @function)
|
||||
|
||||
[
|
||||
"declare"
|
||||
"define"
|
||||
(calling_conv)
|
||||
] @keyword.function
|
||||
|
||||
[
|
||||
"target"
|
||||
"triple"
|
||||
"datalayout"
|
||||
"source_filename"
|
||||
"addrspace"
|
||||
"blockaddress"
|
||||
"align"
|
||||
"syncscope"
|
||||
"within"
|
||||
"uselistorder"
|
||||
"uselistorder_bb"
|
||||
"module"
|
||||
"asm"
|
||||
"sideeffect"
|
||||
"alignstack"
|
||||
"inteldialect"
|
||||
"unwind"
|
||||
"type"
|
||||
"global"
|
||||
"constant"
|
||||
"externally_initialized"
|
||||
"alias"
|
||||
"ifunc"
|
||||
"section"
|
||||
"comdat"
|
||||
"thread_local"
|
||||
"localdynamic"
|
||||
"initialexec"
|
||||
"localexec"
|
||||
"any"
|
||||
"exactmatch"
|
||||
"largest"
|
||||
"nodeduplicate"
|
||||
"samesize"
|
||||
"distinct"
|
||||
"attributes"
|
||||
"vscale"
|
||||
"no_cfi"
|
||||
(linkage_aux)
|
||||
(dso_local)
|
||||
(visibility)
|
||||
(dll_storage_class)
|
||||
(unnamed_addr)
|
||||
(attribute_name)
|
||||
] @keyword
|
||||
|
||||
|
||||
(function_header [
|
||||
(linkage)
|
||||
(calling_conv)
|
||||
(unnamed_addr)
|
||||
] @keyword.function)
|
||||
|
||||
[
|
||||
(string)
|
||||
(cstring)
|
||||
] @string
|
||||
|
||||
(number) @constant.numeric.integer
|
||||
(comment) @comment
|
||||
(string) @string
|
||||
(label) @label
|
||||
(keyword) @keyword
|
||||
"ret" @keyword.control.return
|
||||
(boolean) @constant.builtin.boolean
|
||||
(_ inst_name: "ret" @keyword.control.return)
|
||||
(float) @constant.numeric.float
|
||||
(constant) @constant
|
||||
(identifier) @variable
|
||||
(symbol) @punctuation.delimiter
|
||||
(bracket) @punctuation.bracket
|
||||
|
||||
[
|
||||
(local_var)
|
||||
(global_var)
|
||||
] @variable
|
||||
|
||||
[
|
||||
(struct_value)
|
||||
(array_value)
|
||||
(vector_value)
|
||||
] @constructor
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
"<"
|
||||
">"
|
||||
"<{"
|
||||
"}>"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
","
|
||||
":"
|
||||
] @punctuation.delimiter
|
||||
|
||||
[
|
||||
"="
|
||||
"|"
|
||||
"x"
|
||||
"..."
|
||||
] @operator
|
||||
|
||||
[
|
||||
"true"
|
||||
"false"
|
||||
] @constant.builtin.boolean
|
||||
|
||||
[
|
||||
"undef"
|
||||
"poison"
|
||||
"null"
|
||||
"none"
|
||||
"zeroinitializer"
|
||||
] @constant.builtin
|
||||
|
||||
(ERROR) @error
|
||||
|
8
runtime/queries/llvm/indents.toml
Normal file
8
runtime/queries/llvm/indents.toml
Normal file
@ -0,0 +1,8 @@
|
||||
indent = [
|
||||
"function_body",
|
||||
"instruction",
|
||||
]
|
||||
|
||||
outdent = [
|
||||
"}",
|
||||
]
|
2
runtime/queries/llvm/injections.scm
Normal file
2
runtime/queries/llvm/injections.scm
Normal file
@ -0,0 +1,2 @@
|
||||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
14
runtime/queries/llvm/locals.scm
Normal file
14
runtime/queries/llvm/locals.scm
Normal file
@ -0,0 +1,14 @@
|
||||
; Scopes
|
||||
|
||||
(function_body) @local.scope
|
||||
|
||||
; Definitions
|
||||
|
||||
(argument
|
||||
(value (var (local_var) @local.definition)))
|
||||
|
||||
(instruction
|
||||
(local_var) @local.definition)
|
||||
|
||||
; References
|
||||
(local_var) @local.reference
|
16
runtime/queries/llvm/textobjects.scm
Normal file
16
runtime/queries/llvm/textobjects.scm
Normal file
@ -0,0 +1,16 @@
|
||||
(define
|
||||
body: (_) @function.inside) @function.around
|
||||
|
||||
(struct_type
|
||||
(struct_body) @class.inside) @class.around
|
||||
|
||||
(packed_struct_type
|
||||
(struct_body) @class.inside) @class.around
|
||||
|
||||
(array_type
|
||||
(array_vector_body) @class.inside) @class.around
|
||||
|
||||
(vector_type
|
||||
(array_vector_body) @class.inside) @class.around
|
||||
|
||||
(argument) @parameter.inside
|
@ -10,15 +10,16 @@
|
||||
(fenced_code_block)
|
||||
] @markup.raw.block
|
||||
|
||||
(block_quote) @markup.quote
|
||||
|
||||
(code_span) @markup.raw.inline
|
||||
|
||||
(emphasis) @markup.italic
|
||||
|
||||
(strong_emphasis) @markup.bold
|
||||
|
||||
(link_destination) @markup.underline.link
|
||||
|
||||
; (link_label) @markup.label ; TODO: rename
|
||||
(link_destination) @markup.link.url
|
||||
(link_label) @markup.link.label
|
||||
|
||||
[
|
||||
(list_marker_plus)
|
||||
|
@ -1,6 +1,7 @@
|
||||
(fenced_code_block
|
||||
(info_string) @injection.language
|
||||
(code_fence_content) @injection.content)
|
||||
(code_fence_content) @injection.content
|
||||
(#set! injection.include-children))
|
||||
|
||||
((html_block) @injection.content
|
||||
(#set! injection.language "html"))
|
||||
|
@ -13,7 +13,7 @@
|
||||
] @keyword
|
||||
|
||||
((identifier) @variable.builtin
|
||||
(#match? @variable.builtin "^(__currentSystem|__currentTime|__nixPath|__nixVersion|__storeDir|builtins|false|null|true)$")
|
||||
(#match? @variable.builtin "^(__currentSystem|__currentTime|__nixPath|__nixVersion|__storeDir|builtins)$")
|
||||
(#is-not? local))
|
||||
|
||||
((identifier) @function.builtin
|
||||
@ -33,6 +33,11 @@
|
||||
|
||||
(uri) @string.special.uri
|
||||
|
||||
; boolean
|
||||
((identifier) @constant.builtin.boolean (#match? @constant.builtin.boolean "^(true|false)$")) @constant.builtin.boolean
|
||||
; null
|
||||
((identifier) @constant.builtin (#eq? @constant.builtin "null")) @constant.builtin
|
||||
|
||||
(integer) @constant.numeric.integer
|
||||
(float) @constant.numeric.float
|
||||
|
||||
|
@ -8,6 +8,6 @@ indent = [
|
||||
"match_case",
|
||||
]
|
||||
|
||||
oudent = [
|
||||
outdent = [
|
||||
"}",
|
||||
]
|
||||
|
2
runtime/queries/python/injections.scm
Normal file
2
runtime/queries/python/injections.scm
Normal file
@ -0,0 +1,2 @@
|
||||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user