on 5eb1a25 help sign in
.envrc−1
since splitsnapshots
unifiedsplit
@@ -1,6 +1,5 @@
1watch_file shell.nix
1watch_file shell.nix
2watch_file flake.lock
2watch_file flake.lock
3−watch_file rust-toolchain.toml
4
3
5# try to use flakes, if it fails use normal nix (ie. shell.nix)
4# try to use flakes, if it fails use normal nix (ie. shell.nix)
6use flake || use nix
5use flake || use nix
.github/ISSUE_TEMPLATE/bug_report.yaml−10
@@ -55,16 +55,6 @@ body:
55 placeholder: wezterm 20220101-133340-7edc5b5a
55 placeholder: wezterm 20220101-133340-7edc5b5a
56 validations:
56 validations:
57 required: true
57 required: true
58− - type: input
59− id: installation-method
60− attributes:
61− label: Installation Method
62− description: >
63− How you installed Helix - from a package manager like Homebrew or the
64− AUR, built from source, downloaded a binary from the releases page, etc.
65− placeholder: "source / brew / nixpkgs / flake / releases page"
66− validations:
67− required: true
68 - type: input
58 - type: input
69 id: helix-version
59 id: helix-version
70 attributes:
60 attributes:
.github/workflows/build.yml+7−15
@@ -14,7 +14,7 @@ jobs:
14 runs-on: ubuntu-latest
14 runs-on: ubuntu-latest
15 steps:
15 steps:
16 - name: Checkout sources
16 - name: Checkout sources
17− uses: actions/checkout@v4
17+ uses: actions/checkout@v3
18 - name: Install stable toolchain
18 - name: Install stable toolchain
19 uses: helix-editor/rust-toolchain@v1
19 uses: helix-editor/rust-toolchain@v1
20 with:
20 with:
@@ -22,8 +22,6 @@ jobs:
22 override: true
22 override: true
23
23
24 - uses: Swatinem/rust-cache@v2
24 - uses: Swatinem/rust-cache@v2
25− with:
26− shared-key: "build"
27
25
28 - name: Run cargo check
26 - name: Run cargo check
29 run: cargo check
27 run: cargo check
@@ -36,14 +34,12 @@ jobs:
36 HELIX_LOG_LEVEL: info
34 HELIX_LOG_LEVEL: info
37 steps:
35 steps:
38 - name: Checkout sources
36 - name: Checkout sources
39− uses: actions/checkout@v4
37+ uses: actions/checkout@v3
40
38
41 - name: Install stable toolchain
39 - name: Install stable toolchain
42− uses: dtolnay/rust-toolchain@1.70
40+ uses: dtolnay/rust-toolchain@1.65
43
41
44 - uses: Swatinem/rust-cache@v2
42 - uses: Swatinem/rust-cache@v2
45− with:
46− shared-key: "build"
47
43
48 - name: Cache test tree-sitter grammar
44 - name: Cache test tree-sitter grammar
49 uses: actions/cache@v3
45 uses: actions/cache@v3
@@ -67,16 +63,14 @@ jobs:
67 runs-on: ubuntu-latest
63 runs-on: ubuntu-latest
68 steps:
64 steps:
69 - name: Checkout sources
65 - name: Checkout sources
70− uses: actions/checkout@v4
66+ uses: actions/checkout@v3
71
67
72 - name: Install stable toolchain
68 - name: Install stable toolchain
73− uses: dtolnay/rust-toolchain@1.70
69+ uses: dtolnay/rust-toolchain@1.65
74 with:
70 with:
75 components: rustfmt, clippy
71 components: rustfmt, clippy
76
72
77 - uses: Swatinem/rust-cache@v2
73 - uses: Swatinem/rust-cache@v2
78− with:
79− shared-key: "build"
80
74
81 - name: Run cargo fmt
75 - name: Run cargo fmt
82 run: cargo fmt --all --check
76 run: cargo fmt --all --check
@@ -94,14 +88,12 @@ jobs:
94 runs-on: ubuntu-latest
88 runs-on: ubuntu-latest
95 steps:
89 steps:
96 - name: Checkout sources
90 - name: Checkout sources
97− uses: actions/checkout@v4
91+ uses: actions/checkout@v3
98
92
99 - name: Install stable toolchain
93 - name: Install stable toolchain
100− uses: dtolnay/rust-toolchain@1.70
94+ uses: dtolnay/rust-toolchain@1.65
101
95
102 - uses: Swatinem/rust-cache@v2
96 - uses: Swatinem/rust-cache@v2
103− with:
104− shared-key: "build"
105
97
106 - name: Validate queries
98 - name: Validate queries
107 run: cargo xtask query-check
99 run: cargo xtask query-check
.github/workflows/cachix.yml+3−3
@@ -11,13 +11,13 @@ jobs:
11 runs-on: ubuntu-latest
11 runs-on: ubuntu-latest
12 steps:
12 steps:
13 - name: Checkout sources
13 - name: Checkout sources
14− uses: actions/checkout@v4
14+ uses: actions/checkout@v3
15
15
16 - name: Install nix
16 - name: Install nix
17− uses: cachix/install-nix-action@v24
17+ uses: cachix/install-nix-action@v22
18
18
19 - name: Authenticate with Cachix
19 - name: Authenticate with Cachix
20− uses: cachix/cachix-action@v13
20+ uses: cachix/cachix-action@v12
21 with:
21 with:
22 name: helix
22 name: helix
23 authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
23 authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
.github/workflows/gh-pages.yml+5−5
@@ -11,7 +11,7 @@ jobs:
11 deploy:
11 deploy:
12 runs-on: ubuntu-latest
12 runs-on: ubuntu-latest
13 steps:
13 steps:
14− - uses: actions/checkout@v4
14+ - uses: actions/checkout@v3
15
15
16 - name: Setup mdBook
16 - name: Setup mdBook
17 uses: peaceiris/actions-mdbook@v1
17 uses: peaceiris/actions-mdbook@v1
@@ -26,16 +26,16 @@ jobs:
26 OUTDIR=$(basename ${{ github.ref }})
26 OUTDIR=$(basename ${{ github.ref }})
27 echo "OUTDIR=$OUTDIR" >> $GITHUB_ENV
27 echo "OUTDIR=$OUTDIR" >> $GITHUB_ENV
28
28
29− - name: Deploy stable
29+ - name: Deploy
30 uses: peaceiris/actions-gh-pages@v3
30 uses: peaceiris/actions-gh-pages@v3
31− if: startswith(github.ref, 'refs/tags/')
32 with:
31 with:
33 github_token: ${{ secrets.GITHUB_TOKEN }}
32 github_token: ${{ secrets.GITHUB_TOKEN }}
34 publish_dir: ./book/book
33 publish_dir: ./book/book
34+ destination_dir: ./${{ env.OUTDIR }}
35
35
36− - name: Deploy
36+ - name: Deploy stable
37 uses: peaceiris/actions-gh-pages@v3
37 uses: peaceiris/actions-gh-pages@v3
38+ if: startswith(github.ref, 'refs/tags/')
38 with:
39 with:
39 github_token: ${{ secrets.GITHUB_TOKEN }}
40 github_token: ${{ secrets.GITHUB_TOKEN }}
40 publish_dir: ./book/book
41 publish_dir: ./book/book
41− destination_dir: ./${{ env.OUTDIR }}
.github/workflows/release.yml+15−15
@@ -23,7 +23,7 @@ jobs:
23 runs-on: ubuntu-latest
23 runs-on: ubuntu-latest
24 steps:
24 steps:
25 - name: Checkout sources
25 - name: Checkout sources
26− uses: actions/checkout@v4
26+ uses: actions/checkout@v3
27
27
28 - name: Install stable toolchain
28 - name: Install stable toolchain
29 uses: dtolnay/rust-toolchain@stable
29 uses: dtolnay/rust-toolchain@stable
@@ -36,7 +36,7 @@ jobs:
36 - name: Bundle grammars
36 - name: Bundle grammars
37 run: tar cJf grammars.tar.xz -C runtime/grammars/sources .
37 run: tar cJf grammars.tar.xz -C runtime/grammars/sources .
38
38
39− - uses: actions/upload-artifact@v4
39+ - uses: actions/upload-artifact@v3
40 with:
40 with:
41 name: grammars
41 name: grammars
42 path: grammars.tar.xz
42 path: grammars.tar.xz
@@ -70,11 +70,11 @@ jobs:
70 rust: stable
70 rust: stable
71 target: aarch64-unknown-linux-gnu
71 target: aarch64-unknown-linux-gnu
72 cross: true
72 cross: true
73− # - build: riscv64-linux
73+ - build: riscv64-linux
74− # os: ubuntu-latest
74+ os: ubuntu-latest
75− # rust: stable
75+ rust: stable
76− # target: riscv64gc-unknown-linux-gnu
76+ target: riscv64gc-unknown-linux-gnu
77− # cross: true
77+ cross: true
78 - build: x86_64-macos
78 - build: x86_64-macos
79 os: macos-latest
79 os: macos-latest
80 rust: stable
80 rust: stable
@@ -103,10 +103,10 @@ jobs:
103
103
104 steps:
104 steps:
105 - name: Checkout sources
105 - name: Checkout sources
106− uses: actions/checkout@v4
106+ uses: actions/checkout@v3
107
107
108 - name: Download grammars
108 - name: Download grammars
109− uses: actions/download-artifact@v4
109+ uses: actions/download-artifact@v3
110
110
111 - name: Move grammars under runtime
111 - name: Move grammars under runtime
112 if: "!startsWith(matrix.os, 'windows')"
112 if: "!startsWith(matrix.os, 'windows')"
@@ -160,7 +160,7 @@ jobs:
160
160
161 - name: Build AppImage
161 - name: Build AppImage
162 shell: bash
162 shell: bash
163− if: matrix.build == 'x86_64-linux'
163+ if: matrix.build == 'aarch64-linux' || matrix.build == 'x86_64-linux'
164 run: |
164 run: |
165 # Required as of 22.x https://github.com/AppImage/AppImageKit/wiki/FUSE
165 # Required as of 22.x https://github.com/AppImage/AppImageKit/wiki/FUSE
166 sudo add-apt-repository universe
166 sudo add-apt-repository universe
@@ -220,7 +220,7 @@ jobs:
220 fi
220 fi
221 cp -r runtime dist
221 cp -r runtime dist
222
222
223− - uses: actions/upload-artifact@v4
223+ - uses: actions/upload-artifact@v3
224 with:
224 with:
225 name: bins-${{ matrix.build }}
225 name: bins-${{ matrix.build }}
226 path: dist
226 path: dist
@@ -231,9 +231,9 @@ jobs:
231 runs-on: ubuntu-latest
231 runs-on: ubuntu-latest
232 steps:
232 steps:
233 - name: Checkout sources
233 - name: Checkout sources
234− uses: actions/checkout@v4
234+ uses: actions/checkout@v3
235
235
236− - uses: actions/download-artifact@v4
236+ - uses: actions/download-artifact@v3
237
237
238 - name: Build archive
238 - name: Build archive
239 shell: bash
239 shell: bash
@@ -263,7 +263,7 @@ jobs:
263 mv bins-$platform/hx$exe $pkgname
263 mv bins-$platform/hx$exe $pkgname
264 chmod +x $pkgname/hx$exe
264 chmod +x $pkgname/hx$exe
265
265
266− if [[ "$platform" = "x86_64-linux" ]]; then
266+ if [[ "$platform" = "aarch64-linux" || "$platform" = "x86_64-linux" ]]; then
267 mv bins-$platform/helix-*.AppImage* dist/
267 mv bins-$platform/helix-*.AppImage* dist/
268 fi
268 fi
269
269
@@ -288,7 +288,7 @@ jobs:
288 overwrite: true
288 overwrite: true
289
289
290 - name: Upload binaries as artifact
290 - name: Upload binaries as artifact
291− uses: actions/upload-artifact@v4
291+ uses: actions/upload-artifact@v3
292 if: env.preview == 'true'
292 if: env.preview == 'true'
293 with:
293 with:
294 name: release
294 name: release
CHANGELOG.md−260
@@ -1,263 +1,3 @@
1−# 23.10 (2023-10-24)
2−
3−A big shout out to all the contributors! We had 118 contributors in this release.
4−
5−Breaking changes:
6−
7−- Support multiple language servers per language ([#2507](https://github.com/helix-editor/helix/pull/2507))
8− - This is a breaking change to language configuration
9−
10−Features:
11−
12−- Support multiple language servers per language ([#2507](https://github.com/helix-editor/helix/pull/2507), [#7082](https://github.com/helix-editor/helix/pull/7082), [#7286](https://github.com/helix-editor/helix/pull/7286), [#8374](https://github.com/helix-editor/helix/pull/8374))
13−- Add a statusline element for the selected register ([#7222](https://github.com/helix-editor/helix/pull/7222))
14−- Add `%`, `#`, `.`, `*` and `+` special registers ([#6985](https://github.com/helix-editor/helix/pull/6985))
15−- Add initial support for LSP DidChangeWatchedFiles notifications ([#7665](https://github.com/helix-editor/helix/pull/7665))
16−- Search buffer contents in `global_search` ([#5652](https://github.com/helix-editor/helix/pull/5652))
17−- Add a "smart tab" command that intelligently jumps the cursor on tab ([#4443](https://github.com/helix-editor/helix/pull/4443))
18−- Add a statusline element for whether a file is read-only ([#7222](https://github.com/helix-editor/helix/pull/7222), [#7875](https://github.com/helix-editor/helix/pull/7875))
19−- Syntax highlight regex prompts ([#7738](https://github.com/helix-editor/helix/pull/7738))
20−- Allow defining alignment in indent queries ([#5355](https://github.com/helix-editor/helix/pull/5355))
21−- Show visual feedback in `surround_replace` ([#7588](https://github.com/helix-editor/helix/pull/7588))
22−- Switch to Nucleo for fuzzy matching ([#7814](https://github.com/helix-editor/helix/pull/7814), [#8148](https://github.com/helix-editor/helix/pull/8148), [#8192](https://github.com/helix-editor/helix/pull/8192), [#8194](https://github.com/helix-editor/helix/pull/8194))
23−- Insert a trailing newline on write ([#8157](https://github.com/helix-editor/helix/pull/8157))
24−- Add a `-w`/`--working-dir` CLI flag for specifying a working directory on startup ([#8223](https://github.com/helix-editor/helix/pull/8223), [#8498](https://github.com/helix-editor/helix/pull/8498), [#8520](https://github.com/helix-editor/helix/pull/8520))
25−- Accept a `+N` CLI argument to set the first file's line number ([#8521](https://github.com/helix-editor/helix/pull/8521))
26−- Accept Helix-specific ignore files in `.helix/ignore` and `~/.config/helix/ignore` ([#8099](https://github.com/helix-editor/helix/pull/8099))
27−
28−Commands:
29−
30−- `merge_selections` (`A-minus`) - merge all selections into one selection that covers all ranges ([#7053](https://github.com/helix-editor/helix/pull/7053))
31−- `move_prev_long_word_end` and `extend_prev_long_word_end` - move/extend to the end of the previous WORD ([#6905](https://github.com/helix-editor/helix/pull/6905))
32−- `reverse_selection_contents` - swaps the values of each selection so they are reversed ([#7329](https://github.com/helix-editor/helix/pull/7329))
33−- Add `:rl` and `:rla` aliases for `:reload` and `:reload-all` ([#7158](https://github.com/helix-editor/helix/pull/7158))
34−- `yank_joined` - join the selections and yank to the selected register ([#7195](https://github.com/helix-editor/helix/pull/7195))
35−- `:write-all!` (`:wa!`) - forcibly write all buffers to disk and create any necessary subdirectories ([#7577](https://github.com/helix-editor/helix/pull/7577))
36−- `:redraw` - clear re-render the UI ([#6949](https://github.com/helix-editor/helix/pull/6949))
37−- `:tree-sitter-highlight-name` - show the theme scope name of the highlight under the cursor ([#8170](https://github.com/helix-editor/helix/pull/8170))
38−
39−Usability improvements:
40−
41−- Allow cycling option values at runtime ([#4411](https://github.com/helix-editor/helix/pull/4411), [#7240](https://github.com/helix-editor/helix/pull/7240), [#7877](https://github.com/helix-editor/helix/pull/7877))
42−- Exit gracefully on termination signals ([#7236](https://github.com/helix-editor/helix/pull/7236))
43−- Add plaintext matching fallback to tree-sitter pair matching ([#4288](https://github.com/helix-editor/helix/pull/4288))
44−- Persist register selection in pending keymaps ([0e08349](https://github.com/helix-editor/helix/commit/0e08349))
45−- Propagate the count and register to command palette commands ([b394997](https://github.com/helix-editor/helix/commit/b394997))
46−- Auto indent on `insert_at_line_start` ([#5837](https://github.com/helix-editor/helix/pull/5837))
47−- Add a config option to control whether LSP completions are automatically inserted on preview ([#7189](https://github.com/helix-editor/helix/pull/7189))
48−- Add a config option for default line endings ([#5621](https://github.com/helix-editor/helix/pull/5621), [#7357](https://github.com/helix-editor/helix/pull/7357))
49−- Allow ANSI colors in themes ([#5119](https://github.com/helix-editor/helix/pull/5119))
50−- Match pairs that don't form a standalone tree-sitter node ([#7242](https://github.com/helix-editor/helix/pull/7242))
51−- Allow indent sizes of up to 16 columns ([#7429](https://github.com/helix-editor/helix/pull/7429))
52−- Improve performance of mapping positions through changes ([#7408](https://github.com/helix-editor/helix/pull/7408), [8d39a81](https://github.com/helix-editor/helix/commit/8d39a81), [#7471](https://github.com/helix-editor/helix/pull/7471))
53−- Mark buffers created from stdin as modified ([#7431](https://github.com/helix-editor/helix/pull/7431))
54−- Forcibly shut down uninitialized language servers ([#7449](https://github.com/helix-editor/helix/pull/7449))
55−- Add filename completer for shell prompts ([#7569](https://github.com/helix-editor/helix/pull/7569))
56−- Allow binding F13-F24 ([#7672](https://github.com/helix-editor/helix/pull/7672))
57−- Resolve LSP code actions ([#7677](https://github.com/helix-editor/helix/pull/7677), [#8421](https://github.com/helix-editor/helix/pull/8421))
58−- Save an undo checkpoint before accepting completions ([#7747](https://github.com/helix-editor/helix/pull/7747))
59−- Include gitignored files in debugger completions ([#7936](https://github.com/helix-editor/helix/pull/7936))
60−- Make editor remember the last search register ([#5244](https://github.com/helix-editor/helix/pull/5244))
61−- Open directories with `goto_file` ([#7909](https://github.com/helix-editor/helix/pull/7909))
62−- Use relative path to open buffer in `goto_file` (`gf`) ([#7965](https://github.com/helix-editor/helix/pull/7965))
63−- Support `default` color in themes ([#8083](https://github.com/helix-editor/helix/pull/8083), [#8114](https://github.com/helix-editor/helix/pull/8114))
64−- Toggle between relative and absolute line numbers when the terminal loses focus ([#7955](https://github.com/helix-editor/helix/pull/7955))
65−- Lower default idle-timeout to 250ms ([060e73a](https://github.com/helix-editor/helix/commit/060e73a))
66−- Allow theming diff gutters separately from other diff colors ([#8343](https://github.com/helix-editor/helix/pull/8343))
67−- Style bold/italic/strikethrough in markdown doc popups ([#8385](https://github.com/helix-editor/helix/pull/8385))
68−- Maintain the cursor position and view when splitting with `:hsplit`/`:vsplit` ([#8109](https://github.com/helix-editor/helix/pull/8109))
69−- Accept `-` in macros outside of `<`/`>` ([#8475](https://github.com/helix-editor/helix/pull/8475))
70−- Show all language servers for each language in `--health` ([#7315](https://github.com/helix-editor/helix/pull/7315))
71−- Don't break on hyphens in `:reflow` ([#8569](https://github.com/helix-editor/helix/pull/8569))
72−
73−Fixes:
74−
75−- Update diagnostics correctly on language server exit ([#7111](https://github.com/helix-editor/helix/pull/7111))
76−- Fix off-by-one in `select_references_to_symbol_under_cursor` ([#7132](https://github.com/helix-editor/helix/pull/7132))
77−- Extend selection with repeat-last-motion only if the original motion extended the selection ([#7159](https://github.com/helix-editor/helix/pull/7159))
78−- Fix undefined behavior in the diff gutter ([#7227](https://github.com/helix-editor/helix/pull/7227))
79−- Check that tab width is non-zero ([#7178](https://github.com/helix-editor/helix/pull/7178))
80−- Fix styles being overwritten in table rows with multiple cells ([#7281](https://github.com/helix-editor/helix/pull/7281))
81−- Add file for `--log` CLI arg in help text ([#7307](https://github.com/helix-editor/helix/pull/7307))
82−- Fix underflow when repeating a completion that has a negative shift position ([#7322](https://github.com/helix-editor/helix/pull/7322))
83−- Prefer longer matches in `select_next_sibling` and `select_prev_sibling` ([#7332](https://github.com/helix-editor/helix/pull/7332))
84−- Preview scratch buffers in the jumplist picker ([#7331](https://github.com/helix-editor/helix/pull/7331))
85−- Fix chunking by bytes in tree-sitter parsing ([#7417](https://github.com/helix-editor/helix/pull/7417))
86−- Discard LSP publishDiagnostic from uninitialized servers ([#7467](https://github.com/helix-editor/helix/pull/7467))
87−- Use negotiated position encoding for LSP workspace edits ([#7469](https://github.com/helix-editor/helix/pull/7469))
88−- Fix error message for unknown gutter types in config ([#7534](https://github.com/helix-editor/helix/pull/7534))
89−- Fix `:log-open` when `--log` CLI arg is specified ([#7573](https://github.com/helix-editor/helix/pull/7573), [#7585](https://github.com/helix-editor/helix/pull/7585))
90−- Fix debouncing of LSP messages to fix the last message sticking around ([#7538](https://github.com/helix-editor/helix/pull/7538), [#8023](https://github.com/helix-editor/helix/pull/8023))
91−- Fix crash when the current working directory is deleted ([#7185](https://github.com/helix-editor/helix/pull/7185))
92−- Fix piping to Helix on macOS ([#5468](https://github.com/helix-editor/helix/pull/5468))
93−- Fix crash when parsing overlapping injections ([#7621](https://github.com/helix-editor/helix/pull/7621))
94−- Clear the statusline when the prompt is visible ([#7646](https://github.com/helix-editor/helix/pull/7646))
95−- Fix range formatting error message typo ([#7823](https://github.com/helix-editor/helix/pull/7823))
96−- Skip rendering gutters when gutter width exceeds view width ([#7821](https://github.com/helix-editor/helix/pull/7821))
97−- Center the picker preview using visual lines ([#7837](https://github.com/helix-editor/helix/pull/7837))
98−- Align view correctly for background buffers opened with `A-ret` ([#7691](https://github.com/helix-editor/helix/pull/7691))
99−- Fix cursor resetting to block when quitting via a keybind ([#7931](https://github.com/helix-editor/helix/pull/7931))
100−- Remove path completions for the `:new` command ([#8010](https://github.com/helix-editor/helix/pull/8010))
101−- Use binary path resolved by `which` for formatter commands ([#8064](https://github.com/helix-editor/helix/pull/8064))
102−- Handle crossterm's `hidden` modifier ([#8120](https://github.com/helix-editor/helix/pull/8120))
103−- Clear completion when switching between windows with the mouse ([#8118](https://github.com/helix-editor/helix/pull/8118))
104−- Eagerly remove the last picker (`<space>'`) when the picker has many items ([#8127](https://github.com/helix-editor/helix/pull/8127))
105−- Fix find commands for buffers with non-LF line-endings ([#8111](https://github.com/helix-editor/helix/pull/8111))
106−- Detect the tmux clipboard provider on macOS ([#8182](https://github.com/helix-editor/helix/pull/8182))
107−- Fix syntax highlighting in dynamic picker preview pane ([#8206](https://github.com/helix-editor/helix/pull/8206))
108−- Recognize HTML code tags with attributes as code in markdown previews ([#8397](https://github.com/helix-editor/helix/pull/8397))
109−- Fix multicursor snippet placeholder directions ([#8423](https://github.com/helix-editor/helix/pull/8423))
110−- Only show diagnostic highlights when diagnostics are enabled for a language server ([#8551](https://github.com/helix-editor/helix/pull/8551))
111−
112−Themes:
113−
114−- Improve the selection color in `ferra` ([#7138](https://github.com/helix-editor/helix/pull/7138))
115−- Add `variable.other.member` theming to `spacebones_light` ([#7125](https://github.com/helix-editor/helix/pull/7125))
116−- Update `autumn` and theme the soft-wrap indicator ([#7229](https://github.com/helix-editor/helix/pull/7229))
117−- Add `gruvbox_dark_soft` ([#7139](https://github.com/helix-editor/helix/pull/7139))
118−- Add `merionette` ([#7186](https://github.com/helix-editor/helix/pull/7186))
119−- Add `zed_onedark` and `zed_onelight` ([#7250](https://github.com/helix-editor/helix/pull/7250))
120−- Use light-gray for `onedarker` inlay hint theming ([#7433](https://github.com/helix-editor/helix/pull/7433))
121−- Update the Nord theme to follow the style guidelines ([#7490](https://github.com/helix-editor/helix/pull/7490))
122−- Tune `dark_plus` inlay hint colors ([#7611](https://github.com/helix-editor/helix/pull/7611))
123−- Add `naysayer` ([#7570](https://github.com/helix-editor/helix/pull/7570))
124−- Add `kaolin-dark`, `kaolin-light` and `kaolin-valley-dark` ([#7151](https://github.com/helix-editor/helix/pull/7151))
125−- Fix selection highlighting in gruvbox variants ([#7717](https://github.com/helix-editor/helix/pull/7717))
126−- Add soft-wrap indicator to `gruvbox` ([#7736](https://github.com/helix-editor/helix/pull/7736))
127−- Add missing palette definitions in `everforest_dark` ([#7739](https://github.com/helix-editor/helix/pull/7739))
128−- Increase diagnostics clarity in `pop-dark` ([#7702](https://github.com/helix-editor/helix/pull/7702))
129−- Add `vim_dark_high_contrast` ([#7785](https://github.com/helix-editor/helix/pull/7785))
130−- Add `new_moon` ([#7834](https://github.com/helix-editor/helix/pull/7834))
131−- Add `yellowed` ([#7849](https://github.com/helix-editor/helix/pull/7849))
132−- Improve comment readability for `autumn` ([#7939](https://github.com/helix-editor/helix/pull/7939))
133−- Distinguish active bufferline buffer in `monokai` ([#7983](https://github.com/helix-editor/helix/pull/7983))
134−- Update ruler colors in `nord` ([#7995](https://github.com/helix-editor/helix/pull/7995))
135−- Update Catppuccin themes ([#8102](https://github.com/helix-editor/helix/pull/8102))
136−- Add text focus scope and diagnostics undercurls for `nord` ([#8165](https://github.com/helix-editor/helix/pull/8165))
137−- Add material theme collection ([#8211](https://github.com/helix-editor/helix/pull/8211))
138−- Improve indent line color in `dracula` ([#8266](https://github.com/helix-editor/helix/pull/8266))
139−- Clean up and refactor `papercolor` to use inheritance ([#8276](https://github.com/helix-editor/helix/pull/8276))
140−- Fix `zenburn` inlay hint color ([#8278](https://github.com/helix-editor/helix/pull/8278)a)
141−- Fix picker crash when previewing an invalid range ([e9d0bd7](https://github.com/helix-editor/helix/commit/e9d0bd7))
142−- Correctly center items in the picker preview ([13d4463](https://github.com/helix-editor/helix/commit/13d4463))
143−- Add `cyan_light` ([#8293](https://github.com/helix-editor/helix/pull/8293), [#8587](https://github.com/helix-editor/helix/pull/8587))
144−- Theme HTML tags in `onedark` ([#8409](https://github.com/helix-editor/helix/pull/8409))
145−- Refine `darcula` and `darcula-solid` themes ([#8412](https://github.com/helix-editor/helix/pull/8412))
146−- Improve `nord` highlights ([#8414](https://github.com/helix-editor/helix/pull/8414))
147−- Add `nord-night` ([#8549](https://github.com/helix-editor/helix/pull/8549))
148−
149−New languages:
150−
151−- Blueprint ([#7213](https://github.com/helix-editor/helix/pull/7213), [#8161](https://github.com/helix-editor/helix/pull/8161))
152−- Forth ([#7256](https://github.com/helix-editor/helix/pull/7256), [#7334](https://github.com/helix-editor/helix/pull/7334))
153−- t32 ([#7140](https://github.com/helix-editor/helix/pull/7140), [#7811](https://github.com/helix-editor/helix/pull/7811))
154−- WebC ([#7290](https://github.com/helix-editor/helix/pull/7290))
155−- Persistent DSL for Haskell ([#7261](https://github.com/helix-editor/helix/pull/7261))
156−- F# ([#7619](https://github.com/helix-editor/helix/pull/7619), [#8024](https://github.com/helix-editor/helix/pull/8024))
157−- Wren ([#7765](https://github.com/helix-editor/helix/pull/7765), [#7819](https://github.com/helix-editor/helix/pull/7819))
158−- Unison ([#7724](https://github.com/helix-editor/helix/pull/7724))
159−- Todo.txt ([#7835](https://github.com/helix-editor/helix/pull/7835))
160−- Jinja and Handlebars ([#7233](https://github.com/helix-editor/helix/pull/7233))
161−- Pod ([#7907](https://github.com/helix-editor/helix/pull/7907))
162−- Strace ([#7928](https://github.com/helix-editor/helix/pull/7928))
163−- Gemini ([#8070](https://github.com/helix-editor/helix/pull/8070))
164−- GNU Assembler (GAS) ([#8291](https://github.com/helix-editor/helix/pull/8291))
165−- JSON5 ([#8473](https://github.com/helix-editor/helix/pull/8473))
166−- TEMPL ([#8540](https://github.com/helix-editor/helix/pull/8540))
167−
168−Updated languages and queries:
169−
170−- Add one to the ruler numbers for git-commit ([#7072](https://github.com/helix-editor/helix/pull/7072))
171−- Recognize XAML files as XML ([#7083](https://github.com/helix-editor/helix/pull/7083))
172−- Recognize `Cargo.lock` as TOML ([#7095](https://github.com/helix-editor/helix/pull/7095))
173−- Use Rust grammar for Cairo ([c6d1430](https://github.com/helix-editor/helix/commit/c6d1430))
174−- Update tree-sitter-nickel ([#7059](https://github.com/helix-editor/helix/pull/7059), [#7551](https://github.com/helix-editor/helix/pull/7551))
175−- Tune auto-pair characters for Nickel ([#7059](https://github.com/helix-editor/helix/pull/7059))
176−- Recognize `Vagrantfile` as Ruby ([#7112](https://github.com/helix-editor/helix/pull/7112))
177−- Recognize hidden justfiles as Just ([#7088](https://github.com/helix-editor/helix/pull/7088))
178−- Update Java and TypeScript highlight queries ([#7145](https://github.com/helix-editor/helix/pull/7145))
179−- Recognize `.zimrc` as Bash ([#7146](https://github.com/helix-editor/helix/pull/7146))
180−- Recognize `.gir` as XML ([#7152](https://github.com/helix-editor/helix/pull/7152))
181−- Update tree-sitter-scala ([#7147](https://github.com/helix-editor/helix/pull/7147))
182−- Recognize make file-type as Makefile ([#7212](https://github.com/helix-editor/helix/pull/7212))
183−- Update tree-sitter-verilog ([#7262](https://github.com/helix-editor/helix/pull/7262))
184−- Update tree-sitter-cpp ([#7285](https://github.com/helix-editor/helix/pull/7285))
185−- Support core mode for delve debugger ([#7300](https://github.com/helix-editor/helix/pull/7300))
186−- Add Fortran comment injections ([#7305](https://github.com/helix-editor/helix/pull/7305))
187−- Switch Vue language server to `vue-language-server` ([#7312](https://github.com/helix-editor/helix/pull/7312))
188−- Update tree-sitter-sql ([#7387](https://github.com/helix-editor/helix/pull/7387), [#8464](https://github.com/helix-editor/helix/pull/8464))
189−- Replace the MATLAB tre-sitter grammar ([#7388](https://github.com/helix-editor/helix/pull/7388), [#7442](https://github.com/helix-editor/helix/pull/7442), [#7491](https://github.com/helix-editor/helix/pull/7491), [#7493](https://github.com/helix-editor/helix/pull/7493), [#7511](https://github.com/helix-editor/helix/pull/7511), [#7532](https://github.com/helix-editor/helix/pull/7532), [#8040](https://github.com/helix-editor/helix/pull/8040))
190−- Highlight TOML table headers ([#7441](https://github.com/helix-editor/helix/pull/7441))
191−- Recognize `cppm` file-type as C++ ([#7492](https://github.com/helix-editor/helix/pull/7492))
192−- Refactor ecma language queries into private and public queries ([#7207](https://github.com/helix-editor/helix/pull/7207))
193−- Update tree-sitter-dart ([#7576](https://github.com/helix-editor/helix/pull/7576))
194−- Add shebang for nushell files ([#7606](https://github.com/helix-editor/helix/pull/7606))
195−- Recognize systemd files as INI ([#7592](https://github.com/helix-editor/helix/pull/7592))
196−- Update TypeScript, TSX and Svelte grammars ([#6874](https://github.com/helix-editor/helix/pull/6874))
197−- Enable inlay hints in the Svelte language server ([#7622](https://github.com/helix-editor/helix/pull/7622))
198−- Recognize `Brewfile`s as Ruby ([#7629](https://github.com/helix-editor/helix/pull/7629))
199−- Add more file-types for R ([#7633](https://github.com/helix-editor/helix/pull/7633))
200−- Switch tree-sitter-perl to official upstream parser ([#7644](https://github.com/helix-editor/helix/pull/7644), [#7947](https://github.com/helix-editor/helix/pull/7947))
201−- Fix predicate typo in comment highlights ([#7732](https://github.com/helix-editor/helix/pull/7732))
202−- Update tree-sitter-prql ([#7771](https://github.com/helix-editor/helix/pull/7771))
203−- Recognize `.gitf` as JSON ([#7781](https://github.com/helix-editor/helix/pull/7781))
204−- Switch V language server to `v-analyzer` ([#7760](https://github.com/helix-editor/helix/pull/7760))
205−- Add protobuf language servers ([#7796](https://github.com/helix-editor/helix/pull/7796))
206−- Update tree-sitter-zig ([#7803](https://github.com/helix-editor/helix/pull/7803))
207−- Update tree-sitter-hare ([#7784](https://github.com/helix-editor/helix/pull/7784))
208−- Add Java indent queries ([#7844](https://github.com/helix-editor/helix/pull/7844))
209−- Update tree-sitter-scheme ([979933b](https://github.com/helix-editor/helix/commit/979933b))
210−- Recognize `scm` as Scheme instead of TSQ ([5707151](https://github.com/helix-editor/helix/commit/5707151))
211−- Update tree-sitter-git-commit ([#7831](https://github.com/helix-editor/helix/pull/7831))
212−- Update JavaScript, TypeScript and TSX grammars ([#7852](https://github.com/helix-editor/helix/pull/7852))
213−- Update tree-sitter-nu ([#7873](https://github.com/helix-editor/helix/pull/7873))
214−- Fix YAML indentation ([#6768](https://github.com/helix-editor/helix/pull/6768))
215−- Add `csharp-ls`, Pyright, Pylyzer and add roots for Python ([#7897](https://github.com/helix-editor/helix/pull/7897), [#8032](https://github.com/helix-editor/helix/pull/8032))
216−- Update tree-sitter-slint ([#7893](https://github.com/helix-editor/helix/pull/7893))
217−- Recognize more ZSH file-types as Bash ([#7930](https://github.com/helix-editor/helix/pull/7930))
218−- Recognize `star` extension as Starlark ([#7922](https://github.com/helix-editor/helix/pull/7922))
219−- Fix inline HTML tag highlighting in markdown ([#7960](https://github.com/helix-editor/helix/pull/7960))
220−- Update tree-sitter-robot ([#7970](https://github.com/helix-editor/helix/pull/7970))
221−- Highlight Dart 3 `sealed` and `base` keywords ([#7974](https://github.com/helix-editor/helix/pull/7974))
222−- Add configuration for `ltex-ls` to the default `languages.toml` ([#7838](https://github.com/helix-editor/helix/pull/7838))
223−- Update tree-sitter-strace ([#8087](https://github.com/helix-editor/helix/pull/8087))
224−- Update tree-sitter-gleam, enable auto-format ([#8085](https://github.com/helix-editor/helix/pull/8085))
225−- Update tree-sitter-esdl ([#8222](https://github.com/helix-editor/helix/pull/8222))
226−- Expand ignore file-types ([#8220](https://github.com/helix-editor/helix/pull/8220))
227−- Recognize feed related formats as XML ([#8232](https://github.com/helix-editor/helix/pull/8232))
228−- Improve YAML injections ([#8217](https://github.com/helix-editor/helix/pull/8217))
229−- Add shebangs for TypeScript, Julia, Java and OCaml ([95e994a](https://github.com/helix-editor/helix/commit/95e994a))
230−- Highlight abbreviations in Scheme ([ef23847](https://github.com/helix-editor/helix/commit/ef23847))
231−- Remove backtic auto-pair in OCaml ([#8260](https://github.com/helix-editor/helix/pull/8260))
232−- Recognize `flake.lock` as JSON ([#8304](https://github.com/helix-editor/helix/pull/8304))
233−- Add Python test script injection for Nix ([b4494e1](https://github.com/helix-editor/helix/commit/b4494e1))
234−- Fix Nix comment injection precedence ([37e48f4](https://github.com/helix-editor/helix/commit/37e48f4))
235−- Recognize editorconfig files as INI ([#8308](https://github.com/helix-editor/helix/pull/8308))
236−- Recognize `.babelrc` as JSON ([#8309](https://github.com/helix-editor/helix/pull/8309))
237−- Switch Purescript to its own tree-sitter parser ([#8306](https://github.com/helix-editor/helix/pull/8306), [#8338](https://github.com/helix-editor/helix/pull/8338), [#8527](https://github.com/helix-editor/helix/pull/8527))
238−- Update Unison highlights ([#8315](https://github.com/helix-editor/helix/pull/8315))
239−- Recognize `.webmanifest` as JSON ([#8342](https://github.com/helix-editor/helix/pull/8342))
240−- Recognize polkit policy files as XML ([#8369](https://github.com/helix-editor/helix/pull/8369))
241−- Recognize polkit rules files as JavaScript ([#8370](https://github.com/helix-editor/helix/pull/8370))
242−- Update Go highlight queries ([#8399](https://github.com/helix-editor/helix/pull/8399))
243−- Add shebangs for Makefiles ([#8410](https://github.com/helix-editor/helix/pull/8410))
244−- Add file-type associations from VSCode ([#8388](https://github.com/helix-editor/helix/pull/8388))
245−- Add validation to JSON/CSS language server configs ([#8433](https://github.com/helix-editor/helix/pull/8433))
246−- Add a configuration for the tailwind language server ([#8442](https://github.com/helix-editor/helix/pull/8442))
247−- Add a configuration for the ansible language server ([#7973](https://github.com/helix-editor/helix/pull/7973))
248−- Add a configuration for the GraphQL language server ([#8492](https://github.com/helix-editor/helix/pull/8492))
249−- Indent while statements in Bash ([#8528](https://github.com/helix-editor/helix/pull/8528))
250−- Update tree-sitter-haskell and queries ([#8558](https://github.com/helix-editor/helix/pull/8558))
251−
252−Packaging:
253−
254−- Add an overlay to the Nix flake ([#7078](https://github.com/helix-editor/helix/pull/7078))
255−- Check for `git` before fetching or building grammars ([#7320](https://github.com/helix-editor/helix/pull/7320))
256−- Refactor Nix flake to use Crane ([#7763](https://github.com/helix-editor/helix/pull/7763))
257−- Remove the aarch64 appimage from the release CI ([#7832](https://github.com/helix-editor/helix/pull/7832))
258−- Add desktop and icon files to Nix flake output ([#7979](https://github.com/helix-editor/helix/pull/7979))
259−- Build flake packages with the latest stable Rust ([#8133](https://github.com/helix-editor/helix/pull/8133))
260−
261# 23.05 (2023-05-18)
1# 23.05 (2023-05-18)
262
2
26323.05 is a smaller release focusing on fixes. There were 88 contributors in this release. Thank you all!
323.05 is a smaller release focusing on fixes. There were 88 contributors in this release. Thank you all!
Cargo.lock+608−538
@@ -19,22 +19,41 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19
19
20[[package]]
20[[package]]
21name = "ahash"
21name = "ahash"
22−version = "0.8.6"
22+version = "0.7.6"
23+source = "registry+https://github.com/rust-lang/crates.io-index"
24+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
25+dependencies = [
26+ "getrandom",
27+ "once_cell",
28+ "version_check",
29+]
30+
31+[[package]]
32+name = "ahash"
33+version = "0.8.3"
23source = "registry+https://github.com/rust-lang/crates.io-index"
34source = "registry+https://github.com/rust-lang/crates.io-index"
24−checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
35+checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
25dependencies = [
36dependencies = [
26 "cfg-if",
37 "cfg-if",
27 "getrandom",
38 "getrandom",
28 "once_cell",
39 "once_cell",
29 "version_check",
40 "version_check",
30− "zerocopy",
31]
41]
32
42
33[[package]]
43[[package]]
34name = "aho-corasick"
44name = "aho-corasick"
35−version = "1.1.2"
45+version = "0.7.20"
36source = "registry+https://github.com/rust-lang/crates.io-index"
46source = "registry+https://github.com/rust-lang/crates.io-index"
37−checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
47+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
48+dependencies = [
49+ "memchr",
50+]
51+
52+[[package]]
53+name = "aho-corasick"
54+version = "1.0.2"
55+source = "registry+https://github.com/rust-lang/crates.io-index"
56+checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
38dependencies = [
57dependencies = [
39 "memchr",
58 "memchr",
40]
59]
@@ -62,9 +81,9 @@ dependencies = [
62
81
63[[package]]
82[[package]]
64name = "anyhow"
83name = "anyhow"
65−version = "1.0.76"
84+version = "1.0.72"
66source = "registry+https://github.com/rust-lang/crates.io-index"
85source = "registry+https://github.com/rust-lang/crates.io-index"
67−checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
86+checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
68
87
69[[package]]
88[[package]]
70name = "arc-swap"
89name = "arc-swap"
@@ -88,7 +107,7 @@ dependencies = [
88 "cc",
107 "cc",
89 "cfg-if",
108 "cfg-if",
90 "libc",
109 "libc",
91− "miniz_oxide",
110+ "miniz_oxide 0.7.1",
92 "object",
111 "object",
93 "rustc-demangle",
112 "rustc-demangle",
94]
113]
@@ -101,15 +120,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
101
120
102[[package]]
121[[package]]
103name = "bitflags"
122name = "bitflags"
104−version = "2.4.1"
123+version = "2.3.3"
105source = "registry+https://github.com/rust-lang/crates.io-index"
124source = "registry+https://github.com/rust-lang/crates.io-index"
106−checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
125+checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
107
126
108[[package]]
127[[package]]
109name = "bstr"
128name = "bstr"
110−version = "1.8.0"
129+version = "1.6.0"
111source = "registry+https://github.com/rust-lang/crates.io-index"
130source = "registry+https://github.com/rust-lang/crates.io-index"
112−checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
131+checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
113dependencies = [
132dependencies = [
114 "memchr",
133 "memchr",
115 "regex-automata",
134 "regex-automata",
@@ -131,6 +150,12 @@ version = "3.12.0"
131source = "registry+https://github.com/rust-lang/crates.io-index"
150source = "registry+https://github.com/rust-lang/crates.io-index"
132checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
151checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
133
152
153+[[package]]
154+name = "bytecount"
155+version = "0.6.3"
156+source = "registry+https://github.com/rust-lang/crates.io-index"
157+checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
158+
134[[package]]
159[[package]]
135name = "bytes"
160name = "bytes"
136version = "1.4.0"
161version = "1.4.0"
@@ -145,12 +170,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
145
170
146[[package]]
171[[package]]
147name = "cc"
172name = "cc"
148−version = "1.0.84"
173+version = "1.0.79"
149source = "registry+https://github.com/rust-lang/crates.io-index"
174source = "registry+https://github.com/rust-lang/crates.io-index"
150−checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
175+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
151−dependencies = [
152− "libc",
153−]
154
176
155[[package]]
177[[package]]
156name = "cfg-if"
178name = "cfg-if"
@@ -171,23 +193,25 @@ dependencies = [
171
193
172[[package]]
194[[package]]
173name = "chrono"
195name = "chrono"
174−version = "0.4.31"
196+version = "0.4.26"
175source = "registry+https://github.com/rust-lang/crates.io-index"
197source = "registry+https://github.com/rust-lang/crates.io-index"
176−checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
198+checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
177dependencies = [
199dependencies = [
178 "android-tzdata",
200 "android-tzdata",
179 "iana-time-zone",
201 "iana-time-zone",
180 "num-traits",
202 "num-traits",
181− "windows-targets 0.48.0",
203+ "winapi",
182]
204]
183
205
184[[package]]
206[[package]]
185name = "clipboard-win"
207name = "clipboard-win"
186−version = "5.0.0"
208+version = "4.5.0"
187source = "registry+https://github.com/rust-lang/crates.io-index"
209source = "registry+https://github.com/rust-lang/crates.io-index"
188−checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
210+checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
189dependencies = [
211dependencies = [
190 "error-code",
212 "error-code",
213+ "str-buf",
214+ "winapi",
191]
215]
192
216
193[[package]]
217[[package]]
@@ -221,12 +245,6 @@ version = "0.8.4"
221source = "registry+https://github.com/rust-lang/crates.io-index"
245source = "registry+https://github.com/rust-lang/crates.io-index"
222checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
246checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
223
247
224−[[package]]
225−name = "cov-mark"
226−version = "1.1.0"
227−source = "registry+https://github.com/rust-lang/crates.io-index"
228−checksum = "9ffa3d3e0138386cd4361f63537765cac7ee40698028844635a54495a92f67f3"
229−
230[[package]]
248[[package]]
231name = "crc32fast"
249name = "crc32fast"
232version = "1.3.2"
250version = "1.3.2"
@@ -236,56 +254,13 @@ dependencies = [
236 "cfg-if",
254 "cfg-if",
237]
255]
238
256
239−[[package]]
240−name = "crossbeam-channel"
241−version = "0.5.8"
242−source = "registry+https://github.com/rust-lang/crates.io-index"
243−checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
244−dependencies = [
245− "cfg-if",
246− "crossbeam-utils",
247−]
248−
249−[[package]]
250−name = "crossbeam-deque"
251−version = "0.8.3"
252−source = "registry+https://github.com/rust-lang/crates.io-index"
253−checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
254−dependencies = [
255− "cfg-if",
256− "crossbeam-epoch",
257− "crossbeam-utils",
258−]
259−
260−[[package]]
261−name = "crossbeam-epoch"
262−version = "0.9.15"
263−source = "registry+https://github.com/rust-lang/crates.io-index"
264−checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
265−dependencies = [
266− "autocfg",
267− "cfg-if",
268− "crossbeam-utils",
269− "memoffset",
270− "scopeguard",
271−]
272−
273−[[package]]
274−name = "crossbeam-utils"
275−version = "0.8.16"
276−source = "registry+https://github.com/rust-lang/crates.io-index"
277−checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
278−dependencies = [
279− "cfg-if",
280−]
281−
282[[package]]
257[[package]]
283name = "crossterm"
258name = "crossterm"
284−version = "0.27.0"
259+version = "0.26.1"
285source = "registry+https://github.com/rust-lang/crates.io-index"
260source = "registry+https://github.com/rust-lang/crates.io-index"
286−checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
261+checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
287dependencies = [
262dependencies = [
288− "bitflags 2.4.1",
263+ "bitflags 1.3.2",
289 "crossterm_winapi",
264 "crossterm_winapi",
290 "filedescriptor",
265 "filedescriptor",
291 "futures-core",
266 "futures-core",
@@ -299,9 +274,9 @@ dependencies = [
299
274
300[[package]]
275[[package]]
301name = "crossterm_winapi"
276name = "crossterm_winapi"
302−version = "0.9.1"
277+version = "0.9.0"
303source = "registry+https://github.com/rust-lang/crates.io-index"
278source = "registry+https://github.com/rust-lang/crates.io-index"
304−checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
279+checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
305dependencies = [
280dependencies = [
306 "winapi",
281 "winapi",
307]
282]
@@ -330,7 +305,7 @@ dependencies = [
330 "proc-macro2",
305 "proc-macro2",
331 "quote",
306 "quote",
332 "scratch",
307 "scratch",
333− "syn 2.0.38",
308+ "syn 2.0.25",
334]
309]
335
310
336[[package]]
311[[package]]
@@ -347,7 +322,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
347dependencies = [
322dependencies = [
348 "proc-macro2",
323 "proc-macro2",
349 "quote",
324 "quote",
350− "syn 2.0.38",
325+ "syn 2.0.25",
351]
326]
352
327
353[[package]]
328[[package]]
@@ -364,9 +339,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
364
339
365[[package]]
340[[package]]
366name = "encoding_rs"
341name = "encoding_rs"
367−version = "0.8.33"
342+version = "0.8.32"
368source = "registry+https://github.com/rust-lang/crates.io-index"
343source = "registry+https://github.com/rust-lang/crates.io-index"
369−checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
344+checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
370dependencies = [
345dependencies = [
371 "cfg-if",
346 "cfg-if",
372]
347]
@@ -388,19 +363,34 @@ checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
388
363
389[[package]]
364[[package]]
390name = "errno"
365name = "errno"
391−version = "0.3.8"
366+version = "0.3.1"
367+source = "registry+https://github.com/rust-lang/crates.io-index"
368+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
369+dependencies = [
370+ "errno-dragonfly",
371+ "libc",
372+ "windows-sys 0.48.0",
373+]
374+
375+[[package]]
376+name = "errno-dragonfly"
377+version = "0.1.2"
392source = "registry+https://github.com/rust-lang/crates.io-index"
378source = "registry+https://github.com/rust-lang/crates.io-index"
393−checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
379+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
394dependencies = [
380dependencies = [
381+ "cc",
395 "libc",
382 "libc",
396− "windows-sys 0.52.0",
397]
383]
398
384
399[[package]]
385[[package]]
400name = "error-code"
386name = "error-code"
401−version = "3.0.0"
387+version = "2.3.1"
402source = "registry+https://github.com/rust-lang/crates.io-index"
388source = "registry+https://github.com/rust-lang/crates.io-index"
403−checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
389+checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
390+dependencies = [
391+ "libc",
392+ "str-buf",
393+]
404
394
405[[package]]
395[[package]]
406name = "etcetera"
396name = "etcetera"
@@ -413,15 +403,6 @@ dependencies = [
413 "windows-sys 0.48.0",
403 "windows-sys 0.48.0",
414]
404]
415
405
416−[[package]]
417−name = "faster-hex"
418−version = "0.9.0"
419−source = "registry+https://github.com/rust-lang/crates.io-index"
420−checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
421−dependencies = [
422− "serde",
423−]
424−
425[[package]]
406[[package]]
426name = "fastrand"
407name = "fastrand"
427version = "2.0.0"
408version = "2.0.0"
@@ -448,36 +429,54 @@ dependencies = [
448 "winapi",
429 "winapi",
449]
430]
450
431
432+[[package]]
433+name = "filetime"
434+version = "0.2.21"
435+source = "registry+https://github.com/rust-lang/crates.io-index"
436+checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
437+dependencies = [
438+ "cfg-if",
439+ "libc",
440+ "redox_syscall 0.2.16",
441+ "windows-sys 0.48.0",
442+]
443+
451[[package]]
444[[package]]
452name = "flate2"
445name = "flate2"
453−version = "1.0.27"
446+version = "1.0.25"
454source = "registry+https://github.com/rust-lang/crates.io-index"
447source = "registry+https://github.com/rust-lang/crates.io-index"
455−checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
448+checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
456dependencies = [
449dependencies = [
457 "crc32fast",
450 "crc32fast",
458− "miniz_oxide",
451+ "miniz_oxide 0.6.2",
459]
452]
460
453
454+[[package]]
455+name = "fnv"
456+version = "1.0.7"
457+source = "registry+https://github.com/rust-lang/crates.io-index"
458+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
459+
461[[package]]
460[[package]]
462name = "form_urlencoded"
461name = "form_urlencoded"
463−version = "1.2.1"
462+version = "1.2.0"
464source = "registry+https://github.com/rust-lang/crates.io-index"
463source = "registry+https://github.com/rust-lang/crates.io-index"
465−checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
464+checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
466dependencies = [
465dependencies = [
467 "percent-encoding",
466 "percent-encoding",
468]
467]
469
468
470[[package]]
469[[package]]
471name = "futures-core"
470name = "futures-core"
472−version = "0.3.30"
471+version = "0.3.28"
473source = "registry+https://github.com/rust-lang/crates.io-index"
472source = "registry+https://github.com/rust-lang/crates.io-index"
474−checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
473+checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
475
474
476[[package]]
475[[package]]
477name = "futures-executor"
476name = "futures-executor"
478−version = "0.3.29"
477+version = "0.3.28"
479source = "registry+https://github.com/rust-lang/crates.io-index"
478source = "registry+https://github.com/rust-lang/crates.io-index"
480−checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
479+checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
481dependencies = [
480dependencies = [
482 "futures-core",
481 "futures-core",
483 "futures-task",
482 "futures-task",
@@ -486,15 +485,15 @@ dependencies = [
486
485
487[[package]]
486[[package]]
488name = "futures-task"
487name = "futures-task"
489−version = "0.3.30"
488+version = "0.3.28"
490source = "registry+https://github.com/rust-lang/crates.io-index"
489source = "registry+https://github.com/rust-lang/crates.io-index"
491−checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
490+checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
492
491
493[[package]]
492[[package]]
494name = "futures-util"
493name = "futures-util"
495−version = "0.3.30"
494+version = "0.3.28"
496source = "registry+https://github.com/rust-lang/crates.io-index"
495source = "registry+https://github.com/rust-lang/crates.io-index"
497−checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
496+checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
498dependencies = [
497dependencies = [
499 "futures-core",
498 "futures-core",
500 "futures-task",
499 "futures-task",
@@ -503,6 +502,15 @@ dependencies = [
503 "slab",
502 "slab",
504]
503]
505
504
505+[[package]]
506+name = "fuzzy-matcher"
507+version = "0.3.7"
508+source = "registry+https://github.com/rust-lang/crates.io-index"
509+checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
510+dependencies = [
511+ "thread_local",
512+]
513+
506[[package]]
514[[package]]
507name = "getrandom"
515name = "getrandom"
508version = "0.2.9"
516version = "0.2.9"
@@ -522,13 +530,15 @@ checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
522
530
523[[package]]
531[[package]]
524name = "gix"
532name = "gix"
525−version = "0.57.0"
533+version = "0.48.0"
526source = "registry+https://github.com/rust-lang/crates.io-index"
534source = "registry+https://github.com/rust-lang/crates.io-index"
527−checksum = "721c7497ab24b665ed5a1eb2b3526936aa60068e61ba260651f7e77c52feec69"
535+checksum = "c1e74cea676de7f53a79f3c0365812b11f6814b81e671b8ee4abae6ca09c7881"
528dependencies = [
536dependencies = [
529 "gix-actor",
537 "gix-actor",
538+ "gix-attributes",
530 "gix-commitgraph",
539 "gix-commitgraph",
531 "gix-config",
540 "gix-config",
541+ "gix-credentials",
532 "gix-date",
542 "gix-date",
533 "gix-diff",
543 "gix-diff",
534 "gix-discover",
544 "gix-discover",
@@ -537,16 +547,19 @@ dependencies = [
537 "gix-glob",
547 "gix-glob",
538 "gix-hash",
548 "gix-hash",
539 "gix-hashtable",
549 "gix-hashtable",
550+ "gix-ignore",
551+ "gix-index",
540 "gix-lock",
552 "gix-lock",
541− "gix-macros",
553+ "gix-mailmap",
554+ "gix-negotiate",
542 "gix-object",
555 "gix-object",
543 "gix-odb",
556 "gix-odb",
544 "gix-pack",
557 "gix-pack",
545 "gix-path",
558 "gix-path",
559+ "gix-prompt",
546 "gix-ref",
560 "gix-ref",
547 "gix-refspec",
561 "gix-refspec",
548 "gix-revision",
562 "gix-revision",
549− "gix-revwalk",
550 "gix-sec",
563 "gix-sec",
551 "gix-tempfile",
564 "gix-tempfile",
552 "gix-trace",
565 "gix-trace",
@@ -554,8 +567,10 @@ dependencies = [
554 "gix-url",
567 "gix-url",
555 "gix-utils",
568 "gix-utils",
556 "gix-validate",
569 "gix-validate",
570+ "gix-worktree",
571+ "log",
557 "once_cell",
572 "once_cell",
558− "parking_lot",
573+ "signal-hook",
559 "smallvec",
574 "smallvec",
560 "thiserror",
575 "thiserror",
561 "unicode-normalization",
576 "unicode-normalization",
@@ -563,46 +578,81 @@ dependencies = [
563
578
564[[package]]
579[[package]]
565name = "gix-actor"
580name = "gix-actor"
566−version = "0.29.0"
581+version = "0.23.0"
567source = "registry+https://github.com/rust-lang/crates.io-index"
582source = "registry+https://github.com/rust-lang/crates.io-index"
568−checksum = "886014c4865b93ce268f1d3eddd4fd3261242c3f3ee61eb36009f913016a9059"
583+checksum = "1969b77b9ee4cc1755c841987ec6f7622aaca95e952bcafb76973ae59d1b8716"
569dependencies = [
584dependencies = [
570 "bstr",
585 "bstr",
571 "btoi",
586 "btoi",
572 "gix-date",
587 "gix-date",
573 "itoa",
588 "itoa",
589+ "nom",
590+ "thiserror",
591+]
592+
593+[[package]]
594+name = "gix-attributes"
595+version = "0.14.1"
596+source = "registry+https://github.com/rust-lang/crates.io-index"
597+checksum = "e3772b0129dcd1fc73e985bbd08a1482d082097d2915cb1ee31ce8092b8e4434"
598+dependencies = [
599+ "bstr",
600+ "gix-glob",
601+ "gix-path",
602+ "gix-quote",
603+ "kstring",
604+ "log",
605+ "smallvec",
606+ "thiserror",
607+ "unicode-bom",
608+]
609+
610+[[package]]
611+name = "gix-bitmap"
612+version = "0.2.5"
613+source = "registry+https://github.com/rust-lang/crates.io-index"
614+checksum = "311e2fa997be6560c564b070c5da2d56d038b645a94e1e5796d5d85a350da33c"
615+dependencies = [
574 "thiserror",
616 "thiserror",
575− "winnow 0.5.28",
576]
617]
577
618
578[[package]]
619[[package]]
579name = "gix-chunk"
620name = "gix-chunk"
580−version = "0.4.6"
621+version = "0.4.3"
581source = "registry+https://github.com/rust-lang/crates.io-index"
622source = "registry+https://github.com/rust-lang/crates.io-index"
582−checksum = "2ade37ef69870de0ed966b97c57a3a947a22ff3482a52c3b99b205f77bcb08fb"
623+checksum = "39db5ed0fc0a2e9b1b8265993f7efdbc30379dec268f3b91b7af0c2de4672fdd"
583dependencies = [
624dependencies = [
584 "thiserror",
625 "thiserror",
585]
626]
586
627
628+[[package]]
629+name = "gix-command"
630+version = "0.2.6"
631+source = "registry+https://github.com/rust-lang/crates.io-index"
632+checksum = "bb49ab557a37b0abb2415bca2b10e541277dff0565deb5bd5e99fd95f93f51eb"
633+dependencies = [
634+ "bstr",
635+]
636+
587[[package]]
637[[package]]
588name = "gix-commitgraph"
638name = "gix-commitgraph"
589−version = "0.23.0"
639+version = "0.17.1"
590source = "registry+https://github.com/rust-lang/crates.io-index"
640source = "registry+https://github.com/rust-lang/crates.io-index"
591−checksum = "e7559ea9cefee188cd88b05afcc8e3ef7a3cb4a5c647bccf06b981e591b02b77"
641+checksum = "ed42baa50075d41c1a0931074ce1a97c5797c7c6fe7591d9f1f2dcd448532c26"
592dependencies = [
642dependencies = [
593 "bstr",
643 "bstr",
594 "gix-chunk",
644 "gix-chunk",
595 "gix-features",
645 "gix-features",
596 "gix-hash",
646 "gix-hash",
597− "memmap2",
647+ "memmap2 0.7.1",
598 "thiserror",
648 "thiserror",
599]
649]
600
650
601[[package]]
651[[package]]
602name = "gix-config"
652name = "gix-config"
603−version = "0.33.0"
653+version = "0.25.1"
604source = "registry+https://github.com/rust-lang/crates.io-index"
654source = "registry+https://github.com/rust-lang/crates.io-index"
605−checksum = "9c35dc7f9c00a42bbc9cfa1ca2ec0a78ad1b76ff0736d3d35dfd612962244467"
655+checksum = "817688c7005a716d9363e267913526adea402dabd947f4ba63842d10cc5132af"
606dependencies = [
656dependencies = [
607 "bstr",
657 "bstr",
608 "gix-config-value",
658 "gix-config-value",
@@ -611,32 +661,49 @@ dependencies = [
611 "gix-path",
661 "gix-path",
612 "gix-ref",
662 "gix-ref",
613 "gix-sec",
663 "gix-sec",
664+ "log",
614 "memchr",
665 "memchr",
666+ "nom",
615 "once_cell",
667 "once_cell",
616 "smallvec",
668 "smallvec",
617 "thiserror",
669 "thiserror",
618 "unicode-bom",
670 "unicode-bom",
619− "winnow 0.5.28",
620]
671]
621
672
622[[package]]
673[[package]]
623name = "gix-config-value"
674name = "gix-config-value"
624−version = "0.14.2"
675+version = "0.12.3"
625source = "registry+https://github.com/rust-lang/crates.io-index"
676source = "registry+https://github.com/rust-lang/crates.io-index"
626−checksum = "39f388cb2396aee82d6f460a2e7770659bdf8854e9e5478f7d2b1324a9698284"
677+checksum = "83960be5e99266bcf55dae5a24731bbd39f643bfb68f27e939d6b06836b5b87d"
627dependencies = [
678dependencies = [
628− "bitflags 2.4.1",
679+ "bitflags 2.3.3",
629 "bstr",
680 "bstr",
630 "gix-path",
681 "gix-path",
631 "libc",
682 "libc",
632 "thiserror",
683 "thiserror",
633]
684]
634
685
686+[[package]]
687+name = "gix-credentials"
688+version = "0.16.1"
689+source = "registry+https://github.com/rust-lang/crates.io-index"
690+checksum = "75a75565e0e6e7f80cfa4eb1b05cc448c6846ddd48dcf413a28875fbc11ee9af"
691+dependencies = [
692+ "bstr",
693+ "gix-command",
694+ "gix-config-value",
695+ "gix-path",
696+ "gix-prompt",
697+ "gix-sec",
698+ "gix-url",
699+ "thiserror",
700+]
701+
635[[package]]
702[[package]]
636name = "gix-date"
703name = "gix-date"
637−version = "0.8.2"
704+version = "0.7.0"
638source = "registry+https://github.com/rust-lang/crates.io-index"
705source = "registry+https://github.com/rust-lang/crates.io-index"
639−checksum = "9d85f4a01e0d05c3de585e28bae514d4baf01655e3fc3f14ce6f30bf62405345"
706+checksum = "8e9a04a1d2387c955ec91059d56b673000dd24f3c07cad08ed253e36381782bf"
640dependencies = [
707dependencies = [
641 "bstr",
708 "bstr",
642 "itoa",
709 "itoa",
@@ -646,21 +713,21 @@ dependencies = [
646
713
647[[package]]
714[[package]]
648name = "gix-diff"
715name = "gix-diff"
649−version = "0.39.0"
716+version = "0.32.0"
650source = "registry+https://github.com/rust-lang/crates.io-index"
717source = "registry+https://github.com/rust-lang/crates.io-index"
651−checksum = "ac0e75f5afd2f6c47c800b6b0a000a08045739d0450d20482e8faa42543f62d1"
718+checksum = "aaf5d9b9b521b284ebe53ee69eee33341835ec70edc314f36b2100ea81396121"
652dependencies = [
719dependencies = [
653− "bstr",
654 "gix-hash",
720 "gix-hash",
655 "gix-object",
721 "gix-object",
722+ "imara-diff",
656 "thiserror",
723 "thiserror",
657]
724]
658
725
659[[package]]
726[[package]]
660name = "gix-discover"
727name = "gix-discover"
661−version = "0.28.0"
728+version = "0.21.1"
662source = "registry+https://github.com/rust-lang/crates.io-index"
729source = "registry+https://github.com/rust-lang/crates.io-index"
663−checksum = "f0b63ce2ad81632ac1a84ac370f85a5e580c3261580bd85ea17ff35d3a0bba18"
730+checksum = "272aad20dc63dedba76615373dd8885fb5aebe4795e5b5b0aa2a24e63c82085c"
664dependencies = [
731dependencies = [
665 "bstr",
732 "bstr",
666 "dunce",
733 "dunce",
@@ -673,9 +740,9 @@ dependencies = [
673
740
674[[package]]
741[[package]]
675name = "gix-features"
742name = "gix-features"
676−version = "0.37.0"
743+version = "0.31.1"
677source = "registry+https://github.com/rust-lang/crates.io-index"
744source = "registry+https://github.com/rust-lang/crates.io-index"
678−checksum = "befe7edea299a824504b5acc96d7a3a538125b38c42f3a8379f6912a29c90c81"
745+checksum = "06142d8cff5d17509399b04052b64d2f9b3a311d5cff0b1a32b220f62cd0d595"
679dependencies = [
746dependencies = [
680 "crc32fast",
747 "crc32fast",
681 "flate2",
748 "flate2",
@@ -691,20 +758,20 @@ dependencies = [
691
758
692[[package]]
759[[package]]
693name = "gix-fs"
760name = "gix-fs"
694−version = "0.9.0"
761+version = "0.3.0"
695source = "registry+https://github.com/rust-lang/crates.io-index"
762source = "registry+https://github.com/rust-lang/crates.io-index"
696−checksum = "007017ce93b819ea52c0ec68306f7d72212a2d307c3d70f1548c7141c015d0a1"
763+checksum = "bb15956bc0256594c62a2399fcf6958a02a11724217eddfdc2b49b21b6292496"
697dependencies = [
764dependencies = [
698 "gix-features",
765 "gix-features",
699]
766]
700
767
701[[package]]
768[[package]]
702name = "gix-glob"
769name = "gix-glob"
703−version = "0.15.0"
770+version = "0.9.1"
704source = "registry+https://github.com/rust-lang/crates.io-index"
771source = "registry+https://github.com/rust-lang/crates.io-index"
705−checksum = "61fb116c2516d3a1170e010118f639944a6389872c875a008960cdab2a44ac72"
772+checksum = "c18bdff83143d61e7d60da6183b87542a870d026b2a2d0b30170b8e9c0cd321a"
706dependencies = [
773dependencies = [
707− "bitflags 2.4.1",
774+ "bitflags 2.3.3",
708 "bstr",
775 "bstr",
709 "gix-features",
776 "gix-features",
710 "gix-path",
777 "gix-path",
@@ -712,30 +779,64 @@ dependencies = [
712
779
713[[package]]
780[[package]]
714name = "gix-hash"
781name = "gix-hash"
715−version = "0.14.0"
782+version = "0.11.3"
716source = "registry+https://github.com/rust-lang/crates.io-index"
783source = "registry+https://github.com/rust-lang/crates.io-index"
717−checksum = "c52c3170a17b6031833cd2eb4ad7bc23f2755d06e6e70f78dec21d42e8fe1b30"
784+checksum = "a0dd58cdbe7ffa4032fc111864c80d5f8cecd9a2c9736c97ae7e5be834188272"
718dependencies = [
785dependencies = [
719− "faster-hex",
786+ "hex",
720 "thiserror",
787 "thiserror",
721]
788]
722
789
723[[package]]
790[[package]]
724name = "gix-hashtable"
791name = "gix-hashtable"
725−version = "0.5.0"
792+version = "0.2.3"
726source = "registry+https://github.com/rust-lang/crates.io-index"
793source = "registry+https://github.com/rust-lang/crates.io-index"
727−checksum = "40a838e6366a5e5b84668b6997ce0981b833136468e8ba949f424c0ef2927eba"
794+checksum = "9e133bc56d938eaec1c675af7c681a51de9662b0ada779f45607b967a10da77a"
728dependencies = [
795dependencies = [
729 "gix-hash",
796 "gix-hash",
730− "hashbrown 0.14.3",
797+ "hashbrown 0.14.0",
731 "parking_lot",
798 "parking_lot",
732]
799]
733
800
801+[[package]]
802+name = "gix-ignore"
803+version = "0.4.1"
804+source = "registry+https://github.com/rust-lang/crates.io-index"
805+checksum = "ca801f2d0535210f77b33e2c067d565aedecacc82f1b3dbce26da1388ebc4634"
806+dependencies = [
807+ "bstr",
808+ "gix-glob",
809+ "gix-path",
810+ "unicode-bom",
811+]
812+
813+[[package]]
814+name = "gix-index"
815+version = "0.20.0"
816+source = "registry+https://github.com/rust-lang/crates.io-index"
817+checksum = "68099abdf6ee50ae3c897e8b05de96871cbe54d52a37cdf559101f911b883562"
818+dependencies = [
819+ "bitflags 2.3.3",
820+ "bstr",
821+ "btoi",
822+ "filetime",
823+ "gix-bitmap",
824+ "gix-features",
825+ "gix-hash",
826+ "gix-lock",
827+ "gix-object",
828+ "gix-traverse",
829+ "itoa",
830+ "memmap2 0.7.1",
831+ "smallvec",
832+ "thiserror",
833+]
834+
734[[package]]
835[[package]]
735name = "gix-lock"
836name = "gix-lock"
736−version = "12.0.0"
837+version = "7.0.0"
737source = "registry+https://github.com/rust-lang/crates.io-index"
838source = "registry+https://github.com/rust-lang/crates.io-index"
738−checksum = "6cf112ddee94223c119a8534dad027740dc3aba3365ac5edeef8a7f6660c74db"
839+checksum = "328f50aad713ab606caeaf834459ef915ccdfbb9133ac6cd54616d601aa9249f"
739dependencies = [
840dependencies = [
740 "gix-tempfile",
841 "gix-tempfile",
741 "gix-utils",
842 "gix-utils",
@@ -743,21 +844,38 @@ dependencies = [
743]
844]
744
845
745[[package]]
846[[package]]
746−name = "gix-macros"
847+name = "gix-mailmap"
747−version = "0.1.2"
848+version = "0.15.0"
748source = "registry+https://github.com/rust-lang/crates.io-index"
849source = "registry+https://github.com/rust-lang/crates.io-index"
749−checksum = "fc207b64189cf71bcb17fc841ab99a5d63d0845546b611e2703ce467f659323a"
850+checksum = "1787e3c37fc43b1f7c0e3be6196c6837b3ba5f869190dfeaa444b816f0a7f34b"
750dependencies = [
851dependencies = [
751− "proc-macro2",
852+ "bstr",
752− "quote",
853+ "gix-actor",
753− "syn 2.0.38",
854+ "gix-date",
855+ "thiserror",
856+]
857+
858+[[package]]
859+name = "gix-negotiate"
860+version = "0.4.0"
861+source = "registry+https://github.com/rust-lang/crates.io-index"
862+checksum = "4e7bce64d4452dd609f44d04b14b29da2e0ad2c45fcdf4ce1472a5f5f8ec21c2"
863+dependencies = [
864+ "bitflags 2.3.3",
865+ "gix-commitgraph",
866+ "gix-date",
867+ "gix-hash",
868+ "gix-object",
869+ "gix-revwalk",
870+ "smallvec",
871+ "thiserror",
754]
872]
755
873
756[[package]]
874[[package]]
757name = "gix-object"
875name = "gix-object"
758−version = "0.40.0"
876+version = "0.32.0"
759source = "registry+https://github.com/rust-lang/crates.io-index"
877source = "registry+https://github.com/rust-lang/crates.io-index"
760−checksum = "e4c77e47ffba92127faf632a841fce23d19547e269bd8b88e68961a70eab4e93"
878+checksum = "a953f3d7ffad16734aa3ab1d05807972c80e339d1bd9dde03e0198716b99e2a6"
761dependencies = [
879dependencies = [
762 "bstr",
880 "bstr",
763 "btoi",
881 "btoi",
@@ -766,17 +884,18 @@ dependencies = [
766 "gix-features",
884 "gix-features",
767 "gix-hash",
885 "gix-hash",
768 "gix-validate",
886 "gix-validate",
887+ "hex",
769 "itoa",
888 "itoa",
889+ "nom",
770 "smallvec",
890 "smallvec",
771 "thiserror",
891 "thiserror",
772− "winnow 0.5.28",
773]
892]
774
893
775[[package]]
894[[package]]
776name = "gix-odb"
895name = "gix-odb"
777−version = "0.56.0"
896+version = "0.49.1"
778source = "registry+https://github.com/rust-lang/crates.io-index"
897source = "registry+https://github.com/rust-lang/crates.io-index"
779−checksum = "a3254f2005cc7553ea78e85e816a09150c6f7a64e6b7627b8d1fdc56721bea73"
898+checksum = "f6418cff00ecc2713b58c8e04bff30dda808fbba1a080e7248b299d069894a01"
780dependencies = [
899dependencies = [
781 "arc-swap",
900 "arc-swap",
782 "gix-date",
901 "gix-date",
@@ -793,19 +912,21 @@ dependencies = [
793
912
794[[package]]
913[[package]]
795name = "gix-pack"
914name = "gix-pack"
796−version = "0.46.0"
915+version = "0.39.1"
797source = "registry+https://github.com/rust-lang/crates.io-index"
916source = "registry+https://github.com/rust-lang/crates.io-index"
798−checksum = "02ebc8cd657eec207d82d8f876ca402361308ecd3c87a47935b0299506257b4f"
917+checksum = "414935138d90043ea5898de7a93f02c2558e52652492719470e203ef26a8fd0a"
799dependencies = [
918dependencies = [
800 "clru",
919 "clru",
801 "gix-chunk",
920 "gix-chunk",
921+ "gix-diff",
802 "gix-features",
922 "gix-features",
803 "gix-hash",
923 "gix-hash",
804 "gix-hashtable",
924 "gix-hashtable",
805 "gix-object",
925 "gix-object",
806 "gix-path",
926 "gix-path",
807 "gix-tempfile",
927 "gix-tempfile",
928+ "gix-traverse",
808− "memmap2",
929+ "memmap2 0.7.1",
809 "parking_lot",
930 "parking_lot",
810 "smallvec",
931 "smallvec",
811 "thiserror",
932 "thiserror",
@@ -813,9 +934,9 @@ dependencies = [
813
934
814[[package]]
935[[package]]
815name = "gix-path"
936name = "gix-path"
816−version = "0.10.2"
937+version = "0.8.3"
817source = "registry+https://github.com/rust-lang/crates.io-index"
938source = "registry+https://github.com/rust-lang/crates.io-index"
818−checksum = "10931652a3da126990ac93bce1b1c600cc99d7c268d712b6360ed52174ce1b68"
939+checksum = "dfca182d2575ded2ed38280f1ebf75cd5d3790b77e0872de07854cf085821fbe"
819dependencies = [
940dependencies = [
820 "bstr",
941 "bstr",
821 "gix-trace",
942 "gix-trace",
@@ -824,11 +945,24 @@ dependencies = [
824 "thiserror",
945 "thiserror",
825]
946]
826
947
948+[[package]]
949+name = "gix-prompt"
950+version = "0.5.2"
951+source = "registry+https://github.com/rust-lang/crates.io-index"
952+checksum = "8dfd363fd89a40c1e7bff9c9c1b136cd2002480f724b0c627c1bc771cd5480ec"
953+dependencies = [
954+ "gix-command",
955+ "gix-config-value",
956+ "parking_lot",
957+ "rustix 0.37.15",
958+ "thiserror",
959+]
960+
827[[package]]
961[[package]]
828name = "gix-quote"
962name = "gix-quote"
829−version = "0.4.9"
963+version = "0.4.5"
830source = "registry+https://github.com/rust-lang/crates.io-index"
964source = "registry+https://github.com/rust-lang/crates.io-index"
831−checksum = "6c43530cb94a7759807e6f8d180e17ac9c65673b891645c6c433831dc0cf4342"
965+checksum = "3874de636c2526de26a3405b8024b23ef1a327bebf4845d770d00d48700b6a40"
832dependencies = [
966dependencies = [
833 "bstr",
967 "bstr",
834 "btoi",
968 "btoi",
@@ -837,9 +971,9 @@ dependencies = [
837
971
838[[package]]
972[[package]]
839name = "gix-ref"
973name = "gix-ref"
840−version = "0.40.0"
974+version = "0.32.1"
841source = "registry+https://github.com/rust-lang/crates.io-index"
975source = "registry+https://github.com/rust-lang/crates.io-index"
842−checksum = "baa951b3b850d6d1be4f900768e49af20b76bf9505beac22af723d57249a2f1d"
976+checksum = "39453f4e5f23cddc2e6e4cca2ba20adfdbec29379e3ca829714dfe98ae068ccd"
843dependencies = [
977dependencies = [
844 "gix-actor",
978 "gix-actor",
845 "gix-date",
979 "gix-date",
@@ -851,16 +985,16 @@ dependencies = [
851 "gix-path",
985 "gix-path",
852 "gix-tempfile",
986 "gix-tempfile",
853 "gix-validate",
987 "gix-validate",
854− "memmap2",
988+ "memmap2 0.7.1",
989+ "nom",
855 "thiserror",
990 "thiserror",
856− "winnow 0.5.28",
857]
991]
858
992
859[[package]]
993[[package]]
860name = "gix-refspec"
994name = "gix-refspec"
861−version = "0.21.0"
995+version = "0.13.0"
862source = "registry+https://github.com/rust-lang/crates.io-index"
996source = "registry+https://github.com/rust-lang/crates.io-index"
863−checksum = "88c61f849d58c06e3068a0b601cf10127d2a07cdad00a725ed66cf303f76f6b3"
997+checksum = "b8e76ff1f82fba295a121e31ab02f69642994e532c45c0c899aa393f4b740302"
864dependencies = [
998dependencies = [
865 "bstr",
999 "bstr",
866 "gix-hash",
1000 "gix-hash",
@@ -872,9 +1006,9 @@ dependencies = [
872
1006
873[[package]]
1007[[package]]
874name = "gix-revision"
1008name = "gix-revision"
875−version = "0.25.0"
1009+version = "0.17.0"
876source = "registry+https://github.com/rust-lang/crates.io-index"
1010source = "registry+https://github.com/rust-lang/crates.io-index"
877−checksum = "bcbbf91f4200c5c76802ef5f057b96f5a336827508881fe55a780be71a794d22"
1011+checksum = "237428a7d3978e8572964e1e45d984027c2acc94df47e594baa6c4b0da7c9922"
878dependencies = [
1012dependencies = [
879 "bstr",
1013 "bstr",
880 "gix-date",
1014 "gix-date",
@@ -882,15 +1016,14 @@ dependencies = [
882 "gix-hashtable",
1016 "gix-hashtable",
883 "gix-object",
1017 "gix-object",
884 "gix-revwalk",
1018 "gix-revwalk",
885− "gix-trace",
886 "thiserror",
1019 "thiserror",
887]
1020]
888
1021
889[[package]]
1022[[package]]
890name = "gix-revwalk"
1023name = "gix-revwalk"
891−version = "0.11.0"
1024+version = "0.3.0"
892source = "registry+https://github.com/rust-lang/crates.io-index"
1025source = "registry+https://github.com/rust-lang/crates.io-index"
893−checksum = "33ab66354d83f70f2730391747d0c75f94ef3c3dd40ebde2e206db9faaf7a0a7"
1026+checksum = "028d50fcaf8326a8f79a359490d9ca9fb4e2b51ac9ac86503560d0bcc888d2eb"
894dependencies = [
1027dependencies = [
895 "gix-commitgraph",
1028 "gix-commitgraph",
896 "gix-date",
1029 "gix-date",
@@ -903,40 +1036,42 @@ dependencies = [
903
1036
904[[package]]
1037[[package]]
905name = "gix-sec"
1038name = "gix-sec"
906−version = "0.10.2"
1039+version = "0.8.3"
907source = "registry+https://github.com/rust-lang/crates.io-index"
1040source = "registry+https://github.com/rust-lang/crates.io-index"
908−checksum = "a9963f38a42144253ed4d571882d7db5ef644c12e6726e4b75135597cc9d0e1a"
1041+checksum = "ede298863db2a0574a14070991710551e76d1f47c9783b62d4fcbca17f56371c"
909dependencies = [
1042dependencies = [
910− "bitflags 2.4.1",
1043+ "bitflags 2.3.3",
911 "gix-path",
1044 "gix-path",
912 "libc",
1045 "libc",
913− "windows 0.52.0",
1046+ "windows",
914]
1047]
915
1048
916[[package]]
1049[[package]]
917name = "gix-tempfile"
1050name = "gix-tempfile"
918−version = "12.0.0"
1051+version = "7.0.0"
919source = "registry+https://github.com/rust-lang/crates.io-index"
1052source = "registry+https://github.com/rust-lang/crates.io-index"
920−checksum = "e76a494bd530e1a1309188ff971825a24f159c76c2db0bf71fa5dfb469a2c915"
1053+checksum = "4fac8310c17406ea619af72f42ee46dac795110f68f41b4f4fa231b69889c6a2"
921dependencies = [
1054dependencies = [
922 "gix-fs",
1055 "gix-fs",
923 "libc",
1056 "libc",
924 "once_cell",
1057 "once_cell",
925 "parking_lot",
1058 "parking_lot",
1059+ "signal-hook",
1060+ "signal-hook-registry",
926 "tempfile",
1061 "tempfile",
927]
1062]
928
1063
929[[package]]
1064[[package]]
930name = "gix-trace"
1065name = "gix-trace"
931−version = "0.1.5"
1066+version = "0.1.2"
932source = "registry+https://github.com/rust-lang/crates.io-index"
1067source = "registry+https://github.com/rust-lang/crates.io-index"
933−checksum = "bda62acb44dd86a40c7c3762a5403cfc1ac789ea559df54085cedf79864f809e"
1068+checksum = "103eac621617be3ebe0605c9065ca51a223279a23218aaf67d10daa6e452f663"
934
1069
935[[package]]
1070[[package]]
936name = "gix-traverse"
1071name = "gix-traverse"
937−version = "0.36.0"
1072+version = "0.29.0"
938source = "registry+https://github.com/rust-lang/crates.io-index"
1073source = "registry+https://github.com/rust-lang/crates.io-index"
939−checksum = "8661fab39985c9214e56d81a63ceb5886ac948cec2fba76c39494d1e0e307ea8"
1074+checksum = "e3cdfd54598db4fae57d5ae6f52958422b2d13382d2745796bfe5c8015ffa86e"
940dependencies = [
1075dependencies = [
941 "gix-commitgraph",
1076 "gix-commitgraph",
942 "gix-date",
1077 "gix-date",
@@ -950,9 +1085,9 @@ dependencies = [
950
1085
951[[package]]
1086[[package]]
952name = "gix-url"
1087name = "gix-url"
953−version = "0.26.0"
1088+version = "0.20.1"
954source = "registry+https://github.com/rust-lang/crates.io-index"
1089source = "registry+https://github.com/rust-lang/crates.io-index"
955−checksum = "10a0129c1e8b52736d7c5128300a4485dbc85863001371e2771ac1754bd89fd7"
1090+checksum = "beaede6dbc83f408b19adfd95bb52f1dbf01fb8862c3faf6c6243e2e67fcdfa1"
956dependencies = [
1091dependencies = [
957 "bstr",
1092 "bstr",
958 "gix-features",
1093 "gix-features",
@@ -964,71 +1099,94 @@ dependencies = [
964
1099
965[[package]]
1100[[package]]
966name = "gix-utils"
1101name = "gix-utils"
967−version = "0.1.7"
1102+version = "0.1.4"
968source = "registry+https://github.com/rust-lang/crates.io-index"
1103source = "registry+https://github.com/rust-lang/crates.io-index"
969−checksum = "ab9277a5e32e85d53f738096d872a4a9b76067ad471894ad31bd99c8fa2da1dc"
1104+checksum = "7058c94f4164fcf5b8457d35f6d8f6e1007f9f7f938c9c7684a7e01d23c6ddde"
970dependencies = [
1105dependencies = [
971 "fastrand",
1106 "fastrand",
972]
1107]
973
1108
974[[package]]
1109[[package]]
975name = "gix-validate"
1110name = "gix-validate"
976−version = "0.8.2"
1111+version = "0.7.6"
977source = "registry+https://github.com/rust-lang/crates.io-index"
1112source = "registry+https://github.com/rust-lang/crates.io-index"
978−checksum = "f805ebbdbaa4bfd98e2ee43e6d14099b8a4d9141f5d7b8202fea4d48e44263e7"
1113+checksum = "8d092b594c8af00a3a31fe526d363ee8a51a6f29d8496cdb991ed2f01ec0ec13"
979dependencies = [
1114dependencies = [
980 "bstr",
1115 "bstr",
981 "thiserror",
1116 "thiserror",
982]
1117]
983
1118
1119+[[package]]
1120+name = "gix-worktree"
1121+version = "0.21.1"
1122+source = "registry+https://github.com/rust-lang/crates.io-index"
1123+checksum = "c1363b9aa66b9e14412ac04e1f759827203f491729d92172535a8ce6cde02efa"
1124+dependencies = [
1125+ "bstr",
1126+ "filetime",
1127+ "gix-attributes",
1128+ "gix-features",
1129+ "gix-fs",
1130+ "gix-glob",
1131+ "gix-hash",
1132+ "gix-ignore",
1133+ "gix-index",
1134+ "gix-object",
1135+ "gix-path",
1136+ "io-close",
1137+ "thiserror",
1138+]
1139+
984[[package]]
1140[[package]]
985name = "globset"
1141name = "globset"
986−version = "0.4.14"
1142+version = "0.4.11"
987source = "registry+https://github.com/rust-lang/crates.io-index"
1143source = "registry+https://github.com/rust-lang/crates.io-index"
988−checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
1144+checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df"
989dependencies = [
1145dependencies = [
990− "aho-corasick",
1146+ "aho-corasick 1.0.2",
991 "bstr",
1147 "bstr",
1148+ "fnv",
992 "log",
1149 "log",
993− "regex-automata",
1150+ "regex",
994− "regex-syntax",
995]
1151]
996
1152
997[[package]]
1153[[package]]
998name = "grep-matcher"
1154name = "grep-matcher"
999−version = "0.1.7"
1155+version = "0.1.6"
1000source = "registry+https://github.com/rust-lang/crates.io-index"
1156source = "registry+https://github.com/rust-lang/crates.io-index"
1001−checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02"
1157+checksum = "3902ca28f26945fe35cad349d776f163981d777fee382ccd6ef451126f51b319"
1002dependencies = [
1158dependencies = [
1003 "memchr",
1159 "memchr",
1004]
1160]
1005
1161
1006[[package]]
1162[[package]]
1007name = "grep-regex"
1163name = "grep-regex"
1008−version = "0.1.12"
1164+version = "0.1.11"
1009source = "registry+https://github.com/rust-lang/crates.io-index"
1165source = "registry+https://github.com/rust-lang/crates.io-index"
1010−checksum = "f748bb135ca835da5cbc67ca0e6955f968db9c5df74ca4f56b18e1ddbc68230d"
1166+checksum = "997598b41d53a37a2e3fc5300d5c11d825368c054420a9c65125b8fe1078463f"
1011dependencies = [
1167dependencies = [
1168+ "aho-corasick 0.7.20",
1012 "bstr",
1169 "bstr",
1013 "grep-matcher",
1170 "grep-matcher",
1014 "log",
1171 "log",
1015− "regex-automata",
1172+ "regex",
1016− "regex-syntax",
1173+ "regex-syntax 0.6.29",
1174+ "thread_local",
1017]
1175]
1018
1176
1019[[package]]
1177[[package]]
1020name = "grep-searcher"
1178name = "grep-searcher"
1021−version = "0.1.13"
1179+version = "0.1.11"
1022source = "registry+https://github.com/rust-lang/crates.io-index"
1180source = "registry+https://github.com/rust-lang/crates.io-index"
1023−checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54"
1181+checksum = "5601c4b9f480f0c9ebb40b1f6cbf447b8a50c5369223937a6c5214368c58779f"
1024dependencies = [
1182dependencies = [
1025 "bstr",
1183 "bstr",
1184+ "bytecount",
1026 "encoding_rs",
1185 "encoding_rs",
1027 "encoding_rs_io",
1186 "encoding_rs_io",
1028 "grep-matcher",
1187 "grep-matcher",
1029 "log",
1188 "log",
1030− "memchr",
1031− "memmap2",
1189+ "memmap2 0.5.10",
1032]
1190]
1033
1191
1034[[package]]
1192[[package]]
@@ -1036,36 +1194,37 @@ name = "hashbrown"
1036version = "0.12.3"
1194version = "0.12.3"
1037source = "registry+https://github.com/rust-lang/crates.io-index"
1195source = "registry+https://github.com/rust-lang/crates.io-index"
1038checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1196checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1197+dependencies = [
1198+ "ahash 0.7.6",
1199+]
1039
1200
1040[[package]]
1201[[package]]
1041name = "hashbrown"
1202name = "hashbrown"
1042−version = "0.14.3"
1203+version = "0.14.0"
1043source = "registry+https://github.com/rust-lang/crates.io-index"
1204source = "registry+https://github.com/rust-lang/crates.io-index"
1044−checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
1205+checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
1045dependencies = [
1206dependencies = [
1046− "ahash",
1207+ "ahash 0.8.3",
1047 "allocator-api2",
1208 "allocator-api2",
1048]
1209]
1049
1210
1050[[package]]
1211[[package]]
1051name = "helix-core"
1212name = "helix-core"
1052−version = "23.10.0"
1213+version = "0.6.0"
1053dependencies = [
1214dependencies = [
1054− "ahash",
1215+ "ahash 0.8.3",
1055 "arc-swap",
1216 "arc-swap",
1056− "bitflags 2.4.1",
1217+ "bitflags 2.3.3",
1057 "chrono",
1218 "chrono",
1058 "dunce",
1219 "dunce",
1059 "encoding_rs",
1220 "encoding_rs",
1060 "etcetera",
1221 "etcetera",
1061− "hashbrown 0.14.3",
1222+ "hashbrown 0.14.0",
1062 "helix-loader",
1223 "helix-loader",
1063 "imara-diff",
1224 "imara-diff",
1064 "indoc",
1225 "indoc",
1065 "log",
1226 "log",
1066− "nucleo",
1067 "once_cell",
1227 "once_cell",
1068− "parking_lot",
1069 "quickcheck",
1228 "quickcheck",
1070 "regex",
1229 "regex",
1071 "ropey",
1230 "ropey",
@@ -1084,7 +1243,7 @@ dependencies = [
1084
1243
1085[[package]]
1244[[package]]
1086name = "helix-dap"
1245name = "helix-dap"
1087−version = "23.10.0"
1246+version = "0.6.0"
1088dependencies = [
1247dependencies = [
1089 "anyhow",
1248 "anyhow",
1090 "fern",
1249 "fern",
@@ -1097,17 +1256,9 @@ dependencies = [
1097 "which",
1256 "which",
1098]
1257]
1099
1258
1100−[[package]]
1101−name = "helix-event"
1102−version = "23.10.0"
1103−dependencies = [
1104− "parking_lot",
1105− "tokio",
1106−]
1107−
1108[[package]]
1259[[package]]
1109name = "helix-loader"
1260name = "helix-loader"
1110−version = "23.10.0"
1261+version = "0.6.0"
1111dependencies = [
1262dependencies = [
1112 "anyhow",
1263 "anyhow",
1113 "cc",
1264 "cc",
@@ -1126,7 +1277,7 @@ dependencies = [
1126
1277
1127[[package]]
1278[[package]]
1128name = "helix-lsp"
1279name = "helix-lsp"
1129−version = "23.10.0"
1280+version = "0.6.0"
1130dependencies = [
1281dependencies = [
1131 "anyhow",
1282 "anyhow",
1132 "futures-executor",
1283 "futures-executor",
@@ -1148,11 +1299,11 @@ dependencies = [
1148
1299
1149[[package]]
1300[[package]]
1150name = "helix-parsec"
1301name = "helix-parsec"
1151−version = "23.10.0"
1302+version = "0.6.0"
1152
1303
1153[[package]]
1304[[package]]
1154name = "helix-term"
1305name = "helix-term"
1155−version = "23.10.0"
1306+version = "0.6.0"
1156dependencies = [
1307dependencies = [
1157 "anyhow",
1308 "anyhow",
1158 "arc-swap",
1309 "arc-swap",
@@ -1161,11 +1312,11 @@ dependencies = [
1161 "crossterm",
1312 "crossterm",
1162 "fern",
1313 "fern",
1163 "futures-util",
1314 "futures-util",
1315+ "fuzzy-matcher",
1164 "grep-regex",
1316 "grep-regex",
1165 "grep-searcher",
1317 "grep-searcher",
1166 "helix-core",
1318 "helix-core",
1167 "helix-dap",
1319 "helix-dap",
1168− "helix-event",
1169 "helix-loader",
1320 "helix-loader",
1170 "helix-lsp",
1321 "helix-lsp",
1171 "helix-tui",
1322 "helix-tui",
@@ -1175,9 +1326,7 @@ dependencies = [
1175 "indoc",
1326 "indoc",
1176 "libc",
1327 "libc",
1177 "log",
1328 "log",
1178− "nucleo",
1179 "once_cell",
1329 "once_cell",
1180− "open",
1181 "pulldown-cmark",
1330 "pulldown-cmark",
1182 "serde",
1331 "serde",
1183 "serde_json",
1332 "serde_json",
@@ -1188,15 +1337,14 @@ dependencies = [
1188 "tokio",
1337 "tokio",
1189 "tokio-stream",
1338 "tokio-stream",
1190 "toml",
1339 "toml",
1191− "url",
1192 "which",
1340 "which",
1193]
1341]
1194
1342
1195[[package]]
1343[[package]]
1196name = "helix-tui"
1344name = "helix-tui"
1197−version = "23.10.0"
1345+version = "0.6.0"
1198dependencies = [
1346dependencies = [
1199− "bitflags 2.4.1",
1347+ "bitflags 2.3.3",
1200 "cassowary",
1348 "cassowary",
1201 "crossterm",
1349 "crossterm",
1202 "helix-core",
1350 "helix-core",
@@ -1210,13 +1358,12 @@ dependencies = [
1210
1358
1211[[package]]
1359[[package]]
1212name = "helix-vcs"
1360name = "helix-vcs"
1213−version = "23.10.0"
1361+version = "0.6.0"
1214dependencies = [
1362dependencies = [
1215 "anyhow",
1363 "anyhow",
1216 "arc-swap",
1364 "arc-swap",
1217 "gix",
1365 "gix",
1218 "helix-core",
1366 "helix-core",
1219− "helix-event",
1220 "imara-diff",
1367 "imara-diff",
1221 "log",
1368 "log",
1222 "parking_lot",
1369 "parking_lot",
@@ -1226,18 +1373,17 @@ dependencies = [
1226
1373
1227[[package]]
1374[[package]]
1228name = "helix-view"
1375name = "helix-view"
1229−version = "23.10.0"
1376+version = "0.6.0"
1230dependencies = [
1377dependencies = [
1231 "anyhow",
1378 "anyhow",
1232 "arc-swap",
1379 "arc-swap",
1233− "bitflags 2.4.1",
1380+ "bitflags 2.3.3",
1234 "chardetng",
1381 "chardetng",
1235 "clipboard-win",
1382 "clipboard-win",
1236 "crossterm",
1383 "crossterm",
1237 "futures-util",
1384 "futures-util",
1238 "helix-core",
1385 "helix-core",
1239 "helix-dap",
1386 "helix-dap",
1240− "helix-event",
1241 "helix-loader",
1387 "helix-loader",
1242 "helix-lsp",
1388 "helix-lsp",
1243 "helix-tui",
1389 "helix-tui",
@@ -1246,7 +1392,6 @@ dependencies = [
1246 "log",
1392 "log",
1247 "once_cell",
1393 "once_cell",
1248 "parking_lot",
1394 "parking_lot",
1249− "rustix",
1250 "serde",
1395 "serde",
1251 "serde_json",
1396 "serde_json",
1252 "slotmap",
1397 "slotmap",
@@ -1266,13 +1411,25 @@ dependencies = [
1266 "libc",
1411 "libc",
1267]
1412]
1268
1413
1414+[[package]]
1415+name = "hermit-abi"
1416+version = "0.3.1"
1417+source = "registry+https://github.com/rust-lang/crates.io-index"
1418+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
1419+
1420+[[package]]
1421+name = "hex"
1422+version = "0.4.3"
1423+source = "registry+https://github.com/rust-lang/crates.io-index"
1424+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1425+
1269[[package]]
1426[[package]]
1270name = "home"
1427name = "home"
1271−version = "0.5.5"
1428+version = "0.5.4"
1272source = "registry+https://github.com/rust-lang/crates.io-index"
1429source = "registry+https://github.com/rust-lang/crates.io-index"
1273−checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
1430+checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
1274dependencies = [
1431dependencies = [
1275− "windows-sys 0.48.0",
1432+ "winapi",
1276]
1433]
1277
1434
1278[[package]]
1435[[package]]
@@ -1286,7 +1443,7 @@ dependencies = [
1286 "iana-time-zone-haiku",
1443 "iana-time-zone-haiku",
1287 "js-sys",
1444 "js-sys",
1288 "wasm-bindgen",
1445 "wasm-bindgen",
1289− "windows 0.48.0",
1446+ "windows",
1290]
1447]
1291
1448
1292[[package]]
1449[[package]]
@@ -1301,9 +1458,9 @@ dependencies = [
1301
1458
1302[[package]]
1459[[package]]
1303name = "idna"
1460name = "idna"
1304−version = "0.5.0"
1461+version = "0.4.0"
1305source = "registry+https://github.com/rust-lang/crates.io-index"
1462source = "registry+https://github.com/rust-lang/crates.io-index"
1306−checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
1463+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
1307dependencies = [
1464dependencies = [
1308 "unicode-bidi",
1465 "unicode-bidi",
1309 "unicode-normalization",
1466 "unicode-normalization",
@@ -1311,16 +1468,17 @@ dependencies = [
1311
1468
1312[[package]]
1469[[package]]
1313name = "ignore"
1470name = "ignore"
1314−version = "0.4.21"
1471+version = "0.4.20"
1315source = "registry+https://github.com/rust-lang/crates.io-index"
1472source = "registry+https://github.com/rust-lang/crates.io-index"
1316−checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
1473+checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
1317dependencies = [
1474dependencies = [
1318− "crossbeam-deque",
1319 "globset",
1475 "globset",
1476+ "lazy_static",
1320 "log",
1477 "log",
1321 "memchr",
1478 "memchr",
1322− "regex-automata",
1479+ "regex",
1323 "same-file",
1480 "same-file",
1481+ "thread_local",
1324 "walkdir",
1482 "walkdir",
1325 "winapi-util",
1483 "winapi-util",
1326]
1484]
@@ -1331,7 +1489,7 @@ version = "0.1.5"
1331source = "registry+https://github.com/rust-lang/crates.io-index"
1489source = "registry+https://github.com/rust-lang/crates.io-index"
1332checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
1490checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
1333dependencies = [
1491dependencies = [
1334− "ahash",
1492+ "ahash 0.8.3",
1335 "hashbrown 0.12.3",
1493 "hashbrown 0.12.3",
1336]
1494]
1337
1495
@@ -1342,32 +1500,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1342checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
1500checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
1343dependencies = [
1501dependencies = [
1344 "equivalent",
1502 "equivalent",
1345− "hashbrown 0.14.3",
1503+ "hashbrown 0.14.0",
1346]
1504]
1347
1505
1348[[package]]
1506[[package]]
1349name = "indoc"
1507name = "indoc"
1350−version = "2.0.4"
1508+version = "2.0.3"
1351source = "registry+https://github.com/rust-lang/crates.io-index"
1509source = "registry+https://github.com/rust-lang/crates.io-index"
1352−checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
1510+checksum = "2c785eefb63ebd0e33416dfcb8d6da0bf27ce752843a45632a67bf10d4d4b5c4"
1353
1511
1354[[package]]
1512[[package]]
1355−name = "is-docker"
1513+name = "io-close"
1356−version = "0.2.0"
1514+version = "0.3.7"
1357source = "registry+https://github.com/rust-lang/crates.io-index"
1515source = "registry+https://github.com/rust-lang/crates.io-index"
1358−checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
1516+checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc"
1359dependencies = [
1517dependencies = [
1360− "once_cell",
1518+ "libc",
1519+ "winapi",
1361]
1520]
1362
1521
1363[[package]]
1522[[package]]
1364−name = "is-wsl"
1523+name = "io-lifetimes"
1365−version = "0.4.0"
1524+version = "1.0.10"
1366source = "registry+https://github.com/rust-lang/crates.io-index"
1525source = "registry+https://github.com/rust-lang/crates.io-index"
1367−checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
1526+checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
1368dependencies = [
1527dependencies = [
1369− "is-docker",
1528+ "hermit-abi 0.3.1",
1370− "once_cell",
1529+ "libc",
1530+ "windows-sys 0.48.0",
1371]
1531]
1372
1532
1373[[package]]
1533[[package]]
@@ -1385,17 +1545,32 @@ dependencies = [
1385 "wasm-bindgen",
1545 "wasm-bindgen",
1386]
1546]
1387
1547
1548+[[package]]
1549+name = "kstring"
1550+version = "2.0.0"
1551+source = "registry+https://github.com/rust-lang/crates.io-index"
1552+checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747"
1553+dependencies = [
1554+ "static_assertions",
1555+]
1556+
1557+[[package]]
1558+name = "lazy_static"
1559+version = "1.4.0"
1560+source = "registry+https://github.com/rust-lang/crates.io-index"
1561+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
1562+
1388[[package]]
1563[[package]]
1389name = "libc"
1564name = "libc"
1390−version = "0.2.151"
1565+version = "0.2.147"
1391source = "registry+https://github.com/rust-lang/crates.io-index"
1566source = "registry+https://github.com/rust-lang/crates.io-index"
1392−checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
1567+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
1393
1568
1394[[package]]
1569[[package]]
1395name = "libloading"
1570name = "libloading"
1396−version = "0.8.1"
1571+version = "0.8.0"
1397source = "registry+https://github.com/rust-lang/crates.io-index"
1572source = "registry+https://github.com/rust-lang/crates.io-index"
1398−checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
1573+checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb"
1399dependencies = [
1574dependencies = [
1400 "cfg-if",
1575 "cfg-if",
1401 "windows-sys 0.48.0",
1576 "windows-sys 0.48.0",
@@ -1412,9 +1587,15 @@ dependencies = [
1412
1587
1413[[package]]
1588[[package]]
1414name = "linux-raw-sys"
1589name = "linux-raw-sys"
1415−version = "0.4.11"
1590+version = "0.3.4"
1591+source = "registry+https://github.com/rust-lang/crates.io-index"
1592+checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
1593+
1594+[[package]]
1595+name = "linux-raw-sys"
1596+version = "0.4.3"
1416source = "registry+https://github.com/rust-lang/crates.io-index"
1597source = "registry+https://github.com/rust-lang/crates.io-index"
1417−checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
1598+checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
1418
1599
1419[[package]]
1600[[package]]
1420name = "lock_api"
1601name = "lock_api"
@@ -1428,15 +1609,15 @@ dependencies = [
1428
1609
1429[[package]]
1610[[package]]
1430name = "log"
1611name = "log"
1431−version = "0.4.20"
1612+version = "0.4.19"
1432source = "registry+https://github.com/rust-lang/crates.io-index"
1613source = "registry+https://github.com/rust-lang/crates.io-index"
1433−checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
1614+checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
1434
1615
1435[[package]]
1616[[package]]
1436name = "lsp-types"
1617name = "lsp-types"
1437−version = "0.95.0"
1618+version = "0.94.0"
1438source = "registry+https://github.com/rust-lang/crates.io-index"
1619source = "registry+https://github.com/rust-lang/crates.io-index"
1439−checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
1620+checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
1440dependencies = [
1621dependencies = [
1441 "bitflags 1.3.2",
1622 "bitflags 1.3.2",
1442 "serde",
1623 "serde",
@@ -1447,26 +1628,41 @@ dependencies = [
1447
1628
1448[[package]]
1629[[package]]
1449name = "memchr"
1630name = "memchr"
1450−version = "2.6.3"
1631+version = "2.5.0"
1451source = "registry+https://github.com/rust-lang/crates.io-index"
1632source = "registry+https://github.com/rust-lang/crates.io-index"
1452−checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
1633+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
1453
1634
1454[[package]]
1635[[package]]
1455name = "memmap2"
1636name = "memmap2"
1456−version = "0.9.0"
1637+version = "0.5.10"
1457source = "registry+https://github.com/rust-lang/crates.io-index"
1638source = "registry+https://github.com/rust-lang/crates.io-index"
1458−checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
1639+checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
1459dependencies = [
1640dependencies = [
1460 "libc",
1641 "libc",
1461]
1642]
1462
1643
1463[[package]]
1644[[package]]
1464−name = "memoffset"
1645+name = "memmap2"
1465−version = "0.9.0"
1646+version = "0.7.1"
1466source = "registry+https://github.com/rust-lang/crates.io-index"
1647source = "registry+https://github.com/rust-lang/crates.io-index"
1467−checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
1648+checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6"
1468dependencies = [
1649dependencies = [
1469− "autocfg",
1650+ "libc",
1651+]
1652+
1653+[[package]]
1654+name = "minimal-lexical"
1655+version = "0.2.1"
1656+source = "registry+https://github.com/rust-lang/crates.io-index"
1657+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
1658+
1659+[[package]]
1660+name = "miniz_oxide"
1661+version = "0.6.2"
1662+source = "registry+https://github.com/rust-lang/crates.io-index"
1663+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
1664+dependencies = [
1665+ "adler",
1470]
1666]
1471
1667
1472[[package]]
1668[[package]]
@@ -1480,36 +1676,24 @@ dependencies = [
1480
1676
1481[[package]]
1677[[package]]
1482name = "mio"
1678name = "mio"
1483−version = "0.8.9"
1679+version = "0.8.6"
1484source = "registry+https://github.com/rust-lang/crates.io-index"
1680source = "registry+https://github.com/rust-lang/crates.io-index"
1485−checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
1681+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
1486dependencies = [
1682dependencies = [
1487 "libc",
1683 "libc",
1488 "log",
1684 "log",
1489 "wasi",
1685 "wasi",
1490− "windows-sys 0.48.0",
1686+ "windows-sys 0.45.0",
1491−]
1492−
1493−[[package]]
1494−name = "nucleo"
1495−version = "0.2.1"
1496−source = "registry+https://github.com/rust-lang/crates.io-index"
1497−checksum = "ae5331f4bcce475cf28cb29c95366c3091af4b0aa7703f1a6bc858f29718fdf3"
1498−dependencies = [
1499− "nucleo-matcher",
1500− "parking_lot",
1501− "rayon",
1502]
1687]
1503
1688
1504[[package]]
1689[[package]]
1505−name = "nucleo-matcher"
1690+name = "nom"
1506−version = "0.2.0"
1691+version = "7.1.3"
1507source = "registry+https://github.com/rust-lang/crates.io-index"
1692source = "registry+https://github.com/rust-lang/crates.io-index"
1508−checksum = "1b702b402fe286162d1f00b552a046ce74365d2ac473a2607ff36ba650f9bd57"
1693+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
1509dependencies = [
1694dependencies = [
1510− "cov-mark",
1511 "memchr",
1695 "memchr",
1512− "unicode-segmentation",
1696+ "minimal-lexical",
1513]
1697]
1514
1698
1515[[package]]
1699[[package]]
@@ -1527,7 +1711,7 @@ version = "1.15.0"
1527source = "registry+https://github.com/rust-lang/crates.io-index"
1711source = "registry+https://github.com/rust-lang/crates.io-index"
1528checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
1712checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
1529dependencies = [
1713dependencies = [
1530− "hermit-abi",
1714+ "hermit-abi 0.2.6",
1531 "libc",
1715 "libc",
1532]
1716]
1533
1717
@@ -1551,20 +1735,9 @@ dependencies = [
1551
1735
1552[[package]]
1736[[package]]
1553name = "once_cell"
1737name = "once_cell"
1554−version = "1.19.0"
1738+version = "1.18.0"
1555source = "registry+https://github.com/rust-lang/crates.io-index"
1739source = "registry+https://github.com/rust-lang/crates.io-index"
1556−checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
1740+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
1557−
1558−[[package]]
1559−name = "open"
1560−version = "5.0.1"
1561−source = "registry+https://github.com/rust-lang/crates.io-index"
1562−checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349"
1563−dependencies = [
1564− "is-wsl",
1565− "libc",
1566− "pathdiff",
1567−]
1568
1741
1569[[package]]
1742[[package]]
1570name = "parking_lot"
1743name = "parking_lot"
@@ -1589,23 +1762,17 @@ dependencies = [
1589 "windows-sys 0.45.0",
1762 "windows-sys 0.45.0",
1590]
1763]
1591
1764
1592−[[package]]
1593−name = "pathdiff"
1594−version = "0.2.1"
1595−source = "registry+https://github.com/rust-lang/crates.io-index"
1596−checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
1597−
1598[[package]]
1765[[package]]
1599name = "percent-encoding"
1766name = "percent-encoding"
1600−version = "2.3.1"
1767+version = "2.3.0"
1601source = "registry+https://github.com/rust-lang/crates.io-index"
1768source = "registry+https://github.com/rust-lang/crates.io-index"
1602−checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
1769+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
1603
1770
1604[[package]]
1771[[package]]
1605name = "pin-project-lite"
1772name = "pin-project-lite"
1606−version = "0.2.12"
1773+version = "0.2.9"
1607source = "registry+https://github.com/rust-lang/crates.io-index"
1774source = "registry+https://github.com/rust-lang/crates.io-index"
1608−checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
1775+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
1609
1776
1610[[package]]
1777[[package]]
1611name = "pin-utils"
1778name = "pin-utils"
@@ -1615,18 +1782,18 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1615
1782
1616[[package]]
1783[[package]]
1617name = "proc-macro2"
1784name = "proc-macro2"
1618−version = "1.0.69"
1785+version = "1.0.63"
1619source = "registry+https://github.com/rust-lang/crates.io-index"
1786source = "registry+https://github.com/rust-lang/crates.io-index"
1620−checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
1787+checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
1621dependencies = [
1788dependencies = [
1622 "unicode-ident",
1789 "unicode-ident",
1623]
1790]
1624
1791
1625[[package]]
1792[[package]]
1626name = "prodash"
1793name = "prodash"
1627−version = "28.0.0"
1794+version = "25.0.0"
1628source = "registry+https://github.com/rust-lang/crates.io-index"
1795source = "registry+https://github.com/rust-lang/crates.io-index"
1629−checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
1796+checksum = "3236ce1618b6da4c7b618e0143c4d5b5dc190f75f81c49f248221382f7e9e9ae"
1630
1797
1631[[package]]
1798[[package]]
1632name = "pulldown-cmark"
1799name = "pulldown-cmark"
@@ -1675,28 +1842,6 @@ dependencies = [
1675 "getrandom",
1842 "getrandom",
1676]
1843]
1677
1844
1678−[[package]]
1679−name = "rayon"
1680−version = "1.7.0"
1681−source = "registry+https://github.com/rust-lang/crates.io-index"
1682−checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
1683−dependencies = [
1684− "either",
1685− "rayon-core",
1686−]
1687−
1688−[[package]]
1689−name = "rayon-core"
1690−version = "1.11.0"
1691−source = "registry+https://github.com/rust-lang/crates.io-index"
1692−checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
1693−dependencies = [
1694− "crossbeam-channel",
1695− "crossbeam-deque",
1696− "crossbeam-utils",
1697− "num_cpus",
1698−]
1699−
1700[[package]]
1845[[package]]
1701name = "redox_syscall"
1846name = "redox_syscall"
1702version = "0.2.16"
1847version = "0.2.16"
@@ -1708,47 +1853,53 @@ dependencies = [
1708
1853
1709[[package]]
1854[[package]]
1710name = "redox_syscall"
1855name = "redox_syscall"
1711−version = "0.4.1"
1856+version = "0.3.5"
1712source = "registry+https://github.com/rust-lang/crates.io-index"
1857source = "registry+https://github.com/rust-lang/crates.io-index"
1713−checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
1858+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
1714dependencies = [
1859dependencies = [
1715 "bitflags 1.3.2",
1860 "bitflags 1.3.2",
1716]
1861]
1717
1862
1718[[package]]
1863[[package]]
1719name = "regex"
1864name = "regex"
1720−version = "1.10.2"
1865+version = "1.9.1"
1721source = "registry+https://github.com/rust-lang/crates.io-index"
1866source = "registry+https://github.com/rust-lang/crates.io-index"
1722−checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
1867+checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
1723dependencies = [
1868dependencies = [
1724− "aho-corasick",
1869+ "aho-corasick 1.0.2",
1725 "memchr",
1870 "memchr",
1726 "regex-automata",
1871 "regex-automata",
1727− "regex-syntax",
1872+ "regex-syntax 0.7.3",
1728]
1873]
1729
1874
1730[[package]]
1875[[package]]
1731name = "regex-automata"
1876name = "regex-automata"
1732−version = "0.4.3"
1877+version = "0.3.2"
1733source = "registry+https://github.com/rust-lang/crates.io-index"
1878source = "registry+https://github.com/rust-lang/crates.io-index"
1734−checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
1879+checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf"
1735dependencies = [
1880dependencies = [
1736− "aho-corasick",
1881+ "aho-corasick 1.0.2",
1737 "memchr",
1882 "memchr",
1738− "regex-syntax",
1883+ "regex-syntax 0.7.3",
1739]
1884]
1740
1885
1741[[package]]
1886[[package]]
1742name = "regex-syntax"
1887name = "regex-syntax"
1743−version = "0.8.2"
1888+version = "0.6.29"
1889+source = "registry+https://github.com/rust-lang/crates.io-index"
1890+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
1891+
1892+[[package]]
1893+name = "regex-syntax"
1894+version = "0.7.3"
1744source = "registry+https://github.com/rust-lang/crates.io-index"
1895source = "registry+https://github.com/rust-lang/crates.io-index"
1745−checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
1896+checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846"
1746
1897
1747[[package]]
1898[[package]]
1748name = "ropey"
1899name = "ropey"
1749−version = "1.6.1"
1900+version = "1.6.0"
1750source = "registry+https://github.com/rust-lang/crates.io-index"
1901source = "registry+https://github.com/rust-lang/crates.io-index"
1751−checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
1902+checksum = "53ce7a2c43a32e50d666e33c5a80251b31147bb4b49024bcab11fb6f20c671ed"
1752dependencies = [
1903dependencies = [
1753 "smallvec",
1904 "smallvec",
1754 "str_indices",
1905 "str_indices",
@@ -1762,15 +1913,29 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
1762
1913
1763[[package]]
1914[[package]]
1764name = "rustix"
1915name = "rustix"
1765−version = "0.38.28"
1916+version = "0.37.15"
1766source = "registry+https://github.com/rust-lang/crates.io-index"
1917source = "registry+https://github.com/rust-lang/crates.io-index"
1767−checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
1918+checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece"
1768dependencies = [
1919dependencies = [
1769− "bitflags 2.4.1",
1920+ "bitflags 1.3.2",
1770 "errno",
1921 "errno",
1922+ "io-lifetimes",
1771 "libc",
1923 "libc",
1772− "linux-raw-sys",
1924+ "linux-raw-sys 0.3.4",
1773− "windows-sys 0.52.0",
1925+ "windows-sys 0.48.0",
1926+]
1927+
1928+[[package]]
1929+name = "rustix"
1930+version = "0.38.4"
1931+source = "registry+https://github.com/rust-lang/crates.io-index"
1932+checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
1933+dependencies = [
1934+ "bitflags 2.3.3",
1935+ "errno",
1936+ "libc",
1937+ "linux-raw-sys 0.4.3",
1938+ "windows-sys 0.48.0",
1774]
1939]
1775
1940
1776[[package]]
1941[[package]]
@@ -1802,29 +1967,29 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
1802
1967
1803[[package]]
1968[[package]]
1804name = "serde"
1969name = "serde"
1805−version = "1.0.193"
1970+version = "1.0.175"
1806source = "registry+https://github.com/rust-lang/crates.io-index"
1971source = "registry+https://github.com/rust-lang/crates.io-index"
1807−checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
1972+checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b"
1808dependencies = [
1973dependencies = [
1809 "serde_derive",
1974 "serde_derive",
1810]
1975]
1811
1976
1812[[package]]
1977[[package]]
1813name = "serde_derive"
1978name = "serde_derive"
1814−version = "1.0.193"
1979+version = "1.0.175"
1815source = "registry+https://github.com/rust-lang/crates.io-index"
1980source = "registry+https://github.com/rust-lang/crates.io-index"
1816−checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
1981+checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4"
1817dependencies = [
1982dependencies = [
1818 "proc-macro2",
1983 "proc-macro2",
1819 "quote",
1984 "quote",
1820− "syn 2.0.38",
1985+ "syn 2.0.25",
1821]
1986]
1822
1987
1823[[package]]
1988[[package]]
1824name = "serde_json"
1989name = "serde_json"
1825−version = "1.0.108"
1990+version = "1.0.103"
1826source = "registry+https://github.com/rust-lang/crates.io-index"
1991source = "registry+https://github.com/rust-lang/crates.io-index"
1827−checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
1992+checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
1828dependencies = [
1993dependencies = [
1829 "itoa",
1994 "itoa",
1830 "ryu",
1995 "ryu",
@@ -1839,7 +2004,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
1839dependencies = [
2004dependencies = [
1840 "proc-macro2",
2005 "proc-macro2",
1841 "quote",
2006 "quote",
1842− "syn 2.0.38",
2007+ "syn 2.0.25",
1843]
2008]
1844
2009
1845[[package]]
2010[[package]]
@@ -1910,18 +2075,18 @@ dependencies = [
1910
2075
1911[[package]]
2076[[package]]
1912name = "slotmap"
2077name = "slotmap"
1913−version = "1.0.7"
2078+version = "1.0.6"
1914source = "registry+https://github.com/rust-lang/crates.io-index"
2079source = "registry+https://github.com/rust-lang/crates.io-index"
1915−checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
2080+checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
1916dependencies = [
2081dependencies = [
1917 "version_check",
2082 "version_check",
1918]
2083]
1919
2084
1920[[package]]
2085[[package]]
1921name = "smallvec"
2086name = "smallvec"
1922−version = "1.11.2"
2087+version = "1.11.0"
1923source = "registry+https://github.com/rust-lang/crates.io-index"
2088source = "registry+https://github.com/rust-lang/crates.io-index"
1924−checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
2089+checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
1925
2090
1926[[package]]
2091[[package]]
1927name = "smartstring"
2092name = "smartstring"
@@ -1942,12 +2107,12 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
1942
2107
1943[[package]]
2108[[package]]
1944name = "socket2"
2109name = "socket2"
1945−version = "0.5.5"
2110+version = "0.4.9"
1946source = "registry+https://github.com/rust-lang/crates.io-index"
2111source = "registry+https://github.com/rust-lang/crates.io-index"
1947−checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
2112+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
1948dependencies = [
2113dependencies = [
1949 "libc",
2114 "libc",
1950− "windows-sys 0.48.0",
2115+ "winapi",
1951]
2116]
1952
2117
1953[[package]]
2118[[package]]
@@ -1956,6 +2121,12 @@ version = "1.1.0"
1956source = "registry+https://github.com/rust-lang/crates.io-index"
2121source = "registry+https://github.com/rust-lang/crates.io-index"
1957checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
2122checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1958
2123
2124+[[package]]
2125+name = "str-buf"
2126+version = "1.0.6"
2127+source = "registry+https://github.com/rust-lang/crates.io-index"
2128+checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
2129+
1959[[package]]
2130[[package]]
1960name = "str_indices"
2131name = "str_indices"
1961version = "0.4.1"
2132version = "0.4.1"
@@ -1975,9 +2146,9 @@ dependencies = [
1975
2146
1976[[package]]
2147[[package]]
1977name = "syn"
2148name = "syn"
1978−version = "2.0.38"
2149+version = "2.0.25"
1979source = "registry+https://github.com/rust-lang/crates.io-index"
2150source = "registry+https://github.com/rust-lang/crates.io-index"
1980−checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
2151+checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2"
1981dependencies = [
2152dependencies = [
1982 "proc-macro2",
2153 "proc-macro2",
1983 "quote",
2154 "quote",
@@ -1986,14 +2157,14 @@ dependencies = [
1986
2157
1987[[package]]
2158[[package]]
1988name = "tempfile"
2159name = "tempfile"
1989−version = "3.8.1"
2160+version = "3.7.0"
1990source = "registry+https://github.com/rust-lang/crates.io-index"
2161source = "registry+https://github.com/rust-lang/crates.io-index"
1991−checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
2162+checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998"
1992dependencies = [
2163dependencies = [
1993 "cfg-if",
2164 "cfg-if",
1994 "fastrand",
2165 "fastrand",
1995− "redox_syscall 0.4.1",
2166+ "redox_syscall 0.3.5",
1996− "rustix",
2167+ "rustix 0.38.4",
1997 "windows-sys 0.48.0",
2168 "windows-sys 0.48.0",
1998]
2169]
1999
2170
@@ -2028,22 +2199,32 @@ dependencies = [
2028
2199
2029[[package]]
2200[[package]]
2030name = "thiserror"
2201name = "thiserror"
2031−version = "1.0.52"
2202+version = "1.0.44"
2032source = "registry+https://github.com/rust-lang/crates.io-index"
2203source = "registry+https://github.com/rust-lang/crates.io-index"
2033−checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
2204+checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
2034dependencies = [
2205dependencies = [
2035 "thiserror-impl",
2206 "thiserror-impl",
2036]
2207]
2037
2208
2038[[package]]
2209[[package]]
2039name = "thiserror-impl"
2210name = "thiserror-impl"
2040−version = "1.0.52"
2211+version = "1.0.44"
2041source = "registry+https://github.com/rust-lang/crates.io-index"
2212source = "registry+https://github.com/rust-lang/crates.io-index"
2042−checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
2213+checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
2043dependencies = [
2214dependencies = [
2044 "proc-macro2",
2215 "proc-macro2",
2045 "quote",
2216 "quote",
2046− "syn 2.0.38",
2217+ "syn 2.0.25",
2218+]
2219+
2220+[[package]]
2221+name = "thread_local"
2222+version = "1.1.7"
2223+source = "registry+https://github.com/rust-lang/crates.io-index"
2224+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
2225+dependencies = [
2226+ "cfg-if",
2227+ "once_cell",
2047]
2228]
2048
2229
2049[[package]]
2230[[package]]
@@ -2057,9 +2238,9 @@ dependencies = [
2057
2238
2058[[package]]
2239[[package]]
2059name = "time"
2240name = "time"
2060−version = "0.3.23"
2241+version = "0.3.20"
2061source = "registry+https://github.com/rust-lang/crates.io-index"
2242source = "registry+https://github.com/rust-lang/crates.io-index"
2062−checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
2243+checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
2063dependencies = [
2244dependencies = [
2064 "itoa",
2245 "itoa",
2065 "libc",
2246 "libc",
@@ -2071,15 +2252,15 @@ dependencies = [
2071
2252
2072[[package]]
2253[[package]]
2073name = "time-core"
2254name = "time-core"
2074−version = "0.1.1"
2255+version = "0.1.0"
2075source = "registry+https://github.com/rust-lang/crates.io-index"
2256source = "registry+https://github.com/rust-lang/crates.io-index"
2076−checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
2257+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
2077
2258
2078[[package]]
2259[[package]]
2079name = "time-macros"
2260name = "time-macros"
2080−version = "0.2.10"
2261+version = "0.2.8"
2081source = "registry+https://github.com/rust-lang/crates.io-index"
2262source = "registry+https://github.com/rust-lang/crates.io-index"
2082−checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
2263+checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
2083dependencies = [
2264dependencies = [
2084 "time-core",
2265 "time-core",
2085]
2266]
@@ -2101,10 +2282,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
2101
2282
2102[[package]]
2283[[package]]
2103name = "tokio"
2284name = "tokio"
2104−version = "1.35.1"
2285+version = "1.29.1"
2105source = "registry+https://github.com/rust-lang/crates.io-index"
2286source = "registry+https://github.com/rust-lang/crates.io-index"
2106−checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
2287+checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da"
2107dependencies = [
2288dependencies = [
2289+ "autocfg",
2108 "backtrace",
2290 "backtrace",
2109 "bytes",
2291 "bytes",
2110 "libc",
2292 "libc",
@@ -2120,13 +2302,13 @@ dependencies = [
2120
2302
2121[[package]]
2303[[package]]
2122name = "tokio-macros"
2304name = "tokio-macros"
2123−version = "2.2.0"
2305+version = "2.1.0"
2124source = "registry+https://github.com/rust-lang/crates.io-index"
2306source = "registry+https://github.com/rust-lang/crates.io-index"
2125−checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
2307+checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
2126dependencies = [
2308dependencies = [
2127 "proc-macro2",
2309 "proc-macro2",
2128 "quote",
2310 "quote",
2129− "syn 2.0.38",
2311+ "syn 2.0.25",
2130]
2312]
2131
2313
2132[[package]]
2314[[package]]
@@ -2171,13 +2353,13 @@ dependencies = [
2171 "serde",
2353 "serde",
2172 "serde_spanned",
2354 "serde_spanned",
2173 "toml_datetime",
2355 "toml_datetime",
2174− "winnow 0.4.6",
2356+ "winnow",
2175]
2357]
2176
2358
2177[[package]]
2359[[package]]
2178name = "tree-sitter"
2360name = "tree-sitter"
2179version = "0.20.10"
2361version = "0.20.10"
2180−source = "git+https://github.com/tree-sitter/tree-sitter?rev=ab09ae20d640711174b8da8a654f6b3dec93da1a#ab09ae20d640711174b8da8a654f6b3dec93da1a"
2362+source = "git+https://github.com/tree-sitter/tree-sitter?rev=7d4b0110a9e92161d247a051018ee42b0513e98d#7d4b0110a9e92161d247a051018ee42b0513e98d"
2181dependencies = [
2363dependencies = [
2182 "cc",
2364 "cc",
2183 "regex",
2365 "regex",
@@ -2218,9 +2400,13 @@ checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
2218
2400
2219[[package]]
2401[[package]]
2220name = "unicode-linebreak"
2402name = "unicode-linebreak"
2221−version = "0.1.5"
2403+version = "0.1.4"
2222source = "registry+https://github.com/rust-lang/crates.io-index"
2404source = "registry+https://github.com/rust-lang/crates.io-index"
2223−checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
2405+checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
2406+dependencies = [
2407+ "hashbrown 0.12.3",
2408+ "regex",
2409+]
2224
2410
2225[[package]]
2411[[package]]
2226name = "unicode-normalization"
2412name = "unicode-normalization"
@@ -2239,15 +2425,15 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
2239
2425
2240[[package]]
2426[[package]]
2241name = "unicode-width"
2427name = "unicode-width"
2242−version = "0.1.11"
2428+version = "0.1.10"
2243source = "registry+https://github.com/rust-lang/crates.io-index"
2429source = "registry+https://github.com/rust-lang/crates.io-index"
2244−checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
2430+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
2245
2431
2246[[package]]
2432[[package]]
2247name = "url"
2433name = "url"
2248−version = "2.5.0"
2434+version = "2.4.0"
2249source = "registry+https://github.com/rust-lang/crates.io-index"
2435source = "registry+https://github.com/rust-lang/crates.io-index"
2250−checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
2436+checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
2251dependencies = [
2437dependencies = [
2252 "form_urlencoded",
2438 "form_urlencoded",
2253 "idna",
2439 "idna",
@@ -2263,9 +2449,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
2263
2449
2264[[package]]
2450[[package]]
2265name = "walkdir"
2451name = "walkdir"
2266−version = "2.4.0"
2452+version = "2.3.3"
2267source = "registry+https://github.com/rust-lang/crates.io-index"
2453source = "registry+https://github.com/rust-lang/crates.io-index"
2268−checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
2454+checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
2269dependencies = [
2455dependencies = [
2270 "same-file",
2456 "same-file",
2271 "winapi-util",
2457 "winapi-util",
@@ -2333,15 +2519,13 @@ checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
2333
2519
2334[[package]]
2520[[package]]
2335name = "which"
2521name = "which"
2336−version = "5.0.0"
2522+version = "4.4.0"
2337source = "registry+https://github.com/rust-lang/crates.io-index"
2523source = "registry+https://github.com/rust-lang/crates.io-index"
2338−checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14"
2524+checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
2339dependencies = [
2525dependencies = [
2340 "either",
2526 "either",
2341− "home",
2527+ "libc",
2342 "once_cell",
2528 "once_cell",
2343− "rustix",
2344− "windows-sys 0.48.0",
2345]
2529]
2346
2530
2347[[package]]
2531[[package]]
@@ -2384,25 +2568,6 @@ dependencies = [
2384 "windows-targets 0.48.0",
2568 "windows-targets 0.48.0",
2385]
2569]
2386
2570
2387−[[package]]
2388−name = "windows"
2389−version = "0.52.0"
2390−source = "registry+https://github.com/rust-lang/crates.io-index"
2391−checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
2392−dependencies = [
2393− "windows-core",
2394− "windows-targets 0.52.0",
2395−]
2396−
2397−[[package]]
2398−name = "windows-core"
2399−version = "0.52.0"
2400−source = "registry+https://github.com/rust-lang/crates.io-index"
2401−checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
2402−dependencies = [
2403− "windows-targets 0.52.0",
2404−]
2405−
2406[[package]]
2571[[package]]
2407name = "windows-sys"
2572name = "windows-sys"
2408version = "0.45.0"
2573version = "0.45.0"
@@ -2421,15 +2586,6 @@ dependencies = [
2421 "windows-targets 0.48.0",
2586 "windows-targets 0.48.0",
2422]
2587]
2423
2588
2424−[[package]]
2425−name = "windows-sys"
2426−version = "0.52.0"
2427−source = "registry+https://github.com/rust-lang/crates.io-index"
2428−checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
2429−dependencies = [
2430− "windows-targets 0.52.0",
2431−]
2432−
2433[[package]]
2589[[package]]
2434name = "windows-targets"
2590name = "windows-targets"
2435version = "0.42.2"
2591version = "0.42.2"
@@ -2460,21 +2616,6 @@ dependencies = [
2460 "windows_x86_64_msvc 0.48.0",
2616 "windows_x86_64_msvc 0.48.0",
2461]
2617]
2462
2618
2463−[[package]]
2464−name = "windows-targets"
2465−version = "0.52.0"
2466−source = "registry+https://github.com/rust-lang/crates.io-index"
2467−checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
2468−dependencies = [
2469− "windows_aarch64_gnullvm 0.52.0",
2470− "windows_aarch64_msvc 0.52.0",
2471− "windows_i686_gnu 0.52.0",
2472− "windows_i686_msvc 0.52.0",
2473− "windows_x86_64_gnu 0.52.0",
2474− "windows_x86_64_gnullvm 0.52.0",
2475− "windows_x86_64_msvc 0.52.0",
2476−]
2477−
2478[[package]]
2619[[package]]
2479name = "windows_aarch64_gnullvm"
2620name = "windows_aarch64_gnullvm"
2480version = "0.42.2"
2621version = "0.42.2"
@@ -2487,12 +2628,6 @@ version = "0.48.0"
2487source = "registry+https://github.com/rust-lang/crates.io-index"
2628source = "registry+https://github.com/rust-lang/crates.io-index"
2488checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
2629checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
2489
2630
2490−[[package]]
2491−name = "windows_aarch64_gnullvm"
2492−version = "0.52.0"
2493−source = "registry+https://github.com/rust-lang/crates.io-index"
2494−checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
2495−
2496[[package]]
2631[[package]]
2497name = "windows_aarch64_msvc"
2632name = "windows_aarch64_msvc"
2498version = "0.42.2"
2633version = "0.42.2"
@@ -2505,12 +2640,6 @@ version = "0.48.0"
2505source = "registry+https://github.com/rust-lang/crates.io-index"
2640source = "registry+https://github.com/rust-lang/crates.io-index"
2506checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
2641checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
2507
2642
2508−[[package]]
2509−name = "windows_aarch64_msvc"
2510−version = "0.52.0"
2511−source = "registry+https://github.com/rust-lang/crates.io-index"
2512−checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
2513−
2514[[package]]
2643[[package]]
2515name = "windows_i686_gnu"
2644name = "windows_i686_gnu"
2516version = "0.42.2"
2645version = "0.42.2"
@@ -2523,12 +2652,6 @@ version = "0.48.0"
2523source = "registry+https://github.com/rust-lang/crates.io-index"
2652source = "registry+https://github.com/rust-lang/crates.io-index"
2524checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
2653checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
2525
2654
2526−[[package]]
2527−name = "windows_i686_gnu"
2528−version = "0.52.0"
2529−source = "registry+https://github.com/rust-lang/crates.io-index"
2530−checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
2531−
2532[[package]]
2655[[package]]
2533name = "windows_i686_msvc"
2656name = "windows_i686_msvc"
2534version = "0.42.2"
2657version = "0.42.2"
@@ -2541,12 +2664,6 @@ version = "0.48.0"
2541source = "registry+https://github.com/rust-lang/crates.io-index"
2664source = "registry+https://github.com/rust-lang/crates.io-index"
2542checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
2665checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
2543
2666
2544−[[package]]
2545−name = "windows_i686_msvc"
2546−version = "0.52.0"
2547−source = "registry+https://github.com/rust-lang/crates.io-index"
2548−checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
2549−
2550[[package]]
2667[[package]]
2551name = "windows_x86_64_gnu"
2668name = "windows_x86_64_gnu"
2552version = "0.42.2"
2669version = "0.42.2"
@@ -2559,12 +2676,6 @@ version = "0.48.0"
2559source = "registry+https://github.com/rust-lang/crates.io-index"
2676source = "registry+https://github.com/rust-lang/crates.io-index"
2560checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
2677checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
2561
2678
2562−[[package]]
2563−name = "windows_x86_64_gnu"
2564−version = "0.52.0"
2565−source = "registry+https://github.com/rust-lang/crates.io-index"
2566−checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
2567−
2568[[package]]
2679[[package]]
2569name = "windows_x86_64_gnullvm"
2680name = "windows_x86_64_gnullvm"
2570version = "0.42.2"
2681version = "0.42.2"
@@ -2577,12 +2688,6 @@ version = "0.48.0"
2577source = "registry+https://github.com/rust-lang/crates.io-index"
2688source = "registry+https://github.com/rust-lang/crates.io-index"
2578checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
2689checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
2579
2690
2580−[[package]]
2581−name = "windows_x86_64_gnullvm"
2582−version = "0.52.0"
2583−source = "registry+https://github.com/rust-lang/crates.io-index"
2584−checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
2585−
2586[[package]]
2691[[package]]
2587name = "windows_x86_64_msvc"
2692name = "windows_x86_64_msvc"
2588version = "0.42.2"
2693version = "0.42.2"
@@ -2595,12 +2700,6 @@ version = "0.48.0"
2595source = "registry+https://github.com/rust-lang/crates.io-index"
2700source = "registry+https://github.com/rust-lang/crates.io-index"
2596checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
2701checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
2597
2702
2598−[[package]]
2599−name = "windows_x86_64_msvc"
2600−version = "0.52.0"
2601−source = "registry+https://github.com/rust-lang/crates.io-index"
2602−checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
2603−
2604[[package]]
2703[[package]]
2605name = "winnow"
2704name = "winnow"
2606version = "0.4.6"
2705version = "0.4.6"
@@ -2610,18 +2709,9 @@ dependencies = [
2610 "memchr",
2709 "memchr",
2611]
2710]
2612
2711
2613−[[package]]
2614−name = "winnow"
2615−version = "0.5.28"
2616−source = "registry+https://github.com/rust-lang/crates.io-index"
2617−checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
2618−dependencies = [
2619− "memchr",
2620−]
2621−
2622[[package]]
2712[[package]]
2623name = "xtask"
2713name = "xtask"
2624−version = "23.10.0"
2714+version = "0.6.0"
2625dependencies = [
2715dependencies = [
2626 "helix-core",
2716 "helix-core",
2627 "helix-loader",
2717 "helix-loader",
@@ -2629,23 +2719,3 @@ dependencies = [
2629 "helix-view",
2719 "helix-view",
2630 "toml",
2720 "toml",
2631]
2721]
2632−
2633−[[package]]
2634−name = "zerocopy"
2635−version = "0.7.31"
2636−source = "registry+https://github.com/rust-lang/crates.io-index"
2637−checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d"
2638−dependencies = [
2639− "zerocopy-derive",
2640−]
2641−
2642−[[package]]
2643−name = "zerocopy-derive"
2644−version = "0.7.31"
2645−source = "registry+https://github.com/rust-lang/crates.io-index"
2646−checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a"
2647−dependencies = [
2648− "proc-macro2",
2649− "quote",
2650− "syn 2.0.38",
2651−]
Cargo.toml+1−14
@@ -1,12 +1,10 @@
1[workspace]
1[workspace]
2−resolver = "2"
3members = [
2members = [
4 "helix-core",
3 "helix-core",
5 "helix-view",
4 "helix-view",
6 "helix-term",
5 "helix-term",
7 "helix-tui",
6 "helix-tui",
8 "helix-lsp",
7 "helix-lsp",
9− "helix-event",
10 "helix-dap",
8 "helix-dap",
11 "helix-loader",
9 "helix-loader",
12 "helix-vcs",
10 "helix-vcs",
@@ -36,15 +34,4 @@ package.helix-tui.opt-level = 2
36package.helix-term.opt-level = 2
34package.helix-term.opt-level = 2
37
35
38[workspace.dependencies]
36[workspace.dependencies]
39−tree-sitter = { version = "0.20", git = "https://github.com/tree-sitter/tree-sitter", rev = "ab09ae20d640711174b8da8a654f6b3dec93da1a" }
37+tree-sitter = { version = "0.20", git = "https://github.com/tree-sitter/tree-sitter", rev = "7d4b0110a9e92161d247a051018ee42b0513e98d" }
40−nucleo = "0.2.0"
41−
42−[workspace.package]
43−version = "23.10.0"
44−edition = "2021"
45−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
46−categories = ["editor"]
47−repository = "https://github.com/helix-editor/helix"
48−homepage = "https://helix-editor.com"
49−license = "MPL-2.0"
50−rust-version = "1.70"
README.md+1−1
@@ -18,7 +18,7 @@
18
18
19![Screenshot](./screenshot.png)
19![Screenshot](./screenshot.png)
20
20
21−A [Kakoune](https://github.com/mawww/kakoune) / [Neovim](https://github.com/neovim/neovim) inspired editor, written in Rust.
21+A Kakoune / Neovim inspired editor, written in Rust.
22
22
23The editing model is very heavily based on Kakoune; during development I found
23The editing model is very heavily based on Kakoune; during development I found
24myself agreeing with most of Kakoune's design decisions.
24myself agreeing with most of Kakoune's design decisions.
VERSIONnew file+1
@@ -0,0 +1 @@
1+23.05
book/book.toml−3
@@ -11,6 +11,3 @@ preferred-dark-theme = "colibri"
11git-repository-url = "https://github.com/helix-editor/helix"
11git-repository-url = "https://github.com/helix-editor/helix"
12edit-url-template = "https://github.com/helix-editor/helix/edit/master/book/{path}"
12edit-url-template = "https://github.com/helix-editor/helix/edit/master/book/{path}"
13additional-css = ["custom.css"]
13additional-css = ["custom.css"]
14−
15−[output.html.search]
16−use-boolean-and = true
book/custom.css+1−1
@@ -164,7 +164,7 @@ code.hljs {
164 --searchresults-header-fg: #5f5f71;
164 --searchresults-header-fg: #5f5f71;
165 --searchresults-border-color: #5c5c68;
165 --searchresults-border-color: #5c5c68;
166 --searchresults-li-bg: #242430;
166 --searchresults-li-bg: #242430;
167− --search-mark-bg: #a2cff5;
167+ --search-mark-bg: #acff5;
168}
168}
169
169
170.colibri .content .header {
170.colibri .content .header {
book/src/configuration.md+10−37
@@ -51,7 +51,7 @@ Its settings will be merged with the configuration directory `config.toml` and t
51| `auto-completion` | Enable automatic pop up of auto-completion | `true` |
51| `auto-completion` | Enable automatic pop up of auto-completion | `true` |
52| `auto-format` | Enable automatic formatting on save | `true` |
52| `auto-format` | Enable automatic formatting on save | `true` |
53| `auto-save` | Enable automatic saving on the focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal | `false` |
53| `auto-save` | Enable automatic saving on the focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal | `false` |
54−| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant | `250` |
54+| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant | `400` |
55| `preview-completion-insert` | Whether to apply completion item instantly when selected | `true` |
55| `preview-completion-insert` | Whether to apply completion item instantly when selected | `true` |
56| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
56| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
57| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
57| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
@@ -64,9 +64,6 @@ Its settings will be merged with the configuration directory `config.toml` and t
64| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
64| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
65| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
65| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
66| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
66| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
67−| `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` |
68−| `popup-border` | Draw border around `popup`, `menu`, `all`, or `none` | `none` |
69−| `indent-heuristic` | How the indentation for a newly inserted line is computed: `simple` just copies the indentation level from the previous line, `tree-sitter` computes the indentation based on the syntax tree and `hybrid` combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being `hybrid` -> `tree-sitter` -> `simple`). | `hybrid`
70
67
71### `[editor.statusline]` Section
68### `[editor.statusline]` Section
72
69
@@ -92,7 +89,7 @@ The `[editor.statusline]` key takes the following sub-keys:
92
89
93| Key | Description | Default |
90| Key | Description | Default |
94| --- | --- | --- |
91| --- | --- | --- |
95−| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "read-only-indicator", "file-modification-indicator"]` |
92+| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "file-modification-indicator"]` |
96| `center` | A list of elements aligned to the middle of the statusline | `[]` |
93| `center` | A list of elements aligned to the middle of the statusline | `[]` |
97| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
94| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
98| `separator` | The character used to separate elements in the statusline | `"│"` |
95| `separator` | The character used to separate elements in the statusline | `"│"` |
@@ -111,7 +108,6 @@ The following statusline elements can be configured:
111| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
108| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
112| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
109| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
113| `file-line-ending` | The file line endings (CRLF or LF) |
110| `file-line-ending` | The file line endings (CRLF or LF) |
114−| `read-only-indicator` | An indicator that shows `[readonly]` when a file cannot be written |
115| `total-line-numbers` | The total line numbers of the opened file |
111| `total-line-numbers` | The total line numbers of the opened file |
116| `file-type` | The type of the opened file |
112| `file-type` | The type of the opened file |
117| `diagnostics` | The number of warnings and/or errors |
113| `diagnostics` | The number of warnings and/or errors |
@@ -168,31 +164,16 @@ All git related options are only enabled in a git repository.
168
164
169| Key | Description | Default |
165| Key | Description | Default |
170|--|--|---------|
166|--|--|---------|
171−|`hidden` | Enables ignoring hidden files | `true`
167+|`hidden` | Enables ignoring hidden files | true
172−|`follow-symlinks` | Follow symlinks instead of ignoring them | `true`
168+|`follow-symlinks` | Follow symlinks instead of ignoring them | true
173−|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | `true`
169+|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | true
174−|`parents` | Enables reading ignore files from parent directories | `true`
170+|`parents` | Enables reading ignore files from parent directories | true
175−|`ignore` | Enables reading `.ignore` files | `true`
171+|`ignore` | Enables reading `.ignore` files | true
176−|`git-ignore` | Enables reading `.gitignore` files | `true`
172+|`git-ignore` | Enables reading `.gitignore` files | true
177−|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludesfile` option | `true`
173+|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludefile` option | true
178−|`git-exclude` | Enables reading `.git/info/exclude` files | `true`
174+|`git-exclude` | Enables reading `.git/info/exclude` files | true
179|`max-depth` | Set with an integer value for maximum depth to recurse | Defaults to `None`.
175|`max-depth` | Set with an integer value for maximum depth to recurse | Defaults to `None`.
180
176
181−Ignore files can be placed locally as `.ignore` or put in your home directory as `~/.ignore`. They support the usual ignore and negative ignore (unignore) rules used in `.gitignore` files.
182−
183−Additionally, you can use Helix-specific ignore files by creating a local `.helix/ignore` file in the current workspace or a global `ignore` file located in your Helix config directory:
184−- Linux and Mac: `~/.config/helix/ignore`
185−- Windows: `%AppData%\helix\ignore`
186−
187−Example:
188−
189−```ini
190−# unignore in file picker and global search
191−!.github/
192−!.gitignore
193−!.gitattributes
194−```
195−
196### `[editor.auto-pairs]` Section
177### `[editor.auto-pairs]` Section
197
178
198Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a
179Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a
@@ -366,11 +347,3 @@ max-wrap = 25 # increase value to reduce forced mid-word wrapping
366max-indent-retain = 0
347max-indent-retain = 0
367wrap-indicator = "" # set wrap-indicator to "" to hide it
348wrap-indicator = "" # set wrap-indicator to "" to hide it
368```
349```
369−
370−### `[editor.smart-tab]` Section
371−
372−
373−| Key | Description | Default |
374−|------------|-------------|---------|
375−| `enable` | If set to true, then when the cursor is in a position with non-whitespace to its left, instead of inserting a tab, it will run `move_parent_node_end`. If there is only whitespace to the left, then it inserts a tab as normal. With the default bindings, to explicitly insert a tab character, press Shift-tab. | `true` |
376−| `supersede-menu` | Normally, when a menu is on screen, such as when auto complete is triggered, the tab key is bound to cycling through the items. This means when menus are on screen, one cannot use the tab key to trigger the `smart-tab` command. If this option is set to true, the `smart-tab` command always takes precedence, which means one cannot use the tab key to cycle through menu items. One of the other bindings must be used instead, such as arrow keys or `C-n`/`C-p`. | `false` |
book/src/generated/lang-support.md+15−33
@@ -1,9 +1,8 @@
1| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
1| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
2| --- | --- | --- | --- | --- |
2| --- | --- | --- | --- | --- |
3−| agda | ✓ | | | |
4| astro | ✓ | | | |
3| astro | ✓ | | | |
5| awk | ✓ | ✓ | | `awk-language-server` |
4| awk | ✓ | ✓ | | `awk-language-server` |
6−| bash | ✓ | ✓ | ✓ | `bash-language-server` |
5+| bash | ✓ | | ✓ | `bash-language-server` |
7| bass | ✓ | | | `bass` |
6| bass | ✓ | | | `bass` |
8| beancount | ✓ | | | |
7| beancount | ✓ | | | |
9| bibtex | ✓ | | | `texlab` |
8| bibtex | ✓ | | | `texlab` |
@@ -11,13 +10,13 @@
11| blueprint | ✓ | | | `blueprint-compiler` |
10| blueprint | ✓ | | | `blueprint-compiler` |
12| c | ✓ | ✓ | ✓ | `clangd` |
11| c | ✓ | ✓ | ✓ | `clangd` |
13| c-sharp | ✓ | ✓ | | `OmniSharp` |
12| c-sharp | ✓ | ✓ | | `OmniSharp` |
14−| cabal | | | | `haskell-language-server-wrapper` |
13+| cabal | | | | |
15| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
14| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
16| capnp | ✓ | | ✓ | |
15| capnp | ✓ | | ✓ | |
17| clojure | ✓ | | | `clojure-lsp` |
16| clojure | ✓ | | | `clojure-lsp` |
18| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
17| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
19| comment | ✓ | | | |
18| comment | ✓ | | | |
20−| common-lisp | ✓ | | ✓ | `cl-lsp` |
19+| common-lisp | ✓ | | | `cl-lsp` |
21| cpon | ✓ | | ✓ | |
20| cpon | ✓ | | ✓ | |
22| cpp | ✓ | ✓ | ✓ | `clangd` |
21| cpp | ✓ | ✓ | ✓ | `clangd` |
23| crystal | ✓ | ✓ | | `crystalline` |
22| crystal | ✓ | ✓ | | `crystalline` |
@@ -25,7 +24,6 @@
25| cue | ✓ | | | `cuelsp` |
24| cue | ✓ | | | `cuelsp` |
26| d | ✓ | ✓ | ✓ | `serve-d` |
25| d | ✓ | ✓ | ✓ | `serve-d` |
27| dart | ✓ | | ✓ | `dart` |
26| dart | ✓ | | ✓ | `dart` |
28−| dbml | ✓ | | | |
29| devicetree | ✓ | | | |
27| devicetree | ✓ | | | |
30| dhall | ✓ | ✓ | | `dhall-lsp-server` |
28| dhall | ✓ | ✓ | | `dhall-lsp-server` |
31| diff | ✓ | | | |
29| diff | ✓ | | | |
@@ -46,9 +44,7 @@
46| forth | ✓ | | | `forth-lsp` |
44| forth | ✓ | | | `forth-lsp` |
47| fortran | ✓ | | ✓ | `fortls` |
45| fortran | ✓ | | ✓ | `fortls` |
48| fsharp | ✓ | | | `fsautocomplete` |
46| fsharp | ✓ | | | `fsautocomplete` |
49−| gas | ✓ | ✓ | | |
50| gdscript | ✓ | ✓ | ✓ | |
47| gdscript | ✓ | ✓ | ✓ | |
51−| gemini | ✓ | | | |
52| git-attributes | ✓ | | | |
48| git-attributes | ✓ | | | |
53| git-commit | ✓ | ✓ | | |
49| git-commit | ✓ | ✓ | | |
54| git-config | ✓ | | | |
50| git-config | ✓ | | | |
@@ -56,13 +52,12 @@
56| git-rebase | ✓ | | | |
52| git-rebase | ✓ | | | |
57| gleam | ✓ | ✓ | | `gleam` |
53| gleam | ✓ | ✓ | | `gleam` |
58| glsl | ✓ | ✓ | ✓ | |
54| glsl | ✓ | ✓ | ✓ | |
59−| gn | ✓ | | | |
55+| go | ✓ | ✓ | ✓ | `gopls` |
60−| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
61| godot-resource | ✓ | | | |
56| godot-resource | ✓ | | | |
62| gomod | ✓ | | | `gopls` |
57| gomod | ✓ | | | `gopls` |
63| gotmpl | ✓ | | | `gopls` |
58| gotmpl | ✓ | | | `gopls` |
64| gowork | ✓ | | | `gopls` |
59| gowork | ✓ | | | `gopls` |
65−| graphql | ✓ | | | `graphql-lsp` |
60+| graphql | ✓ | | | |
66| hare | ✓ | | | |
61| hare | ✓ | | | |
67| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
62| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
68| haskell-persistent | ✓ | | | |
63| haskell-persistent | ✓ | | | |
@@ -74,18 +69,15 @@
74| idris | | | | `idris2-lsp` |
69| idris | | | | `idris2-lsp` |
75| iex | ✓ | | | |
70| iex | ✓ | | | |
76| ini | ✓ | | | |
71| ini | ✓ | | | |
77−| janet | ✓ | | | |
78−| java | ✓ | ✓ | ✓ | `jdtls` |
72+| java | ✓ | ✓ | | `jdtls` |
79| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
73| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
80−| jinja | ✓ | | | |
81| jsdoc | ✓ | | | |
74| jsdoc | ✓ | | | |
82| json | ✓ | | ✓ | `vscode-json-language-server` |
75| json | ✓ | | ✓ | `vscode-json-language-server` |
83−| json5 | ✓ | | | |
84| jsonnet | ✓ | | | `jsonnet-language-server` |
76| jsonnet | ✓ | | | `jsonnet-language-server` |
85| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
77| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
86| julia | ✓ | ✓ | ✓ | `julia` |
78| julia | ✓ | ✓ | ✓ | `julia` |
87| just | ✓ | ✓ | ✓ | |
79| just | ✓ | ✓ | ✓ | |
88−| kdl | ✓ | ✓ | ✓ | |
80+| kdl | ✓ | | | |
89| kotlin | ✓ | | | `kotlin-language-server` |
81| kotlin | ✓ | | | `kotlin-language-server` |
90| latex | ✓ | ✓ | | `texlab` |
82| latex | ✓ | ✓ | | `texlab` |
91| lean | ✓ | | | `lean` |
83| lean | ✓ | | | `lean` |
@@ -93,8 +85,6 @@
93| llvm | ✓ | ✓ | ✓ | |
85| llvm | ✓ | ✓ | ✓ | |
94| llvm-mir | ✓ | ✓ | ✓ | |
86| llvm-mir | ✓ | ✓ | ✓ | |
95| llvm-mir-yaml | ✓ | | ✓ | |
87| llvm-mir-yaml | ✓ | | ✓ | |
96−| log | ✓ | | | |
97−| lpf | ✓ | | | |
98| lua | ✓ | ✓ | ✓ | `lua-language-server` |
88| lua | ✓ | ✓ | ✓ | `lua-language-server` |
99| make | ✓ | | | |
89| make | ✓ | | | |
100| markdoc | ✓ | | | `markdoc-ls` |
90| markdoc | ✓ | | | `markdoc-ls` |
@@ -109,8 +99,7 @@
109| nickel | ✓ | | ✓ | `nls` |
99| nickel | ✓ | | ✓ | `nls` |
110| nim | ✓ | ✓ | ✓ | `nimlangserver` |
100| nim | ✓ | ✓ | ✓ | `nimlangserver` |
111| nix | ✓ | | | `nil` |
101| nix | ✓ | | | `nil` |
112−| nu | ✓ | | | `nu` |
102+| nu | ✓ | | | |
113−| nunjucks | ✓ | | | |
114| ocaml | ✓ | | ✓ | `ocamllsp` |
103| ocaml | ✓ | | ✓ | `ocamllsp` |
115| ocaml-interface | ✓ | | | `ocamllsp` |
104| ocaml-interface | ✓ | | | `ocamllsp` |
116| odin | ✓ | | ✓ | `ols` |
105| odin | ✓ | | ✓ | `ols` |
@@ -120,20 +109,19 @@
120| pascal | ✓ | ✓ | | `pasls` |
109| pascal | ✓ | ✓ | | `pasls` |
121| passwd | ✓ | | | |
110| passwd | ✓ | | | |
122| pem | ✓ | | | |
111| pem | ✓ | | | |
123−| perl | ✓ | ✓ | ✓ | `perlnavigator` |
112+| perl | ✓ | | | `perlnavigator` |
124| php | ✓ | ✓ | ✓ | `intelephense` |
113| php | ✓ | ✓ | ✓ | `intelephense` |
125| po | ✓ | ✓ | | |
114| po | ✓ | ✓ | | |
126−| pod | ✓ | | | |
127| ponylang | ✓ | ✓ | ✓ | |
115| ponylang | ✓ | ✓ | ✓ | |
128| prisma | ✓ | | | `prisma-language-server` |
116| prisma | ✓ | | | `prisma-language-server` |
129| prolog | | | | `swipl` |
117| prolog | | | | `swipl` |
130−| protobuf | ✓ | | ✓ | `bufls`, `pb` |
118+| protobuf | ✓ | | ✓ | |
131| prql | ✓ | | | |
119| prql | ✓ | | | |
132−| purescript | ✓ | ✓ | | `purescript-language-server` |
120+| purescript | ✓ | | | `purescript-language-server` |
133| python | ✓ | ✓ | ✓ | `pylsp` |
121| python | ✓ | ✓ | ✓ | `pylsp` |
134| qml | ✓ | | ✓ | `qmlls` |
122| qml | ✓ | | ✓ | `qmlls` |
135| r | ✓ | | | `R` |
123| r | ✓ | | | `R` |
136−| racket | ✓ | | ✓ | `racket` |
124+| racket | ✓ | | | `racket` |
137| regex | ✓ | | | |
125| regex | ✓ | | | |
138| rego | ✓ | | | `regols` |
126| rego | ✓ | | | `regols` |
139| rescript | ✓ | ✓ | | `rescript-language-server` |
127| rescript | ✓ | ✓ | | `rescript-language-server` |
@@ -145,34 +133,28 @@
145| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
133| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
146| sage | ✓ | ✓ | | |
134| sage | ✓ | ✓ | | |
147| scala | ✓ | | ✓ | `metals` |
135| scala | ✓ | | ✓ | `metals` |
148−| scheme | ✓ | | ✓ | |
136+| scheme | ✓ | | | |
149| scss | ✓ | | | `vscode-css-language-server` |
137| scss | ✓ | | | `vscode-css-language-server` |
150| slint | ✓ | | ✓ | `slint-lsp` |
138| slint | ✓ | | ✓ | `slint-lsp` |
151−| smali | ✓ | | ✓ | |
152| smithy | ✓ | | | `cs` |
139| smithy | ✓ | | | `cs` |
153| sml | ✓ | | | |
140| sml | ✓ | | | |
154| solidity | ✓ | | | `solc` |
141| solidity | ✓ | | | `solc` |
155| sql | ✓ | | | |
142| sql | ✓ | | | |
156| sshclientconfig | ✓ | | | |
143| sshclientconfig | ✓ | | | |
157| starlark | ✓ | ✓ | | |
144| starlark | ✓ | ✓ | | |
158−| strace | ✓ | | | |
159| svelte | ✓ | | ✓ | `svelteserver` |
145| svelte | ✓ | | ✓ | `svelteserver` |
160| sway | ✓ | ✓ | ✓ | `forc` |
146| sway | ✓ | ✓ | ✓ | `forc` |
161| swift | ✓ | | | `sourcekit-lsp` |
147| swift | ✓ | | | `sourcekit-lsp` |
162| t32 | ✓ | | | |
148| t32 | ✓ | | | |
163| tablegen | ✓ | ✓ | ✓ | |
149| tablegen | ✓ | ✓ | ✓ | |
164| task | ✓ | | | |
150| task | ✓ | | | |
165−| templ | ✓ | | | `templ` |
166| tfvars | ✓ | | ✓ | `terraform-ls` |
151| tfvars | ✓ | | ✓ | `terraform-ls` |
167−| todotxt | ✓ | | | |
168| toml | ✓ | | | `taplo` |
152| toml | ✓ | | | `taplo` |
169| tsq | ✓ | | | |
153| tsq | ✓ | | | |
170| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
154| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
171| twig | ✓ | | | |
155| twig | ✓ | | | |
172| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
156| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
173−| typst | ✓ | | | `typst-lsp` |
174| ungrammar | ✓ | | | |
157| ungrammar | ✓ | | | |
175−| unison | ✓ | | | |
176| uxntal | ✓ | | | |
158| uxntal | ✓ | | | |
177| v | ✓ | ✓ | ✓ | `v-analyzer` |
159| v | ✓ | ✓ | ✓ | `v-analyzer` |
178| vala | ✓ | | | `vala-language-server` |
160| vala | ✓ | | | `vala-language-server` |
@@ -185,9 +167,9 @@
185| webc | ✓ | | | |
167| webc | ✓ | | | |
186| wgsl | ✓ | | | `wgsl_analyzer` |
168| wgsl | ✓ | | | `wgsl_analyzer` |
187| wit | ✓ | | ✓ | |
169| wit | ✓ | | ✓ | |
188−| wren | ✓ | ✓ | ✓ | |
170+| wren | ✓ | ✓ | | |
189| xit | ✓ | | | |
171| xit | ✓ | | | |
190| xml | ✓ | | ✓ | |
172| xml | ✓ | | ✓ | |
191−| yaml | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
173+| yaml | ✓ | | ✓ | `yaml-language-server` |
192| yuck | ✓ | | | |
174| yuck | ✓ | | | |
193| zig | ✓ | ✓ | ✓ | `zls` |
175| zig | ✓ | ✓ | ✓ | `zls` |
book/src/generated/typable-cmd.md+1−4
@@ -17,7 +17,7 @@
17| `:write-buffer-close!`, `:wbc!` | Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt) |
17| `:write-buffer-close!`, `:wbc!` | Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt) |
18| `:new`, `:n` | Create a new scratch buffer. |
18| `:new`, `:n` | Create a new scratch buffer. |
19| `:format`, `:fmt` | Format the file using the LSP formatter. |
19| `:format`, `:fmt` | Format the file using the LSP formatter. |
20−| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.) |
20+| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
21| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
21| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
22| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
22| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
23| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
23| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
@@ -55,7 +55,6 @@
55| `:lsp-restart` | Restarts the language servers used by the current doc |
55| `:lsp-restart` | Restarts the language servers used by the current doc |
56| `:lsp-stop` | Stops the language servers that are used by the current doc |
56| `:lsp-stop` | Stops the language servers that are used by the current doc |
57| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
57| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
58−| `:tree-sitter-highlight-name` | Display name of tree-sitter highlight scope under the cursor. |
59| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
58| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
60| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
59| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
61| `:debug-eval` | Evaluate expression in current debug context. |
60| `:debug-eval` | Evaluate expression in current debug context. |
@@ -84,5 +83,3 @@
84| `:run-shell-command`, `:sh` | Run a shell command |
83| `:run-shell-command`, `:sh` | Run a shell command |
85| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
84| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
86| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
85| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
87−| `:redraw` | Clear and re-render the whole UI |
88−| `:move` | Move the current buffer and its corresponding file to a different path |
book/src/guides/adding_languages.md−2
@@ -36,7 +36,6 @@ below.
363. Refer to the
363. Refer to the
37 [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
37 [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
38 for more information on writing queries.
38 for more information on writing queries.
39−4. A list of highlight captures can be found [on the themes page](https://docs.helix-editor.com/themes.html#scopes).
40
39
41> 💡 In Helix, the first matching query takes precedence when evaluating
40> 💡 In Helix, the first matching query takes precedence when evaluating
42> queries, which is different from other editors such as Neovim where the last
41> queries, which is different from other editors such as Neovim where the last
@@ -52,4 +51,3 @@ below.
52 grammars.
51 grammars.
53- If a parser is causing a segfault, or you want to remove it, make sure to
52- If a parser is causing a segfault, or you want to remove it, make sure to
54 remove the compiled parser located at `runtime/grammars/<name>.so`.
53 remove the compiled parser located at `runtime/grammars/<name>.so`.
55−- If you are attempting to add queries and Helix is unable to locate them, ensure that the environment variable `HELIX_RUNTIME` is set to the location of the `runtime` folder you're developing in.
book/src/guides/indent.md+43−319
@@ -1,308 +1,76 @@
1# Adding indent queries
1# Adding indent queries
2
2
3−Helix uses tree-sitter to correctly indent new lines. This requires a tree-
3+Helix uses tree-sitter to correctly indent new lines. This requires
4−sitter grammar and an `indent.scm` query file placed in `runtime/queries/
4+a tree-sitter grammar and an `indent.scm` query file placed in
5−{language}/indents.scm`. The indentation for a line is calculated by traversing
5+`runtime/queries/{language}/indents.scm`. The indentation for a line
6−the syntax tree from the lowest node at the beginning of the new line (see
6+is calculated by traversing the syntax tree from the lowest node at the
7−[Indent queries](#indent-queries)). Each of these nodes contributes to the total
7+beginning of the new line. Each of these nodes contributes to the total
8−indent when it is captured by the query (in what way depends on the name of
8+indent when it is captured by the query (in what way depends on the name
9−the capture.
9+of the capture).
10
10
11Note that it matters where these added indents begin. For example,
11Note that it matters where these added indents begin. For example,
12multiple indent level increases that start on the same line only increase
12multiple indent level increases that start on the same line only increase
13−the total indent level by 1. See [Capture types](#capture-types).
13+the total indent level by 1.
14
14
15−By default, Helix uses the `hybrid` indentation heuristic. This means that
15+## Scopes
16−indent queries are not used to compute the expected absolute indentation of a
17−line but rather the expected difference in indentation between the new and an
18−already existing line. This difference is then added to the actual indentation
19−of the already existing line. Since this makes errors in the indent queries
20−harder to find, it is recommended to disable it when testing via
21−`:set indent-heuristic tree-sitter`. The rest of this guide assumes that
22−the `tree-sitter` heuristic is used.
23
16
24−## Indent queries
17+Added indents don't always apply to the whole node. For example, in most
25−
18+cases when a node should be indented, we actually only want everything
26−When Helix is inserting a new line through `o`, `O`, or `<ret>`, to determine
19+except for its first line to be indented. For this, there are several
27−the indent level for the new line, the query in `indents.scm` is run on the
20+scopes (more scopes may be added in the future if required):
28−document. The starting position of the query is the end of the line above where
29−a new line will be inserted.
30−
31−For `o`, the inserted line is the line below the cursor, so that starting
32−position of the query is the end of the current line.
33−
34−```rust
35−fn need_hero(some_hero: Hero, life: Life) -> {
36− matches!(some_hero, Hero { // ←─────────────────╮
37− strong: true,//←╮ ↑ ↑ │
38− fast: true, // │ │ ╰── query start │
39− sure: true, // │ ╰───── cursor ├─ traversal
40− soon: true, // ╰──────── new line inserted │ start node
41− }) && // │
42−// ↑ │
43−// ╰───────────────────────────────────────────────╯
44− some_hero > life
45−}
46−```
47−
48−For `O`, the newly inserted line is the *current* line, so the starting position
49−of the query is the end of the line above the cursor.
50−
51−```rust
52−fn need_hero(some_hero: Hero, life: Life) -> { // ←─╮
53− matches!(some_hero, Hero { // ←╮ ↑ │
54− strong: true,// ↑ ╭───╯ │ │
55− fast: true, // │ │ query start ─╯ │
56− sure: true, // ╰───┼ cursor ├─ traversal
57− soon: true, // ╰ new line inserted │ start node
58− }) && // │
59− some_hero > life // │
60−} // ←──────────────────────────────────────────────╯
61−```
62−
63−From this starting node, the syntax tree is traversed up until the root node.
64−Each indent capture is collected along the way, and then combined according to
65−their [capture types](#capture-types) and [scopes](#scopes) to a final indent
66−level for the line.
67−
68−### Capture types
69−
70−- `@indent` (default scope `tail`):
71− Increase the indent level by 1. Multiple occurrences in the same line *do not*
72− stack. If there is at least one `@indent` and one `@outdent` capture on the
73− same line, the indent level isn't changed at all.
74−- `@outdent` (default scope `all`):
75− Decrease the indent level by 1. The same rules as for `@indent` apply.
76−- `@indent.always` (default scope `tail`):
77− Increase the indent level by 1. Multiple occurrences on the same line *do*
78− stack. The final indent level is `@indent.always` – `@outdent.always`. If
79− an `@indent` and an `@indent.always` are on the same line, the `@indent` is
80− ignored.
81−- `@outdent.always` (default scope `all`):
82− Decrease the indent level by 1. The same rules as for `@indent.always` apply.
83−- `@align` (default scope `all`):
84− Align everything inside this node to some anchor. The anchor is given
85− by the start of the node captured by `@anchor` in the same pattern.
86− Every pattern with an `@align` should contain exactly one `@anchor`.
87− Indent (and outdent) for nodes below (in terms of their starting line)
88− the `@align` node is added to the indentation required for alignment.
89−- `@extend`:
90− Extend the range of this node to the end of the line and to lines that are
91− indented more than the line that this node starts on. This is useful for
92− languages like Python, where for the purpose of indentation some nodes (like
93− functions or classes) should also contain indented lines that follow them.
94−- `@extend.prevent-once`:
95− Prevents the first extension of an ancestor of this node. For example, in Python
96− a return expression always ends the block that it is in. Note that this only
97− stops the extension of the next `@extend` capture. If multiple ancestors are
98− captured, only the extension of the innermost one is prevented. All other
99− ancestors are unaffected (regardless of whether the innermost ancestor would
100− actually have been extended).
101−
102−#### `@indent` / `@outdent`
103−
104−Consider this example:
105−
106−```rust
107−fn shout(things: Vec<Thing>) {
108− // ↑
109− // ├───────────────────────╮ indent level
110− // @indent ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
111− // │
112− let it_all = |out| { things.filter(|thing| { // │ 1
113− // ↑ ↑ │
114− // ├───────────────────────┼─────┼┄┄┄┄┄┄┄┄┄┄┄┄┄┄
115− // @indent @indent │
116− // │ 2
117− thing.can_do_with(out) // │
118− })}; // ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
119− //↑↑↑ │ 1
120−} //╰┼┴──────────────────────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄
121−// 3x @outdent
122−```
123−
124−```scm
125−((block) @indent)
126−["}" ")"] @outdent
127−```
128−
129−Note how on the second line, we have two blocks begin on the same line. In this
130−case, since both captures occur on the same line, they are combined and only
131−result in a net increase of 1. Also note that the closing `}`s are part of the
132−`@indent` captures, but the 3 `@outdent`s also combine into 1 and result in that
133−line losing one indent level.
134−
135−#### `@extend` / `@extend.prevent-once`
136−
137−For an example of where `@extend` can be useful, consider Python, which is
138−whitespace-sensitive.
139−
140−```scm
141−]
142− (parenthesized_expression)
143− (function_definition)
144− (class_definition)
145−] @indent
146−
147−```
148−
149−```python
150−class Hero:
151− def __init__(self, strong, fast, sure, soon):# ←─╮
152− self.is_strong = strong # │
153− self.is_fast = fast # ╭─── query start │
154− self.is_sure = sure # │ ╭─ cursor │
155− self.is_soon = soon # │ │ │
156− # ↑ ↑ │ │ │
157− # │ ╰──────╯ │ │
158− # ╰─────────────────────╯ │
159− # ├─ traversal
160− def need_hero(self, life): # │ start node
161− return ( # │
162− self.is_strong # │
163− and self.is_fast # │
164− and self.is_sure # │
165− and self.is_soon # │
166− and self > life # │
167− ) # ←─────────────────────────────────────────╯
168−```
169−
170−Without braces to catch the scope of the function, the smallest descendant of
171−the cursor on a line feed ends up being the entire inside of the class. Because
172−of this, it will miss the entire function node and its indent capture, leading
173−to an indent level one too small.
174−
175−To address this case, `@extend` tells helix to "extend" the captured node's span
176−to the line feed and every consecutive line that has a greater indent level than
177−the line of the node.
178−
179−```scm
180−(parenthesized_expression) @indent
181−
182−]
183− (function_definition)
184− (class_definition)
185−] @indent @extend
186−
187−```
188−
189−```python
190−class Hero:
191− def __init__(self, strong, fast, sure, soon):# ←─╮
192− self.is_strong = strong # │
193− self.is_fast = fast # ╭─── query start ├─ traversal
194− self.is_sure = sure # │ ╭─ cursor │ start node
195− self.is_soon = soon # │ │ ←───────────────╯
196− # ↑ ↑ │ │
197− # │ ╰──────╯ │
198− # ╰─────────────────────╯
199− def need_hero(self, life):
200− return (
201− self.is_strong
202− and self.is_fast
203− and self.is_sure
204− and self.is_soon
205− and self > life
206− )
207−```
208−
209−Furthermore, there are some cases where extending to everything with a greater
210−indent level may not be desirable. Consider the `need_hero` function above. If
211−our cursor is on the last line of the returned expression.
212−
213−```python
214−class Hero:
215− def __init__(self, strong, fast, sure, soon):
216− self.is_strong = strong
217− self.is_fast = fast
218− self.is_sure = sure
219− self.is_soon = soon
220−
221− def need_hero(self, life):
222− return (
223− self.is_strong
224− and self.is_fast
225− and self.is_sure
226− and self.is_soon
227− and self > life
228− ) # ←─── cursor
229− #←────────── where cursor should go on new line
230−```
231
21
232−In Python, the are a few tokens that will always end a scope, such as a return
22+- `all`:
233−statement. Since the scope ends, so should the indent level. But because the
23+This scope applies to the whole captured node. This is only different from
234−function span is extended to every line with a greater indent level, a new line
24+`tail` when the captured node is the first node on its line.
235−would just continue on the same level. And an `@outdent` would not help us here
236−either, since it would cause everything in the parentheses to become outdented
237−as well.
238
25
239−To help, we need to signal an end to the extension. We can do this with
26+- `tail`:
240−`@extend.prevent-once`.
27+This scope applies to everything except for the first line of the
28+captured node.
241
29
30+Every capture type has a default scope which should do the right thing
31+in most situations. When a different scope is required, this can be
32+changed by using a `#set!` declaration anywhere in the pattern:
242```scm
33```scm
243−(parenthesized_expression) @indent
34+(assignment_expression
244−
35+ right: (_) @indent
245−]
36+ (#set! "scope" "all"))
246− (function_definition)
247− (class_definition)
248−] @indent @extend
249−
250−(return_statement) @extend.prevent-once
251```
37```
252
38
253−#### `@indent.always` / `@outdent.always`
39+## Capture types
254−
255−As mentioned before, normally if there is more than one `@indent` or `@outdent`
256−capture on the same line, they are combined.
257
40
258−Sometimes, there are cases when you may want to ensure that every indent capture
41+- `@indent` (default scope `tail`):
259−is additive, regardless of how many occur on the same line. Consider this
42+Increase the indent level by 1. Multiple occurrences in the same line
260−example in YAML.
43+don't stack. If there is at least one `@indent` and one `@outdent`
44+capture on the same line, the indent level isn't changed at all.
261
45
262−```yaml
46+- `@outdent` (default scope `all`):
263− - foo: bar
47+Decrease the indent level by 1. The same rules as for `@indent` apply.
264−# ↑ ↑
265−# │ ╰─────────────── start of map
266−# ╰───────────────── start of list element
267− baz: quux # ←─── cursor
268− # ←───────────── where the cursor should go on a new line
269− garply: waldo
270− - quux:
271− bar: baz
272− xyzzy: thud
273− fred: plugh
274−```
275
48
276−In YAML, you often have lists of maps. In these cases, the syntax is such that
49+- `@extend`:
277−the list element and the map both start on the same line. But we really do want
50+Extend the range of this node to the end of the line and to lines that
278−to start an indentation for each of these so that subsequent keys in the map
51+are indented more than the line that this node starts on. This is useful
279−hang over the list and align properly. This is where `@indent.always` helps.
52+for languages like Python, where for the purpose of indentation some nodes
53+(like functions or classes) should also contain indented lines that follow them.
280
54
281−```scm
55+- `@extend.prevent-once`:
282−((block_sequence_item) @item @indent.always @extend
56+Prevents the first extension of an ancestor of this node. For example, in Python
283− (#not-one-line? @item))
57+a return expression always ends the block that it is in. Note that this only stops the
58+extension of the next `@extend` capture. If multiple ancestors are captured,
59+only the extension of the innermost one is prevented. All other ancestors are unaffected
60+(regardless of whether the innermost ancestor would actually have been extended).
284
61
285−((block_mapping_pair
286− key: (_) @key
287− value: (_) @val
288− (#not-same-line? @key @val)
289− ) @indent.always @extend
290−)
291−```
292
62
293## Predicates
63## Predicates
294
64
295In some cases, an S-expression cannot express exactly what pattern should be matched.
65In some cases, an S-expression cannot express exactly what pattern should be matched.
296For that, tree-sitter allows for predicates to appear anywhere within a pattern,
66For that, tree-sitter allows for predicates to appear anywhere within a pattern,
297similar to how `#set!` declarations work:
67similar to how `#set!` declarations work:
298−
299```scm
68```scm
300(some_kind
69(some_kind
301 (child_kind) @indent
70 (child_kind) @indent
302 (#predicate? arg1 arg2 ...)
71 (#predicate? arg1 arg2 ...)
303)
72)
304```
73```
305−
306The number of arguments depends on the predicate that's used.
74The number of arguments depends on the predicate that's used.
307Each argument is either a capture (`@name`) or a string (`"some string"`).
75Each argument is either a capture (`@name`) or a string (`"some string"`).
308The following predicates are supported by tree-sitter:
76The following predicates are supported by tree-sitter:
@@ -323,47 +91,3 @@ argument (a string).
323
91
324- `#same-line?`/`#not-same-line?`:
92- `#same-line?`/`#not-same-line?`:
325The captures given by the 2 arguments must/must not start on the same line.
93The captures given by the 2 arguments must/must not start on the same line.
326−
327−- `#one-line?`/`#not-one-line?`:
328−The captures given by the fist argument must/must span a total of one line.
329−
330−### Scopes
331−
332−Added indents don't always apply to the whole node. For example, in most
333−cases when a node should be indented, we actually only want everything
334−except for its first line to be indented. For this, there are several
335−scopes (more scopes may be added in the future if required):
336−
337−- `tail`:
338−This scope applies to everything except for the first line of the
339−captured node.
340−- `all`:
341−This scope applies to the whole captured node. This is only different from
342−`tail` when the captured node is the first node on its line.
343−
344−For example, imagine we have the following function
345−
346−```rust
347−fn aha() { // ←─────────────────────────────────────╮
348− let take = "on me"; // ←──────────────╮ scope: │
349− let take = "me on"; // ├─ "tail" ├─ (block) @indent
350− let ill = be_gone_days(1 || 2); // │ │
351−} // ←───────────────────────────────────┴──────────┴─ "}" @outdent
352− // scope: "all"
353−```
354−
355−We can write the following query with the `#set!` declaration:
356−
357− ```scm
358− ((block) @indent
359− (#set! "scope" "tail"))
360− ("}" @outdent
361− (#set! "scope" "all"))
362− ```
363−
364−As we can see, the "tail" scope covers the node, except for the first line.
365−Everything up to and including the closing brace gets an indent level of 1.
366−Then, on the closing brace, we encounter an outdent with a scope of "all", which
367−means the first line is included, and the indent level is cancelled out on this
368−line. (Note these scopes are the defaults for `@indent` and `@outdent`—they are
369−written explicitly for demonstration.)
book/src/guides/injection.md+1−1
@@ -1,7 +1,7 @@
1# Adding Injection Queries
1# Adding Injection Queries
2
2
3Writing language injection queries allows one to highlight a specific node as a different language.
3Writing language injection queries allows one to highlight a specific node as a different language.
4−In addition to the [standard][upstream-docs] language injection options used by tree-sitter, there
4+In addition to the [standard](upstream-docs) language injection options used by tree-sitter, there
5are a few Helix specific extensions that allow for more control.
5are a few Helix specific extensions that allow for more control.
6
6
7And example of a simple query that would highlight all strings as bash in Nix:
7And example of a simple query that would highlight all strings as bash in Nix:
book/src/install.md+6−30
@@ -13,7 +13,6 @@
13 - [AppImage](#appimage)
13 - [AppImage](#appimage)
14- [macOS](#macos)
14- [macOS](#macos)
15 - [Homebrew Core](#homebrew-core)
15 - [Homebrew Core](#homebrew-core)
16− - [MacPorts](#macports)
17- [Windows](#windows)
16- [Windows](#windows)
18 - [Winget](#winget)
17 - [Winget](#winget)
19 - [Scoop](#scoop)
18 - [Scoop](#scoop)
@@ -65,7 +64,10 @@ sudo apt install helix
65
64
66### Fedora/RHEL
65### Fedora/RHEL
67
66
67+Enable the `COPR` repository for Helix:
68+
68```sh
69```sh
70+sudo dnf copr enable varlad/helix
69sudo dnf install helix
71sudo dnf install helix
70```
72```
71
73
@@ -131,12 +133,6 @@ chmod +x helix-*.AppImage # change permission for executable mode
131brew install helix
133brew install helix
132```
134```
133
135
134−### MacPorts
135−
136−```sh
137−port install helix
138−```
139−
140## Windows
136## Windows
141
137
142Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
138Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
@@ -228,7 +224,7 @@ Or, create a symbolic link:
228ln -Ts $PWD/runtime ~/.config/helix/runtime
224ln -Ts $PWD/runtime ~/.config/helix/runtime
229```
225```
230
226
231−If the above command fails to create a symbolic link because the file exists either move `~/.config/helix/runtime` to a new location or delete it, then run the symlink command above again.
227+If the above command fails to create a symbolic link because the file exists either move `~/.config/helix/runtime` to a new location or delete it, then run the symlink command above again.
232
228
233#### Windows
229#### Windows
234
230
@@ -261,32 +257,12 @@ following order:
2611. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
2571. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
262 developing and testing helix only).
258 developing and testing helix only).
2632. `runtime/` subdirectory of OS-dependent helix user config directory.
2592. `runtime/` subdirectory of OS-dependent helix user config directory.
264−3. `$HELIX_RUNTIME`
260+3. `$HELIX_RUNTIME`.
265−4. Distribution-specific fallback directory (set at compile time—not run time—
266− with the `HELIX_DEFAULT_RUNTIME` environment variable)
267−5. `runtime/` subdirectory of path to Helix executable.
261+4. `runtime/` subdirectory of path to Helix executable.
268
262
269This order also sets the priority for selecting which file will be used if multiple runtime
263This order also sets the priority for selecting which file will be used if multiple runtime
270directories have files with the same name.
264directories have files with the same name.
271
265
272−#### Note to packagers
273−
274−If you are making a package of Helix for end users, to provide a good out of
275−the box experience, you should set the `HELIX_DEFAULT_RUNTIME` environment
276−variable at build time (before invoking `cargo build`) to a directory which
277−will store the final runtime files after installation. For example, say you want
278−to package the runtime into `/usr/lib/helix/runtime`. The rough steps a build
279−script could follow are:
280−
281−1. `export HELIX_DEFAULT_RUNTIME=/usr/lib/helix/runtime`
282−1. `cargo build --profile opt --locked --path helix-term`
283−1. `cp -r runtime $BUILD_DIR/usr/lib/helix/`
284−1. `cp target/opt/hx $BUILD_DIR/usr/bin/hx`
285−
286−This way the resulting `hx` binary will always look for its runtime directory in
287−`/usr/lib/helix/runtime` if the user has no custom runtime in `~/.config/helix`
288−or `HELIX_RUNTIME`.
289−
290### Validating the installation
266### Validating the installation
291
267
292To make sure everything is set up as expected you should run the Helix health
268To make sure everything is set up as expected you should run the Helix health
book/src/keymap.md+15−16
@@ -25,7 +25,7 @@
25
25
26## Normal mode
26## Normal mode
27
27
28−Normal mode is the default mode when you launch helix. You can return to it from other modes by pressing the `Escape` key.
28+Normal mode is the default mode when you launch helix. Return to it from other modes by typing `Escape`.
29
29
30### Movement
30### Movement
31
31
@@ -291,7 +291,7 @@ This layer is a kludge of mappings, mostly pickers.
291| `w` | Enter [window mode](#window-mode) | N/A |
291| `w` | Enter [window mode](#window-mode) | N/A |
292| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
292| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
293| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
293| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
294−| `y` | Yank selections to clipboard | `yank_to_clipboard` |
294+| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` |
295| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
295| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
296| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
296| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
297| `/` | Global search in workspace folder | `global_search` |
297| `/` | Global search in workspace folder | `global_search` |
@@ -411,20 +411,19 @@ you to selectively add search terms to your selections.
411
411
412Keys to use within picker. Remapping currently not supported.
412Keys to use within picker. Remapping currently not supported.
413
413
414−| Key | Description |
414+| Key | Description |
415−| ----- | ------------- |
415+| ----- | ------------- |
416−| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
416+| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
417−| `Tab`, `Down`, `Ctrl-n` | Next entry |
417+| `Tab`, `Down`, `Ctrl-n` | Next entry |
418−| `PageUp`, `Ctrl-u` | Page up |
418+| `PageUp`, `Ctrl-u` | Page up |
419−| `PageDown`, `Ctrl-d` | Page down |
419+| `PageDown`, `Ctrl-d` | Page down |
420−| `Home` | Go to first entry |
420+| `Home` | Go to first entry |
421−| `End` | Go to last entry |
421+| `End` | Go to last entry |
422−| `Enter` | Open selected |
422+| `Enter` | Open selected |
423−| `Alt-Enter` | Open selected in the background without closing the picker |
424−| `Ctrl-s` | Open horizontally |
423+| `Ctrl-s` | Open horizontally |
425−| `Ctrl-v` | Open vertically |
424+| `Ctrl-v` | Open vertically |
426−| `Ctrl-t` | Toggle preview |
425+| `Ctrl-t` | Toggle preview |
427−| `Escape`, `Ctrl-c` | Close picker |
426+| `Escape`, `Ctrl-c` | Close picker |
428
427
429## Prompt
428## Prompt
430
429
book/src/languages.md+1−3
@@ -66,10 +66,8 @@ These configuration keys are available:
66| `language-servers` | The Language Servers used for this language. See below for more information in the section [Configuring Language Servers for a language](#configuring-language-servers-for-a-language) |
66| `language-servers` | The Language Servers used for this language. See below for more information in the section [Configuring Language Servers for a language](#configuring-language-servers-for-a-language) |
67| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
67| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
68| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout |
68| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout |
69−| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
70| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
69| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
71| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml`. Overwrites the setting of the same name in `config.toml` if set. |
70| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml`. Overwrites the setting of the same name in `config.toml` if set. |
72−| `persistent-diagnostic-sources` | An array of LSP diagnostic sources assumed unchanged when the language server resends the same set of diagnostics. Helix can track the position for these diagnostics internally instead. Useful for diagnostics that are recomputed on save.
73
71
74### File-type detection and the `file-types` key
72### File-type detection and the `file-types` key
75
73
@@ -129,7 +127,7 @@ These are the available options for a language server.
129| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
127| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
130
128
131A `format` sub-table within `config` can be used to pass extra formatting options to
129A `format` sub-table within `config` can be used to pass extra formatting options to
132−[Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting).
130+[Document Formatting Requests](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-17.md#document-formatting-request--leftwards_arrow_with_hook).
133For example, with typescript:
131For example, with typescript:
134
132
135```toml
133```toml
book/src/themes.md+1−9
@@ -70,7 +70,6 @@ over it and is merged into the default palette.
70
70
71| Color Name |
71| Color Name |
72| --- |
72| --- |
73−| `default` |
74| `black` |
73| `black` |
75| `red` |
74| `red` |
76| `green` |
75| `green` |
@@ -155,7 +154,6 @@ We use a similar set of scopes as
155
154
156- `type` - Types
155- `type` - Types
157 - `builtin` - Primitive types provided by the language (`int`, `usize`)
156 - `builtin` - Primitive types provided by the language (`int`, `usize`)
158− - `parameter` - Generic type parameters (`T`)
159 - `enum`
157 - `enum`
160 - `variant`
158 - `variant`
161- `constructor`
159- `constructor`
@@ -246,12 +244,9 @@ We use a similar set of scopes as
246
244
247- `diff` - version control changes
245- `diff` - version control changes
248 - `plus` - additions
246 - `plus` - additions
249− - `gutter` - gutter indicator
250 - `minus` - deletions
247 - `minus` - deletions
251− - `gutter` - gutter indicator
252 - `delta` - modifications
248 - `delta` - modifications
253 - `moved` - renamed or moved files/changes
249 - `moved` - renamed or moved files/changes
254− - `gutter` - gutter indicator
255
250
256#### Interface
251#### Interface
257
252
@@ -296,14 +291,11 @@ These scopes are used for theming the editor interface:
296| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
291| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
297| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
292| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
298| `ui.statusline.separator` | Separator character in statusline |
293| `ui.statusline.separator` | Separator character in statusline |
299−| `ui.bufferline` | Style for the buffer line |
300−| `ui.bufferline.active` | Style for the active buffer in buffer line |
301−| `ui.bufferline.background` | Style for bufferline background |
302| `ui.popup` | Documentation popups (e.g. Space + k) |
294| `ui.popup` | Documentation popups (e.g. Space + k) |
303| `ui.popup.info` | Prompt for multiple key options |
295| `ui.popup.info` | Prompt for multiple key options |
304| `ui.window` | Borderlines separating splits |
296| `ui.window` | Borderlines separating splits |
305| `ui.help` | Description box for commands |
297| `ui.help` | Description box for commands |
306−| `ui.text` | Default text style, command prompts, popup text, etc. |
298+| `ui.text` | Command prompts, popup text, etc. |
307| `ui.text.focus` | The currently selected line in the picker |
299| `ui.text.focus` | The currently selected line in the picker |
308| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
300| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
309| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
301| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
book/src/usage.md+5−21
@@ -37,35 +37,19 @@ If a register is selected before invoking a change or delete command, the select
37- `"hc` - Store the selection in register `h` and then change it (delete and enter insert mode).
37- `"hc` - Store the selection in register `h` and then change it (delete and enter insert mode).
38- `"md` - Store the selection in register `m` and delete it.
38- `"md` - Store the selection in register `m` and delete it.
39
39
40−### Default registers
40+### Special registers
41−
42−Commands that use registers, like yank (`y`), use a default register if none is specified.
43−These registers are used as defaults:
44
41
45| Register character | Contains |
42| Register character | Contains |
46| --- | --- |
43| --- | --- |
47| `/` | Last search |
44| `/` | Last search |
48| `:` | Last executed command |
45| `:` | Last executed command |
49| `"` | Last yanked text |
46| `"` | Last yanked text |
50−| `@` | Last recorded macro |
47+| `_` | Black hole |
51
48
52−### Special registers
49+The system clipboard is not directly supported by a special register. Instead, special commands and keybindings are provided. Refer to the
50+[key map](keymap.md#space-mode) for more details.
53
51
54−Some registers have special behavior when read from and written to.
52+The black hole register is a no-op register, meaning that no data will be read or written to it.
55−
56−| Register character | When read | When written |
57−| --- | --- | --- |
58−| `_` | No values are returned | All values are discarded |
59−| `#` | Selection indices (first selection is `1`, second is `2`, etc.) | This register is not writable |
60−| `.` | Contents of the current selections | This register is not writable |
61−| `%` | Name of the current file | This register is not writable |
62−| `+` | Reads from the system clipboard | Joins and yanks to the system clipboard |
63−| `*` | Reads from the primary clipboard | Joins and yanks to the primary clipboard |
64−
65−When yanking multiple selections to the clipboard registers, the selections
66−are joined with newlines. Pasting from these registers will paste multiple
67−selections if the clipboard was last yanked to by the Helix session. Otherwise
68−the clipboard contents are pasted as one selection.
69
53
70## Surround
54## Surround
71
55
book/theme/index.hbs+15−18
@@ -1,11 +1,11 @@
1<!DOCTYPE HTML>
1<!DOCTYPE HTML>
2−<html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
2+<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
3 <head>
3 <head>
4 <!-- Book generated using mdBook -->
4 <!-- Book generated using mdBook -->
5 <meta charset="UTF-8">
5 <meta charset="UTF-8">
6 <title>{{ title }}</title>
6 <title>{{ title }}</title>
7 {{#if is_print }}
7 {{#if is_print }}
8− <meta name="robots" content="noindex">
8+ <meta name="robots" content="noindex" />
9 {{/if}}
9 {{/if}}
10 {{#if base_url}}
10 {{#if base_url}}
11 <base href="{{ base_url }}">
11 <base href="{{ base_url }}">
@@ -17,7 +17,7 @@
17
17
18 <meta name="description" content="{{ description }}">
18 <meta name="description" content="{{ description }}">
19 <meta name="viewport" content="width=device-width, initial-scale=1">
19 <meta name="viewport" content="width=device-width, initial-scale=1">
20− <meta name="theme-color" content="#ffffff">
20+ <meta name="theme-color" content="#ffffff" />
21
21
22 {{#if favicon_svg}}
22 {{#if favicon_svg}}
23 <link rel="icon" href="{{ path_to_root }}favicon.svg">
23 <link rel="icon" href="{{ path_to_root }}favicon.svg">
@@ -34,6 +34,8 @@
34
34
35 <!-- Fonts -->
35 <!-- Fonts -->
36 <link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
36 <link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
37+ <link rel="preconnect" href="https://fonts.gstatic.com">
38+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;400;500;700&display=swap" rel="stylesheet">
37 {{#if copy_fonts}}
39 {{#if copy_fonts}}
38 <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
40 <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
39 {{/if}}
41 {{/if}}
@@ -53,7 +55,7 @@
53 <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
55 <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
54 {{/if}}
56 {{/if}}
55 </head>
57 </head>
56− <body class="sidebar-visible no-js">
58+ <body>
57 <div id="body-container">
59 <div id="body-container">
58 <!-- Provide site root to javascript -->
60 <!-- Provide site root to javascript -->
59 <script>
61 <script>
@@ -83,29 +85,24 @@
83 try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
85 try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
84 if (theme === null || theme === undefined) { theme = default_theme; }
86 if (theme === null || theme === undefined) { theme = default_theme; }
85 var html = document.querySelector('html');
87 var html = document.querySelector('html');
88+ html.classList.remove('no-js')
86 html.classList.remove('{{ default_theme }}')
89 html.classList.remove('{{ default_theme }}')
87 html.classList.add(theme);
90 html.classList.add(theme);
88− var body = document.querySelector('body');
89− body.classList.remove('no-js')
90− body.classList.add('js');
91+ html.classList.add('js');
91 </script>
92 </script>
92
93
93− <input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
94−
95 <!-- Hide / unhide sidebar before it is displayed -->
94 <!-- Hide / unhide sidebar before it is displayed -->
96 <script>
95 <script>
97− var body = document.querySelector('body');
96+ var html = document.querySelector('html');
98 var sidebar = null;
97 var sidebar = null;
99− var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
100 if (document.body.clientWidth >= 1080) {
98 if (document.body.clientWidth >= 1080) {
101 try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
99 try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
102 sidebar = sidebar || 'visible';
100 sidebar = sidebar || 'visible';
103 } else {
101 } else {
104 sidebar = 'hidden';
102 sidebar = 'hidden';
105 }
103 }
106− sidebar_toggle.checked = sidebar === 'visible';
107− body.classList.remove('sidebar-visible');
104+ html.classList.remove('sidebar-visible');
108− body.classList.add("sidebar-" + sidebar);
105+ html.classList.add("sidebar-" + sidebar);
109 </script>
106 </script>
110
107
111 <nav id="sidebar" class="sidebar" aria-label="Table of contents">
108 <nav id="sidebar" class="sidebar" aria-label="Table of contents">
@@ -144,9 +141,9 @@
144 <div id="menu-bar-hover-placeholder"></div>
141 <div id="menu-bar-hover-placeholder"></div>
145 <div id="menu-bar" class="menu-bar sticky">
142 <div id="menu-bar" class="menu-bar sticky">
146 <div class="left-buttons">
143 <div class="left-buttons">
147− <label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
144+ <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
148 <i class="fa fa-bars"></i>
145 <i class="fa fa-bars"></i>
149− </label>
146+ </button>
150 <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
147 <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
151 <i class="fa fa-paint-brush"></i>
148 <i class="fa fa-paint-brush"></i>
152 </button>
149 </button>
@@ -223,7 +220,7 @@
223 {{/previous}}
220 {{/previous}}
224
221
225 {{#next}}
222 {{#next}}
226− <a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
223+ <a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
227 <i class="fa fa-angle-right"></i>
224 <i class="fa fa-angle-right"></i>
228 </a>
225 </a>
229 {{/next}}
226 {{/next}}
@@ -241,7 +238,7 @@
241 {{/previous}}
238 {{/previous}}
242
239
243 {{#next}}
240 {{#next}}
244− <a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
241+ <a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
245 <i class="fa fa-angle-right"></i>
242 <i class="fa fa-angle-right"></i>
246 </a>
243 </a>
247 {{/next}}
244 {{/next}}
contrib/Helix.appdata.xml−15
@@ -5,7 +5,6 @@
5 <project_license>MPL-2.0</project_license>
5 <project_license>MPL-2.0</project_license>
6 <name>Helix</name>
6 <name>Helix</name>
7 <summary>A post-modern text editor</summary>
7 <summary>A post-modern text editor</summary>
8− <summary xml:lang="ar">مُحَرِّرُ نُصُوصٍ سَابِقٌ لِعَهدِه</summary>
9
8
10 <description>
9 <description>
11 <p>
10 <p>
@@ -18,17 +17,6 @@
18 <li>Smart, incremental syntax highlighting and code editing via tree-sitter</li>
17 <li>Smart, incremental syntax highlighting and code editing via tree-sitter</li>
19 </ul>
18 </ul>
20 </description>
19 </description>
21− <description xml:lang="ar">
22− <p>
23− مُحَرِّرُ نُصُوصٍ يَعمَلُ فِي الطَّرَفِيَّة، مُستَلهَمٌ مِن Kakoune وَ Neovim وَمَكتُوبٌ بِلُغَةِ رَست البَرمَجِيَّة.
24− </p>
25− <ul>
26− <li>تَحرِيرٌ وَضعِيٌّ شَبيهٌ بِـVim</li>
27− <li>تَحدِيدَاتٌ لِلنَّصِ مُتَعَدِّدَة</li>
28− <li>دَعْمٌ مُدمَجٌ لِخَوادِمِ اللُّغَات</li>
29− <li>تَحرِيرُ التَّعلِيمَاتِ البَّرمَجِيَّةِ مَعَ تَمييزٍ لِلتَّركِيبِ النَّحُويِّ بِواسِطَةِ tree-sitter</li>
30− </ul>
31− </description>
32
20
33 <launchable type="desktop-id">Helix.desktop</launchable>
21 <launchable type="desktop-id">Helix.desktop</launchable>
34
22
@@ -48,9 +36,6 @@
48 <content_rating type="oars-1.1" />
36 <content_rating type="oars-1.1" />
49
37
50 <releases>
38 <releases>
51− <release version="23.10" date="2023-10-24">
52− <url>https://helix-editor.com/news/release-23-10-highlights/</url>
53− </release>
54 <release version="23.05" date="2023-05-18">
39 <release version="23.05" date="2023-05-18">
55 <url>https://github.com/helix-editor/helix/releases/tag/23.05</url>
40 <url>https://github.com/helix-editor/helix/releases/tag/23.05</url>
56 </release>
41 </release>
contrib/Helix.desktop+1−3
@@ -1,7 +1,6 @@
1[Desktop Entry]
1[Desktop Entry]
2Name=Helix
2Name=Helix
3GenericName=Text Editor
3GenericName=Text Editor
4−GenericName[ar]=مُحَرِّرُ نُصُوص
5GenericName[de]=Texteditor
4GenericName[de]=Texteditor
6GenericName[fr]=Éditeur de texte
5GenericName[fr]=Éditeur de texte
7GenericName[ru]=Текстовый редактор
6GenericName[ru]=Текстовый редактор
@@ -10,7 +9,7 @@ GenericName[tr]=Metin Düzenleyici
10Comment=Edit text files
9Comment=Edit text files
11Comment[af]=Redigeer tekslêers
10Comment[af]=Redigeer tekslêers
12Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ
11Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ
13−Comment[ar]=مُحَرِّرُ مِلَفَّاتٍ نَصِّيَّة
12+Comment[ar]=حرّر ملفات نصية
14Comment[az]=Mətn fayllarını redaktə edin
13Comment[az]=Mətn fayllarını redaktə edin
15Comment[be]=Рэдагаваньне тэкставых файлаў
14Comment[be]=Рэдагаваньне тэкставых файлаў
16Comment[bg]=Редактиране на текстови файлове
15Comment[bg]=Редактиране на текстови файлове
@@ -80,7 +79,6 @@ Exec=hx %F
80Terminal=true
79Terminal=true
81Type=Application
80Type=Application
82Keywords=Text;editor;
81Keywords=Text;editor;
83−Keywords[ar]=نص;نصوص;محرر;
84Keywords[fr]=Texte;éditeur;
82Keywords[fr]=Texte;éditeur;
85Keywords[ru]=текст;текстовый редактор;
83Keywords[ru]=текст;текстовый редактор;
86Keywords[sr]=Текст;едитор;
84Keywords[sr]=Текст;едитор;
docs/CONTRIBUTING.md+3−10
@@ -29,15 +29,9 @@ files, run
29cargo xtask docgen
29cargo xtask docgen
30```
30```
31
31
32−inside the project. We use [xtask][xtask] as an ad-hoc task runner.
32+inside the project. We use [xtask][xtask] as an ad-hoc task runner and
33−
33+thus do not require any dependencies other than `cargo` (You don't have
34−To preview the book itself, install [mdbook][mdbook]. Then, run
34+to `cargo install` anything either).
35−
36−```shell
37−mdbook serve book
38−```
39−
40−and visit [http://localhost:3000](http://localhost:3000).
41
35
42# Testing
36# Testing
43
37
@@ -64,5 +58,4 @@ The current MSRV and future changes to the MSRV are listed in the [Firefox docum
64[architecture.md]: ./architecture.md
58[architecture.md]: ./architecture.md
65[docs]: https://docs.helix-editor.com/
59[docs]: https://docs.helix-editor.com/
66[xtask]: https://github.com/matklad/cargo-xtask
60[xtask]: https://github.com/matklad/cargo-xtask
67−[mdbook]: https://rust-lang.github.io/mdBook/guide/installation.html
68[helpers.rs]: ../helix-term/tests/test/helpers.rs
61[helpers.rs]: ../helix-term/tests/test/helpers.rs
docs/releases.md+5−12
@@ -1,23 +1,16 @@
1## Checklist
1## Checklist
2
2
3Helix releases are versioned in the Calendar Versioning scheme:
3Helix releases are versioned in the Calendar Versioning scheme:
4−`YY.0M(.MICRO)`, for example, `22.05` for May of 2022, or in a patch release,
4+`YY.0M(.MICRO)`, for example, `22.05` for May of 2022. In these instructions
5−`22.05.1`. In these instructions we'll use `<tag>` as a placeholder for the tag
5+we'll use `<tag>` as a placeholder for the tag being published.
6−being published.
7
6
8* Merge the changelog PR
7* Merge the changelog PR
9* Add new `<release>` entry in `contrib/Helix.appdata.xml` with release information according to the [AppStream spec](https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Releases.html)
8* Add new `<release>` entry in `contrib/Helix.appdata.xml` with release information according to the [AppStream spec](https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Releases.html)
10* Tag and push
9* Tag and push
11 * `git tag -s -m "<tag>" -a <tag> && git push`
10 * `git tag -s -m "<tag>" -a <tag> && git push`
12 * Make sure to switch to master and pull first
11 * Make sure to switch to master and pull first
13−* Edit the `Cargo.toml` file and change the date in the `version` field to the next planned release
12+* Edit the `VERSION` file and change the date to the next planned release
14− * Due to Cargo having a strict requirement on SemVer with 3 or more version
15− numbers, a `0` is required in the micro version; however, unless we are
16− publishing a patch release after a major release, the `.0` is dropped in
17− the user facing version.
18− * Releases are planned to happen every two months, so `22.05.0` would change to `22.07.0`
13+ * Releases are planned to happen every two months, so `22.05` would change to `22.07`
19− * If we are pushing a patch/bugfix release in the same month as the previous
20− release, bump the micro version, e.g. `22.07.0` to `22.07.1`
21* Wait for the Release CI to finish
14* Wait for the Release CI to finish
22 * It will automatically turn the git tag into a GitHub release when it uploads artifacts
15 * It will automatically turn the git tag into a GitHub release when it uploads artifacts
23* Edit the new release
16* Edit the new release
@@ -64,4 +57,4 @@ versions for convenience:
64> release. For the full log, check out the git log.
57> release. For the full log, check out the git log.
65
58
66Typically, small changes like dependencies or documentation updates, refactors,
59Typically, small changes like dependencies or documentation updates, refactors,
67−or meta changes like GitHub Actions work are left out.
60+or meta changes like GitHub Actions work are left out.
flake.lock+35−12
@@ -2,16 +2,23 @@
2 "nodes": {
2 "nodes": {
3 "crane": {
3 "crane": {
4 "inputs": {
4 "inputs": {
5+ "flake-compat": "flake-compat",
6+ "flake-utils": [
7+ "flake-utils"
8+ ],
5 "nixpkgs": [
9 "nixpkgs": [
6 "nixpkgs"
10 "nixpkgs"
11+ ],
12+ "rust-overlay": [
13+ "rust-overlay"
7 ]
14 ]
8 },
15 },
9 "locked": {
16 "locked": {
10− "lastModified": 1701025348,
17+ "lastModified": 1688772518,
11− "narHash": "sha256-42GHmYH+GF7VjwGSt+fVT1CQuNpGanJbNgVHTAZppUM=",
18+ "narHash": "sha256-ol7gZxwvgLnxNSZwFTDJJ49xVY5teaSvF7lzlo3YQfM=",
12 "owner": "ipetkov",
19 "owner": "ipetkov",
13 "repo": "crane",
20 "repo": "crane",
14− "rev": "42afaeb1a0325194a7cdb526332d2cb92fddd07b",
21+ "rev": "8b08e96c9af8c6e3a2b69af5a7fa168750fcf88e",
15 "type": "github"
22 "type": "github"
16 },
23 },
17 "original": {
24 "original": {
@@ -20,16 +27,32 @@
20 "type": "github"
27 "type": "github"
21 }
28 }
22 },
29 },
30+ "flake-compat": {
31+ "flake": false,
32+ "locked": {
33+ "lastModified": 1673956053,
34+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
35+ "owner": "edolstra",
36+ "repo": "flake-compat",
37+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
38+ "type": "github"
39+ },
40+ "original": {
41+ "owner": "edolstra",
42+ "repo": "flake-compat",
43+ "type": "github"
44+ }
45+ },
23 "flake-utils": {
46 "flake-utils": {
24 "inputs": {
47 "inputs": {
25 "systems": "systems"
48 "systems": "systems"
26 },
49 },
27 "locked": {
50 "locked": {
28− "lastModified": 1694529238,
51+ "lastModified": 1689068808,
29− "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
52+ "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
30 "owner": "numtide",
53 "owner": "numtide",
31 "repo": "flake-utils",
54 "repo": "flake-utils",
32− "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
55+ "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
33 "type": "github"
56 "type": "github"
34 },
57 },
35 "original": {
58 "original": {
@@ -40,11 +63,11 @@
40 },
63 },
41 "nixpkgs": {
64 "nixpkgs": {
42 "locked": {
65 "locked": {
43− "lastModified": 1700794826,
66+ "lastModified": 1690272529,
44− "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=",
67+ "narHash": "sha256-MakzcKXEdv/I4qJUtq/k/eG+rVmyOZLnYNC2w1mB59Y=",
45 "owner": "nixos",
68 "owner": "nixos",
46 "repo": "nixpkgs",
69 "repo": "nixpkgs",
47− "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8",
70+ "rev": "ef99fa5c5ed624460217c31ac4271cfb5cb2502c",
48 "type": "github"
71 "type": "github"
49 },
72 },
50 "original": {
73 "original": {
@@ -72,11 +95,11 @@
72 ]
95 ]
73 },
96 },
74 "locked": {
97 "locked": {
75− "lastModified": 1701137803,
98+ "lastModified": 1690424156,
76− "narHash": "sha256-0LcPAdql5IhQSUXJx3Zna0dYTgdIoYO7zUrsKgiBd04=",
99+ "narHash": "sha256-Bpml+L280tHTQpwpC5/BJbU4HSvEzMvW8IZ4gAXimhE=",
77 "owner": "oxalica",
100 "owner": "oxalica",
78 "repo": "rust-overlay",
101 "repo": "rust-overlay",
79− "rev": "9dd940c967502f844eacea52a61e9596268d4f70",
102+ "rev": "f335a0213504c7e6481c359dc1009be9cf34432c",
80 "type": "github"
103 "type": "github"
81 },
104 },
82 "original": {
105 "original": {
flake.nix+24−27
@@ -13,6 +13,8 @@
13 };
13 };
14 crane = {
14 crane = {
15 url = "github:ipetkov/crane";
15 url = "github:ipetkov/crane";
16+ inputs.rust-overlay.follows = "rust-overlay";
17+ inputs.flake-utils.follows = "flake-utils";
16 inputs.nixpkgs.follows = "nixpkgs";
18 inputs.nixpkgs.follows = "nixpkgs";
17 };
19 };
18 };
20 };
@@ -41,6 +43,7 @@
41 ".ignore"
43 ".ignore"
42 ".github"
44 ".github"
43 ".gitignore"
45 ".gitignore"
46+ "logo.svg"
44 "logo_dark.svg"
47 "logo_dark.svg"
45 "logo_light.svg"
48 "logo_light.svg"
46 "rust-toolchain.toml"
49 "rust-toolchain.toml"
@@ -48,6 +51,7 @@
48 "runtime"
51 "runtime"
49 "screenshot.png"
52 "screenshot.png"
50 "book"
53 "book"
54+ "contrib"
51 "docs"
55 "docs"
52 "README.md"
56 "README.md"
53 "CHANGELOG.md"
57 "CHANGELOG.md"
@@ -119,32 +123,25 @@
119 then ''$RUSTFLAGS -C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment''
123 then ''$RUSTFLAGS -C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment''
120 else "$RUSTFLAGS";
124 else "$RUSTFLAGS";
121 rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
125 rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
122− craneLibMSRV = (crane.mkLib pkgs).overrideToolchain rustToolchain;
126+ craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
123− craneLibStable = (crane.mkLib pkgs).overrideToolchain pkgs.pkgsBuildHost.rust-bin.stable.latest.default;
124− commonArgs = {
127+ commonArgs =
125− inherit stdenv;
128+ {
126− inherit (craneLibMSRV.crateNameFromCargoToml {cargoToml = ./helix-term/Cargo.toml;}) pname;
129+ inherit stdenv;
127− inherit (craneLibMSRV.crateNameFromCargoToml {cargoToml = ./Cargo.toml;}) version;
128− src = filteredSource;
130+ src = filteredSource;
129− # disable fetching and building of tree-sitter grammars in the helix-term build.rs
131+ # disable fetching and building of tree-sitter grammars in the helix-term build.rs
130− HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
132+ HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
131− buildInputs = [stdenv.cc.cc.lib];
133+ buildInputs = [stdenv.cc.cc.lib];
132− # disable tests
134+ # disable tests
133− doCheck = false;
135+ doCheck = false;
134− meta.mainProgram = "hx";
136+ meta.mainProgram = "hx";
135− };
137+ }
138+ // craneLib.crateNameFromCargoToml {cargoToml = ./helix-term/Cargo.toml;};
136− cargoArtifacts = craneLibMSRV.buildDepsOnly commonArgs;
139+ cargoArtifacts = craneLib.buildDepsOnly commonArgs;
137 in {
140 in {
138 packages = {
141 packages = {
139− helix-unwrapped = craneLibStable.buildPackage (commonArgs
142+ helix-unwrapped = craneLib.buildPackage (commonArgs
140 // {
143 // {
141− cargoArtifacts = craneLibStable.buildDepsOnly commonArgs;
144+ inherit cargoArtifacts;
142− postInstall = ''
143− mkdir -p $out/share/applications $out/share/icons/hicolor/scalable/apps $out/share/icons/hicolor/256x256/apps
144− cp contrib/Helix.desktop $out/share/applications
145− cp logo.svg $out/share/icons/hicolor/scalable/apps/helix.svg
146− cp contrib/helix.png $out/share/icons/hicolor/256x256/apps
147− '';
148 });
145 });
149 helix = makeOverridableHelix self.packages.${system}.helix-unwrapped {};
146 helix = makeOverridableHelix self.packages.${system}.helix-unwrapped {};
150 default = self.packages.${system}.helix;
147 default = self.packages.${system}.helix;
@@ -154,20 +151,20 @@
154 # Build the crate itself
151 # Build the crate itself
155 inherit (self.packages.${system}) helix;
152 inherit (self.packages.${system}) helix;
156
153
157− clippy = craneLibMSRV.cargoClippy (commonArgs
154+ clippy = craneLib.cargoClippy (commonArgs
158 // {
155 // {
159 inherit cargoArtifacts;
156 inherit cargoArtifacts;
160 cargoClippyExtraArgs = "--all-targets -- --deny warnings";
157 cargoClippyExtraArgs = "--all-targets -- --deny warnings";
161 });
158 });
162
159
163− fmt = craneLibMSRV.cargoFmt commonArgs;
160+ fmt = craneLib.cargoFmt commonArgs;
164
161
165− doc = craneLibMSRV.cargoDoc (commonArgs
162+ doc = craneLib.cargoDoc (commonArgs
166 // {
163 // {
167 inherit cargoArtifacts;
164 inherit cargoArtifacts;
168 });
165 });
169
166
170− test = craneLibMSRV.cargoTest (commonArgs
167+ test = craneLib.cargoTest (commonArgs
171 // {
168 // {
172 inherit cargoArtifacts;
169 inherit cargoArtifacts;
173 });
170 });
grammars.nix+21−24
@@ -5,7 +5,6 @@
5 runCommand,
5 runCommand,
6 yj,
6 yj,
7 includeGrammarIf ? _: true,
7 includeGrammarIf ? _: true,
8− grammarOverlays ? [],
9 ...
8 ...
10}: let
9}: let
11 # HACK: nix < 2.6 has a bug in the toml parser, so we convert to JSON
10 # HACK: nix < 2.6 has a bug in the toml parser, so we convert to JSON
@@ -49,22 +48,22 @@
49 then sourceGitHub
48 then sourceGitHub
50 else sourceGit;
49 else sourceGit;
51 in
50 in
52− stdenv.mkDerivation {
51+ stdenv.mkDerivation rec {
53 # see https://github.com/NixOS/nixpkgs/blob/fbdd1a7c0bc29af5325e0d7dd70e804a972eb465/pkgs/development/tools/parsing/tree-sitter/grammar.nix
52 # see https://github.com/NixOS/nixpkgs/blob/fbdd1a7c0bc29af5325e0d7dd70e804a972eb465/pkgs/development/tools/parsing/tree-sitter/grammar.nix
54
53
55 pname = "helix-tree-sitter-${grammar.name}";
54 pname = "helix-tree-sitter-${grammar.name}";
56 version = grammar.source.rev;
55 version = grammar.source.rev;
57
56
58− src = source;
57+ src =
59− sourceRoot = if builtins.hasAttr "subpath" grammar.source then
58+ if builtins.hasAttr "subpath" grammar.source
60− "source/${grammar.source.subpath}"
59+ then "${source}/${grammar.source.subpath}"
61− else
62− "source";
60+ else source;
63
61
62+ dontUnpack = true;
64 dontConfigure = true;
63 dontConfigure = true;
65
64
66 FLAGS = [
65 FLAGS = [
67− "-Isrc"
66+ "-I${src}/src"
68 "-g"
67 "-g"
69 "-O3"
68 "-O3"
70 "-fPIC"
69 "-fPIC"
@@ -77,13 +76,13 @@
77 buildPhase = ''
76 buildPhase = ''
78 runHook preBuild
77 runHook preBuild
79
78
80− if [[ -e src/scanner.cc ]]; then
79+ if [[ -e "$src/src/scanner.cc" ]]; then
81− $CXX -c src/scanner.cc -o scanner.o $FLAGS
80+ $CXX -c "$src/src/scanner.cc" -o scanner.o $FLAGS
82− elif [[ -e src/scanner.c ]]; then
81+ elif [[ -e "$src/src/scanner.c" ]]; then
83− $CC -c src/scanner.c -o scanner.o $FLAGS
82+ $CC -c "$src/src/scanner.c" -o scanner.o $FLAGS
84 fi
83 fi
85
84
86− $CC -c src/parser.c -o parser.o $FLAGS
85+ $CC -c "$src/src/parser.c" -o parser.o $FLAGS
87 $CXX -shared -o $NAME.so *.o
86 $CXX -shared -o $NAME.so *.o
88
87
89 ls -al
88 ls -al
@@ -106,17 +105,15 @@
106 '';
105 '';
107 };
106 };
108 grammarsToBuild = builtins.filter includeGrammarIf gitGrammars;
107 grammarsToBuild = builtins.filter includeGrammarIf gitGrammars;
108+ builtGrammars =
109− builtGrammars = builtins.map (grammar: {
109+ builtins.map (grammar: {
110− inherit (grammar) name;
110+ inherit (grammar) name;
111− value = buildGrammar grammar;
111+ artifact = buildGrammar grammar;
112+ })
112− }) grammarsToBuild;
113+ grammarsToBuild;
113− extensibleGrammars =
114− lib.makeExtensible (self: builtins.listToAttrs builtGrammars);
115− overlayedGrammars = lib.pipe extensibleGrammars
116− (builtins.map (overlay: grammar: grammar.extend overlay) grammarOverlays);
117− grammarLinks = lib.mapAttrsToList
114+ grammarLinks =
118− (name: artifact: "ln -s ${artifact}/${name}.so $out/${name}.so")
115+ builtins.map (grammar: "ln -s ${grammar.artifact}/${grammar.name}.so $out/${grammar.name}.so")
119− (lib.filterAttrs (n: v: lib.isDerivation v) overlayedGrammars);
116+ builtGrammars;
120in
117in
121 runCommand "consolidated-helix-grammars" {} ''
118 runCommand "consolidated-helix-grammars" {} ''
122 mkdir -p $out
119 mkdir -p $out
helix-core/Cargo.toml+14−18
@@ -1,24 +1,23 @@
1[package]
1[package]
2name = "helix-core"
2name = "helix-core"
3+version = "0.6.0"
4+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5+edition = "2021"
6+license = "MPL-2.0"
3description = "Helix editor core editing primitives"
7description = "Helix editor core editing primitives"
8+categories = ["editor"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
4include = ["src/**/*", "README.md"]
11include = ["src/**/*", "README.md"]
5−version.workspace = true
6−authors.workspace = true
7−edition.workspace = true
8−license.workspace = true
9−rust-version.workspace = true
10−categories.workspace = true
11−repository.workspace = true
12−homepage.workspace = true
13
12
14[features]
13[features]
15unicode-lines = ["ropey/unicode_lines"]
14unicode-lines = ["ropey/unicode_lines"]
16integration = []
15integration = []
17
16
18[dependencies]
17[dependencies]
19−helix-loader = { path = "../helix-loader" }
18+helix-loader = { version = "0.6", path = "../helix-loader" }
20
19
21−ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
20+ropey = { version = "1.6.0", default-features = false, features = ["simd"] }
22smallvec = "1.11"
21smallvec = "1.11"
23smartstring = "1.0.1"
22smartstring = "1.0.1"
24unicode-segmentation = "1.10"
23unicode-segmentation = "1.10"
@@ -27,12 +26,12 @@ unicode-general-category = "0.6"
27# slab = "0.4.2"
26# slab = "0.4.2"
28slotmap = "1.0"
27slotmap = "1.0"
29tree-sitter.workspace = true
28tree-sitter.workspace = true
30−once_cell = "1.19"
29+once_cell = "1.18"
31arc-swap = "1"
30arc-swap = "1"
32regex = "1"
31regex = "1"
33−bitflags = "2.4"
32+bitflags = "2.3"
34−ahash = "0.8.6"
33+ahash = "0.8.3"
35−hashbrown = { version = "0.14.3", features = ["raw"] }
34+hashbrown = { version = "0.14.0", features = ["raw"] }
36dunce = "1.0"
35dunce = "1.0"
37
36
38log = "0.4"
37log = "0.4"
@@ -49,9 +48,6 @@ chrono = { version = "0.4", default-features = false, features = ["alloc", "std"
49etcetera = "0.8"
48etcetera = "0.8"
50textwrap = "0.16.0"
49textwrap = "0.16.0"
51
50
52−nucleo.workspace = true
53−parking_lot = "0.12"
54−
55[dev-dependencies]
51[dev-dependencies]
56quickcheck = { version = "1", default-features = false }
52quickcheck = { version = "1", default-features = false }
57−indoc = "2.0.4"
53+indoc = "2.0.3"
helix-core/src/diagnostic.rs−4
@@ -39,10 +39,6 @@ pub enum DiagnosticTag {
39#[derive(Debug, Clone)]
39#[derive(Debug, Clone)]
40pub struct Diagnostic {
40pub struct Diagnostic {
41 pub range: Range,
41 pub range: Range,
42− // whether this diagnostic ends at the end of(or inside) a word
43− pub ends_at_word: bool,
44− pub starts_at_word: bool,
45− pub zero_width: bool,
46 pub line: usize,
42 pub line: usize,
47 pub message: String,
43 pub message: String,
48 pub severity: Option<Severity>,
44 pub severity: Option<Severity>,
helix-core/src/fuzzy.rsdeleted−43
@@ -1,43 +0,0 @@
1−use std::ops::DerefMut;
2−
3−use nucleo::pattern::{Atom, AtomKind, CaseMatching};
4−use nucleo::Config;
5−use parking_lot::Mutex;
6−
7−pub struct LazyMutex<T> {
8− inner: Mutex<Option<T>>,
9− init: fn() -> T,
10−}
11−
12−impl<T> LazyMutex<T> {
13− pub const fn new(init: fn() -> T) -> Self {
14− Self {
15− inner: Mutex::new(None),
16− init,
17− }
18− }
19−
20− pub fn lock(&self) -> impl DerefMut<Target = T> + '_ {
21− parking_lot::MutexGuard::map(self.inner.lock(), |val| val.get_or_insert_with(self.init))
22− }
23−}
24−
25−pub static MATCHER: LazyMutex<nucleo::Matcher> = LazyMutex::new(nucleo::Matcher::default);
26−
27−/// convenience function to easily fuzzy match
28−/// on a (relatively small list of inputs). This is not recommended for building a full tui
29−/// application that can match large numbers of matches as all matching is done on the current
30−/// thread, effectively blocking the UI
31−pub fn fuzzy_match<T: AsRef<str>>(
32− pattern: &str,
33− items: impl IntoIterator<Item = T>,
34− path: bool,
35−) -> Vec<(T, u16)> {
36− let mut matcher = MATCHER.lock();
37− matcher.config = Config::DEFAULT;
38− if path {
39− matcher.config.set_match_paths();
40− }
41− let pattern = Atom::new(pattern, CaseMatching::Smart, AtomKind::Fuzzy, false);
42− pattern.match_list(items, &mut matcher)
43−}
helix-core/src/graphemes.rs+1−1
@@ -481,7 +481,7 @@ impl<'a> From<String> for GraphemeStr<'a> {
481 let ptr = Box::into_raw(g.into_bytes().into_boxed_slice()) as *mut u8;
481 let ptr = Box::into_raw(g.into_bytes().into_boxed_slice()) as *mut u8;
482 GraphemeStr {
482 GraphemeStr {
483 ptr: unsafe { NonNull::new_unchecked(ptr) },
483 ptr: unsafe { NonNull::new_unchecked(ptr) },
484− len: (i32::try_from(len).unwrap() as u32) | Self::MASK_OWNED,
484+ len: i32::try_from(len).unwrap() as u32,
485 phantom: PhantomData,
485 phantom: PhantomData,
486 }
486 }
487 }
487 }
helix-core/src/indent.rs+127−586
@@ -1,14 +1,13 @@
1−use std::{borrow::Cow, collections::HashMap};
1+use std::collections::HashMap;
2
2
3use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
3use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
4
4
5use crate::{
5use crate::{
6 chars::{char_is_line_ending, char_is_whitespace},
6 chars::{char_is_line_ending, char_is_whitespace},
7− find_first_non_whitespace_char,
8− graphemes::{grapheme_width, tab_width_at},
7+ graphemes::tab_width_at,
9− syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
8+ syntax::{LanguageConfiguration, RopeProvider, Syntax},
10 tree_sitter::Node,
9 tree_sitter::Node,
11− Position, Rope, RopeGraphemes, RopeSlice,
10+ Rope, RopeSlice,
12};
11};
13
12
14/// Enum representing indentation style.
13/// Enum representing indentation style.
@@ -22,7 +21,7 @@ pub enum IndentStyle {
22
21
23// 16 spaces
22// 16 spaces
24const INDENTS: &str = " ";
23const INDENTS: &str = " ";
25−pub const MAX_INDENT: u8 = 16;
24+const MAX_INDENT: u8 = 16;
26
25
27impl IndentStyle {
26impl IndentStyle {
28 /// Creates an `IndentStyle` from an indentation string.
27 /// Creates an `IndentStyle` from an indentation string.
@@ -197,56 +196,6 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: us
197 len / indent_width
196 len / indent_width
198}
197}
199
198
200−/// Create a string of tabs & spaces that has the same visual width as the given RopeSlice (independent of the tab width).
201−fn whitespace_with_same_width(text: RopeSlice) -> String {
202− let mut s = String::new();
203− for grapheme in RopeGraphemes::new(text) {
204− if grapheme == "\t" {
205− s.push('\t');
206− } else {
207− s.extend(std::iter::repeat(' ').take(grapheme_width(&Cow::from(grapheme))));
208− }
209− }
210− s
211−}
212−
213−fn add_indent_level(
214− mut base_indent: String,
215− added_indent_level: isize,
216− indent_style: &IndentStyle,
217− tab_width: usize,
218−) -> String {
219− if added_indent_level >= 0 {
220− // Adding a non-negative indent is easy, we can simply append the indent string
221− base_indent.push_str(&indent_style.as_str().repeat(added_indent_level as usize));
222− base_indent
223− } else {
224− // In this case, we want to return a prefix of `base_indent`.
225− // Since the width of a tab depends on its offset, we cannot simply iterate over
226− // the chars of `base_indent` in reverse until we have the desired indent reduction,
227− // instead we iterate over them twice in forward direction.
228− let base_indent_rope = RopeSlice::from(base_indent.as_str());
229− #[allow(deprecated)]
230− let base_indent_width =
231− crate::visual_coords_at_pos(base_indent_rope, base_indent_rope.len_chars(), tab_width)
232− .col;
233− let target_indent_width = base_indent_width
234− .saturating_sub((-added_indent_level) as usize * indent_style.indent_width(tab_width));
235− #[allow(deprecated)]
236− let char_end_idx = crate::pos_at_visual_coords(
237− base_indent_rope,
238− Position {
239− row: 0,
240− col: target_indent_width,
241− },
242− tab_width,
243− );
244− let byte_end_idx = base_indent_rope.char_to_byte(char_end_idx);
245− base_indent.truncate(byte_end_idx);
246− base_indent
247− }
248−}
249−
250/// Computes for node and all ancestors whether they are the first node on their line.
199/// Computes for node and all ancestors whether they are the first node on their line.
251/// The first entry in the return value represents the root node, the last one the node itself
200/// The first entry in the return value represents the root node, the last one the node itself
252fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bool> {
201fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bool> {
@@ -288,140 +237,68 @@ fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bo
288/// This is usually constructed in one of 2 ways:
237/// This is usually constructed in one of 2 ways:
289/// - Successively add indent captures to get the (added) indent from a single line
238/// - Successively add indent captures to get the (added) indent from a single line
290/// - Successively add the indent results for each line
239/// - Successively add the indent results for each line
291−/// The string that this indentation defines starts with the string contained in the align field (unless it is None), followed by:
292−/// - max(0, indent - outdent) tabs, if tabs are used for indentation
293−/// - max(0, indent - outdent)*indent_width spaces, if spaces are used for indentation
294−#[derive(Default, Debug, PartialEq, Eq, Clone)]
240+#[derive(Default)]
295−pub struct Indentation<'a> {
241+pub struct Indentation {
242+ /// The total indent (the number of indent levels) is defined as max(0, indent-outdent).
243+ /// The string that this results in depends on the indent style (spaces or tabs, etc.)
296 indent: usize,
244 indent: usize,
297− indent_always: usize,
298 outdent: usize,
245 outdent: usize,
299− outdent_always: usize,
300− /// The alignment, as a string containing only tabs & spaces. Storing this as a string instead of e.g.
301− /// the (visual) width ensures that the alignment is preserved even if the tab width changes.
302− align: Option<RopeSlice<'a>>,
303}
246}
304−
305−impl<'a> Indentation<'a> {
247+impl Indentation {
306 /// Add some other [Indentation] to this.
248 /// Add some other [Indentation] to this.
307− /// The added indent should be the total added indent from one line.
249+ /// The added indent should be the total added indent from one line
308− /// Indent should always be added starting from the bottom (or equivalently, the innermost tree-sitter node).
309− fn add_line(&mut self, added: Indentation<'a>) {
250+ fn add_line(&mut self, added: &Indentation) {
310− // Align overrides the indent from outer scopes.
251+ if added.indent > 0 && added.outdent == 0 {
311− if self.align.is_some() {
252+ self.indent += 1;
312− return;
253+ } else if added.outdent > 0 && added.indent == 0 {
313− }
254+ self.outdent += 1;
314− if added.align.is_some() {
315− self.align = added.align;
316− return;
317 }
255 }
318− self.indent += added.indent;
319− self.indent_always += added.indent_always;
320− self.outdent += added.outdent;
321− self.outdent_always += added.outdent_always;
322 }
256 }
323−
324 /// Add an indent capture to this indent.
257 /// Add an indent capture to this indent.
325− /// Only captures that apply to the same line should be added together in this way (otherwise use `add_line`)
258+ /// All the captures that are added in this way should be on the same line.
326− /// and the captures should be added starting from the innermost tree-sitter node (currently this only matters
327− /// if multiple `@align` patterns occur on the same line).
328− fn add_capture(&mut self, added: IndentCaptureType<'a>) {
259+ fn add_capture(&mut self, added: IndentCaptureType) {
329 match added {
260 match added {
330 IndentCaptureType::Indent => {
261 IndentCaptureType::Indent => {
331− if self.indent_always == 0 {
332− self.indent = 1;
262+ self.indent = 1;
333− }
334− }
335− IndentCaptureType::IndentAlways => {
336− // any time we encounter an `indent.always` on the same line, we
337− // want to cancel out all regular indents
338− self.indent_always += 1;
339− self.indent = 0;
340 }
263 }
341 IndentCaptureType::Outdent => {
264 IndentCaptureType::Outdent => {
342− if self.outdent_always == 0 {
343− self.outdent = 1;
265+ self.outdent = 1;
344− }
345− }
346− IndentCaptureType::OutdentAlways => {
347− self.outdent_always += 1;
348− self.outdent = 0;
349− }
350− IndentCaptureType::Align(align) => {
351− if self.align.is_none() {
352− self.align = Some(align);
353− }
354 }
266 }
355 }
267 }
356 }
268 }
357− fn net_indent(&self) -> isize {
358− (self.indent + self.indent_always) as isize
359− - ((self.outdent + self.outdent_always) as isize)
360− }
361− /// Convert `self` into a string, taking into account the computed and actual indentation of some other line.
362− fn relative_indent(
363− &self,
364− other_computed_indent: &Self,
365− other_leading_whitespace: RopeSlice,
366− indent_style: &IndentStyle,
269+ fn as_string(&self, indent_style: &IndentStyle) -> String {
367− tab_width: usize,
270+ let indent_level = if self.indent >= self.outdent {
368− ) -> Option<String> {
271+ self.indent - self.outdent
369− if self.align == other_computed_indent.align {
370− // If self and baseline are either not aligned to anything or both aligned the same way,
371− // we can simply take `other_leading_whitespace` and add some indent / outdent to it (in the second
372− // case, the alignment should already be accounted for in `other_leading_whitespace`).
373− let indent_diff = self.net_indent() - other_computed_indent.net_indent();
374− Some(add_indent_level(
375− String::from(other_leading_whitespace),
376− indent_diff,
377− indent_style,
378− tab_width,
379− ))
380 } else {
272 } else {
381− // If the alignment of both lines is different, we cannot compare their indentation in any meaningful way
273+ log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
382− None
274+ 0
383− }
275+ };
384− }
276+ indent_style.as_str().repeat(indent_level)
385− pub fn to_string(&self, indent_style: &IndentStyle, tab_width: usize) -> String {
386− add_indent_level(
387− self.align
388− .map_or_else(String::new, whitespace_with_same_width),
389− self.net_indent(),
390− indent_style,
391− tab_width,
392− )
393 }
277 }
394}
278}
395
279
396/// An indent definition which corresponds to a capture from the indent query
280/// An indent definition which corresponds to a capture from the indent query
397−#[derive(Debug)]
398−struct IndentCapture<'a> {
281+struct IndentCapture {
399− capture_type: IndentCaptureType<'a>,
282+ capture_type: IndentCaptureType,
400 scope: IndentScope,
283 scope: IndentScope,
401}
284}
402−#[derive(Debug, Clone, PartialEq)]
285+#[derive(Clone, Copy)]
403−enum IndentCaptureType<'a> {
286+enum IndentCaptureType {
404 Indent,
287 Indent,
405− IndentAlways,
406 Outdent,
288 Outdent,
407− OutdentAlways,
408− /// Alignment given as a string of whitespace
409− Align(RopeSlice<'a>),
410}
289}
411−
412−impl<'a> IndentCaptureType<'a> {
290+impl IndentCaptureType {
413 fn default_scope(&self) -> IndentScope {
291 fn default_scope(&self) -> IndentScope {
414 match self {
292 match self {
415− IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
293+ IndentCaptureType::Indent => IndentScope::Tail,
416− IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All,
294+ IndentCaptureType::Outdent => IndentScope::All,
417− IndentCaptureType::Align(_) => IndentScope::All,
418 }
295 }
419 }
296 }
420}
297}
421/// This defines which part of a node an [IndentCapture] applies to.
298/// This defines which part of a node an [IndentCapture] applies to.
422/// Each [IndentCaptureType] has a default scope, but the scope can be changed
299/// Each [IndentCaptureType] has a default scope, but the scope can be changed
423/// with `#set!` property declarations.
300/// with `#set!` property declarations.
424−#[derive(Debug, Clone, Copy)]
301+#[derive(Clone, Copy)]
425enum IndentScope {
302enum IndentScope {
426 /// The indent applies to the whole node
303 /// The indent applies to the whole node
427 All,
304 All,
@@ -431,7 +308,6 @@ enum IndentScope {
431
308
432/// A capture from the indent query which does not define an indent but extends
309/// A capture from the indent query which does not define an indent but extends
433/// the range of a node. This is used before the indent is calculated.
310/// the range of a node. This is used before the indent is calculated.
434−#[derive(Debug)]
435enum ExtendCapture {
311enum ExtendCapture {
436 Extend,
312 Extend,
437 PreventOnce,
313 PreventOnce,
@@ -440,41 +316,24 @@ enum ExtendCapture {
440/// The result of running a tree-sitter indent query. This stores for
316/// The result of running a tree-sitter indent query. This stores for
441/// each node (identified by its ID) the relevant captures (already filtered
317/// each node (identified by its ID) the relevant captures (already filtered
442/// by predicates).
318/// by predicates).
443−#[derive(Debug)]
444−struct IndentQueryResult<'a> {
319+struct IndentQueryResult {
445− indent_captures: HashMap<usize, Vec<IndentCapture<'a>>>,
320+ indent_captures: HashMap<usize, Vec<IndentCapture>>,
446 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
321 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
447}
322}
448
323
449−fn get_node_start_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
450− let mut node_line = node.start_position().row;
451− // Adjust for the new line that will be inserted
452− if new_line_byte_pos.map_or(false, |pos| node.start_byte() >= pos) {
453− node_line += 1;
454− }
455− node_line
456−}
457−fn get_node_end_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
458− let mut node_line = node.end_position().row;
459− // Adjust for the new line that will be inserted (with a strict inequality since end_byte is exclusive)
460− if new_line_byte_pos.map_or(false, |pos| node.end_byte() > pos) {
461− node_line += 1;
462− }
463− node_line
464−}
465−
466−fn query_indents<'a>(
324+fn query_indents(
467 query: &Query,
325 query: &Query,
468 syntax: &Syntax,
326 syntax: &Syntax,
469 cursor: &mut QueryCursor,
327 cursor: &mut QueryCursor,
470− text: RopeSlice<'a>,
328+ text: RopeSlice,
471 range: std::ops::Range<usize>,
329 range: std::ops::Range<usize>,
472− new_line_byte_pos: Option<usize>,
330+ // Position of the (optional) newly inserted line break.
331+ // Given as (line, byte_pos)
332+ new_line_break: Option<(usize, usize)>,
473−) -> IndentQueryResult<'a> {
333+) -> IndentQueryResult {
474 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
334 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
475 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
335 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
476 cursor.set_byte_range(range);
336 cursor.set_byte_range(range);
477−
478 // Iterate over all captures from the query
337 // Iterate over all captures from the query
479 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
338 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
480 // Skip matches where not all custom predicates are fulfilled
339 // Skip matches where not all custom predicates are fulfilled
@@ -501,13 +360,21 @@ fn query_indents<'a>(
501 Some(QueryPredicateArg::Capture(capt1)),
360 Some(QueryPredicateArg::Capture(capt1)),
502 Some(QueryPredicateArg::Capture(capt2))
361 Some(QueryPredicateArg::Capture(capt2))
503 ) => {
362 ) => {
363+ let get_line_num = |node: Node| {
364+ let mut node_line = node.start_position().row;
365+ // Adjust for the new line that will be inserted
366+ if let Some((line, byte)) = new_line_break {
367+ if node_line==line && node.start_byte()>=byte {
368+ node_line += 1;
369+ }
370+ }
371+ node_line
372+ };
504 let n1 = m.nodes_for_capture_index(*capt1).next();
373 let n1 = m.nodes_for_capture_index(*capt1).next();
505 let n2 = m.nodes_for_capture_index(*capt2).next();
374 let n2 = m.nodes_for_capture_index(*capt2).next();
506 match (n1, n2) {
375 match (n1, n2) {
507 (Some(n1), Some(n2)) => {
376 (Some(n1), Some(n2)) => {
508− let n1_line = get_node_start_line(n1, new_line_byte_pos);
377+ let same_line = get_line_num(n1)==get_line_num(n2);
509− let n2_line = get_node_start_line(n2, new_line_byte_pos);
510− let same_line = n1_line == n2_line;
511 same_line==(pred.operator.as_ref()=="same-line?")
378 same_line==(pred.operator.as_ref()=="same-line?")
512 }
379 }
513 _ => true,
380 _ => true,
@@ -518,23 +385,6 @@ fn query_indents<'a>(
518 }
385 }
519 }
386 }
520 }
387 }
521− "one-line?" | "not-one-line?" => match pred.args.get(0) {
522− Some(QueryPredicateArg::Capture(capture_idx)) => {
523− let node = m.nodes_for_capture_index(*capture_idx).next();
524−
525− match node {
526− Some(node) => {
527− let (start_line, end_line) = (get_node_start_line(node,new_line_byte_pos), get_node_end_line(node, new_line_byte_pos));
528− let one_line = end_line == start_line;
529− one_line != (pred.operator.as_ref() == "not-one-line?")
530− },
531− _ => true,
532− }
533− }
534− _ => {
535− panic!("Invalid indent query: Arguments to \"not-kind-eq?\" must be a capture and a string");
536− }
537− },
538 _ => {
388 _ => {
539 panic!(
389 panic!(
540 "Invalid indent query: Unknown predicate (\"{}\")",
390 "Invalid indent query: Unknown predicate (\"{}\")",
@@ -545,28 +395,11 @@ fn query_indents<'a>(
545 }) {
395 }) {
546 continue;
396 continue;
547 }
397 }
548− // A list of pairs (node_id, indent_capture) that are added by this match.
549− // They cannot be added to indent_captures immediately since they may depend on other captures (such as an @anchor).
550− let mut added_indent_captures: Vec<(usize, IndentCapture)> = Vec::new();
551− // The row/column position of the optional anchor in this query
552− let mut anchor: Option<tree_sitter::Node> = None;
553 for capture in m.captures {
398 for capture in m.captures {
554 let capture_name = query.capture_names()[capture.index as usize].as_str();
399 let capture_name = query.capture_names()[capture.index as usize].as_str();
555 let capture_type = match capture_name {
400 let capture_type = match capture_name {
556 "indent" => IndentCaptureType::Indent,
401 "indent" => IndentCaptureType::Indent,
557− "indent.always" => IndentCaptureType::IndentAlways,
558 "outdent" => IndentCaptureType::Outdent,
402 "outdent" => IndentCaptureType::Outdent,
559− "outdent.always" => IndentCaptureType::OutdentAlways,
560− // The alignment will be updated to the correct value at the end, when the anchor is known.
561− "align" => IndentCaptureType::Align(RopeSlice::from("")),
562− "anchor" => {
563− if anchor.is_some() {
564− log::error!("Invalid indent query: Encountered more than one @anchor in the same match.")
565− } else {
566− anchor = Some(capture.node);
567− }
568− continue;
569− }
570 "extend" => {
403 "extend" => {
571 extend_captures
404 extend_captures
572 .entry(capture.node.id())
405 .entry(capture.node.id())
@@ -616,40 +449,17 @@ fn query_indents<'a>(
616 }
449 }
617 }
450 }
618 }
451 }
619− added_indent_captures.push((capture.node.id(), indent_capture))
620− }
621− for (node_id, mut capture) in added_indent_captures {
622− // Set the anchor for all align queries.
623− if let IndentCaptureType::Align(_) = capture.capture_type {
624− let anchor = match anchor {
625− None => {
626− log::error!(
627− "Invalid indent query: @align requires an accompanying @anchor."
628− );
629− continue;
630− }
631− Some(anchor) => anchor,
632− };
633− capture.capture_type = IndentCaptureType::Align(
634− text.line(anchor.start_position().row)
635− .byte_slice(0..anchor.start_position().column),
636− );
637− }
638 indent_captures
452 indent_captures
639− .entry(node_id)
453+ .entry(capture.node.id())
454+ // Most entries only need to contain a single IndentCapture
640 .or_insert_with(|| Vec::with_capacity(1))
455 .or_insert_with(|| Vec::with_capacity(1))
641− .push(capture);
456+ .push(indent_capture);
642 }
457 }
643 }
458 }
644−
645− let result = IndentQueryResult {
459+ IndentQueryResult {
646 indent_captures,
460 indent_captures,
647 extend_captures,
461 extend_captures,
648− };
462+ }
649−
650− log::trace!("indent result = {:?}", result);
651−
652− result
653}
463}
654
464
655/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
465/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
@@ -719,27 +529,61 @@ fn extend_nodes<'a>(
719 }
529 }
720}
530}
721
531
722−/// Prepare an indent query by computing:
532+/// Use the syntax tree to determine the indentation for a given position.
723−/// - The node from which to start the query (this is non-trivial due to `@extend` captures)
533+/// This can be used in 2 ways:
724−/// - The indent captures for all relevant nodes.
534+///
535+/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
536+/// - In this case, pos should be inside the first tree-sitter node on that line.
537+/// In most cases, this can just be the first non-whitespace on that line.
538+/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
539+/// after pos were moved to a new line.
540+///
541+/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
542+/// Each of these nodes produces some [Indentation] for:
543+///
544+/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
545+/// - The line after the node. This is defined by:
546+/// - The scope `tail`.
547+/// - The scope `all` if this node is not the first node on its line.
548+/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
549+/// The indents from different nodes for the same line are then combined.
550+/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
551+///
552+/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
553+/// ```ignore
554+/// some_function(|| {
555+/// // Both the function parameters as well as the contained block should be indented.
556+/// // Because they are on the same line, this only yields one indent level
557+/// });
558+/// ```
559+///
560+/// ```ignore
561+/// some_function(
562+/// param1,
563+/// || {
564+/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
565+/// },
566+/// );
567+/// ```
725#[allow(clippy::too_many_arguments)]
568#[allow(clippy::too_many_arguments)]
726−fn init_indent_query<'a, 'b>(
569+pub fn treesitter_indent_for_pos(
727 query: &Query,
570 query: &Query,
728− syntax: &'a Syntax,
571+ syntax: &Syntax,
729− text: RopeSlice<'b>,
572+ indent_style: &IndentStyle,
730 tab_width: usize,
573 tab_width: usize,
731 indent_width: usize,
574 indent_width: usize,
575+ text: RopeSlice,
732 line: usize,
576 line: usize,
733− byte_pos: usize,
577+ pos: usize,
734− new_line_byte_pos: Option<usize>,
578+ new_line: bool,
735−) -> Option<(Node<'a>, HashMap<usize, Vec<IndentCapture<'b>>>)> {
579+) -> Option<String> {
580+ let byte_pos = text.char_to_byte(pos);
736 // The innermost tree-sitter node which is considered for the indent
581 // The innermost tree-sitter node which is considered for the indent
737 // computation. It may change if some predeceding node is extended
582 // computation. It may change if some predeceding node is extended
738 let mut node = syntax
583 let mut node = syntax
739 .tree()
584 .tree()
740 .root_node()
585 .root_node()
741 .descendant_for_byte_range(byte_pos, byte_pos)?;
586 .descendant_for_byte_range(byte_pos, byte_pos)?;
742−
743 let (query_result, deepest_preceding) = {
587 let (query_result, deepest_preceding) = {
744 // The query range should intersect with all nodes directly preceding
588 // The query range should intersect with all nodes directly preceding
745 // the position of the indent query in case one of them is extended.
589 // the position of the indent query in case one of them is extended.
@@ -770,12 +614,13 @@ fn init_indent_query<'a, 'b>(
770 &mut cursor,
614 &mut cursor,
771 text,
615 text,
772 query_range,
616 query_range,
773− new_line_byte_pos,
617+ new_line.then_some((line, byte_pos)),
774 );
618 );
775 ts_parser.cursors.push(cursor);
619 ts_parser.cursors.push(cursor);
776 (query_result, deepest_preceding)
620 (query_result, deepest_preceding)
777 })
621 })
778 };
622 };
623+ let indent_captures = query_result.indent_captures;
779 let extend_captures = query_result.extend_captures;
624 let extend_captures = query_result.extend_captures;
780
625
781 // Check for extend captures, potentially changing the node that the indent calculation starts with
626 // Check for extend captures, potentially changing the node that the indent calculation starts with
@@ -790,68 +635,6 @@ fn init_indent_query<'a, 'b>(
790 indent_width,
635 indent_width,
791 );
636 );
792 }
637 }
793− Some((node, query_result.indent_captures))
794−}
795−
796−/// Use the syntax tree to determine the indentation for a given position.
797−/// This can be used in 2 ways:
798−///
799−/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
800−/// - In this case, pos should be inside the first tree-sitter node on that line.
801−/// In most cases, this can just be the first non-whitespace on that line.
802−/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
803−/// after pos were moved to a new line.
804−///
805−/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
806−/// Each of these nodes produces some [Indentation] for:
807−///
808−/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
809−/// - The line after the node. This is defined by:
810−/// - The scope `tail`.
811−/// - The scope `all` if this node is not the first node on its line.
812−/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
813−/// The indents from different nodes for the same line are then combined.
814−/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
815−///
816−/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
817−/// ```ignore
818−/// some_function(|| {
819−/// // Both the function parameters as well as the contained block should be indented.
820−/// // Because they are on the same line, this only yields one indent level
821−/// });
822−/// ```
823−///
824−/// ```ignore
825−/// some_function(
826−/// param1,
827−/// || {
828−/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
829−/// },
830−/// );
831−/// ```
832−#[allow(clippy::too_many_arguments)]
833−pub fn treesitter_indent_for_pos<'a>(
834− query: &Query,
835− syntax: &Syntax,
836− tab_width: usize,
837− indent_width: usize,
838− text: RopeSlice<'a>,
839− line: usize,
840− pos: usize,
841− new_line: bool,
842−) -> Option<Indentation<'a>> {
843− let byte_pos = text.char_to_byte(pos);
844− let new_line_byte_pos = new_line.then_some(byte_pos);
845− let (mut node, mut indent_captures) = init_indent_query(
846− query,
847− syntax,
848− text,
849− tab_width,
850− indent_width,
851− line,
852− byte_pos,
853− new_line_byte_pos,
854− )?;
855 let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos));
638 let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos));
856
639
857 let mut result = Indentation::default();
640 let mut result = Indentation::default();
@@ -859,16 +642,12 @@ pub fn treesitter_indent_for_pos<'a>(
859 // even if there are multiple "indent" nodes on the same line
642 // even if there are multiple "indent" nodes on the same line
860 let mut indent_for_line = Indentation::default();
643 let mut indent_for_line = Indentation::default();
861 let mut indent_for_line_below = Indentation::default();
644 let mut indent_for_line_below = Indentation::default();
862−
863 loop {
645 loop {
864 // This can safely be unwrapped because `first_in_line` contains
646 // This can safely be unwrapped because `first_in_line` contains
865 // one entry for each ancestor of the node (which is what we iterate over)
647 // one entry for each ancestor of the node (which is what we iterate over)
866 let is_first = *first_in_line.last().unwrap();
648 let is_first = *first_in_line.last().unwrap();
867−
868− // Apply all indent definitions for this node.
649+ // Apply all indent definitions for this node
869− // Since we only iterate over each node once, we can remove the
870− // corresponding captures from the HashMap to avoid cloning them.
871− if let Some(definitions) = indent_captures.remove(&node.id()) {
650+ if let Some(definitions) = indent_captures.get(&node.id()) {
872 for definition in definitions {
651 for definition in definitions {
873 match definition.scope {
652 match definition.scope {
874 IndentScope::All => {
653 IndentScope::All => {
@@ -886,22 +665,28 @@ pub fn treesitter_indent_for_pos<'a>(
886 }
665 }
887
666
888 if let Some(parent) = node.parent() {
667 if let Some(parent) = node.parent() {
889− let node_line = get_node_start_line(node, new_line_byte_pos);
668+ let mut node_line = node.start_position().row;
890− let parent_line = get_node_start_line(parent, new_line_byte_pos);
669+ let mut parent_line = parent.start_position().row;
891−
670+ if node_line == line && new_line {
671+ // Also consider the line that will be inserted
672+ if node.start_byte() >= byte_pos {
673+ node_line += 1;
674+ }
675+ if parent.start_byte() >= byte_pos {
676+ parent_line += 1;
677+ }
678+ };
892 if node_line != parent_line {
679 if node_line != parent_line {
893− // Don't add indent for the line below the line of the query
894 if node_line < line + (new_line as usize) {
680 if node_line < line + (new_line as usize) {
681+ // Don't add indent for the line below the line of the query
895− result.add_line(indent_for_line_below);
682+ result.add_line(&indent_for_line_below);
896 }
683 }
897−
898 if node_line == parent_line + 1 {
684 if node_line == parent_line + 1 {
899 indent_for_line_below = indent_for_line;
685 indent_for_line_below = indent_for_line;
900 } else {
686 } else {
901− result.add_line(indent_for_line);
687+ result.add_line(&indent_for_line);
902 indent_for_line_below = Indentation::default();
688 indent_for_line_below = Indentation::default();
903 }
689 }
904−
905 indent_for_line = Indentation::default();
690 indent_for_line = Indentation::default();
906 }
691 }
907
692
@@ -913,13 +698,13 @@ pub fn treesitter_indent_for_pos<'a>(
913 if (node.start_position().row < line)
698 if (node.start_position().row < line)
914 || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
699 || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
915 {
700 {
916− result.add_line(indent_for_line_below);
701+ result.add_line(&indent_for_line_below);
917 }
702 }
918− result.add_line(indent_for_line);
703+ result.add_line(&indent_for_line);
919 break;
704 break;
920 }
705 }
921 }
706 }
922− Some(result)
707+ Some(result.as_string(indent_style))
923}
708}
924
709
925/// Returns the indentation for a new line.
710/// Returns the indentation for a new line.
@@ -928,7 +713,6 @@ pub fn treesitter_indent_for_pos<'a>(
928pub fn indent_for_newline(
713pub fn indent_for_newline(
929 language_config: Option<&LanguageConfiguration>,
714 language_config: Option<&LanguageConfiguration>,
930 syntax: Option<&Syntax>,
715 syntax: Option<&Syntax>,
931− indent_heuristic: &IndentationHeuristic,
932 indent_style: &IndentStyle,
716 indent_style: &IndentStyle,
933 tab_width: usize,
717 tab_width: usize,
934 text: RopeSlice,
718 text: RopeSlice,
@@ -937,18 +721,14 @@ pub fn indent_for_newline(
937 current_line: usize,
721 current_line: usize,
938) -> String {
722) -> String {
939 let indent_width = indent_style.indent_width(tab_width);
723 let indent_width = indent_style.indent_width(tab_width);
940− if let (
941− IndentationHeuristic::TreeSitter | IndentationHeuristic::Hybrid,
942− Some(query),
943− Some(syntax),
724+ if let (Some(query), Some(syntax)) = (
944− ) = (
945− indent_heuristic,
946 language_config.and_then(|config| config.indent_query()),
725 language_config.and_then(|config| config.indent_query()),
947 syntax,
726 syntax,
948 ) {
727 ) {
949 if let Some(indent) = treesitter_indent_for_pos(
728 if let Some(indent) = treesitter_indent_for_pos(
950 query,
729 query,
951 syntax,
730 syntax,
731+ indent_style,
952 tab_width,
732 tab_width,
953 indent_width,
733 indent_width,
954 text,
734 text,
@@ -956,57 +736,9 @@ pub fn indent_for_newline(
956 line_before_end_pos,
736 line_before_end_pos,
957 true,
737 true,
958 ) {
738 ) {
959− if *indent_heuristic == IndentationHeuristic::Hybrid {
960− // We want to compute the indentation not only based on the
961− // syntax tree but also on the actual indentation of a previous
962− // line. This makes indentation computation more resilient to
963− // incomplete queries, incomplete source code & differing indentation
964− // styles for the same language.
965− // However, using the indent of a previous line as a baseline may not
966− // make sense, e.g. if it has a different alignment than the new line.
967− // In order to prevent edge cases with long running times, we only try
968− // a constant number of (non-empty) lines.
969− const MAX_ATTEMPTS: usize = 4;
970− let mut num_attempts = 0;
971− for line_idx in (0..=line_before).rev() {
972− let line = text.line(line_idx);
973− let first_non_whitespace_char = match find_first_non_whitespace_char(line) {
974− Some(i) => i,
975− None => {
976− continue;
977− }
978− };
979− if let Some(indent) = (|| {
980− let computed_indent = treesitter_indent_for_pos(
981− query,
982− syntax,
983− tab_width,
984− indent_width,
985− text,
986− line_idx,
987− text.line_to_char(line_idx) + first_non_whitespace_char,
988− false,
989− )?;
990− let leading_whitespace = line.slice(0..first_non_whitespace_char);
991− indent.relative_indent(
992− &computed_indent,
993− leading_whitespace,
994− indent_style,
995− tab_width,
996− )
997− })() {
998− return indent;
739+ return indent;
999− }
1000− num_attempts += 1;
1001− if num_attempts == MAX_ATTEMPTS {
1002− break;
1003− }
1004− }
1005− }
1006− return indent.to_string(indent_style, tab_width);
1007 };
740 };
1008 }
741 }
1009− // Fallback in case we either don't have indent queries or they failed for some reason
1010 let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
742 let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
1011 indent_style.as_str().repeat(indent_level)
743 indent_style.as_str().repeat(indent_level)
1012}
744}
@@ -1078,195 +810,4 @@ mod test {
1078 2
810 2
1079 );
811 );
1080 }
812 }
1081−
1082− #[test]
1083− fn add_capture() {
1084− let indent = || Indentation {
1085− indent: 1,
1086− ..Default::default()
1087− };
1088− let indent_always = || Indentation {
1089− indent_always: 1,
1090− ..Default::default()
1091− };
1092− let outdent = || Indentation {
1093− outdent: 1,
1094− ..Default::default()
1095− };
1096− let outdent_always = || Indentation {
1097− outdent_always: 1,
1098− ..Default::default()
1099− };
1100−
1101− fn add_capture<'a>(
1102− mut indent: Indentation<'a>,
1103− capture: IndentCaptureType<'a>,
1104− ) -> Indentation<'a> {
1105− indent.add_capture(capture);
1106− indent
1107− }
1108−
1109− // adding an indent to no indent makes an indent
1110− assert_eq!(
1111− indent(),
1112− add_capture(Indentation::default(), IndentCaptureType::Indent)
1113− );
1114− assert_eq!(
1115− indent_always(),
1116− add_capture(Indentation::default(), IndentCaptureType::IndentAlways)
1117− );
1118− assert_eq!(
1119− outdent(),
1120− add_capture(Indentation::default(), IndentCaptureType::Outdent)
1121− );
1122− assert_eq!(
1123− outdent_always(),
1124− add_capture(Indentation::default(), IndentCaptureType::OutdentAlways)
1125− );
1126−
1127− // adding an indent to an already indented has no effect
1128− assert_eq!(indent(), add_capture(indent(), IndentCaptureType::Indent));
1129− assert_eq!(
1130− outdent(),
1131− add_capture(outdent(), IndentCaptureType::Outdent)
1132− );
1133−
1134− // adding an always to a regular makes it always
1135− assert_eq!(
1136− indent_always(),
1137− add_capture(indent(), IndentCaptureType::IndentAlways)
1138− );
1139− assert_eq!(
1140− outdent_always(),
1141− add_capture(outdent(), IndentCaptureType::OutdentAlways)
1142− );
1143−
1144− // adding an always to an always is additive
1145− assert_eq!(
1146− Indentation {
1147− indent_always: 2,
1148− ..Default::default()
1149− },
1150− add_capture(indent_always(), IndentCaptureType::IndentAlways)
1151− );
1152− assert_eq!(
1153− Indentation {
1154− outdent_always: 2,
1155− ..Default::default()
1156− },
1157− add_capture(outdent_always(), IndentCaptureType::OutdentAlways)
1158− );
1159−
1160− // adding regular to always should be associative
1161− assert_eq!(
1162− Indentation {
1163− indent_always: 1,
1164− ..Default::default()
1165− },
1166− add_capture(
1167− add_capture(indent(), IndentCaptureType::Indent),
1168− IndentCaptureType::IndentAlways
1169− )
1170− );
1171− assert_eq!(
1172− Indentation {
1173− indent_always: 1,
1174− ..Default::default()
1175− },
1176− add_capture(
1177− add_capture(indent(), IndentCaptureType::IndentAlways),
1178− IndentCaptureType::Indent
1179− )
1180− );
1181− assert_eq!(
1182− Indentation {
1183− outdent_always: 1,
1184− ..Default::default()
1185− },
1186− add_capture(
1187− add_capture(outdent(), IndentCaptureType::Outdent),
1188− IndentCaptureType::OutdentAlways
1189− )
1190− );
1191− assert_eq!(
1192− Indentation {
1193− outdent_always: 1,
1194− ..Default::default()
1195− },
1196− add_capture(
1197− add_capture(outdent(), IndentCaptureType::OutdentAlways),
1198− IndentCaptureType::Outdent
1199− )
1200− );
1201− }
1202−
1203− #[test]
1204− fn test_relative_indent() {
1205− let indent_style = IndentStyle::Spaces(4);
1206− let tab_width: usize = 4;
1207− let no_align = [
1208− Indentation::default(),
1209− Indentation {
1210− indent: 1,
1211− ..Default::default()
1212− },
1213− Indentation {
1214− indent: 5,
1215− outdent: 1,
1216− ..Default::default()
1217− },
1218− ];
1219− let align = no_align.clone().map(|indent| Indentation {
1220− align: Some(RopeSlice::from("12345")),
1221− ..indent
1222− });
1223− let different_align = Indentation {
1224− align: Some(RopeSlice::from("123456")),
1225− ..Default::default()
1226− };
1227−
1228− // Check that relative and absolute indentation computation are the same when the line we compare to is
1229− // indented as we expect.
1230− let check_consistency = |indent: &Indentation, other: &Indentation| {
1231− assert_eq!(
1232− indent.relative_indent(
1233− other,
1234− RopeSlice::from(other.to_string(&indent_style, tab_width).as_str()),
1235− &indent_style,
1236− tab_width
1237− ),
1238− Some(indent.to_string(&indent_style, tab_width))
1239− );
1240− };
1241− for a in &no_align {
1242− for b in &no_align {
1243− check_consistency(a, b);
1244− }
1245− }
1246− for a in &align {
1247− for b in &align {
1248− check_consistency(a, b);
1249− }
1250− }
1251−
1252− // Relative indent computation makes no sense if the alignment differs
1253− assert_eq!(
1254− align[0].relative_indent(
1255− &no_align[0],
1256− RopeSlice::from(" "),
1257− &indent_style,
1258− tab_width
1259− ),
1260− None
1261− );
1262− assert_eq!(
1263− align[0].relative_indent(
1264− &different_align,
1265− RopeSlice::from(" "),
1266− &indent_style,
1267− tab_width
1268− ),
1269− None
1270− );
1271− }
1272}
813}
helix-core/src/lib.rs+1−1
@@ -7,7 +7,6 @@ pub mod config;
7pub mod diagnostic;
7pub mod diagnostic;
8pub mod diff;
8pub mod diff;
9pub mod doc_formatter;
9pub mod doc_formatter;
10−pub mod fuzzy;
11pub mod graphemes;
10pub mod graphemes;
12pub mod history;
11pub mod history;
13pub mod increment;
12pub mod increment;
@@ -19,6 +18,7 @@ pub mod movement;
19pub mod object;
18pub mod object;
20pub mod path;
19pub mod path;
21mod position;
20mod position;
21+pub mod register;
22pub mod search;
22pub mod search;
23pub mod selection;
23pub mod selection;
24pub mod shellwords;
24pub mod shellwords;
helix-core/src/movement.rs+1−80
@@ -16,7 +16,7 @@ use crate::{
16 syntax::LanguageConfiguration,
16 syntax::LanguageConfiguration,
17 text_annotations::TextAnnotations,
17 text_annotations::TextAnnotations,
18 textobject::TextObject,
18 textobject::TextObject,
19− visual_offset_from_block, Range, RopeSlice, Selection, Syntax,
19+ visual_offset_from_block, Range, RopeSlice,
20};
20};
21
21
22#[derive(Debug, Copy, Clone, PartialEq, Eq)]
22#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -556,85 +556,6 @@ pub fn goto_treesitter_object(
556 last_range
556 last_range
557}
557}
558
558
559−fn find_parent_start(mut node: Node) -> Option<Node> {
560− let start = node.start_byte();
561−
562− while node.start_byte() >= start || !node.is_named() {
563− node = node.parent()?;
564− }
565−
566− Some(node)
567−}
568−
569−pub fn move_parent_node_end(
570− syntax: &Syntax,
571− text: RopeSlice,
572− selection: Selection,
573− dir: Direction,
574− movement: Movement,
575−) -> Selection {
576− let tree = syntax.tree();
577−
578− selection.transform(|range| {
579− let start_from = text.char_to_byte(range.from());
580− let start_to = text.char_to_byte(range.to());
581−
582− let mut node = match tree
583− .root_node()
584− .named_descendant_for_byte_range(start_from, start_to)
585− {
586− Some(node) => node,
587− None => {
588− log::debug!(
589− "no descendant found for byte range: {} - {}",
590− start_from,
591− start_to
592− );
593− return range;
594− }
595− };
596−
597− let mut end_head = match dir {
598− // moving forward, we always want to move one past the end of the
599− // current node, so use the end byte of the current node, which is an exclusive
600− // end of the range
601− Direction::Forward => text.byte_to_char(node.end_byte()),
602−
603− // moving backward, we want the cursor to land on the start char of
604− // the current node, or if it is already at the start of a node, to traverse up to
605− // the parent
606− Direction::Backward => {
607− let end_head = text.byte_to_char(node.start_byte());
608−
609− // if we're already on the beginning, look up to the parent
610− if end_head == range.cursor(text) {
611− node = find_parent_start(node).unwrap_or(node);
612− text.byte_to_char(node.start_byte())
613− } else {
614− end_head
615− }
616− }
617− };
618−
619− if movement == Movement::Move {
620− // preserve direction of original range
621− if range.direction() == Direction::Forward {
622− Range::new(end_head, end_head + 1)
623− } else {
624− Range::new(end_head + 1, end_head)
625− }
626− } else {
627− // if we end up with a forward range, then adjust it to be one past
628− // where we want
629− if end_head >= range.anchor {
630− end_head += 1;
631− }
632−
633− Range::new(range.anchor, end_head)
634− }
635− })
636−}
637−
638#[cfg(test)]
559#[cfg(test)]
639mod test {
560mod test {
640 use ropey::Rope;
561 use ropey::Rope;
helix-core/src/path.rs+2−2
@@ -85,7 +85,7 @@ pub fn get_normalized_path(path: &Path) -> PathBuf {
85///
85///
86/// This function is used instead of `std::fs::canonicalize` because we don't want to verify
86/// This function is used instead of `std::fs::canonicalize` because we don't want to verify
87/// here if the path exists, just normalize it's components.
87/// here if the path exists, just normalize it's components.
88−pub fn get_canonicalized_path(path: &Path) -> PathBuf {
88+pub fn get_canonicalized_path(path: &Path) -> std::io::Result<PathBuf> {
89 let path = expand_tilde(path);
89 let path = expand_tilde(path);
90 let path = if path.is_relative() {
90 let path = if path.is_relative() {
91 helix_loader::current_working_dir().join(path)
91 helix_loader::current_working_dir().join(path)
@@ -93,7 +93,7 @@ pub fn get_canonicalized_path(path: &Path) -> PathBuf {
93 path
93 path
94 };
94 };
95
95
96− get_normalized_path(path.as_path())
96+ Ok(get_normalized_path(path.as_path()))
97}
97}
98
98
99pub fn get_relative_path(path: &Path) -> PathBuf {
99pub fn get_relative_path(path: &Path) -> PathBuf {
helix-core/src/register.rsnew file+89
@@ -0,0 +1,89 @@
1+use std::collections::HashMap;
2+
3+#[derive(Debug)]
4+pub struct Register {
5+ name: char,
6+ values: Vec<String>,
7+}
8+
9+impl Register {
10+ pub const fn new(name: char) -> Self {
11+ Self {
12+ name,
13+ values: Vec::new(),
14+ }
15+ }
16+
17+ pub fn new_with_values(name: char, values: Vec<String>) -> Self {
18+ Self { name, values }
19+ }
20+
21+ pub const fn name(&self) -> char {
22+ self.name
23+ }
24+
25+ pub fn read(&self) -> &[String] {
26+ &self.values
27+ }
28+
29+ pub fn write(&mut self, values: Vec<String>) {
30+ self.values = values;
31+ }
32+
33+ pub fn push(&mut self, value: String) {
34+ self.values.push(value);
35+ }
36+}
37+
38+/// Currently just wraps a `HashMap` of `Register`s
39+#[derive(Debug, Default)]
40+pub struct Registers {
41+ inner: HashMap<char, Register>,
42+}
43+
44+impl Registers {
45+ pub fn get(&self, name: char) -> Option<&Register> {
46+ self.inner.get(&name)
47+ }
48+
49+ pub fn read(&self, name: char) -> Option<&[String]> {
50+ self.get(name).map(|reg| reg.read())
51+ }
52+
53+ pub fn write(&mut self, name: char, values: Vec<String>) {
54+ if name != '_' {
55+ self.inner
56+ .insert(name, Register::new_with_values(name, values));
57+ }
58+ }
59+
60+ pub fn push(&mut self, name: char, value: String) {
61+ if name != '_' {
62+ if let Some(r) = self.inner.get_mut(&name) {
63+ r.push(value);
64+ } else {
65+ self.write(name, vec![value]);
66+ }
67+ }
68+ }
69+
70+ pub fn first(&self, name: char) -> Option<&String> {
71+ self.read(name).and_then(|entries| entries.first())
72+ }
73+
74+ pub fn last(&self, name: char) -> Option<&String> {
75+ self.read(name).and_then(|entries| entries.last())
76+ }
77+
78+ pub fn inner(&self) -> &HashMap<char, Register> {
79+ &self.inner
80+ }
81+
82+ pub fn clear(&mut self) {
83+ self.inner.clear();
84+ }
85+
86+ pub fn remove(&mut self, name: char) -> Option<Register> {
87+ self.inner.remove(&name)
88+ }
89+}
helix-core/src/selection.rs+2−10
@@ -630,19 +630,11 @@ impl Selection {
630 self.transform(|range| Range::point(range.cursor(text)))
630 self.transform(|range| Range::point(range.cursor(text)))
631 }
631 }
632
632
633− pub fn fragments<'a>(
633+ pub fn fragments<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = Cow<str>> + 'a {
634− &'a self,
635− text: RopeSlice<'a>,
636− ) -> impl DoubleEndedIterator<Item = Cow<'a, str>> + ExactSizeIterator<Item = Cow<str>> + 'a
637− {
638 self.ranges.iter().map(move |range| range.fragment(text))
634 self.ranges.iter().map(move |range| range.fragment(text))
639 }
635 }
640
636
641− pub fn slices<'a>(
637+ pub fn slices<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = RopeSlice> + 'a {
642− &'a self,
643− text: RopeSlice<'a>,
644− ) -> impl DoubleEndedIterator<Item = RopeSlice<'a>> + ExactSizeIterator<Item = RopeSlice<'a>> + 'a
645− {
646 self.ranges.iter().map(move |range| range.slice(text))
638 self.ranges.iter().map(move |range| range.slice(text))
647 }
639 }
648
640
helix-core/src/syntax.rs+8−24
@@ -101,8 +101,7 @@ pub struct LanguageConfiguration {
101 pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
101 pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
102 #[serde(default)]
102 #[serde(default)]
103 pub shebangs: Vec<String>, // interpreter(s) associated with language
103 pub shebangs: Vec<String>, // interpreter(s) associated with language
104− #[serde(default)]
105− pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
104+ pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
106 pub comment_token: Option<String>,
105 pub comment_token: Option<String>,
107 pub text_width: Option<usize>,
106 pub text_width: Option<usize>,
108 pub soft_wrap: Option<SoftWrap>,
107 pub soft_wrap: Option<SoftWrap>,
@@ -155,8 +154,6 @@ pub struct LanguageConfiguration {
155 /// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
154 /// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
156 /// Falling back to the current working directory if none are configured.
155 /// Falling back to the current working directory if none are configured.
157 pub workspace_lsp_roots: Option<Vec<PathBuf>>,
156 pub workspace_lsp_roots: Option<Vec<PathBuf>>,
158− #[serde(default)]
159− pub persistent_diagnostic_sources: Vec<String>,
160}
157}
161
158
162#[derive(Debug, PartialEq, Eq, Hash)]
159#[derive(Debug, PartialEq, Eq, Hash)]
@@ -214,7 +211,10 @@ impl<'de> Deserialize<'de> for FileType {
214 {
211 {
215 match map.next_entry::<String, String>()? {
212 match map.next_entry::<String, String>()? {
216 Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
213 Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
217− suffix.replace('/', std::path::MAIN_SEPARATOR_STR)
214+ // FIXME: use `suffix.replace('/', std::path::MAIN_SEPARATOR_STR)`
215+ // if MSRV is updated to 1.68
216+ let mut separator = [0; 1];
217+ suffix.replace('/', std::path::MAIN_SEPARATOR.encode_utf8(&mut separator))
218 })),
218 })),
219 Some((key, _value)) => Err(serde::de::Error::custom(format!(
219 Some((key, _value)) => Err(serde::de::Error::custom(format!(
220 "unknown key in `file-types` list: {}",
220 "unknown key in `file-types` list: {}",
@@ -444,22 +444,6 @@ pub struct IndentationConfiguration {
444 pub unit: String,
444 pub unit: String,
445}
445}
446
446
447−/// How the indentation for a newly inserted line should be determined.
448−/// If the selected heuristic is not available (e.g. because the current
449−/// language has no tree-sitter indent queries), a simpler one will be used.
450−#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
451−#[serde(rename_all = "kebab-case")]
452−pub enum IndentationHeuristic {
453− /// Just copy the indentation of the line that the cursor is currently on.
454− Simple,
455− /// Use tree-sitter indent queries to compute the expected absolute indentation level of the new line.
456− TreeSitter,
457− /// Use tree-sitter indent queries to compute the expected difference in indentation between the new line
458− /// and the line before. Add this to the actual indentation level of the line before.
459− #[default]
460− Hybrid,
461−}
462−
463/// Configuration for auto pairs
447/// Configuration for auto pairs
464#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
448#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
465#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
449#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
@@ -978,7 +962,7 @@ impl Syntax {
978 let res = syntax.update(source, source, &ChangeSet::new(source));
962 let res = syntax.update(source, source, &ChangeSet::new(source));
979
963
980 if res.is_err() {
964 if res.is_err() {
981− log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
965+ log::error!("TS parser failed, disabeling TS for the current buffer: {res:?}");
982 return None;
966 return None;
983 }
967 }
984 Some(syntax)
968 Some(syntax)
@@ -1630,7 +1614,7 @@ impl<'a> Iterator for ChunksBytes<'a> {
1630}
1614}
1631
1615
1632pub struct RopeProvider<'a>(pub RopeSlice<'a>);
1616pub struct RopeProvider<'a>(pub RopeSlice<'a>);
1633−impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> {
1617+impl<'a> TextProvider<'a> for RopeProvider<'a> {
1634 type I = ChunksBytes<'a>;
1618 type I = ChunksBytes<'a>;
1635
1619
1636 fn text(&mut self, node: Node) -> Self::I {
1620 fn text(&mut self, node: Node) -> Self::I {
@@ -1644,7 +1628,7 @@ impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> {
1644struct HighlightIterLayer<'a> {
1628struct HighlightIterLayer<'a> {
1645 _tree: Option<Tree>,
1629 _tree: Option<Tree>,
1646 cursor: QueryCursor,
1630 cursor: QueryCursor,
1647− captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>, &'a [u8]>>>,
1631+ captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>>>>,
1648 config: &'a HighlightConfiguration,
1632 config: &'a HighlightConfiguration,
1649 highlight_end_stack: Vec<usize>,
1633 highlight_end_stack: Vec<usize>,
1650 scope_stack: Vec<LocalScope<'a>>,
1634 scope_stack: Vec<LocalScope<'a>>,
helix-core/src/transaction.rs+14−73
@@ -1,7 +1,7 @@
1use ropey::RopeSlice;
1use ropey::RopeSlice;
2use smallvec::SmallVec;
2use smallvec::SmallVec;
3
3
4−use crate::{chars::char_is_word, Range, Rope, Selection, Tendril};
4+use crate::{Range, Rope, Selection, Tendril};
5use std::{borrow::Cow, iter::once};
5use std::{borrow::Cow, iter::once};
6
6
7/// (from, to, replacement)
7/// (from, to, replacement)
@@ -23,30 +23,6 @@ pub enum Operation {
23pub enum Assoc {
23pub enum Assoc {
24 Before,
24 Before,
25 After,
25 After,
26− /// Acts like `After` if a word character is inserted
27− /// after the position, otherwise acts like `Before`
28− AfterWord,
29− /// Acts like `Before` if a word character is inserted
30− /// before the position, otherwise acts like `After`
31− BeforeWord,
32−}
33−
34−impl Assoc {
35− /// Whether to stick to gaps
36− fn stay_at_gaps(self) -> bool {
37− !matches!(self, Self::BeforeWord | Self::AfterWord)
38− }
39−
40− fn insert_offset(self, s: &str) -> usize {
41− let chars = s.chars().count();
42− match self {
43− Assoc::After => chars,
44− Assoc::AfterWord => s.chars().take_while(|&c| char_is_word(c)).count(),
45− // return position before inserted text
46− Assoc::Before => 0,
47− Assoc::BeforeWord => chars - s.chars().rev().take_while(|&c| char_is_word(c)).count(),
48− }
49− }
50}
26}
51
27
52#[derive(Debug, Default, Clone, PartialEq, Eq)]
28#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -439,6 +415,8 @@ impl ChangeSet {
439 map!(|pos, _| (old_end > pos).then_some(new_pos), i);
415 map!(|pos, _| (old_end > pos).then_some(new_pos), i);
440 }
416 }
441 Insert(s) => {
417 Insert(s) => {
418+ let ins = s.chars().count();
419+
442 // a subsequent delete means a replace, consume it
420 // a subsequent delete means a replace, consume it
443 if let Some((_, Delete(len))) = iter.peek() {
421 if let Some((_, Delete(len))) = iter.peek() {
444 iter.next();
422 iter.next();
@@ -446,13 +424,13 @@ impl ChangeSet {
446 old_end = old_pos + len;
424 old_end = old_pos + len;
447 // in range of replaced text
425 // in range of replaced text
448 map!(
426 map!(
449− |pos, assoc: Assoc| (old_end > pos).then(|| {
427+ |pos, assoc| (old_end > pos).then(|| {
450 // at point or tracking before
428 // at point or tracking before
451− if pos == old_pos && assoc.stay_at_gaps() {
429+ if pos == old_pos || assoc == Assoc::Before {
452 new_pos
430 new_pos
453 } else {
431 } else {
454 // place to end of insert
432 // place to end of insert
455− new_pos + assoc.insert_offset(s)
433+ new_pos + ins
456 }
434 }
457 }),
435 }),
458 i
436 i
@@ -460,15 +438,20 @@ impl ChangeSet {
460 } else {
438 } else {
461 // at insert point
439 // at insert point
462 map!(
440 map!(
463− |pos, assoc: Assoc| (old_pos == pos).then(|| {
441+ |pos, assoc| (old_pos == pos).then(|| {
464 // return position before inserted text
442 // return position before inserted text
465− new_pos + assoc.insert_offset(s)
443+ if assoc == Assoc::Before {
444+ new_pos
445+ } else {
446+ // after text
447+ new_pos + ins
448+ }
466 }),
449 }),
467 i
450 i
468 );
451 );
469 }
452 }
470
453
471− new_pos += s.chars().count();
454+ new_pos += ins;
472 }
455 }
473 }
456 }
474 old_pos = old_end;
457 old_pos = old_end;
@@ -901,48 +884,6 @@ mod test {
901 let mut positions = [4, 2];
884 let mut positions = [4, 2];
902 cs.update_positions(positions.iter_mut().map(|pos| (pos, Assoc::After)));
885 cs.update_positions(positions.iter_mut().map(|pos| (pos, Assoc::After)));
903 assert_eq!(positions, [4, 2]);
886 assert_eq!(positions, [4, 2]);
904− // stays at word boundary
905− let cs = ChangeSet {
906− changes: vec![
907− Retain(2), // <space><space>
908− Insert(" ab".into()),
909− Retain(2), // cd
910− Insert("de ".into()),
911− ],
912− len: 4,
913− len_after: 10,
914− };
915− assert_eq!(cs.map_pos(2, Assoc::BeforeWord), 3);
916− assert_eq!(cs.map_pos(4, Assoc::AfterWord), 9);
917− let cs = ChangeSet {
918− changes: vec![
919− Retain(1), // <space>
920− Insert(" b".into()),
921− Delete(1), // c
922− Retain(1), // d
923− Insert("e ".into()),
924− Delete(1), // <space>
925− ],
926− len: 5,
927− len_after: 7,
928− };
929− assert_eq!(cs.map_pos(1, Assoc::BeforeWord), 2);
930− assert_eq!(cs.map_pos(3, Assoc::AfterWord), 5);
931− let cs = ChangeSet {
932− changes: vec![
933− Retain(1), // <space>
934− Insert("a".into()),
935− Delete(2), // <space>b
936− Retain(1), // d
937− Insert("e".into()),
938− Delete(1), // f
939− Retain(1), // <space>
940− ],
941− len: 5,
942− len_after: 7,
943− };
944− assert_eq!(cs.map_pos(2, Assoc::BeforeWord), 1);
945− assert_eq!(cs.map_pos(4, Assoc::AfterWord), 4);
946 }
887 }
947
888
948 #[test]
889 #[test]
helix-core/src/wrap.rs+1−3
@@ -1,9 +1,7 @@
1use smartstring::{LazyCompact, SmartString};
1use smartstring::{LazyCompact, SmartString};
2−use textwrap::{Options, WordSplitter::NoHyphenation};
3
2
4/// Given a slice of text, return the text re-wrapped to fit it
3/// Given a slice of text, return the text re-wrapped to fit it
5/// within the given width.
4/// within the given width.
6pub fn reflow_hard_wrap(text: &str, text_width: usize) -> SmartString<LazyCompact> {
5pub fn reflow_hard_wrap(text: &str, text_width: usize) -> SmartString<LazyCompact> {
7− let options = Options::new(text_width).word_splitter(NoHyphenation);
8− textwrap::refill(text, options).into()
6+ textwrap::refill(text, text_width).into()
9}
7}
helix-core/tests/data/indent/cpp.cppdeleted−48
@@ -1,48 +0,0 @@
1−std::vector<std::string>
2−fn_with_many_parameters(int parm1, long parm2, float parm3, double parm4,
3− char* parm5, bool parm6);
4−
5−std::vector<std::string>
6−fn_with_many_parameters(int parm1, long parm2, float parm3, double parm4,
7− char* parm5, bool parm6) {
8− auto lambda = []() {
9− return 0;
10− };
11− auto lambda_with_a_really_long_name_that_uses_a_whole_line
12− = [](int some_more_aligned_parameters,
13− std::string parm2) {
14− do_smth();
15− };
16− if (brace_on_same_line) {
17− do_smth();
18− } else if (brace_on_next_line)
19− {
20− do_smth();
21− } else if (another_condition) {
22− do_smth();
23− }
24− else {
25− do_smth();
26− }
27− if (inline_if_statement)
28− do_smth();
29− if (another_inline_if_statement)
30− return [](int parm1, char* parm2) {
31− this_is_a_really_pointless_lambda();
32− };
33−
34− switch (var) {
35− case true:
36− return -1;
37− case false:
38− return 42;
39− }
40−}
41−
42−class MyClass : public MyBaseClass {
43−public:
44− MyClass();
45− void public_fn();
46−private:
47− super_secret_private_fn();
48−}
helix-core/tests/data/indent/indent.rsnew file+1
@@ -0,0 +1 @@
1+../../../src/indent.rs
helix-core/tests/data/indent/languages.toml−13
@@ -11,16 +11,3 @@ indent = { tab-width = 4, unit = " " }
11[[grammar]]
11[[grammar]]
12name = "rust"
12name = "rust"
13source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "0431a2c60828731f27491ee9fdefe25e250ce9c9" }
13source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "0431a2c60828731f27491ee9fdefe25e250ce9c9" }
14−
15−[[language]]
16−name = "cpp"
17−scope = "source.cpp"
18−injection-regex = "cpp"
19−file-types = ["cc", "hh", "c++", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino", "C", "H"]
20−roots = []
21−comment-token = "//"
22−indent = { tab-width = 2, unit = " " }
23−
24−[[grammar]]
25−name = "cpp"
26−source = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "2d2c4aee8672af4c7c8edff68e7dd4c07e88d2b1" }
helix-core/tests/indent.rs+23−151
@@ -1,122 +1,20 @@
1use helix_core::{
1use helix_core::{
2 indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle},
2 indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle},
3− syntax::{Configuration, Loader},
3+ syntax::Loader,
4 Syntax,
4 Syntax,
5};
5};
6−use ropey::Rope;
7−use std::{ops::Range, path::PathBuf, process::Command};
6+use std::path::PathBuf;
8
7
9#[test]
8#[test]
10fn test_treesitter_indent_rust() {
9fn test_treesitter_indent_rust() {
11− standard_treesitter_test("rust.rs", "source.rust");
10+ test_treesitter_indent("rust.rs", "source.rust");
12}
11}
13−
14−#[test]
15−fn test_treesitter_indent_cpp() {
16− standard_treesitter_test("cpp.cpp", "source.cpp");
17−}
18−
19#[test]
12#[test]
20−fn test_treesitter_indent_rust_helix() {
13+fn test_treesitter_indent_rust_2() {
21− // We pin a specific git revision to prevent unrelated changes from causing the indent tests to fail.
14+ test_treesitter_indent("indent.rs", "source.rust");
22− // Ideally, someone updates this once in a while and fixes any errors that occur.
15+ // TODO Use commands.rs as indentation test.
23− let rev = "af382768cdaf89ff547dbd8f644a1bddd90e7c8f";
16+ // Currently this fails because we can't align the parameters of a closure yet
24− let files = Command::new("git")
17+ // test_treesitter_indent("commands.rs", "source.rust");
25− .args([
26− "ls-tree",
27− "-r",
28− "--name-only",
29− "--full-tree",
30− rev,
31− "helix-term/src",
32− ])
33− .output()
34− .unwrap();
35− let files = String::from_utf8(files.stdout).unwrap();
36−
37− let ignored_files = vec![
38− // Contains many macros that tree-sitter does not parse in a meaningful way and is otherwise not very interesting
39− "helix-term/src/health.rs",
40− ];
41−
42− for file in files.split_whitespace() {
43− if ignored_files.contains(&file) {
44− continue;
45− }
46− let ignored_lines: Vec<Range<usize>> = match file {
47− "helix-term/src/application.rs" => vec![
48− // We can't handle complicated indent rules inside macros (`json!` in this case) since
49− // the tree-sitter grammar only parses them as `token_tree` and `identifier` nodes.
50− 1045..1051,
51− ],
52− "helix-term/src/commands.rs" => vec![
53− // This is broken because of the current handling of `call_expression`
54− // (i.e. having an indent query for it but outdenting again in specific cases).
55− // The indent query is needed to correctly handle multi-line arguments in function calls
56− // inside indented `field_expression` nodes (which occurs fairly often).
57− //
58− // Once we have the `@indent.always` capture type, it might be possible to just have an indent
59− // capture for the `arguments` field of a call expression. That could enable us to correctly
60− // handle this.
61− 2226..2230,
62− ],
63− "helix-term/src/commands/dap.rs" => vec![
64− // Complex `format!` macro
65− 46..52,
66− ],
67− "helix-term/src/commands/lsp.rs" => vec![
68− // Macro
69− 624..627,
70− // Return type declaration of a closure. `cargo fmt` adds an additional space here,
71− // which we cannot (yet) model with our indent queries.
72− 878..879,
73− // Same as in `helix-term/src/commands.rs`
74− 1335..1343,
75− ],
76− "helix-term/src/config.rs" => vec![
77− // Multiline string
78− 146..152,
79− ],
80− "helix-term/src/keymap.rs" => vec![
81− // Complex macro (see above)
82− 456..470,
83− // Multiline string without indent
84− 563..567,
85− ],
86− "helix-term/src/main.rs" => vec![
87− // Multiline string
88− 44..70,
89− ],
90− "helix-term/src/ui/completion.rs" => vec![
91− // Macro
92− 218..232,
93− ],
94− "helix-term/src/ui/editor.rs" => vec![
95− // The chained function calls here are not indented, probably because of the comment
96− // in between. Since `cargo fmt` doesn't even attempt to format it, there's probably
97− // no point in trying to indent this correctly.
98− 342..350,
99− ],
100− "helix-term/src/ui/lsp.rs" => vec![
101− // Macro
102− 56..61,
103− ],
104− "helix-term/src/ui/statusline.rs" => vec![
105− // Same as in `helix-term/src/commands.rs`
106− 436..442,
107− 450..456,
108− ],
109− _ => Vec::new(),
110− };
111−
112− let git_object = rev.to_string() + ":" + file;
113− let content = Command::new("git")
114− .args(["cat-file", "blob", &git_object])
115− .output()
116− .unwrap();
117− let doc = Rope::from_reader(&mut content.stdout.as_slice()).unwrap();
118− test_treesitter_indent(file, doc, "source.rust", ignored_lines);
119− }
120}
18}
121
19
122#[test]
20#[test]
@@ -152,41 +50,20 @@ fn test_indent_level_for_line_with_spaces_and_tabs() {
152 assert_eq!(indent_level, 2)
50 assert_eq!(indent_level, 2)
153}
51}
154
52
155−fn indent_tests_dir() -> PathBuf {
53+fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
156 let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
54 let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
157 test_dir.push("tests/data/indent");
55 test_dir.push("tests/data/indent");
158− test_dir
159−}
160
56
161−fn indent_test_path(name: &str) -> PathBuf {
57+ let mut test_file = test_dir.clone();
162− let mut path = indent_tests_dir();
58+ test_file.push(file_name);
163− path.push(name);
164− path
165−}
166−
167−fn indent_tests_config() -> Configuration {
168− let mut config_path = indent_tests_dir();
169− config_path.push("languages.toml");
170− let config = std::fs::read_to_string(config_path).unwrap();
171− toml::from_str(&config).unwrap()
172−}
173−
174−fn standard_treesitter_test(file_name: &str, lang_scope: &str) {
175− let test_path = indent_test_path(file_name);
176− let test_file = std::fs::File::open(test_path).unwrap();
59+ let test_file = std::fs::File::open(test_file).unwrap();
177 let doc = ropey::Rope::from_reader(test_file).unwrap();
60 let doc = ropey::Rope::from_reader(test_file).unwrap();
178− test_treesitter_indent(file_name, doc, lang_scope, Vec::new())
179−}
180
61
181−/// Test that all the lines in the given file are indented as expected.
62+ let mut config_file = test_dir;
182−/// ignored_lines is a list of (1-indexed) line ranges that are excluded from this test.
63+ config_file.push("languages.toml");
183−fn test_treesitter_indent(
64+ let config = std::fs::read_to_string(config_file).unwrap();
184− test_name: &str,
65+ let config = toml::from_str(&config).unwrap();
185− doc: Rope,
186− lang_scope: &str,
187− ignored_lines: Vec<std::ops::Range<usize>>,
188−) {
189− let loader = Loader::new(indent_tests_config());
66+ let loader = Loader::new(config);
190
67
191 // set runtime path so we can find the queries
68 // set runtime path so we can find the queries
192 let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
69 let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
@@ -194,7 +71,6 @@ fn test_treesitter_indent(
194 std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
71 std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
195
72
196 let language_config = loader.language_config_for_scope(lang_scope).unwrap();
73 let language_config = loader.language_config_for_scope(lang_scope).unwrap();
197− let indent_style = IndentStyle::from_str(&language_config.indent.as_ref().unwrap().unit);
198 let highlight_config = language_config.highlight_config(&[]).unwrap();
74 let highlight_config = language_config.highlight_config(&[]).unwrap();
199 let text = doc.slice(..);
75 let text = doc.slice(..);
200 let syntax = Syntax::new(text, highlight_config, std::sync::Arc::new(loader)).unwrap();
76 let syntax = Syntax::new(text, highlight_config, std::sync::Arc::new(loader)).unwrap();
@@ -202,27 +78,23 @@ fn test_treesitter_indent(
202
78
203 for i in 0..doc.len_lines() {
79 for i in 0..doc.len_lines() {
204 let line = text.line(i);
80 let line = text.line(i);
205− if ignored_lines.iter().any(|range| range.contains(&(i + 1))) {
206− continue;
207− }
208 if let Some(pos) = helix_core::find_first_non_whitespace_char(line) {
81 if let Some(pos) = helix_core::find_first_non_whitespace_char(line) {
209− let tab_width: usize = 4;
82+ let tab_and_indent_width: usize = 4;
210 let suggested_indent = treesitter_indent_for_pos(
83 let suggested_indent = treesitter_indent_for_pos(
211 indent_query,
84 indent_query,
212 &syntax,
85 &syntax,
213− tab_width,
86+ &IndentStyle::Spaces(tab_and_indent_width as u8),
214− indent_style.indent_width(tab_width),
87+ tab_and_indent_width,
88+ tab_and_indent_width,
215 text,
89 text,
216 i,
90 i,
217 text.line_to_char(i) + pos,
91 text.line_to_char(i) + pos,
218 false,
92 false,
219 )
93 )
220− .unwrap()
94+ .unwrap();
221− .to_string(&indent_style, tab_width);
222 assert!(
95 assert!(
223 line.get_slice(..pos).map_or(false, |s| s == suggested_indent),
96 line.get_slice(..pos).map_or(false, |s| s == suggested_indent),
224− "Wrong indentation for file {:?} on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
97+ "Wrong indentation on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
225− test_name,
226 i+1,
98 i+1,
227 line.slice(..line.len_chars()-1),
99 line.slice(..line.len_chars()-1),
228 suggested_indent,
100 suggested_indent,
helix-dap/Cargo.toml+9−11
@@ -1,27 +1,25 @@
1[package]
1[package]
2name = "helix-dap"
2name = "helix-dap"
3+version = "0.6.0"
4+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5+edition = "2018"
6+license = "MPL-2.0"
3description = "DAP client implementation for Helix project"
7description = "DAP client implementation for Helix project"
4−version.workspace = true
5−authors.workspace = true
6−edition.workspace = true
7−license.workspace = true
8−rust-version.workspace = true
9−categories.workspace = true
8+categories = ["editor"]
10−repository.workspace = true
9+repository = "https://github.com/helix-editor/helix"
11−homepage.workspace = true
10+homepage = "https://helix-editor.com"
12
11
13# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
14
13
15[dependencies]
14[dependencies]
16−helix-core = { path = "../helix-core" }
15+helix-core = { version = "0.6", path = "../helix-core" }
17−
18anyhow = "1.0"
16anyhow = "1.0"
19log = "0.4"
17log = "0.4"
20serde = { version = "1.0", features = ["derive"] }
18serde = { version = "1.0", features = ["derive"] }
21serde_json = "1.0"
19serde_json = "1.0"
22thiserror = "1.0"
20thiserror = "1.0"
23tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] }
21tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] }
24−which = "5.0.0"
22+which = "4.4"
25
23
26[dev-dependencies]
24[dev-dependencies]
27fern = "0.6"
25fern = "0.6"
helix-event/Cargo.tomldeleted−16
@@ -1,16 +0,0 @@
1−[package]
2−name = "helix-event"
3−version.workspace = true
4−authors.workspace = true
5−edition.workspace = true
6−license.workspace = true
7−rust-version.workspace = true
8−categories.workspace = true
9−repository.workspace = true
10−homepage.workspace = true
11−
12−# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13−
14−[dependencies]
15−tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot"] }
16−parking_lot = { version = "0.12", features = ["send_guard"] }
helix-event/src/lib.rsdeleted−8
@@ -1,8 +0,0 @@
1−//! `helix-event` contains systems that allow (often async) communication between
2−//! different editor components without strongly coupling them. Currently this
3−//! crate only contains some smaller facilities but the intend is to add more
4−//! functionality in the future ( like a generic hook system)
5−
6−pub use redraw::{lock_frame, redraw_requested, request_redraw, start_frame, RenderLockGuard};
7−
8−mod redraw;
helix-event/src/redraw.rsdeleted−49
@@ -1,49 +0,0 @@
1−//! Signals that control when/if the editor redraws
2−
3−use std::future::Future;
4−
5−use parking_lot::{RwLock, RwLockReadGuard};
6−use tokio::sync::Notify;
7−
8−/// A `Notify` instance that can be used to (asynchronously) request
9−/// the editor the render a new frame.
10−static REDRAW_NOTIFY: Notify = Notify::const_new();
11−
12−/// A `RwLock` that prevents the next frame from being
13−/// drawn until an exclusive (write) lock can be acquired.
14−/// This allows asynchsonous tasks to acquire `non-exclusive`
15−/// locks (read) to prevent the next frame from being drawn
16−/// until a certain computation has finished.
17−static RENDER_LOCK: RwLock<()> = RwLock::new(());
18−
19−pub type RenderLockGuard = RwLockReadGuard<'static, ()>;
20−
21−/// Requests that the editor is redrawn. The redraws are debounced (currently to
22−/// 30FPS) so this can be called many times without causing a ton of frames to
23−/// be rendered.
24−pub fn request_redraw() {
25− REDRAW_NOTIFY.notify_one();
26−}
27−
28−/// Returns a future that will yield once a redraw has been asynchronously
29−/// requested using [`request_redraw`].
30−pub fn redraw_requested() -> impl Future<Output = ()> {
31− REDRAW_NOTIFY.notified()
32−}
33−
34−/// Wait until all locks acquired with [`lock_frame`] have been released.
35−/// This function is called before rendering and is intended to allow the frame
36−/// to wait for async computations that should be included in the current frame.
37−pub fn start_frame() {
38− drop(RENDER_LOCK.write());
39− // exhaust any leftover redraw notifications
40− let notify = REDRAW_NOTIFY.notified();
41− tokio::pin!(notify);
42− notify.enable();
43−}
44−
45−/// Acquires the render lock which will prevent the next frame from being drawn
46−/// until the returned guard is dropped.
47−pub fn lock_frame() -> RenderLockGuard {
48− RENDER_LOCK.read()
49−}
helix-loader/Cargo.toml+11−12
@@ -1,14 +1,13 @@
1[package]
1[package]
2name = "helix-loader"
2name = "helix-loader"
3−description = "Build bootstrapping for Helix crates"
3+version = "0.6.0"
4−version.workspace = true
4+description = "A post-modern text editor."
5−authors.workspace = true
5+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
6−edition.workspace = true
6+edition = "2021"
7−license.workspace = true
7+license = "MPL-2.0"
8−rust-version.workspace = true
9−categories.workspace = true
8+categories = ["editor"]
10−repository.workspace = true
9+repository = "https://github.com/helix-editor/helix"
11−homepage.workspace = true
10+homepage = "https://helix-editor.com"
12
11
13[[bin]]
12[[bin]]
14name = "hx-loader"
13name = "hx-loader"
@@ -20,16 +19,16 @@ serde = { version = "1.0", features = ["derive"] }
20toml = "0.7"
19toml = "0.7"
21etcetera = "0.8"
20etcetera = "0.8"
22tree-sitter.workspace = true
21tree-sitter.workspace = true
23−once_cell = "1.19"
22+once_cell = "1.18"
24log = "0.4"
23log = "0.4"
25−which = "5.0.0"
24+which = "4.4"
26
25
27# TODO: these two should be on !wasm32 only
26# TODO: these two should be on !wasm32 only
28
27
29# cloning/compiling tree-sitter grammars
28# cloning/compiling tree-sitter grammars
30cc = { version = "1" }
29cc = { version = "1" }
31threadpool = { version = "1.0" }
30threadpool = { version = "1.0" }
32−tempfile = "3.8.1"
31+tempfile = "3.7.0"
33dunce = "1.0.4"
32dunce = "1.0.4"
34
33
35[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
34[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
helix-loader/build.rs+4−14
@@ -2,17 +2,7 @@ use std::borrow::Cow;
2use std::path::Path;
2use std::path::Path;
3use std::process::Command;
3use std::process::Command;
4
4
5−const MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR");
5+const VERSION: &str = include_str!("../VERSION");
6−const MINOR: &str = env!("CARGO_PKG_VERSION_MINOR");
7−const PATCH: &str = env!("CARGO_PKG_VERSION_PATCH");
8−
9−fn get_calver() -> String {
10− if PATCH == "0" {
11− format!("{MAJOR}.{MINOR}")
12− } else {
13− format!("{MAJOR}.{MINOR}.{PATCH}")
14− }
15−}
16
6
17fn main() {
7fn main() {
18 let git_hash = Command::new("git")
8 let git_hash = Command::new("git")
@@ -22,10 +12,9 @@ fn main() {
22 .filter(|output| output.status.success())
12 .filter(|output| output.status.success())
23 .and_then(|x| String::from_utf8(x.stdout).ok());
13 .and_then(|x| String::from_utf8(x.stdout).ok());
24
14
25− let calver = get_calver();
26 let version: Cow<_> = match &git_hash {
15 let version: Cow<_> = match &git_hash {
27− Some(git_hash) => format!("{} ({})", calver, &git_hash[..8]).into(),
16+ Some(git_hash) => format!("{} ({})", VERSION, &git_hash[..8]).into(),
28− None => calver.into(),
17+ None => VERSION.into(),
29 };
18 };
30
19
31 println!(
20 println!(
@@ -33,6 +22,7 @@ fn main() {
33 std::env::var("TARGET").unwrap()
22 std::env::var("TARGET").unwrap()
34 );
23 );
35
24
25+ println!("cargo:rerun-if-changed=../VERSION");
36 println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
26 println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
37
27
38 if git_hash.is_none() {
28 if git_hash.is_none() {
helix-loader/src/lib.rs+4−13
@@ -33,9 +33,9 @@ pub fn current_working_dir() -> PathBuf {
33 path
33 path
34}
34}
35
35
36−pub fn set_current_working_dir(path: impl AsRef<Path>) -> std::io::Result<()> {
36+pub fn set_current_working_dir(path: PathBuf) -> std::io::Result<()> {
37 let path = dunce::canonicalize(path)?;
37 let path = dunce::canonicalize(path)?;
38− std::env::set_current_dir(&path)?;
38+ std::env::set_current_dir(path.clone())?;
39 let mut cwd = CWD.write().unwrap();
39 let mut cwd = CWD.write().unwrap();
40 *cwd = Some(path);
40 *cwd = Some(path);
41 Ok(())
41 Ok(())
@@ -60,8 +60,7 @@ pub fn initialize_log_file(specified_file: Option<PathBuf>) {
60/// 1. sibling directory to `CARGO_MANIFEST_DIR` (if environment variable is set)
60/// 1. sibling directory to `CARGO_MANIFEST_DIR` (if environment variable is set)
61/// 2. subdirectory of user config directory (always included)
61/// 2. subdirectory of user config directory (always included)
62/// 3. `HELIX_RUNTIME` (if environment variable is set)
62/// 3. `HELIX_RUNTIME` (if environment variable is set)
63−/// 4. `HELIX_DEFAULT_RUNTIME` (if environment variable is set *at build time*)
64−/// 5. subdirectory of path to helix executable (always included)
63+/// 4. subdirectory of path to helix executable (always included)
65///
64///
66/// Postcondition: returns at least two paths (they might not exist).
65/// Postcondition: returns at least two paths (they might not exist).
67fn prioritize_runtime_dirs() -> Vec<PathBuf> {
66fn prioritize_runtime_dirs() -> Vec<PathBuf> {
@@ -82,14 +81,6 @@ fn prioritize_runtime_dirs() -> Vec<PathBuf> {
82 rt_dirs.push(dir.into());
81 rt_dirs.push(dir.into());
83 }
82 }
84
83
85− // If this variable is set during build time, it will always be included
86− // in the lookup list. This allows downstream packagers to set a fallback
87− // directory to a location that is conventional on their distro so that they
88− // need not resort to a wrapper script or a global environment variable.
89− if let Some(dir) = std::option_env!("HELIX_DEFAULT_RUNTIME") {
90− rt_dirs.push(dir.into());
91− }
92−
93 // fallback to location of the executable being run
84 // fallback to location of the executable being run
94 // canonicalize the path in case the executable is symlinked
85 // canonicalize the path in case the executable is symlinked
95 let exe_rt_dir = std::env::current_exe()
86 let exe_rt_dir = std::env::current_exe()
@@ -289,7 +280,7 @@ mod merge_toml_tests {
289 let cwd = current_working_dir();
280 let cwd = current_working_dir();
290 assert_ne!(cwd, new_path);
281 assert_ne!(cwd, new_path);
291
282
292− set_current_working_dir(&new_path).expect("Couldn't set new path");
283+ set_current_working_dir(new_path.clone()).expect("Couldn't set new path");
293
284
294 let cwd = current_working_dir();
285 let cwd = current_working_dir();
295 assert_eq!(cwd, new_path);
286 assert_eq!(cwd, new_path);
helix-lsp/Cargo.toml+14−15
@@ -1,32 +1,31 @@
1[package]
1[package]
2name = "helix-lsp"
2name = "helix-lsp"
3+version = "0.6.0"
4+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5+edition = "2021"
6+license = "MPL-2.0"
3description = "LSP client implementation for Helix project"
7description = "LSP client implementation for Helix project"
4−version.workspace = true
5−authors.workspace = true
6−edition.workspace = true
7−license.workspace = true
8−rust-version.workspace = true
9−categories.workspace = true
8+categories = ["editor"]
10−repository.workspace = true
9+repository = "https://github.com/helix-editor/helix"
11−homepage.workspace = true
10+homepage = "https://helix-editor.com"
12
11
13# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
12# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
14
13
15[dependencies]
14[dependencies]
16−helix-core = { path = "../helix-core" }
15+helix-core = { version = "0.6", path = "../helix-core" }
17−helix-loader = { path = "../helix-loader" }
16+helix-loader = { version = "0.6", path = "../helix-loader" }
18−helix-parsec = { path = "../helix-parsec" }
17+helix-parsec = { version = "0.6", path = "../helix-parsec" }
19
18
20anyhow = "1.0"
19anyhow = "1.0"
21futures-executor = "0.3"
20futures-executor = "0.3"
22futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
21futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
23−globset = "0.4.14"
22+globset = "0.4.11"
24log = "0.4"
23log = "0.4"
25−lsp-types = { version = "0.95" }
24+lsp-types = { version = "0.94" }
26serde = { version = "1.0", features = ["derive"] }
25serde = { version = "1.0", features = ["derive"] }
27serde_json = "1.0"
26serde_json = "1.0"
28thiserror = "1.0"
27thiserror = "1.0"
29−tokio = { version = "1.35", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
28+tokio = { version = "1.29", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
30tokio-stream = "0.1.14"
29tokio-stream = "0.1.14"
31−which = "5.0.0"
30+which = "4.4"
32parking_lot = "0.12.1"
31parking_lot = "0.12.1"
helix-lsp/src/client.rs+13−87
@@ -401,22 +401,12 @@ impl Client {
401 &self,
401 &self,
402 params: R::Params,
402 params: R::Params,
403 ) -> impl Future<Output = Result<Value>>
403 ) -> impl Future<Output = Result<Value>>
404− where
405− R::Params: serde::Serialize,
406− {
407− self.call_with_timeout::<R>(params, self.req_timeout)
408− }
409−
410− fn call_with_timeout<R: lsp::request::Request>(
411− &self,
412− params: R::Params,
413− timeout_secs: u64,
414− ) -> impl Future<Output = Result<Value>>
415 where
404 where
416 R::Params: serde::Serialize,
405 R::Params: serde::Serialize,
417 {
406 {
418 let server_tx = self.server_tx.clone();
407 let server_tx = self.server_tx.clone();
419 let id = self.next_request_id();
408 let id = self.next_request_id();
409+ let timeout_secs = self.req_timeout;
420
410
421 async move {
411 async move {
422 use std::time::Duration;
412 use std::time::Duration;
@@ -558,11 +548,6 @@ impl Client {
558 dynamic_registration: Some(true),
548 dynamic_registration: Some(true),
559 relative_pattern_support: Some(false),
549 relative_pattern_support: Some(false),
560 }),
550 }),
561− file_operations: Some(lsp::WorkspaceFileOperationsClientCapabilities {
562− will_rename: Some(true),
563− did_rename: Some(true),
564− ..Default::default()
565− }),
566 ..Default::default()
551 ..Default::default()
567 }),
552 }),
568 text_document: Some(lsp::TextDocumentClientCapabilities {
553 text_document: Some(lsp::TextDocumentClientCapabilities {
@@ -667,7 +652,6 @@ impl Client {
667 version: Some(String::from(VERSION_AND_GIT_HASH)),
652 version: Some(String::from(VERSION_AND_GIT_HASH)),
668 }),
653 }),
669 locale: None, // TODO
654 locale: None, // TODO
670− work_done_progress_params: lsp::WorkDoneProgressParams::default(),
671 };
655 };
672
656
673 self.request::<lsp::request::Initialize>(params).await
657 self.request::<lsp::request::Initialize>(params).await
@@ -716,65 +700,6 @@ impl Client {
716 })
700 })
717 }
701 }
718
702
719− pub fn prepare_file_rename(
720− &self,
721− old_uri: &lsp::Url,
722− new_uri: &lsp::Url,
723− ) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
724− let capabilities = self.capabilities.get().unwrap();
725−
726− // Return early if the server does not support willRename feature
727− match &capabilities.workspace {
728− Some(workspace) => match &workspace.file_operations {
729− Some(op) => {
730− op.will_rename.as_ref()?;
731− }
732− _ => return None,
733− },
734− _ => return None,
735− }
736−
737− let files = vec![lsp::FileRename {
738− old_uri: old_uri.to_string(),
739− new_uri: new_uri.to_string(),
740− }];
741− let request = self.call_with_timeout::<lsp::request::WillRenameFiles>(
742− lsp::RenameFilesParams { files },
743− 5,
744− );
745−
746− Some(async move {
747− let json = request.await?;
748− let response: Option<lsp::WorkspaceEdit> = serde_json::from_value(json)?;
749− Ok(response.unwrap_or_default())
750− })
751− }
752−
753− pub fn did_file_rename(
754− &self,
755− old_uri: &lsp::Url,
756− new_uri: &lsp::Url,
757− ) -> Option<impl Future<Output = std::result::Result<(), Error>>> {
758− let capabilities = self.capabilities.get().unwrap();
759−
760− // Return early if the server does not support DidRename feature
761− match &capabilities.workspace {
762− Some(workspace) => match &workspace.file_operations {
763− Some(op) => {
764− op.did_rename.as_ref()?;
765− }
766− _ => return None,
767− },
768− _ => return None,
769− }
770−
771− let files = vec![lsp::FileRename {
772− old_uri: old_uri.to_string(),
773− new_uri: new_uri.to_string(),
774− }];
775− Some(self.notify::<lsp::notification::DidRenameFiles>(lsp::RenameFilesParams { files }))
776− }
777−
778 // -------------------------------------------------------------------------------------------
703 // -------------------------------------------------------------------------------------------
779 // Text document
704 // Text document
780 // -------------------------------------------------------------------------------------------
705 // -------------------------------------------------------------------------------------------
@@ -970,19 +895,20 @@ impl Client {
970 ) -> Option<impl Future<Output = Result<()>>> {
895 ) -> Option<impl Future<Output = Result<()>>> {
971 let capabilities = self.capabilities.get().unwrap();
896 let capabilities = self.capabilities.get().unwrap();
972
897
973− let include_text = match &capabilities.text_document_sync.as_ref()? {
898+ let include_text = match &capabilities.text_document_sync {
974− lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
899+ Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
975− save: options,
900+ save: Some(options),
976 ..
901 ..
977− }) => match options.as_ref()? {
902+ })) => match options {
978 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
903 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
979 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
904 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
980 include_text,
905 include_text,
981 }) => include_text.unwrap_or(false),
906 }) => include_text.unwrap_or(false),
982− lsp::TextDocumentSyncSaveOptions::Supported(false) => return None,
907+ // Supported(false)
908+ _ => return None,
983 },
909 },
984− // see: https://github.com/microsoft/language-server-protocol/issues/288
910+ // unsupported
985− lsp::TextDocumentSyncCapability::Kind(..) => false,
911+ _ => return None,
986 };
912 };
987
913
988 Some(self.notify::<lsp::notification::DidSaveTextDocument>(
914 Some(self.notify::<lsp::notification::DidSaveTextDocument>(
@@ -1045,12 +971,12 @@ impl Client {
1045 ) -> Option<impl Future<Output = Result<Value>>> {
971 ) -> Option<impl Future<Output = Result<Value>>> {
1046 let capabilities = self.capabilities.get().unwrap();
972 let capabilities = self.capabilities.get().unwrap();
1047
973
1048− // Return early if the server does not support resolving code actions.
974+ // Return early if the server does not support resolving code action.
1049− match capabilities.code_action_provider {
975+ match capabilities.completion_provider {
1050− Some(lsp::CodeActionProviderCapability::Options(lsp::CodeActionOptions {
976+ Some(lsp::CompletionOptions {
1051 resolve_provider: Some(true),
977 resolve_provider: Some(true),
1052 ..
978 ..
1053− })) => (),
979+ }) => (),
1054 _ => return None,
980 _ => return None,
1055 }
981 }
1056
982
helix-lsp/src/lib.rs+28−32
@@ -445,36 +445,36 @@ pub mod util {
445 // the tabstop closest to the range simply replaces `head` while anchor remains in place
445 // the tabstop closest to the range simply replaces `head` while anchor remains in place
446 // the remaining tabstops receive their own single-width cursor
446 // the remaining tabstops receive their own single-width cursor
447 if range.head < range.anchor {
447 if range.head < range.anchor {
448− let last_idx = tabstops.len() - 1;
449− let last_tabstop = tabstop_anchor + tabstops[last_idx].0;
448+ let first_tabstop = tabstop_anchor + tabstops[0].1;
450
449
451 // if selection is forward but was moved to the right it is
450 // if selection is forward but was moved to the right it is
452 // contained entirely in the replacement text, just do a point
451 // contained entirely in the replacement text, just do a point
453 // selection (fallback below)
452 // selection (fallback below)
454− if range.anchor > last_tabstop {
453+ if range.anchor >= first_tabstop {
455− let range = Range::new(range.anchor, last_tabstop);
454+ let range = Range::new(range.anchor, first_tabstop);
456 mapped_selection.push(range);
455 mapped_selection.push(range);
457− let rem_tabstops = tabstops[..last_idx]
456+ let rem_tabstops = tabstops[1..]
458 .iter()
457 .iter()
459− .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
458+ .map(|tabstop| Range::point(tabstop_anchor + tabstop.1));
460 mapped_selection.extend(rem_tabstops);
459 mapped_selection.extend(rem_tabstops);
461 continue;
460 continue;
462 }
461 }
463 } else {
462 } else {
463+ let last_idx = tabstops.len() - 1;
464− let first_tabstop = tabstop_anchor + tabstops[0].0;
464+ let last_tabstop = tabstop_anchor + tabstops[last_idx].1;
465
465
466 // if selection is forward but was moved to the right it is
466 // if selection is forward but was moved to the right it is
467 // contained entirely in the replacement text, just do a point
467 // contained entirely in the replacement text, just do a point
468 // selection (fallback below)
468 // selection (fallback below)
469− if range.anchor < first_tabstop {
469+ if range.anchor <= last_tabstop {
470 // we can't properly compute the the next grapheme
470 // we can't properly compute the the next grapheme
471 // here because the transaction hasn't been applied yet
471 // here because the transaction hasn't been applied yet
472 // that is not a problem because the range gets grapheme aligned anyway
472 // that is not a problem because the range gets grapheme aligned anyway
473 // tough so just adding one will always cause head to be grapheme
473 // tough so just adding one will always cause head to be grapheme
474 // aligned correctly when applied to the document
474 // aligned correctly when applied to the document
475− let range = Range::new(range.anchor, first_tabstop + 1);
475+ let range = Range::new(range.anchor, last_tabstop + 1);
476 mapped_selection.push(range);
476 mapped_selection.push(range);
477− let rem_tabstops = tabstops[1..]
477+ let rem_tabstops = tabstops[..last_idx]
478 .iter()
478 .iter()
479 .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
479 .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
480 mapped_selection.extend(rem_tabstops);
480 mapped_selection.extend(rem_tabstops);
@@ -749,40 +749,36 @@ impl Registry {
749 }
749 }
750 }
750 }
751
751
752− pub fn get<'a>(
752+ pub fn get(
753− &'a mut self,
753+ &mut self,
754− language_config: &'a LanguageConfiguration,
754+ language_config: &LanguageConfiguration,
755− doc_path: Option<&'a std::path::PathBuf>,
755+ doc_path: Option<&std::path::PathBuf>,
756− root_dirs: &'a [PathBuf],
756+ root_dirs: &[PathBuf],
757 enable_snippets: bool,
757 enable_snippets: bool,
758− ) -> impl Iterator<Item = (LanguageServerName, Result<Arc<Client>>)> + 'a {
758+ ) -> Result<HashMap<LanguageServerName, Arc<Client>>> {
759+ language_config
759− language_config.language_servers.iter().map(
760+ .language_servers
761+ .iter()
760− move |LanguageServerFeatures { name, .. }| {
762+ .map(|LanguageServerFeatures { name, .. }| {
761 if let Some(clients) = self.inner.get(name) {
763 if let Some(clients) = self.inner.get(name) {
762 if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| {
764 if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| {
763 client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
765 client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
764 }) {
766 }) {
765− return (name.to_owned(), Ok(client.clone()));
767+ return Ok((name.to_owned(), client.clone()));
766 }
768 }
767 }
769 }
768− match self.start_client(
770+ let client = self.start_client(
769 name.clone(),
771 name.clone(),
770 language_config,
772 language_config,
771 doc_path,
773 doc_path,
772 root_dirs,
774 root_dirs,
773 enable_snippets,
775 enable_snippets,
774− ) {
776+ )?;
775− Ok(client) => {
777+ let clients = self.inner.entry(name.clone()).or_default();
776− self.inner
777− .entry(name.to_owned())
778− .or_default()
779− .push(client.clone());
778+ clients.push(client.clone());
780− (name.clone(), Ok(client))
779+ Ok((name.clone(), client))
781− }
780+ })
782− Err(err) => (name.to_owned(), Err(err)),
781+ .collect()
783− }
784− },
785− )
786 }
782 }
787
783
788 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
784 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
helix-parsec/Cargo.toml+7−8
@@ -1,14 +1,13 @@
1[package]
1[package]
2name = "helix-parsec"
2name = "helix-parsec"
3+version = "0.6.0"
4+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5+edition = "2021"
6+license = "MPL-2.0"
3description = "Parser combinators for Helix"
7description = "Parser combinators for Helix"
8+categories = ["editor"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
4include = ["src/**/*", "README.md"]
11include = ["src/**/*", "README.md"]
5−version.workspace = true
6−authors.workspace = true
7−edition.workspace = true
8−license.workspace = true
9−rust-version.workspace = true
10−categories.workspace = true
11−repository.workspace = true
12−homepage.workspace = true
13
12
14[dependencies]
13[dependencies]
helix-term/Cargo.toml+25−30
@@ -1,16 +1,16 @@
1[package]
1[package]
2name = "helix-term"
2name = "helix-term"
3+version = "0.6.0"
3description = "A post-modern text editor."
4description = "A post-modern text editor."
5+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
6+edition = "2021"
7+license = "MPL-2.0"
8+categories = ["editor", "command-line-utilities"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
4include = ["src/**/*", "README.md"]
11include = ["src/**/*", "README.md"]
5default-run = "hx"
12default-run = "hx"
6−version.workspace = true
7−authors.workspace = true
8−edition.workspace = true
9−license.workspace = true
10−rust-version.workspace = true
13+rust-version = "1.65"
11−categories.workspace = true
12−repository.workspace = true
13−homepage.workspace = true
14
14
15[features]
15[features]
16default = ["git"]
16default = ["git"]
@@ -23,22 +23,21 @@ name = "hx"
23path = "src/main.rs"
23path = "src/main.rs"
24
24
25[dependencies]
25[dependencies]
26−helix-core = { path = "../helix-core" }
26+helix-core = { version = "0.6", path = "../helix-core" }
27−helix-event = { path = "../helix-event" }
28−helix-view = { path = "../helix-view" }
27+helix-view = { version = "0.6", path = "../helix-view" }
29−helix-lsp = { path = "../helix-lsp" }
28+helix-lsp = { version = "0.6", path = "../helix-lsp" }
30−helix-dap = { path = "../helix-dap" }
29+helix-dap = { version = "0.6", path = "../helix-dap" }
31−helix-vcs = { path = "../helix-vcs" }
30+helix-vcs = { version = "0.6", path = "../helix-vcs" }
32−helix-loader = { path = "../helix-loader" }
31+helix-loader = { version = "0.6", path = "../helix-loader" }
33
32
34anyhow = "1"
33anyhow = "1"
35−once_cell = "1.19"
34+once_cell = "1.18"
36
35
37−which = "5.0.0"
36+which = "4.4"
38
37
39tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
38tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
40tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
39tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
41−crossterm = { version = "0.27", features = ["event-stream"] }
40+crossterm = { version = "0.26", features = ["event-stream"] }
42signal-hook = "0.3"
41signal-hook = "0.3"
43tokio-stream = "0.1"
42tokio-stream = "0.1"
44futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
43futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
@@ -50,17 +49,13 @@ chrono = { version = "0.4", default-features = false, features = ["clock"] }
50log = "0.4"
49log = "0.4"
51
50
52# File picker
51# File picker
53−nucleo.workspace = true
52+fuzzy-matcher = "0.3"
54ignore = "0.4"
53ignore = "0.4"
55# markdown doc rendering
54# markdown doc rendering
56pulldown-cmark = { version = "0.9", default-features = false }
55pulldown-cmark = { version = "0.9", default-features = false }
57# file type detection
56# file type detection
58content_inspector = "0.2.4"
57content_inspector = "0.2.4"
59
58
60−# opening URLs
61−open = "5.0.1"
62−url = "2.5.0"
63−
64# config
59# config
65toml = "0.7"
60toml = "0.7"
66
61
@@ -68,20 +63,20 @@ serde_json = "1.0"
68serde = { version = "1.0", features = ["derive"] }
63serde = { version = "1.0", features = ["derive"] }
69
64
70# ripgrep for global search
65# ripgrep for global search
71−grep-regex = "0.1.12"
66+grep-regex = "0.1.11"
72−grep-searcher = "0.1.13"
67+grep-searcher = "0.1.11"
73
68
74[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
69[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
75signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
70signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
76−libc = "0.2.151"
71+libc = "0.2.147"
77
72
78[target.'cfg(target_os = "macos")'.dependencies]
73[target.'cfg(target_os = "macos")'.dependencies]
79−crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] }
74+crossterm = { version = "0.26", features = ["event-stream", "use-dev-tty"] }
80
75
81[build-dependencies]
76[build-dependencies]
82−helix-loader = { path = "../helix-loader" }
77+helix-loader = { version = "0.6", path = "../helix-loader" }
83
78
84[dev-dependencies]
79[dev-dependencies]
85smallvec = "1.11"
80smallvec = "1.11"
86−indoc = "2.0.4"
81+indoc = "2.0.3"
87−tempfile = "3.8.1"
82+tempfile = "3.7.0"
helix-term/src/application.rs+81−114
@@ -1,7 +1,6 @@
1use arc_swap::{access::Map, ArcSwap};
1use arc_swap::{access::Map, ArcSwap};
2use futures_util::Stream;
2use futures_util::Stream;
3use helix_core::{
3use helix_core::{
4− chars::char_is_word,
5 diagnostic::{DiagnosticTag, NumberOrString},
4 diagnostic::{DiagnosticTag, NumberOrString},
6 path::get_relative_path,
5 path::get_relative_path,
7 pos_at_coords, syntax, Selection,
6 pos_at_coords, syntax, Selection,
@@ -34,9 +33,12 @@ use crate::{
34};
33};
35
34
36use log::{debug, error, warn};
35use log::{debug, error, warn};
37−#[cfg(not(feature = "integration"))]
38−use std::io::stdout;
36+use std::{
39−use std::{collections::btree_map::Entry, io::stdin, path::Path, sync::Arc};
37+ collections::btree_map::Entry,
38+ io::{stdin, stdout},
39+ path::Path,
40+ sync::Arc,
41+};
40
42
41use anyhow::{Context, Error};
43use anyhow::{Context, Error};
42
44
@@ -161,21 +163,17 @@ impl Application {
161 let path = helix_loader::runtime_file(Path::new("tutor"));
163 let path = helix_loader::runtime_file(Path::new("tutor"));
162 editor.open(&path, Action::VerticalSplit)?;
164 editor.open(&path, Action::VerticalSplit)?;
163 // Unset path to prevent accidentally saving to the original tutor file.
165 // Unset path to prevent accidentally saving to the original tutor file.
164− doc_mut!(editor).set_path(None);
166+ doc_mut!(editor).set_path(None)?;
165 } else if !args.files.is_empty() {
167 } else if !args.files.is_empty() {
166− let mut files_it = args.files.into_iter().peekable();
168+ let first = &args.files[0].0; // we know it's not empty
167−
168− // If the first file is a directory, skip it and open a picker
169− if let Some((first, _)) = files_it.next_if(|(p, _)| p.is_dir()) {
169+ if first.is_dir() {
170+ helix_loader::set_current_working_dir(first.clone())?;
171+ editor.new_file(Action::VerticalSplit);
170− let picker = ui::file_picker(first, &config.load().editor);
172+ let picker = ui::file_picker(".".into(), &config.load().editor);
171 compositor.push(Box::new(overlaid(picker)));
173 compositor.push(Box::new(overlaid(picker)));
172− }
174+ } else {
173−
174− // If there are any more files specified, open them
175− if files_it.peek().is_some() {
176− let mut nr_of_files = 0;
175+ let nr_of_files = args.files.len();
177− for (file, pos) in files_it {
176+ for (i, (file, pos)) in args.files.into_iter().enumerate() {
178− nr_of_files += 1;
179 if file.is_dir() {
177 if file.is_dir() {
180 return Err(anyhow::anyhow!(
178 return Err(anyhow::anyhow!(
181 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
179 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
@@ -187,7 +185,7 @@ impl Application {
187 // option. If neither of those two arguments are passed
185 // option. If neither of those two arguments are passed
188 // in, just load the files normally.
186 // in, just load the files normally.
189 let action = match args.split {
187 let action = match args.split {
190− _ if nr_of_files == 1 => Action::VerticalSplit,
188+ _ if i == 0 => Action::VerticalSplit,
191 Some(Layout::Vertical) => Action::VerticalSplit,
189 Some(Layout::Vertical) => Action::VerticalSplit,
192 Some(Layout::Horizontal) => Action::HorizontalSplit,
190 Some(Layout::Horizontal) => Action::HorizontalSplit,
193 None => Action::Load,
191 None => Action::Load,
@@ -214,8 +212,6 @@ impl Application {
214 // does not affect views without pos since it is at the top
212 // does not affect views without pos since it is at the top
215 let (view, doc) = current!(editor);
213 let (view, doc) = current!(editor);
216 align_view(doc, view, Align::Center);
214 align_view(doc, view, Align::Center);
217− } else {
218− editor.new_file(Action::VerticalSplit);
219 }
215 }
220 } else if stdin().is_tty() || cfg!(feature = "integration") {
216 } else if stdin().is_tty() || cfg!(feature = "integration") {
221 editor.new_file(Action::VerticalSplit);
217 editor.new_file(Action::VerticalSplit);
@@ -258,19 +254,22 @@ impl Application {
258 }
254 }
259
255
260 async fn render(&mut self) {
256 async fn render(&mut self) {
261− if self.compositor.full_redraw {
262− self.terminal.clear().expect("Cannot clear the terminal");
263− self.compositor.full_redraw = false;
264− }
265−
266 let mut cx = crate::compositor::Context {
257 let mut cx = crate::compositor::Context {
267 editor: &mut self.editor,
258 editor: &mut self.editor,
268 jobs: &mut self.jobs,
259 jobs: &mut self.jobs,
269 scroll: None,
260 scroll: None,
270 };
261 };
271
262
272− helix_event::start_frame();
263+ // Acquire mutable access to the redraw_handle lock
264+ // to ensure that there are no tasks running that want to block rendering
265+ drop(cx.editor.redraw_handle.1.write().await);
273 cx.editor.needs_redraw = false;
266 cx.editor.needs_redraw = false;
267+ {
268+ // exhaust any leftover redraw notifications
269+ let notify = cx.editor.redraw_handle.0.notified();
270+ tokio::pin!(notify);
271+ notify.enable();
272+ }
274
273
275 let area = self
274 let area = self
276 .terminal
275 .terminal
@@ -292,7 +291,7 @@ impl Application {
292
291
293 pub async fn event_loop<S>(&mut self, input_stream: &mut S)
292 pub async fn event_loop<S>(&mut self, input_stream: &mut S)
294 where
293 where
295− S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
294+ S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
296 {
295 {
297 self.render().await;
296 self.render().await;
298
297
@@ -305,7 +304,7 @@ impl Application {
305
304
306 pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool
305 pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool
307 where
306 where
308− S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
307+ S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
309 {
308 {
310 loop {
309 loop {
311 if self.editor.should_close() {
310 if self.editor.should_close() {
@@ -558,7 +557,16 @@ impl Application {
558 let bytes = doc_save_event.text.len_bytes();
557 let bytes = doc_save_event.text.len_bytes();
559
558
560 if doc.path() != Some(&doc_save_event.path) {
559 if doc.path() != Some(&doc_save_event.path) {
561− doc.set_path(Some(&doc_save_event.path));
560+ if let Err(err) = doc.set_path(Some(&doc_save_event.path)) {
561+ log::error!(
562+ "error setting path for doc '{:?}': {}",
563+ doc.path(),
564+ err.to_string(),
565+ );
566+
567+ self.editor.set_error(err.to_string());
568+ return;
569+ }
562
570
563 let loader = self.editor.syn_loader.clone();
571 let loader = self.editor.syn_loader.clone();
564
572
@@ -594,7 +602,7 @@ impl Application {
594 EditorEvent::LanguageServerMessage((id, call)) => {
602 EditorEvent::LanguageServerMessage((id, call)) => {
595 self.handle_language_server_message(call, id).await;
603 self.handle_language_server_message(call, id).await;
596 // limit render calls for fast language server messages
604 // limit render calls for fast language server messages
597− helix_event::request_redraw();
605+ self.editor.redraw_handle.0.notify_one();
598 }
606 }
599 EditorEvent::DebuggerEvent(payload) => {
607 EditorEvent::DebuggerEvent(payload) => {
600 let needs_render = self.editor.handle_debugger_message(payload).await;
608 let needs_render = self.editor.handle_debugger_message(payload).await;
@@ -602,9 +610,6 @@ impl Application {
602 self.render().await;
610 self.render().await;
603 }
611 }
604 }
612 }
605− EditorEvent::Redraw => {
606− self.render().await;
607− }
608 EditorEvent::IdleTimer => {
613 EditorEvent::IdleTimer => {
609 self.editor.clear_idle_timer();
614 self.editor.clear_idle_timer();
610 self.handle_idle_timeout().await;
615 self.handle_idle_timeout().await;
@@ -619,7 +624,10 @@ impl Application {
619 false
624 false
620 }
625 }
621
626
622− pub async fn handle_terminal_events(&mut self, event: std::io::Result<CrosstermEvent>) {
627+ pub async fn handle_terminal_events(
628+ &mut self,
629+ event: Result<CrosstermEvent, crossterm::ErrorKind>,
630+ ) {
623 let mut cx = crate::compositor::Context {
631 let mut cx = crate::compositor::Context {
624 editor: &mut self.editor,
632 editor: &mut self.editor,
625 jobs: &mut self.jobs,
633 jobs: &mut self.jobs,
@@ -718,7 +726,7 @@ impl Application {
718 ));
726 ));
719 }
727 }
720 }
728 }
721− Notification::PublishDiagnostics(mut params) => {
729+ Notification::PublishDiagnostics(params) => {
722 let path = match params.uri.to_file_path() {
730 let path = match params.uri.to_file_path() {
723 Ok(path) => path,
731 Ok(path) => path,
724 Err(_) => {
732 Err(_) => {
@@ -732,69 +740,31 @@ impl Application {
732 return;
740 return;
733 }
741 }
734 let offset_encoding = language_server.offset_encoding();
742 let offset_encoding = language_server.offset_encoding();
735− // have to inline the function because of borrow checking...
743+ let doc = self.editor.document_by_path_mut(&path).filter(|doc| {
736− let doc = self.editor.documents.values_mut()
737− .find(|doc| doc.path().map(|p| p == &path).unwrap_or(false))
738− .filter(|doc| {
739− if let Some(version) = params.version {
744+ if let Some(version) = params.version {
740− if version != doc.version() {
745+ if version != doc.version() {
741− log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
746+ log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
742− return false;
747+ return false;
743− }
744 }
748 }
749+ }
750+
745− true
751+ true
746− });
752+ });
747
753
748 if let Some(doc) = doc {
754 if let Some(doc) = doc {
749− let lang_conf = doc.language.clone();
755+ let lang_conf = doc.language_config();
750− let text = doc.text().clone();
756+ let text = doc.text();
751−
752− let mut unchaged_diag_sources_ = Vec::new();
753− if let Some(lang_conf) = &lang_conf {
754− if let Some(old_diagnostics) =
755− self.editor.diagnostics.get(&params.uri)
756− {
757− if !lang_conf.persistent_diagnostic_sources.is_empty() {
758− // Sort diagnostics first by severity and then by line numbers.
759− // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
760− params
761− .diagnostics
762− .sort_unstable_by_key(|d| (d.severity, d.range.start));
763− }
764− for source in &lang_conf.persistent_diagnostic_sources {
765− let new_diagnostics = params
766− .diagnostics
767− .iter()
768− .filter(|d| d.source.as_ref() == Some(source));
769− let old_diagnostics = old_diagnostics
770− .iter()
771− .filter(|(d, d_server)| {
772− *d_server == server_id
773− && d.source.as_ref() == Some(source)
774− })
775− .map(|(d, _)| d);
776− if new_diagnostics.eq(old_diagnostics) {
777− unchaged_diag_sources_.push(source.clone())
778− }
779− }
780− }
781− }
782
757
783− let unchaged_diag_sources = &unchaged_diag_sources_;
758+ let diagnostics = params
784− let diagnostics =
759+ .diagnostics
760+ .iter()
785− params.diagnostics.iter().filter_map(move |diagnostic| {
761+ .filter_map(|diagnostic| {
786 use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
762 use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
787 use lsp::DiagnosticSeverity;
763 use lsp::DiagnosticSeverity;
788
764
789− if diagnostic.source.as_ref().map_or(false, |source| {
790− unchaged_diag_sources.contains(source)
791− }) {
792− return None;
793− }
794−
795 // TODO: convert inside server
765 // TODO: convert inside server
796 let start = if let Some(start) = lsp_pos_to_pos(
766 let start = if let Some(start) = lsp_pos_to_pos(
797− &text,
767+ text,
798 diagnostic.range.start,
768 diagnostic.range.start,
799 offset_encoding,
769 offset_encoding,
800 ) {
770 ) {
@@ -805,13 +775,14 @@ impl Application {
805 };
775 };
806
776
807 let end = if let Some(end) =
777 let end = if let Some(end) =
808− lsp_pos_to_pos(&text, diagnostic.range.end, offset_encoding)
778+ lsp_pos_to_pos(text, diagnostic.range.end, offset_encoding)
809 {
779 {
810 end
780 end
811 } else {
781 } else {
812 log::warn!("lsp position out of bounds - {:?}", diagnostic);
782 log::warn!("lsp position out of bounds - {:?}", diagnostic);
813 return None;
783 return None;
814 };
784 };
785+
815 let severity =
786 let severity =
816 diagnostic.severity.map(|severity| match severity {
787 diagnostic.severity.map(|severity| match severity {
817 DiagnosticSeverity::ERROR => Error,
788 DiagnosticSeverity::ERROR => Error,
@@ -824,7 +795,7 @@ impl Application {
824 ),
795 ),
825 });
796 });
826
797
827− if let Some(lang_conf) = &lang_conf {
798+ if let Some(lang_conf) = lang_conf {
828 if let Some(severity) = severity {
799 if let Some(severity) = severity {
829 if severity < lang_conf.diagnostic_severity {
800 if severity < lang_conf.diagnostic_severity {
830 return None;
801 return None;
@@ -863,17 +834,8 @@ impl Application {
863 Vec::new()
834 Vec::new()
864 };
835 };
865
836
866− let ends_at_word = start != end
867− && end != 0
868− && text.get_char(end - 1).map_or(false, char_is_word);
869− let starts_at_word = start != end
870− && text.get_char(start).map_or(false, char_is_word);
871−
872 Some(Diagnostic {
837 Some(Diagnostic {
873 range: Range { start, end },
838 range: Range { start, end },
874− ends_at_word,
875− starts_at_word,
876− zero_width: start == end,
877 line: diagnostic.range.start.line as usize,
839 line: diagnostic.range.start.line as usize,
878 message: diagnostic.message.clone(),
840 message: diagnostic.message.clone(),
879 severity,
841 severity,
@@ -883,33 +845,38 @@ impl Application {
883 data: diagnostic.data.clone(),
845 data: diagnostic.data.clone(),
884 language_server_id: server_id,
846 language_server_id: server_id,
885 })
847 })
886− });
848+ })
849+ .collect();
887
850
888− doc.replace_diagnostics(diagnostics, unchaged_diag_sources, server_id);
851+ doc.replace_diagnostics(diagnostics, server_id);
889 }
852 }
890
853
891− let diagnostics = params.diagnostics.into_iter().map(|d| (d, server_id));
854+ let mut diagnostics = params
855+ .diagnostics
856+ .into_iter()
857+ .map(|d| (d, server_id))
858+ .collect();
892
859
893 // Insert the original lsp::Diagnostics here because we may have no open document
860 // Insert the original lsp::Diagnostics here because we may have no open document
894 // for diagnosic message and so we can't calculate the exact position.
861 // for diagnosic message and so we can't calculate the exact position.
895 // When using them later in the diagnostics picker, we calculate them on-demand.
862 // When using them later in the diagnostics picker, we calculate them on-demand.
896− let diagnostics = match self.editor.diagnostics.entry(params.uri) {
863+ match self.editor.diagnostics.entry(params.uri) {
897 Entry::Occupied(o) => {
864 Entry::Occupied(o) => {
898 let current_diagnostics = o.into_mut();
865 let current_diagnostics = o.into_mut();
899 // there may entries of other language servers, which is why we can't overwrite the whole entry
866 // there may entries of other language servers, which is why we can't overwrite the whole entry
900 current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
867 current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
901− current_diagnostics.extend(diagnostics);
868+ current_diagnostics.append(&mut diagnostics);
902− current_diagnostics
903 // Sort diagnostics first by severity and then by line numbers.
869 // Sort diagnostics first by severity and then by line numbers.
870+ // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
871+ current_diagnostics
872+ .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
873+ }
874+ Entry::Vacant(v) => {
875+ diagnostics
876+ .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
877+ v.insert(diagnostics);
904 }
878 }
905− Entry::Vacant(v) => v.insert(diagnostics.collect()),
906 };
879 };
907−
908− // Sort diagnostics first by severity and then by line numbers.
909− // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
910− diagnostics.sort_unstable_by_key(|(d, server_id)| {
911− (d.severity, d.range.start, *server_id)
912− });
913 }
880 }
914 Notification::ShowMessage(params) => {
881 Notification::ShowMessage(params) => {
915 log::warn!("unhandled window/showMessage: {:?}", params);
882 log::warn!("unhandled window/showMessage: {:?}", params);
@@ -1201,7 +1168,7 @@ impl Application {
1201
1168
1202 pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
1169 pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
1203 where
1170 where
1204− S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
1171+ S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
1205 {
1172 {
1206 self.claim_term().await?;
1173 self.claim_term().await?;
1207
1174
helix-term/src/args.rs−29
@@ -17,14 +17,12 @@ pub struct Args {
17 pub log_file: Option<PathBuf>,
17 pub log_file: Option<PathBuf>,
18 pub config_file: Option<PathBuf>,
18 pub config_file: Option<PathBuf>,
19 pub files: Vec<(PathBuf, Position)>,
19 pub files: Vec<(PathBuf, Position)>,
20− pub working_directory: Option<PathBuf>,
21}
20}
22
21
23impl Args {
22impl Args {
24 pub fn parse_args() -> Result<Args> {
23 pub fn parse_args() -> Result<Args> {
25 let mut args = Args::default();
24 let mut args = Args::default();
26 let mut argv = std::env::args().peekable();
25 let mut argv = std::env::args().peekable();
27− let mut line_number = 0;
28
26
29 argv.next(); // skip the program, we don't care about that
27 argv.next(); // skip the program, we don't care about that
30
28
@@ -61,20 +59,6 @@ impl Args {
61 Some(path) => args.log_file = Some(path.into()),
59 Some(path) => args.log_file = Some(path.into()),
62 None => anyhow::bail!("--log must specify a path to write"),
60 None => anyhow::bail!("--log must specify a path to write"),
63 },
61 },
64− "-w" | "--working-dir" => match argv.next().as_deref() {
65− Some(path) => {
66− args.working_directory = if Path::new(path).is_dir() {
67− Some(PathBuf::from(path))
68− } else {
69− anyhow::bail!(
70− "--working-dir specified does not exist or is not a directory"
71− )
72− }
73− }
74− None => {
75− anyhow::bail!("--working-dir must specify an initial working directory")
76− }
77− },
78 arg if arg.starts_with("--") => {
62 arg if arg.starts_with("--") => {
79 anyhow::bail!("unexpected double dash argument: {}", arg)
63 anyhow::bail!("unexpected double dash argument: {}", arg)
80 }
64 }
@@ -89,13 +73,6 @@ impl Args {
89 }
73 }
90 }
74 }
91 }
75 }
92− arg if arg.starts_with('+') => {
93− let arg = &arg[1..];
94− line_number = match arg.parse::<usize>() {
95− Ok(n) => n.saturating_sub(1),
96− _ => anyhow::bail!("bad line number after +"),
97− };
98− }
99 arg => args.files.push(parse_file(arg)),
76 arg => args.files.push(parse_file(arg)),
100 }
77 }
101 }
78 }
@@ -105,12 +82,6 @@ impl Args {
105 args.files.push(parse_file(&arg));
82 args.files.push(parse_file(&arg));
106 }
83 }
107
84
108− if let Some(file) = args.files.first_mut() {
109− if line_number != 0 {
110− file.1.row = line_number;
111− }
112− }
113−
114 Ok(args)
85 Ok(args)
115 }
86 }
116}
87}
helix-term/src/commands.rs+351−545
@@ -32,6 +32,7 @@ use helix_core::{
32 RopeReader, RopeSlice, Selection, SmallVec, Tendril, Transaction,
32 RopeReader, RopeSlice, Selection, SmallVec, Tendril, Transaction,
33};
33};
34use helix_view::{
34use helix_view::{
35+ clipboard::ClipboardType,
35 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
36 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
36 editor::{Action, CompleteAction},
37 editor::{Action, CompleteAction},
37 info::Info,
38 info::Info,
@@ -43,6 +44,7 @@ use helix_view::{
43};
44};
44
45
45use anyhow::{anyhow, bail, ensure, Context as _};
46use anyhow::{anyhow, bail, ensure, Context as _};
47+use fuzzy_matcher::FuzzyMatcher;
46use insert::*;
48use insert::*;
47use movement::Movement;
49use movement::Movement;
48
50
@@ -59,14 +61,9 @@ use crate::{
59};
61};
60
62
61use crate::job::{self, Jobs};
63use crate::job::{self, Jobs};
62−use futures_util::{stream::FuturesUnordered, TryStreamExt};
64+use futures_util::{stream::FuturesUnordered, StreamExt, TryStreamExt};
63−use std::{
64− collections::{HashMap, HashSet},
65+use std::{collections::HashMap, fmt, future::Future};
65− fmt,
66− future::Future,
67− io::Read,
68− num::NonZeroUsize,
66+use std::{collections::HashSet, num::NonZeroUsize};
69−};
70
67
71use std::{
68use std::{
72 borrow::Cow,
69 borrow::Cow,
@@ -75,11 +72,11 @@ use std::{
75
72
76use once_cell::sync::Lazy;
73use once_cell::sync::Lazy;
77use serde::de::{self, Deserialize, Deserializer};
74use serde::de::{self, Deserialize, Deserializer};
78−use url::Url;
79
75
80use grep_regex::RegexMatcherBuilder;
76use grep_regex::RegexMatcherBuilder;
81use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
77use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
82use ignore::{DirEntry, WalkBuilder, WalkState};
78use ignore::{DirEntry, WalkBuilder, WalkState};
79+use tokio_stream::wrappers::UnboundedReceiverStream;
83
80
84pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
81pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
85
82
@@ -251,8 +248,6 @@ impl MappableCommand {
251 move_prev_long_word_start, "Move to start of previous long word",
248 move_prev_long_word_start, "Move to start of previous long word",
252 move_next_long_word_end, "Move to end of next long word",
249 move_next_long_word_end, "Move to end of next long word",
253 move_prev_long_word_end, "Move to end of previous long word",
250 move_prev_long_word_end, "Move to end of previous long word",
254− move_parent_node_end, "Move to end of the parent node",
255− move_parent_node_start, "Move to beginning of the parent node",
256 extend_next_word_start, "Extend to start of next word",
251 extend_next_word_start, "Extend to start of next word",
257 extend_prev_word_start, "Extend to start of previous word",
252 extend_prev_word_start, "Extend to start of previous word",
258 extend_next_word_end, "Extend to end of next word",
253 extend_next_word_end, "Extend to end of next word",
@@ -261,8 +256,6 @@ impl MappableCommand {
261 extend_prev_long_word_start, "Extend to start of previous long word",
256 extend_prev_long_word_start, "Extend to start of previous long word",
262 extend_next_long_word_end, "Extend to end of next long word",
257 extend_next_long_word_end, "Extend to end of next long word",
263 extend_prev_long_word_end, "Extend to end of prev long word",
258 extend_prev_long_word_end, "Extend to end of prev long word",
264− extend_parent_node_end, "Extend to end of the parent node",
265− extend_parent_node_start, "Extend to beginning of the parent node",
266 find_till_char, "Move till next occurrence of char",
259 find_till_char, "Move till next occurrence of char",
267 find_next_char, "Move to next occurrence of char",
260 find_next_char, "Move to next occurrence of char",
268 extend_till_char, "Extend till next occurrence of char",
261 extend_till_char, "Extend till next occurrence of char",
@@ -337,7 +330,7 @@ impl MappableCommand {
337 goto_implementation, "Goto implementation",
330 goto_implementation, "Goto implementation",
338 goto_file_start, "Goto line number <n> else file start",
331 goto_file_start, "Goto line number <n> else file start",
339 goto_file_end, "Goto file end",
332 goto_file_end, "Goto file end",
340− goto_file, "Goto files/URLs in selection",
333+ goto_file, "Goto files in selection",
341 goto_file_hsplit, "Goto files in selection (hsplit)",
334 goto_file_hsplit, "Goto files in selection (hsplit)",
342 goto_file_vsplit, "Goto files in selection (vsplit)",
335 goto_file_vsplit, "Goto files in selection (vsplit)",
343 goto_reference, "Goto references",
336 goto_reference, "Goto references",
@@ -369,7 +362,6 @@ impl MappableCommand {
369 extend_to_line_end, "Extend to line end",
362 extend_to_line_end, "Extend to line end",
370 extend_to_line_end_newline, "Extend to line end",
363 extend_to_line_end_newline, "Extend to line end",
371 signature_help, "Show signature help",
364 signature_help, "Show signature help",
372− smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.",
373 insert_tab, "Insert tab char",
365 insert_tab, "Insert tab char",
374 insert_newline, "Insert newline char",
366 insert_newline, "Insert newline char",
375 delete_char_backward, "Delete previous char",
367 delete_char_backward, "Delete previous char",
@@ -384,8 +376,6 @@ impl MappableCommand {
384 later, "Move forward in history",
376 later, "Move forward in history",
385 commit_undo_checkpoint, "Commit changes to new checkpoint",
377 commit_undo_checkpoint, "Commit changes to new checkpoint",
386 yank, "Yank selection",
378 yank, "Yank selection",
387− yank_to_clipboard, "Yank selections to clipboard",
388− yank_to_primary_clipboard, "Yank selections to primary clipboard",
389 yank_joined, "Join and yank selections",
379 yank_joined, "Join and yank selections",
390 yank_joined_to_clipboard, "Join and yank selections to clipboard",
380 yank_joined_to_clipboard, "Join and yank selections to clipboard",
391 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
381 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
@@ -990,7 +980,6 @@ fn align_selections(cx: &mut Context) {
990
980
991 let transaction = Transaction::change(doc.text(), changes.into_iter());
981 let transaction = Transaction::change(doc.text(), changes.into_iter());
992 doc.apply(&transaction, view.id);
982 doc.apply(&transaction, view.id);
993− exit_select_mode(cx);
994}
983}
995
984
996fn goto_window(cx: &mut Context, align: Align) {
985fn goto_window(cx: &mut Context, align: Align) {
@@ -1165,10 +1154,6 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
1165 let (view, doc) = current_ref!(cx.editor);
1154 let (view, doc) = current_ref!(cx.editor);
1166 let text = doc.text();
1155 let text = doc.text();
1167 let selections = doc.selection(view.id);
1156 let selections = doc.selection(view.id);
1168− let rel_path = doc
1169− .relative_path()
1170− .map(|path| path.parent().unwrap().to_path_buf())
1171− .unwrap_or_default();
1172 let mut paths: Vec<_> = selections
1157 let mut paths: Vec<_> = selections
1173 .iter()
1158 .iter()
1174 .map(|r| text.slice(r.from()..r.to()).to_string())
1159 .map(|r| text.slice(r.from()..r.to()).to_string())
@@ -1196,80 +1181,16 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
1196 .to_string(),
1181 .to_string(),
1197 );
1182 );
1198 }
1183 }
1199−
1200 for sel in paths {
1184 for sel in paths {
1201 let p = sel.trim();
1185 let p = sel.trim();
1202− if p.is_empty() {
1186+ if !p.is_empty() {
1203− continue;
1204− }
1205−
1206− if let Ok(url) = Url::parse(p) {
1207− return open_url(cx, url, action);
1208− }
1209−
1210− let path = &rel_path.join(p);
1211− if path.is_dir() {
1212− let picker = ui::file_picker(path.into(), &cx.editor.config());
1213− cx.push_layer(Box::new(overlaid(picker)));
1214− } else if let Err(e) = cx.editor.open(path, action) {
1187+ if let Err(e) = cx.editor.open(&PathBuf::from(p), action) {
1215− cx.editor.set_error(format!("Open file failed: {:?}", e));
1216− }
1217− }
1218−}
1219−
1220−/// Opens the given url. If the URL points to a valid textual file it is open in helix.
1221−// Otherwise, the file is open using external program.
1222−fn open_url(cx: &mut Context, url: Url, action: Action) {
1223− let doc = doc!(cx.editor);
1224− let rel_path = doc
1225− .relative_path()
1226− .map(|path| path.parent().unwrap().to_path_buf())
1227− .unwrap_or_default();
1228−
1229− if url.scheme() != "file" {
1230− return open_external_url(cx, url);
1231− }
1232−
1233− let content_type = std::fs::File::open(url.path()).and_then(|file| {
1234− // Read up to 1kb to detect the content type
1235− let mut read_buffer = Vec::new();
1236− let n = file.take(1024).read_to_end(&mut read_buffer)?;
1237− Ok(content_inspector::inspect(&read_buffer[..n]))
1238− });
1239−
1240− // we attempt to open binary files - files that can't be open in helix - using external
1241− // program as well, e.g. pdf files or images
1242− match content_type {
1243− Ok(content_inspector::ContentType::BINARY) => open_external_url(cx, url),
1244− Ok(_) | Err(_) => {
1245− let path = &rel_path.join(url.path());
1246− if path.is_dir() {
1247− let picker = ui::file_picker(path.into(), &cx.editor.config());
1248− cx.push_layer(Box::new(overlaid(picker)));
1249− } else if let Err(e) = cx.editor.open(path, action) {
1250 cx.editor.set_error(format!("Open file failed: {:?}", e));
1188 cx.editor.set_error(format!("Open file failed: {:?}", e));
1251 }
1189 }
1252 }
1190 }
1253 }
1191 }
1254}
1192}
1255
1193
1256−/// Opens URL in external program.
1257−fn open_external_url(cx: &mut Context, url: Url) {
1258− let commands = open::commands(url.as_str());
1259− cx.jobs.callback(async {
1260− for cmd in commands {
1261− let mut command = tokio::process::Command::new(cmd.get_program());
1262− command.args(cmd.get_args());
1263− if command.output().await.is_ok() {
1264− return Ok(job::Callback::Editor(Box::new(|_| {})));
1265− }
1266− }
1267− Ok(job::Callback::Editor(Box::new(move |editor| {
1268− editor.set_error("Opening URL in external program failed")
1269− })))
1270− });
1271−}
1272−
1273fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
1194fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
1274where
1195where
1275 F: Fn(RopeSlice, Range, usize) -> Range,
1196 F: Fn(RopeSlice, Range, usize) -> Range,
@@ -1318,65 +1239,6 @@ fn extend_next_long_word_end(cx: &mut Context) {
1318 extend_word_impl(cx, movement::move_next_long_word_end)
1239 extend_word_impl(cx, movement::move_next_long_word_end)
1319}
1240}
1320
1241
1321−/// Separate branch to find_char designed only for `<ret>` char.
1322−//
1323−// This is necessary because the one document can have different line endings inside. And we
1324−// cannot predict what character to find when <ret> is pressed. On the current line it can be `lf`
1325−// but on the next line it can be `crlf`. That's why [`find_char_impl`] cannot be applied here.
1326−fn find_char_line_ending(
1327− cx: &mut Context,
1328− count: usize,
1329− direction: Direction,
1330− inclusive: bool,
1331− extend: bool,
1332−) {
1333− let (view, doc) = current!(cx.editor);
1334− let text = doc.text().slice(..);
1335−
1336− let selection = doc.selection(view.id).clone().transform(|range| {
1337− let cursor = range.cursor(text);
1338− let cursor_line = range.cursor_line(text);
1339−
1340− // Finding the line where we're going to find <ret>. Depends mostly on
1341− // `count`, but also takes into account edge cases where we're already at the end
1342− // of a line or the beginning of a line
1343− let find_on_line = match direction {
1344− Direction::Forward => {
1345− let on_edge = line_end_char_index(&text, cursor_line) == cursor;
1346− let line = cursor_line + count - 1 + (on_edge as usize);
1347− if line >= text.len_lines() - 1 {
1348− return range;
1349− } else {
1350− line
1351− }
1352− }
1353− Direction::Backward => {
1354− let on_edge = text.line_to_char(cursor_line) == cursor && !inclusive;
1355− let line = cursor_line as isize - (count as isize - 1 + on_edge as isize);
1356− if line <= 0 {
1357− return range;
1358− } else {
1359− line as usize
1360− }
1361− }
1362− };
1363−
1364− let pos = match (direction, inclusive) {
1365− (Direction::Forward, true) => line_end_char_index(&text, find_on_line),
1366− (Direction::Forward, false) => line_end_char_index(&text, find_on_line) - 1,
1367− (Direction::Backward, true) => line_end_char_index(&text, find_on_line - 1),
1368− (Direction::Backward, false) => text.line_to_char(find_on_line),
1369− };
1370−
1371− if extend {
1372− range.put_cursor(text, pos, true)
1373− } else {
1374− Range::point(range.cursor(text)).put_cursor(text, pos, true)
1375− }
1376− });
1377− doc.set_selection(view.id, selection);
1378−}
1379−
1380fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bool) {
1242fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bool) {
1381 // TODO: count is reset to 1 before next key so we move it into the closure here.
1243 // TODO: count is reset to 1 before next key so we move it into the closure here.
1382 // Would be nice to carry over.
1244 // Would be nice to carry over.
@@ -1390,9 +1252,13 @@ fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bo
1390 KeyEvent {
1252 KeyEvent {
1391 code: KeyCode::Enter,
1253 code: KeyCode::Enter,
1392 ..
1254 ..
1393− } => {
1255+ } =>
1394− find_char_line_ending(cx, count, direction, inclusive, extend);
1256+ // TODO: this isn't quite correct when CRLF is involved.
1395− return;
1257+ // This hack will work in most cases, since documents don't
1258+ // usually mix line endings. But we should fix it eventually
1259+ // anyway.
1260+ {
1261+ doc!(cx.editor).line_ending.as_str().chars().next().unwrap()
1396 }
1262 }
1397
1263
1398 KeyEvent {
1264 KeyEvent {
@@ -1594,7 +1460,6 @@ where
1594 });
1460 });
1595
1461
1596 doc.apply(&transaction, view.id);
1462 doc.apply(&transaction, view.id);
1597− exit_select_mode(cx);
1598}
1463}
1599
1464
1600fn switch_case(cx: &mut Context) {
1465fn switch_case(cx: &mut Context) {
@@ -1836,8 +1701,8 @@ fn select_regex(cx: &mut Context) {
1836 "select:".into(),
1701 "select:".into(),
1837 Some(reg),
1702 Some(reg),
1838 ui::completers::none,
1703 ui::completers::none,
1839− move |cx, regex, event| {
1704+ move |editor, regex, event| {
1840− let (view, doc) = current!(cx.editor);
1705+ let (view, doc) = current!(editor);
1841 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1706 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1842 return;
1707 return;
1843 }
1708 }
@@ -1858,8 +1723,8 @@ fn split_selection(cx: &mut Context) {
1858 "split:".into(),
1723 "split:".into(),
1859 Some(reg),
1724 Some(reg),
1860 ui::completers::none,
1725 ui::completers::none,
1861− move |cx, regex, event| {
1726+ move |editor, regex, event| {
1862− let (view, doc) = current!(cx.editor);
1727+ let (view, doc) = current!(editor);
1863 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1728 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1864 return;
1729 return;
1865 }
1730 }
@@ -1982,11 +1847,11 @@ fn search_impl(
1982
1847
1983fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
1848fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
1984 let mut items = reg
1849 let mut items = reg
1985− .and_then(|reg| cx.editor.registers.read(reg, cx.editor))
1850+ .and_then(|reg| cx.editor.registers.get(reg))
1986− .map_or(Vec::new(), |reg| reg.take(200).collect());
1851+ .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect());
1987 items.sort_unstable();
1852 items.sort_unstable();
1988 items.dedup();
1853 items.dedup();
1989− items.into_iter().map(|value| value.to_string()).collect()
1854+ items.into_iter().cloned().collect()
1990}
1855}
1991
1856
1992fn search(cx: &mut Context) {
1857fn search(cx: &mut Context) {
@@ -2023,14 +1888,12 @@ fn searcher(cx: &mut Context, direction: Direction) {
2023 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
1888 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
2024 .collect()
1889 .collect()
2025 },
1890 },
2026− move |cx, regex, event| {
1891+ move |editor, regex, event| {
2027− if event == PromptEvent::Validate {
1892+ if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
2028− cx.editor.registers.last_search_register = reg;
2029− } else if event != PromptEvent::Update {
2030 return;
1893 return;
2031 }
1894 }
2032 search_impl(
1895 search_impl(
2033− cx.editor,
1896+ editor,
2034 &contents,
1897 &contents,
2035 &regex,
1898 &regex,
2036 Movement::Move,
1899 Movement::Move,
@@ -2045,13 +1908,11 @@ fn searcher(cx: &mut Context, direction: Direction) {
2045
1908
2046fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
1909fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
2047 let count = cx.count();
1910 let count = cx.count();
2048− let register = cx
2049− .register
2050− .unwrap_or(cx.editor.registers.last_search_register);
2051 let config = cx.editor.config();
1911 let config = cx.editor.config();
2052 let scrolloff = config.scrolloff;
1912 let scrolloff = config.scrolloff;
2053− if let Some(query) = cx.editor.registers.first(register, cx.editor) {
1913+ let (_, doc) = current!(cx.editor);
2054− let doc = doc!(cx.editor);
1914+ let registers = &cx.editor.registers;
1915+ if let Some(query) = registers.read('/').and_then(|query| query.last()) {
2055 let contents = doc.text().slice(..).to_string();
1916 let contents = doc.text().slice(..).to_string();
2056 let search_config = &config.search;
1917 let search_config = &config.search;
2057 let case_insensitive = if search_config.smart_case {
1918 let case_insensitive = if search_config.smart_case {
@@ -2060,7 +1921,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
2060 false
1921 false
2061 };
1922 };
2062 let wrap_around = search_config.wrap_around;
1923 let wrap_around = search_config.wrap_around;
2063− if let Ok(regex) = RegexBuilder::new(&query)
1924+ if let Ok(regex) = RegexBuilder::new(query)
2064 .case_insensitive(case_insensitive)
1925 .case_insensitive(case_insensitive)
2065 .multi_line(true)
1926 .multi_line(true)
2066 .build()
1927 .build()
@@ -2100,7 +1961,6 @@ fn extend_search_prev(cx: &mut Context) {
2100}
1961}
2101
1962
2102fn search_selection(cx: &mut Context) {
1963fn search_selection(cx: &mut Context) {
2103− let register = cx.register.unwrap_or('/');
2104 let (view, doc) = current!(cx.editor);
1964 let (view, doc) = current!(cx.editor);
2105 let contents = doc.text().slice(..);
1965 let contents = doc.text().slice(..);
2106
1966
@@ -2113,24 +1973,13 @@ fn search_selection(cx: &mut Context) {
2113 .collect::<Vec<_>>()
1973 .collect::<Vec<_>>()
2114 .join("|");
1974 .join("|");
2115
1975
2116− let msg = format!("register '{}' set to '{}'", register, &regex);
1976+ let msg = format!("register '{}' set to '{}'", '/', &regex);
2117− match cx.editor.registers.push(register, regex) {
1977+ cx.editor.registers.push('/', regex);
2118− Ok(_) => {
2119− cx.editor.registers.last_search_register = register;
2120− cx.editor.set_status(msg)
1978+ cx.editor.set_status(msg);
2121− }
2122− Err(err) => cx.editor.set_error(err.to_string()),
2123− }
2124}
1979}
2125
1980
2126fn make_search_word_bounded(cx: &mut Context) {
1981fn make_search_word_bounded(cx: &mut Context) {
2127− // Defaults to the active search register instead `/` to be more ergonomic assuming most people
2128− // would use this command following `search_selection`. This avoids selecting the register
2129− // twice.
2130− let register = cx
2131− .register
2132− .unwrap_or(cx.editor.registers.last_search_register);
2133− let regex = match cx.editor.registers.first(register, cx.editor) {
1982+ let regex = match cx.editor.registers.last('/') {
2134 Some(regex) => regex,
1983 Some(regex) => regex,
2135 None => return,
1984 None => return,
2136 };
1985 };
@@ -2148,19 +1997,14 @@ fn make_search_word_bounded(cx: &mut Context) {
2148 if !start_anchored {
1997 if !start_anchored {
2149 new_regex.push_str("\\b");
1998 new_regex.push_str("\\b");
2150 }
1999 }
2151− new_regex.push_str(&regex);
2000+ new_regex.push_str(regex);
2152 if !end_anchored {
2001 if !end_anchored {
2153 new_regex.push_str("\\b");
2002 new_regex.push_str("\\b");
2154 }
2003 }
2155
2004
2156− let msg = format!("register '{}' set to '{}'", register, &new_regex);
2005+ let msg = format!("register '{}' set to '{}'", '/', &new_regex);
2157− match cx.editor.registers.push(register, new_regex) {
2006+ cx.editor.registers.push('/', new_regex);
2158− Ok(_) => {
2159− cx.editor.registers.last_search_register = register;
2160− cx.editor.set_status(msg)
2007+ cx.editor.set_status(msg);
2161− }
2162− Err(err) => cx.editor.set_error(err.to_string()),
2163− }
2164}
2008}
2165
2009
2166fn global_search(cx: &mut Context) {
2010fn global_search(cx: &mut Context) {
@@ -2199,11 +2043,13 @@ fn global_search(cx: &mut Context) {
2199 }
2043 }
2200 }
2044 }
2201
2045
2046+ let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<FileResult>();
2202 let config = cx.editor.config();
2047 let config = cx.editor.config();
2203 let smart_case = config.search.smart_case;
2048 let smart_case = config.search.smart_case;
2204 let file_picker_config = config.file_picker.clone();
2049 let file_picker_config = config.file_picker.clone();
2205
2050
2206 let reg = cx.register.unwrap_or('/');
2051 let reg = cx.register.unwrap_or('/');
2052+
2207 let completions = search_completions(cx, Some(reg));
2053 let completions = search_completions(cx, Some(reg));
2208 ui::regex_prompt(
2054 ui::regex_prompt(
2209 cx,
2055 cx,
@@ -2216,66 +2062,52 @@ fn global_search(cx: &mut Context) {
2216 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
2062 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
2217 .collect()
2063 .collect()
2218 },
2064 },
2219− move |cx, regex, event| {
2065+ move |editor, regex, event| {
2220 if event != PromptEvent::Validate {
2066 if event != PromptEvent::Validate {
2221 return;
2067 return;
2222 }
2068 }
2223− cx.editor.registers.last_search_register = reg;
2224
2069
2225− let current_path = doc_mut!(cx.editor).path().cloned();
2226− let documents: Vec<_> = cx
2070+ let documents: Vec<_> = editor
2227− .editor
2228 .documents()
2071 .documents()
2229− .map(|doc| (doc.path().cloned(), doc.text().to_owned()))
2072+ .map(|doc| (doc.path(), doc.text()))
2230 .collect();
2073 .collect();
2231
2074
2232 if let Ok(matcher) = RegexMatcherBuilder::new()
2075 if let Ok(matcher) = RegexMatcherBuilder::new()
2233 .case_smart(smart_case)
2076 .case_smart(smart_case)
2234 .build(regex.as_str())
2077 .build(regex.as_str())
2235 {
2078 {
2079+ let searcher = SearcherBuilder::new()
2080+ .binary_detection(BinaryDetection::quit(b'\x00'))
2081+ .build();
2082+
2236 let search_root = helix_loader::current_working_dir();
2083 let search_root = helix_loader::current_working_dir();
2237 if !search_root.exists() {
2084 if !search_root.exists() {
2238− cx.editor
2239− .set_error("Current working directory does not exist");
2085+ editor.set_error("Current working directory does not exist");
2240 return;
2086 return;
2241 }
2087 }
2242
2088
2243− let (picker, injector) = Picker::stream(current_path);
2244−
2245 let dedup_symlinks = file_picker_config.deduplicate_links;
2089 let dedup_symlinks = file_picker_config.deduplicate_links;
2246 let absolute_root = search_root
2090 let absolute_root = search_root
2247 .canonicalize()
2091 .canonicalize()
2248 .unwrap_or_else(|_| search_root.clone());
2092 .unwrap_or_else(|_| search_root.clone());
2249− let injector_ = injector.clone();
2093+
2250−
2251− std::thread::spawn(move || {
2252− let searcher = SearcherBuilder::new()
2253− .binary_detection(BinaryDetection::quit(b'\x00'))
2254− .build();
2255−
2256− let mut walk_builder = WalkBuilder::new(search_root);
2094+ WalkBuilder::new(search_root)
2257−
2258− walk_builder
2259− .hidden(file_picker_config.hidden)
2095+ .hidden(file_picker_config.hidden)
2260− .parents(file_picker_config.parents)
2096+ .parents(file_picker_config.parents)
2261− .ignore(file_picker_config.ignore)
2097+ .ignore(file_picker_config.ignore)
2262− .follow_links(file_picker_config.follow_symlinks)
2098+ .follow_links(file_picker_config.follow_symlinks)
2263− .git_ignore(file_picker_config.git_ignore)
2099+ .git_ignore(file_picker_config.git_ignore)
2264− .git_global(file_picker_config.git_global)
2100+ .git_global(file_picker_config.git_global)
2265− .git_exclude(file_picker_config.git_exclude)
2101+ .git_exclude(file_picker_config.git_exclude)
2266− .max_depth(file_picker_config.max_depth)
2102+ .max_depth(file_picker_config.max_depth)
2267− .filter_entry(move |entry| {
2103+ .filter_entry(move |entry| {
2268− filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2104+ filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2269− });
2105+ })
2270−
2271− walk_builder
2272− .add_custom_ignore_filename(helix_loader::config_dir().join("ignore"));
2273− walk_builder.add_custom_ignore_filename(".helix/ignore");
2274−
2275− walk_builder.build_parallel().run(|| {
2106+ .build_parallel()
2107+ .run(|| {
2276 let mut searcher = searcher.clone();
2108 let mut searcher = searcher.clone();
2277 let matcher = matcher.clone();
2109 let matcher = matcher.clone();
2278− let injector = injector_.clone();
2110+ let all_matches_sx = all_matches_sx.clone();
2279 let documents = &documents;
2111 let documents = &documents;
2280 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
2112 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
2281 let entry = match entry {
2113 let entry = match entry {
@@ -2289,24 +2121,21 @@ fn global_search(cx: &mut Context) {
2289 _ => return WalkState::Continue,
2121 _ => return WalkState::Continue,
2290 };
2122 };
2291
2123
2292− let mut stop = false;
2293 let sink = sinks::UTF8(|line_num, _| {
2124 let sink = sinks::UTF8(|line_num, _| {
2294− stop = injector
2125+ all_matches_sx
2295− .push(FileResult::new(entry.path(), line_num as usize - 1))
2126+ .send(FileResult::new(entry.path(), line_num as usize - 1))
2296− .is_err();
2127+ .unwrap();
2297
2128
2298− Ok(!stop)
2129+ Ok(true)
2299 });
2130 });
2300 let doc = documents.iter().find(|&(doc_path, _)| {
2131 let doc = documents.iter().find(|&(doc_path, _)| {
2301− doc_path
2302− .as_ref()
2303− .map_or(false, |doc_path| doc_path == entry.path())
2132+ doc_path.map_or(false, |doc_path| doc_path == entry.path())
2304 });
2133 });
2305
2134
2306 let result = if let Some((_, doc)) = doc {
2135 let result = if let Some((_, doc)) = doc {
2307 // there is already a buffer for this file
2136 // there is already a buffer for this file
2308 // search the buffer instead of the file because it's faster
2137 // search the buffer instead of the file because it's faster
2309− // and captures new edits without requiring a save
2138+ // and captures new edits without requireing a save
2310 if searcher.multi_line_with_matcher(&matcher) {
2139 if searcher.multi_line_with_matcher(&matcher) {
2311 // in this case a continous buffer is required
2140 // in this case a continous buffer is required
2312 // convert the rope to a string
2141 // convert the rope to a string
@@ -2330,66 +2159,67 @@ fn global_search(cx: &mut Context) {
2330 err
2159 err
2331 );
2160 );
2332 }
2161 }
2333− if stop {
2334− WalkState::Quit
2335− } else {
2336− WalkState::Continue
2162+ WalkState::Continue
2337− }
2338 })
2163 })
2339 });
2164 });
2340− });
2341−
2342− cx.jobs.callback(async move {
2343− let call = move |_: &mut Editor, compositor: &mut Compositor| {
2344− let picker = Picker::with_stream(
2345− picker,
2346− injector,
2347− move |cx, FileResult { path, line_num }, action| {
2348− let doc = match cx.editor.open(path, action) {
2349− Ok(id) => doc_mut!(cx.editor, &id),
2350− Err(e) => {
2351− cx.editor.set_error(format!(
2352− "Failed to open file '{}': {}",
2353− path.display(),
2354− e
2355− ));
2356− return;
2357− }
2358− };
2359−
2360− let line_num = *line_num;
2361− let view = view_mut!(cx.editor);
2362− let text = doc.text();
2363− if line_num >= text.len_lines() {
2364− cx.editor.set_error(
2365− "The line you jumped to does not exist anymore because the file has changed.",
2366− );
2367− return;
2368− }
2369− let start = text.line_to_char(line_num);
2370− let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2371−
2372− doc.set_selection(view.id, Selection::single(start, end));
2373− if action.align_view(view, doc.id()) {
2374− align_view(doc, view, Align::Center);
2375− }
2376− },
2377− )
2378− .with_preview(
2379− |_editor, FileResult { path, line_num }| {
2380− Some((path.clone().into(), Some((*line_num, *line_num))))
2381− },
2382− );
2383− compositor.push(Box::new(overlaid(picker)))
2384− };
2385− Ok(Callback::EditorCompositor(Box::new(call)))
2386− })
2387 } else {
2165 } else {
2388 // Otherwise do nothing
2166 // Otherwise do nothing
2389 // log::warn!("Global Search Invalid Pattern")
2167 // log::warn!("Global Search Invalid Pattern")
2390 }
2168 }
2391 },
2169 },
2392 );
2170 );
2171+
2172+ let current_path = doc_mut!(cx.editor).path().cloned();
2173+
2174+ let show_picker = async move {
2175+ let all_matches: Vec<FileResult> =
2176+ UnboundedReceiverStream::new(all_matches_rx).collect().await;
2177+ let call: job::Callback = Callback::EditorCompositor(Box::new(
2178+ move |editor: &mut Editor, compositor: &mut Compositor| {
2179+ if all_matches.is_empty() {
2180+ if !editor.is_err() {
2181+ editor.set_status("No matches found");
2182+ }
2183+ return;
2184+ }
2185+
2186+ let picker = Picker::new(
2187+ all_matches,
2188+ current_path,
2189+ move |cx, FileResult { path, line_num }, action| {
2190+ match cx.editor.open(path, action) {
2191+ Ok(_) => {}
2192+ Err(e) => {
2193+ cx.editor.set_error(format!(
2194+ "Failed to open file '{}': {}",
2195+ path.display(),
2196+ e
2197+ ));
2198+ return;
2199+ }
2200+ }
2201+
2202+ let line_num = *line_num;
2203+ let (view, doc) = current!(cx.editor);
2204+ let text = doc.text();
2205+ if line_num >= text.len_lines() {
2206+ cx.editor.set_error("The line you jumped to does not exist anymore because the file has changed.");
2207+ return;
2208+ }
2209+ let start = text.line_to_char(line_num);
2210+ let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2211+
2212+ doc.set_selection(view.id, Selection::single(start, end));
2213+ align_view(doc, view, Align::Center);
2214+ }).with_preview(|_editor, FileResult { path, line_num }| {
2215+ Some((path.clone().into(), Some((*line_num, *line_num))))
2216+ });
2217+ compositor.push(Box::new(overlaid(picker)));
2218+ },
2219+ ));
2220+ Ok(call)
2221+ };
2222+ cx.jobs.callback(show_picker);
2393}
2223}
2394
2224
2395enum Extend {
2225enum Extend {
@@ -2537,10 +2367,7 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
2537 let text = doc.text().slice(..);
2367 let text = doc.text().slice(..);
2538 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
2368 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
2539 let reg_name = cx.register.unwrap_or('"');
2369 let reg_name = cx.register.unwrap_or('"');
2540− if let Err(err) = cx.editor.registers.write(reg_name, values) {
2370+ cx.editor.registers.write(reg_name, values);
2541− cx.editor.set_error(err.to_string());
2542− return;
2543− }
2544 };
2371 };
2545
2372
2546 // then delete
2373 // then delete
@@ -2680,10 +2507,6 @@ fn insert_mode(cx: &mut Context) {
2680 .transform(|range| Range::new(range.to(), range.from()));
2507 .transform(|range| Range::new(range.to(), range.from()));
2681
2508
2682 doc.set_selection(view.id, selection);
2509 doc.set_selection(view.id, selection);
2683−
2684− // [TODO] temporary workaround until we're not using the idle timer to
2685− // trigger auto completions any more
2686− cx.editor.clear_idle_timer();
2687}
2510}
2688
2511
2689// inserts at the end of each selection
2512// inserts at the end of each selection
@@ -2744,7 +2567,6 @@ fn file_picker_in_current_buffer_directory(cx: &mut Context) {
2744 let picker = ui::file_picker(path, &cx.editor.config());
2567 let picker = ui::file_picker(path, &cx.editor.config());
2745 cx.push_layer(Box::new(overlaid(picker)));
2568 cx.push_layer(Box::new(overlaid(picker)));
2746}
2569}
2747−
2748fn file_picker_in_current_directory(cx: &mut Context) {
2570fn file_picker_in_current_directory(cx: &mut Context) {
2749 let cwd = helix_loader::current_working_dir();
2571 let cwd = helix_loader::current_working_dir();
2750 if !cwd.exists() {
2572 if !cwd.exists() {
@@ -2901,11 +2723,9 @@ fn jumplist_picker(cx: &mut Context) {
2901 |cx, meta, action| {
2723 |cx, meta, action| {
2902 cx.editor.switch(meta.id, action);
2724 cx.editor.switch(meta.id, action);
2903 let config = cx.editor.config();
2725 let config = cx.editor.config();
2904− let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id));
2726+ let (view, doc) = current!(cx.editor);
2905 doc.set_selection(view.id, meta.selection.clone());
2727 doc.set_selection(view.id, meta.selection.clone());
2906− if action.align_view(view, doc.id()) {
2907− view.ensure_cursor_in_view_center(doc, config.scrolloff);
2728+ view.ensure_cursor_in_view_center(doc, config.scrolloff);
2908− }
2909 },
2729 },
2910 )
2730 )
2911 .with_preview(|editor, meta| {
2731 .with_preview(|editor, meta| {
@@ -3053,7 +2873,6 @@ fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) {
3053 let indent = indent::indent_for_newline(
2873 let indent = indent::indent_for_newline(
3054 language_config,
2874 language_config,
3055 syntax,
2875 syntax,
3056− &doc.config.load().indent_heuristic,
3057 &doc.indent_style,
2876 &doc.indent_style,
3058 tab_width,
2877 tab_width,
3059 text,
2878 text,
@@ -3158,7 +2977,6 @@ fn open(cx: &mut Context, open: Open) {
3158 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
2977 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
3159 Open::Above => range.from(),
2978 Open::Above => range.from(),
3160 });
2979 });
3161−
3162 let new_line = match open {
2980 let new_line = match open {
3163 // adjust position to the end of the line (next line - 1)
2981 // adjust position to the end of the line (next line - 1)
3164 Open::Below => cursor_line + 1,
2982 Open::Below => cursor_line + 1,
@@ -3166,15 +2984,13 @@ fn open(cx: &mut Context, open: Open) {
3166 Open::Above => cursor_line,
2984 Open::Above => cursor_line,
3167 };
2985 };
3168
2986
3169− let line_num = new_line.saturating_sub(1);
3170−
3171 // Index to insert newlines after, as well as the char width
2987 // Index to insert newlines after, as well as the char width
3172 // to use to compensate for those inserted newlines.
2988 // to use to compensate for those inserted newlines.
3173 let (line_end_index, line_end_offset_width) = if new_line == 0 {
2989 let (line_end_index, line_end_offset_width) = if new_line == 0 {
3174 (0, 0)
2990 (0, 0)
3175 } else {
2991 } else {
3176 (
2992 (
3177− line_end_char_index(&text, line_num),
2993+ line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
3178 doc.line_ending.len_chars(),
2994 doc.line_ending.len_chars(),
3179 )
2995 )
3180 };
2996 };
@@ -3182,15 +2998,13 @@ fn open(cx: &mut Context, open: Open) {
3182 let indent = indent::indent_for_newline(
2998 let indent = indent::indent_for_newline(
3183 doc.language_config(),
2999 doc.language_config(),
3184 doc.syntax(),
3000 doc.syntax(),
3185− &doc.config.load().indent_heuristic,
3186 &doc.indent_style,
3001 &doc.indent_style,
3187 doc.tab_width(),
3002 doc.tab_width(),
3188 text,
3003 text,
3189− line_num,
3004+ new_line.saturating_sub(1),
3190 line_end_index,
3005 line_end_index,
3191 cursor_line,
3006 cursor_line,
3192 );
3007 );
3193−
3194 let indent_len = indent.len();
3008 let indent_len = indent.len();
3195 let mut text = String::with_capacity(1 + indent_len);
3009 let mut text = String::with_capacity(1 + indent_len);
3196 text.push_str(doc.line_ending.as_str());
3010 text.push_str(doc.line_ending.as_str());
@@ -3616,7 +3430,6 @@ pub mod insert {
3616 }
3430 }
3617
3431
3618 use helix_core::auto_pairs;
3432 use helix_core::auto_pairs;
3619− use helix_view::editor::SmartTabConfig;
3620
3433
3621 pub fn insert_char(cx: &mut Context, c: char) {
3434 pub fn insert_char(cx: &mut Context, c: char) {
3622 let (view, doc) = current_ref!(cx.editor);
3435 let (view, doc) = current_ref!(cx.editor);
@@ -3642,31 +3455,6 @@ pub mod insert {
3642 }
3455 }
3643 }
3456 }
3644
3457
3645− pub fn smart_tab(cx: &mut Context) {
3646− let (view, doc) = current_ref!(cx.editor);
3647− let view_id = view.id;
3648−
3649− if matches!(
3650− cx.editor.config().smart_tab,
3651− Some(SmartTabConfig { enable: true, .. })
3652− ) {
3653− let cursors_after_whitespace = doc.selection(view_id).ranges().iter().all(|range| {
3654− let cursor = range.cursor(doc.text().slice(..));
3655− let current_line_num = doc.text().char_to_line(cursor);
3656− let current_line_start = doc.text().line_to_char(current_line_num);
3657− let left = doc.text().slice(current_line_start..cursor);
3658− left.chars().all(|c| c.is_whitespace())
3659− });
3660−
3661− if !cursors_after_whitespace {
3662− move_parent_node_end(cx);
3663− return;
3664− }
3665− }
3666−
3667− insert_tab(cx);
3668− }
3669−
3670 pub fn insert_tab(cx: &mut Context) {
3458 pub fn insert_tab(cx: &mut Context) {
3671 let (view, doc) = current!(cx.editor);
3459 let (view, doc) = current!(cx.editor);
3672 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
3460 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
@@ -3722,7 +3510,6 @@ pub mod insert {
3722 let indent = indent::indent_for_newline(
3510 let indent = indent::indent_for_newline(
3723 doc.language_config(),
3511 doc.language_config(),
3724 doc.syntax(),
3512 doc.syntax(),
3725− &doc.config.load().indent_heuristic,
3726 &doc.indent_style,
3513 &doc.indent_style,
3727 doc.tab_width(),
3514 doc.tab_width(),
3728 text,
3515 text,
@@ -3963,22 +3750,7 @@ fn commit_undo_checkpoint(cx: &mut Context) {
3963// Yank / Paste
3750// Yank / Paste
3964
3751
3965fn yank(cx: &mut Context) {
3752fn yank(cx: &mut Context) {
3966− yank_impl(cx.editor, cx.register.unwrap_or('"'));
3967− exit_select_mode(cx);
3968−}
3969−
3970−fn yank_to_clipboard(cx: &mut Context) {
3971− yank_impl(cx.editor, '+');
3972− exit_select_mode(cx);
3973−}
3974−
3975−fn yank_to_primary_clipboard(cx: &mut Context) {
3976− yank_impl(cx.editor, '*');
3977− exit_select_mode(cx);
3978−}
3979−
3980−fn yank_impl(editor: &mut Editor, register: char) {
3981− let (view, doc) = current!(editor);
3753+ let (view, doc) = current!(cx.editor);
3982 let text = doc.text().slice(..);
3754 let text = doc.text().slice(..);
3983
3755
3984 let values: Vec<String> = doc
3756 let values: Vec<String> = doc
@@ -3986,15 +3758,19 @@ fn yank_impl(editor: &mut Editor, register: char) {
3986 .fragments(text)
3758 .fragments(text)
3987 .map(Cow::into_owned)
3759 .map(Cow::into_owned)
3988 .collect();
3760 .collect();
3989− let selections = values.len();
3990
3761
3991− match editor.registers.write(register, values) {
3762+ let msg = format!(
3992− Ok(_) => editor.set_status(format!(
3993− "yanked {selections} selection{} to register {register}",
3763+ "yanked {} selection(s) to register {}",
3994− if selections == 1 { "" } else { "s" }
3764+ values.len(),
3995− )),
3765+ cx.register.unwrap_or('"')
3996− Err(err) => editor.set_error(err.to_string()),
3766+ );
3997− }
3767+
3768+ cx.editor
3769+ .registers
3770+ .write(cx.register.unwrap_or('"'), values);
3771+
3772+ cx.editor.set_status(msg);
3773+ exit_select_mode(cx);
3998}
3774}
3999
3775
4000fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
3776fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
@@ -4002,7 +3778,6 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
4002 let text = doc.text().slice(..);
3778 let text = doc.text().slice(..);
4003
3779
4004 let selection = doc.selection(view.id);
3780 let selection = doc.selection(view.id);
4005− let selections = selection.len();
4006 let joined = selection
3781 let joined = selection
4007 .fragments(text)
3782 .fragments(text)
4008 .fold(String::new(), |mut acc, fragment| {
3783 .fold(String::new(), |mut acc, fragment| {
@@ -4013,52 +3788,104 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
4013 acc
3788 acc
4014 });
3789 });
4015
3790
3791+ let msg = format!(
3792+ "joined and yanked {} selection(s) to register {}",
3793+ selection.len(),
3794+ register,
3795+ );
3796+
4016− match editor.registers.write(register, vec![joined]) {
3797+ editor.registers.write(register, vec![joined]);
4017− Ok(_) => editor.set_status(format!(
3798+ editor.set_status(msg);
4018− "joined and yanked {selections} selection{} to register {register}",
4019− if selections == 1 { "" } else { "s" }
4020− )),
4021− Err(err) => editor.set_error(err.to_string()),
4022− }
4023}
3799}
4024
3800
4025fn yank_joined(cx: &mut Context) {
3801fn yank_joined(cx: &mut Context) {
4026− let separator = doc!(cx.editor).line_ending.as_str();
3802+ let line_ending = doc!(cx.editor).line_ending;
4027− yank_joined_impl(cx.editor, separator, cx.register.unwrap_or('"'));
3803+ let register = cx.register.unwrap_or('"');
3804+ yank_joined_impl(cx.editor, line_ending.as_str(), register);
4028 exit_select_mode(cx);
3805 exit_select_mode(cx);
4029}
3806}
4030
3807
4031−fn yank_joined_to_clipboard(cx: &mut Context) {
3808+fn yank_joined_to_clipboard_impl(
4032− let line_ending = doc!(cx.editor).line_ending;
3809+ editor: &mut Editor,
4033− yank_joined_impl(cx.editor, line_ending.as_str(), '+');
3810+ separator: &str,
4034− exit_select_mode(cx);
3811+ clipboard_type: ClipboardType,
3812+) -> anyhow::Result<()> {
3813+ let (view, doc) = current!(editor);
3814+ let text = doc.text().slice(..);
3815+
3816+ let values: Vec<String> = doc
3817+ .selection(view.id)
3818+ .fragments(text)
3819+ .map(Cow::into_owned)
3820+ .collect();
3821+
3822+ let clipboard_text = match clipboard_type {
3823+ ClipboardType::Clipboard => "system clipboard",
3824+ ClipboardType::Selection => "primary clipboard",
3825+ };
3826+
3827+ let msg = format!(
3828+ "joined and yanked {} selection(s) to {}",
3829+ values.len(),
3830+ clipboard_text,
3831+ );
3832+
3833+ let joined = values.join(separator);
3834+
3835+ editor
3836+ .clipboard_provider
3837+ .set_contents(joined, clipboard_type)
3838+ .context("Couldn't set system clipboard content")?;
3839+
3840+ editor.set_status(msg);
3841+
3842+ Ok(())
4035}
3843}
4036
3844
4037−fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3845+fn yank_joined_to_clipboard(cx: &mut Context) {
4038 let line_ending = doc!(cx.editor).line_ending;
3846 let line_ending = doc!(cx.editor).line_ending;
3847+ let _ =
4039− yank_joined_impl(cx.editor, line_ending.as_str(), '*');
3848+ yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Clipboard);
4040 exit_select_mode(cx);
3849 exit_select_mode(cx);
4041}
3850}
4042
3851
3852+fn yank_main_selection_to_clipboard_impl(
4043−fn yank_primary_selection_impl(editor: &mut Editor, register: char) {
3853+ editor: &mut Editor,
3854+ clipboard_type: ClipboardType,
3855+) -> anyhow::Result<()> {
4044 let (view, doc) = current!(editor);
3856 let (view, doc) = current!(editor);
4045 let text = doc.text().slice(..);
3857 let text = doc.text().slice(..);
4046
3858
3859+ let message_text = match clipboard_type {
3860+ ClipboardType::Clipboard => "yanked main selection to system clipboard",
3861+ ClipboardType::Selection => "yanked main selection to primary clipboard",
3862+ };
3863+
4047− let selection = doc.selection(view.id).primary().fragment(text).to_string();
3864+ let value = doc.selection(view.id).primary().fragment(text);
4048
3865
4049− match editor.registers.write(register, vec![selection]) {
3866+ if let Err(e) = editor
4050− Ok(_) => editor.set_status(format!("yanked primary selection to register {register}",)),
3867+ .clipboard_provider
4051− Err(err) => editor.set_error(err.to_string()),
3868+ .set_contents(value.into_owned(), clipboard_type)
3869+ {
3870+ bail!("Couldn't set system clipboard content: {}", e);
4052 }
3871 }
3872+
3873+ editor.set_status(message_text);
3874+ Ok(())
4053}
3875}
4054
3876
4055fn yank_main_selection_to_clipboard(cx: &mut Context) {
3877fn yank_main_selection_to_clipboard(cx: &mut Context) {
4056− yank_primary_selection_impl(cx.editor, '+');
3878+ let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard);
4057− exit_select_mode(cx);
3879+}
3880+
3881+fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3882+ let line_ending = doc!(cx.editor).line_ending;
3883+ let _ =
3884+ yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Selection);
4058}
3885}
4059
3886
4060fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
3887fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
4061− yank_primary_selection_impl(cx.editor, '*');
3888+ let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection);
4062 exit_select_mode(cx);
3889 exit_select_mode(cx);
4063}
3890}
4064
3891
@@ -4156,99 +3983,146 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
4156 };
3983 };
4157 let (view, doc) = current!(cx.editor);
3984 let (view, doc) = current!(cx.editor);
4158 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
3985 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
4159− exit_select_mode(cx);
3986+}
3987+
3988+fn paste_clipboard_impl(
3989+ editor: &mut Editor,
3990+ action: Paste,
3991+ clipboard_type: ClipboardType,
3992+ count: usize,
3993+) -> anyhow::Result<()> {
3994+ let (view, doc) = current!(editor);
3995+ match editor.clipboard_provider.get_contents(clipboard_type) {
3996+ Ok(contents) => {
3997+ paste_impl(&[contents], doc, view, action, count, editor.mode);
3998+ Ok(())
3999+ }
4000+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
4001+ }
4160}
4002}
4161
4003
4162fn paste_clipboard_after(cx: &mut Context) {
4004fn paste_clipboard_after(cx: &mut Context) {
4005+ let _ = paste_clipboard_impl(
4006+ cx.editor,
4163− paste(cx.editor, '+', Paste::After, cx.count());
4007+ Paste::After,
4164− exit_select_mode(cx);
4008+ ClipboardType::Clipboard,
4009+ cx.count(),
4010+ );
4165}
4011}
4166
4012
4167fn paste_clipboard_before(cx: &mut Context) {
4013fn paste_clipboard_before(cx: &mut Context) {
4014+ let _ = paste_clipboard_impl(
4015+ cx.editor,
4168− paste(cx.editor, '+', Paste::Before, cx.count());
4016+ Paste::Before,
4169− exit_select_mode(cx);
4017+ ClipboardType::Clipboard,
4018+ cx.count(),
4019+ );
4170}
4020}
4171
4021
4172fn paste_primary_clipboard_after(cx: &mut Context) {
4022fn paste_primary_clipboard_after(cx: &mut Context) {
4023+ let _ = paste_clipboard_impl(
4024+ cx.editor,
4173− paste(cx.editor, '*', Paste::After, cx.count());
4025+ Paste::After,
4174− exit_select_mode(cx);
4026+ ClipboardType::Selection,
4027+ cx.count(),
4028+ );
4175}
4029}
4176
4030
4177fn paste_primary_clipboard_before(cx: &mut Context) {
4031fn paste_primary_clipboard_before(cx: &mut Context) {
4032+ let _ = paste_clipboard_impl(
4033+ cx.editor,
4178− paste(cx.editor, '*', Paste::Before, cx.count());
4034+ Paste::Before,
4179− exit_select_mode(cx);
4035+ ClipboardType::Selection,
4036+ cx.count(),
4037+ );
4180}
4038}
4181
4039
4182fn replace_with_yanked(cx: &mut Context) {
4040fn replace_with_yanked(cx: &mut Context) {
4041+ let count = cx.count();
4183− replace_with_yanked_impl(cx.editor, cx.register.unwrap_or('"'), cx.count());
4042+ let reg_name = cx.register.unwrap_or('"');
4043+ let (view, doc) = current!(cx.editor);
4044+ let registers = &mut cx.editor.registers;
4045+
4046+ if let Some(values) = registers.read(reg_name) {
4047+ if !values.is_empty() {
4048+ let repeat = std::iter::repeat(
4049+ values
4050+ .last()
4051+ .map(|value| Tendril::from(&value.repeat(count)))
4052+ .unwrap(),
4053+ );
4054+ let mut values = values
4055+ .iter()
4056+ .map(|value| Tendril::from(&value.repeat(count)))
4057+ .chain(repeat);
4058+ let selection = doc.selection(view.id);
4059+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4060+ if !range.is_empty() {
4061+ (range.from(), range.to(), Some(values.next().unwrap()))
4062+ } else {
4063+ (range.from(), range.to(), None)
4064+ }
4065+ });
4066+
4067+ doc.apply(&transaction, view.id);
4184− exit_select_mode(cx);
4068+ exit_select_mode(cx);
4069+ }
4070+ }
4185}
4071}
4186
4072
4187−fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
4073+fn replace_selections_with_clipboard_impl(
4188− let Some(values) = editor.registers
4074+ cx: &mut Context,
4189− .read(register, editor)
4075+ clipboard_type: ClipboardType,
4190− .filter(|values| values.len() > 0) else { return };
4076+) -> anyhow::Result<()> {
4191− let values: Vec<_> = values.map(|value| value.to_string()).collect();
4077+ let count = cx.count();
4078+ let (view, doc) = current!(cx.editor);
4192
4079
4193− let (view, doc) = current!(editor);
4080+ match cx.editor.clipboard_provider.get_contents(clipboard_type) {
4194− let repeat = std::iter::repeat(
4081+ Ok(contents) => {
4195− values
4196− .last()
4197− .map(|value| Tendril::from(&value.repeat(count)))
4198− .unwrap(),
4199− );
4200− let mut values = values
4201− .iter()
4202− .map(|value| Tendril::from(&value.repeat(count)))
4203− .chain(repeat);
4204− let selection = doc.selection(view.id);
4082+ let selection = doc.selection(view.id);
4205− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4083+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4206− if !range.is_empty() {
4084+ (
4207− (range.from(), range.to(), Some(values.next().unwrap()))
4085+ range.from(),
4208− } else {
4086+ range.to(),
4209− (range.from(), range.to(), None)
4087+ Some(contents.repeat(count).as_str().into()),
4088+ )
4089+ });
4090+
4091+ doc.apply(&transaction, view.id);
4092+ doc.append_changes_to_history(view);
4210 }
4093 }
4094+ Err(e) => return Err(e.context("Couldn't get system clipboard contents")),
4211− });
4095+ }
4212
4096
4213− doc.apply(&transaction, view.id);
4097+ exit_select_mode(cx);
4098+ Ok(())
4214}
4099}
4215
4100
4216fn replace_selections_with_clipboard(cx: &mut Context) {
4101fn replace_selections_with_clipboard(cx: &mut Context) {
4217− replace_with_yanked_impl(cx.editor, '+', cx.count());
4102+ let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard);
4218− exit_select_mode(cx);
4219}
4103}
4220
4104
4221fn replace_selections_with_primary_clipboard(cx: &mut Context) {
4105fn replace_selections_with_primary_clipboard(cx: &mut Context) {
4222− replace_with_yanked_impl(cx.editor, '*', cx.count());
4106+ let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Selection);
4223− exit_select_mode(cx);
4224}
4107}
4225
4108
4226−fn paste(editor: &mut Editor, register: char, pos: Paste, count: usize) {
4109+fn paste(cx: &mut Context, pos: Paste) {
4227− let Some(values) = editor.registers.read(register, editor) else { return };
4110+ let count = cx.count();
4228− let values: Vec<_> = values.map(|value| value.to_string()).collect();
4111+ let reg_name = cx.register.unwrap_or('"');
4112+ let (view, doc) = current!(cx.editor);
4113+ let registers = &mut cx.editor.registers;
4229
4114
4230− let (view, doc) = current!(editor);
4115+ if let Some(values) = registers.read(reg_name) {
4231− paste_impl(&values, doc, view, pos, count, editor.mode);
4116+ paste_impl(values, doc, view, pos, count, cx.editor.mode);
4117+ }
4232}
4118}
4233
4119
4234fn paste_after(cx: &mut Context) {
4120fn paste_after(cx: &mut Context) {
4235− paste(
4236− cx.editor,
4237− cx.register.unwrap_or('"'),
4238− Paste::After,
4121+ paste(cx, Paste::After)
4239− cx.count(),
4240− );
4241− exit_select_mode(cx);
4242}
4122}
4243
4123
4244fn paste_before(cx: &mut Context) {
4124fn paste_before(cx: &mut Context) {
4245− paste(
4246− cx.editor,
4247− cx.register.unwrap_or('"'),
4248− Paste::Before,
4125+ paste(cx, Paste::Before)
4249− cx.count(),
4250− );
4251− exit_select_mode(cx);
4252}
4126}
4253
4127
4254fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
4128fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
@@ -4287,7 +4161,6 @@ fn indent(cx: &mut Context) {
4287 }),
4161 }),
4288 );
4162 );
4289 doc.apply(&transaction, view.id);
4163 doc.apply(&transaction, view.id);
4290− exit_select_mode(cx);
4291}
4164}
4292
4165
4293fn unindent(cx: &mut Context) {
4166fn unindent(cx: &mut Context) {
@@ -4327,7 +4200,6 @@ fn unindent(cx: &mut Context) {
4327 let transaction = Transaction::change(doc.text(), changes.into_iter());
4200 let transaction = Transaction::change(doc.text(), changes.into_iter());
4328
4201
4329 doc.apply(&transaction, view.id);
4202 doc.apply(&transaction, view.id);
4330− exit_select_mode(cx);
4331}
4203}
4332
4204
4333fn format_selections(cx: &mut Context) {
4205fn format_selections(cx: &mut Context) {
@@ -4357,7 +4229,7 @@ fn format_selections(cx: &mut Context) {
4357 })
4229 })
4358 else {
4230 else {
4359 cx.editor
4231 cx.editor
4360− .set_error("No configured language server supports range formatting");
4232+ .set_error("No configured language server does not support range formatting");
4361 return;
4233 return;
4362 };
4234 };
4363
4235
@@ -4394,9 +4266,10 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4394 use movement::skip_while;
4266 use movement::skip_while;
4395 let (view, doc) = current!(cx.editor);
4267 let (view, doc) = current!(cx.editor);
4396 let text = doc.text();
4268 let text = doc.text();
4397− let slice = text.slice(..);
4269+ let slice = doc.text().slice(..);
4398
4270
4399 let mut changes = Vec::new();
4271 let mut changes = Vec::new();
4272+ let fragment = Tendril::from(" ");
4400
4273
4401 for selection in doc.selection(view.id) {
4274 for selection in doc.selection(view.id) {
4402 let (start, mut end) = selection.line_range(slice);
4275 let (start, mut end) = selection.line_range(slice);
@@ -4412,13 +4285,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4412 let mut end = text.line_to_char(line + 1);
4285 let mut end = text.line_to_char(line + 1);
4413 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
4286 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
4414
4287
4415− let separator = if end == line_end_char_index(&slice, line + 1) {
4288+ // need to skip from start, not end
4416− // the joining line contains only space-characters => don't include a whitespace when joining
4289+ let change = (start, end, Some(fragment.clone()));
4417− None
4418− } else {
4419− Some(Tendril::from(" "))
4420− };
4421− changes.push((start, end, separator));
4290+ changes.push(change);
4422 }
4291 }
4423 }
4292 }
4424
4293
@@ -4430,6 +4299,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4430 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
4299 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
4431 changes.dedup();
4300 changes.dedup();
4432
4301
4302+ // TODO: joining multiple empty lines should be replaced by a single space.
4303+ // need to merge change ranges that touch
4304+
4433 // select inserted spaces
4305 // select inserted spaces
4434 let transaction = if select_space {
4306 let transaction = if select_space {
4435 let ranges: SmallVec<_> = changes
4307 let ranges: SmallVec<_> = changes
@@ -4441,9 +4313,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4441 })
4313 })
4442 .collect();
4314 .collect();
4443 let selection = Selection::new(ranges, 0);
4315 let selection = Selection::new(ranges, 0);
4444− Transaction::change(text, changes.into_iter()).with_selection(selection)
4316+ Transaction::change(doc.text(), changes.into_iter()).with_selection(selection)
4445 } else {
4317 } else {
4446− Transaction::change(text, changes.into_iter())
4318+ Transaction::change(doc.text(), changes.into_iter())
4447 };
4319 };
4448
4320
4449 doc.apply(&transaction, view.id);
4321 doc.apply(&transaction, view.id);
@@ -4457,8 +4329,8 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
4457 if remove { "remove:" } else { "keep:" }.into(),
4329 if remove { "remove:" } else { "keep:" }.into(),
4458 Some(reg),
4330 Some(reg),
4459 ui::completers::none,
4331 ui::completers::none,
4460− move |cx, regex, event| {
4332+ move |editor, regex, event| {
4461− let (view, doc) = current!(cx.editor);
4333+ let (view, doc) = current!(editor);
4462 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
4334 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
4463 return;
4335 return;
4464 }
4336 }
@@ -4819,49 +4691,6 @@ fn select_prev_sibling(cx: &mut Context) {
4819 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
4691 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
4820}
4692}
4821
4693
4822−fn move_node_bound_impl(cx: &mut Context, dir: Direction, movement: Movement) {
4823− let motion = move |editor: &mut Editor| {
4824− let (view, doc) = current!(editor);
4825−
4826− if let Some(syntax) = doc.syntax() {
4827− let text = doc.text().slice(..);
4828− let current_selection = doc.selection(view.id);
4829−
4830− let selection = movement::move_parent_node_end(
4831− syntax,
4832− text,
4833− current_selection.clone(),
4834− dir,
4835− movement,
4836− );
4837−
4838− doc.set_selection(view.id, selection);
4839−
4840− // [TODO] temporary workaround until we're not using the idle timer to
4841− // trigger auto completions any more
4842− editor.clear_idle_timer();
4843− }
4844− };
4845−
4846− cx.editor.apply_motion(motion);
4847−}
4848−
4849−pub fn move_parent_node_end(cx: &mut Context) {
4850− move_node_bound_impl(cx, Direction::Forward, Movement::Move)
4851−}
4852−
4853−pub fn move_parent_node_start(cx: &mut Context) {
4854− move_node_bound_impl(cx, Direction::Backward, Movement::Move)
4855−}
4856−
4857−pub fn extend_parent_node_end(cx: &mut Context) {
4858− move_node_bound_impl(cx, Direction::Forward, Movement::Extend)
4859−}
4860−
4861−pub fn extend_parent_node_start(cx: &mut Context) {
4862− move_node_bound_impl(cx, Direction::Backward, Movement::Extend)
4863−}
4864−
4865fn match_brackets(cx: &mut Context) {
4694fn match_brackets(cx: &mut Context) {
4866 let (view, doc) = current!(cx.editor);
4695 let (view, doc) = current!(cx.editor);
4867 let is_select = cx.editor.mode == Mode::Select;
4696 let is_select = cx.editor.mode == Mode::Select;
@@ -4975,19 +4804,17 @@ fn transpose_view(cx: &mut Context) {
4975 cx.editor.transpose_view()
4804 cx.editor.transpose_view()
4976}
4805}
4977
4806
4978−/// Open a new split in the given direction specified by the action.
4807+// split helper, clear it later
4979−///
4980−/// Maintain the current view (both the cursor's position and view in document).
4981−fn split(editor: &mut Editor, action: Action) {
4808+fn split(cx: &mut Context, action: Action) {
4982− let (view, doc) = current!(editor);
4809+ let (view, doc) = current!(cx.editor);
4983 let id = doc.id();
4810 let id = doc.id();
4984 let selection = doc.selection(view.id).clone();
4811 let selection = doc.selection(view.id).clone();
4985 let offset = view.offset;
4812 let offset = view.offset;
4986
4813
4987− editor.switch(id, action);
4814+ cx.editor.switch(id, action);
4988
4815
4989 // match the selection in the previous view
4816 // match the selection in the previous view
4990− let (view, doc) = current!(editor);
4817+ let (view, doc) = current!(cx.editor);
4991 doc.set_selection(view.id, selection);
4818 doc.set_selection(view.id, selection);
4992 // match the view scroll offset (switch doesn't handle this fully
4819 // match the view scroll offset (switch doesn't handle this fully
4993 // since the selection is only matched after the split)
4820 // since the selection is only matched after the split)
@@ -4995,7 +4822,7 @@ fn split(editor: &mut Editor, action: Action) {
4995}
4822}
4996
4823
4997fn hsplit(cx: &mut Context) {
4824fn hsplit(cx: &mut Context) {
4998− split(cx.editor, Action::HorizontalSplit);
4825+ split(cx, Action::HorizontalSplit);
4999}
4826}
5000
4827
5001fn hsplit_new(cx: &mut Context) {
4828fn hsplit_new(cx: &mut Context) {
@@ -5003,7 +4830,7 @@ fn hsplit_new(cx: &mut Context) {
5003}
4830}
5004
4831
5005fn vsplit(cx: &mut Context) {
4832fn vsplit(cx: &mut Context) {
5006− split(cx.editor, Action::VerticalSplit);
4833+ split(cx, Action::VerticalSplit);
5007}
4834}
5008
4835
5009fn vsplit_new(cx: &mut Context) {
4836fn vsplit_new(cx: &mut Context) {
@@ -5052,12 +4879,7 @@ fn insert_register(cx: &mut Context) {
5052 if let Some(ch) = event.char() {
4879 if let Some(ch) = event.char() {
5053 cx.editor.autoinfo = None;
4880 cx.editor.autoinfo = None;
5054 cx.register = Some(ch);
4881 cx.register = Some(ch);
5055− paste(
5056− cx.editor,
5057− cx.register.unwrap_or('"'),
5058− Paste::Cursor,
4882+ paste(cx, Paste::Cursor);
5059− cx.count(),
5060− );
5061 }
4883 }
5062 })
4884 })
5063}
4885}
@@ -5609,18 +5431,12 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
5609 };
5431 };
5610
5432
5611 // These `usize`s cannot underflow because selection ranges cannot overlap.
5433 // These `usize`s cannot underflow because selection ranges cannot overlap.
5612− let anchor = to
5434+ // Once the MSRV is 1.66.0 (mixed_integer_ops is stabilized), we can use checked
5613− .checked_add_signed(offset)
5435+ // arithmetic to assert this.
5614− .expect("Selection ranges cannot overlap")
5436+ let anchor = (to as isize + offset - deleted_len as isize) as usize;
5615− .checked_sub(deleted_len)
5616− .expect("Selection ranges cannot overlap");
5617 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
5437 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
5618 ranges.push(new_range);
5438 ranges.push(new_range);
5619− offset = offset
5439+ offset = offset + output_len as isize - deleted_len as isize;
5620− .checked_add_unsigned(output_len)
5621− .expect("Selection ranges cannot overlap")
5622− .checked_sub_unsigned(deleted_len)
5623− .expect("Selection ranges cannot overlap");
5624
5440
5625 changes.push((from, to, Some(output)));
5441 changes.push((from, to, Some(output)));
5626 }
5442 }
@@ -5759,7 +5575,6 @@ fn increment_impl(cx: &mut Context, increment_direction: IncrementDirection) {
5759 let transaction = Transaction::change(doc.text(), changes.into_iter());
5575 let transaction = Transaction::change(doc.text(), changes.into_iter());
5760 let transaction = transaction.with_selection(new_selection);
5576 let transaction = transaction.with_selection(new_selection);
5761 doc.apply(&transaction, view.id);
5577 doc.apply(&transaction, view.id);
5762− exit_select_mode(cx);
5763 }
5578 }
5764}
5579}
5765
5580
@@ -5778,12 +5593,9 @@ fn record_macro(cx: &mut Context) {
5778 }
5593 }
5779 })
5594 })
5780 .collect::<String>();
5595 .collect::<String>();
5781− match cx.editor.registers.write(reg, vec![s]) {
5596+ cx.editor.registers.write(reg, vec![s]);
5782− Ok(_) => cx
5783− .editor
5597+ cx.editor
5784− .set_status(format!("Recorded to register [{}]", reg)),
5598+ .set_status(format!("Recorded to register [{}]", reg));
5785− Err(err) => cx.editor.set_error(err.to_string()),
5786− }
5787 } else {
5599 } else {
5788 let reg = cx.register.take().unwrap_or('@');
5600 let reg = cx.register.take().unwrap_or('@');
5789 cx.editor.macro_recording = Some((reg, Vec::new()));
5601 cx.editor.macro_recording = Some((reg, Vec::new()));
@@ -5803,14 +5615,8 @@ fn replay_macro(cx: &mut Context) {
5803 return;
5615 return;
5804 }
5616 }
5805
5617
5806− let keys: Vec<KeyEvent> = if let Some(keys) = cx
5618+ let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
5807− .editor
5808− .registers
5809− .read(reg, cx.editor)
5810− .filter(|values| values.len() == 1)
5811− .map(|mut values| values.next().unwrap())
5812− {
5813− match helix_view::input::parse_macro(&keys) {
5619+ match helix_view::input::parse_macro(keys_str) {
5814 Ok(keys) => keys,
5620 Ok(keys) => keys,
5815 Err(err) => {
5621 Err(err) => {
5816 cx.editor.set_error(format!("Invalid macro: {}", err));
5622 cx.editor.set_error(format!("Invalid macro: {}", err));
helix-term/src/commands/dap.rs+4−13
@@ -8,7 +8,7 @@ use dap::{StackFrame, Thread, ThreadStates};
8use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion, DebugTemplate};
8use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion, DebugTemplate};
9use helix_dap::{self as dap, Client};
9use helix_dap::{self as dap, Client};
10use helix_lsp::block_on;
10use helix_lsp::block_on;
11−use helix_view::{editor::Breakpoint, graphics::Margin};
11+use helix_view::editor::Breakpoint;
12
12
13use serde_json::{to_value, Value};
13use serde_json::{to_value, Value};
14use tokio_stream::wrappers::UnboundedReceiverStream;
14use tokio_stream::wrappers::UnboundedReceiverStream;
@@ -339,12 +339,8 @@ fn debug_parameter_prompt(
339 .to_owned();
339 .to_owned();
340
340
341 let completer = match field_type {
341 let completer = match field_type {
342− "filename" => |editor: &Editor, input: &str| {
342+ "filename" => ui::completers::filename,
343− ui::completers::filename_with_git_ignore(editor, input, false)
343+ "directory" => ui::completers::directory,
344− },
345− "directory" => |editor: &Editor, input: &str| {
346− ui::completers::directory_with_git_ignore(editor, input, false)
347− },
348 _ => ui::completers::none,
344 _ => ui::completers::none,
349 };
345 };
350
346
@@ -581,12 +577,7 @@ pub fn dap_variables(cx: &mut Context) {
581 }
577 }
582
578
583 let contents = Text::from(tui::text::Text::from(variables));
579 let contents = Text::from(tui::text::Text::from(variables));
584− let margin = if cx.editor.popup_border() {
585− Margin::all(1)
586− } else {
587− Margin::none()
588− };
589− let popup = Popup::new("dap-variables", contents).margin(margin);
580+ let popup = Popup::new("dap-variables", contents);
590 cx.replace_or_push_layer("dap-variables", popup);
581 cx.replace_or_push_layer("dap-variables", popup);
591}
582}
592
583
helix-term/src/commands/lsp.rs+57−34
@@ -23,7 +23,6 @@ use helix_core::{
23use helix_view::{
23use helix_view::{
24 document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
24 document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
25 editor::Action,
25 editor::Action,
26− graphics::Margin,
27 theme::Style,
26 theme::Style,
28 Document, View,
27 Document, View,
29};
28};
@@ -50,7 +49,7 @@ use std::{
50/// If there is no configured language server that supports the feature, this displays a status message.
49/// If there is no configured language server that supports the feature, this displays a status message.
51/// Using this macro in a context where the editor automatically queries the LSP
50/// Using this macro in a context where the editor automatically queries the LSP
52/// (instead of when the user explicitly does so via a keybind like `gd`)
51/// (instead of when the user explicitly does so via a keybind like `gd`)
53−/// will spam the "No configured language server supports \<feature>" status message confusingly.
52+/// will spam the "No configured language server supports <feature>" status message confusingly.
54#[macro_export]
53#[macro_export]
55macro_rules! language_server_with_feature {
54macro_rules! language_server_with_feature {
56 ($editor:expr, $doc:expr, $feature:expr) => {{
55 ($editor:expr, $doc:expr, $feature:expr) => {{
@@ -196,6 +195,7 @@ fn location_to_file_location(location: &lsp::Location) -> FileLocation {
196 (path.into(), line)
195 (path.into(), line)
197}
196}
198
197
198+// TODO: share with symbol picker(symbol.location)
199fn jump_to_location(
199fn jump_to_location(
200 editor: &mut Editor,
200 editor: &mut Editor,
201 location: &lsp::Location,
201 location: &lsp::Location,
@@ -213,16 +213,15 @@ fn jump_to_location(
213 return;
213 return;
214 }
214 }
215 };
215 };
216−
217− let doc = match editor.open(&path, action) {
216+ match editor.open(&path, action) {
218− Ok(id) => doc_mut!(editor, &id),
217+ Ok(_) => (),
219 Err(err) => {
218 Err(err) => {
220 let err = format!("failed to open path: {:?}: {:?}", location.uri, err);
219 let err = format!("failed to open path: {:?}: {:?}", location.uri, err);
221 editor.set_error(err);
220 editor.set_error(err);
222 return;
221 return;
223 }
222 }
224− };
223+ }
225− let view = view_mut!(editor);
224+ let (view, doc) = current!(editor);
226 // TODO: convert inside server
225 // TODO: convert inside server
227 let new_range =
226 let new_range =
228 if let Some(new_range) = lsp_range_to_range(doc.text(), location.range, offset_encoding) {
227 if let Some(new_range) = lsp_range_to_range(doc.text(), location.range, offset_encoding) {
@@ -234,22 +233,45 @@ fn jump_to_location(
234 // we flip the range so that the cursor sits on the start of the symbol
233 // we flip the range so that the cursor sits on the start of the symbol
235 // (for example start of the function).
234 // (for example start of the function).
236 doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
235 doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
237− if action.align_view(view, doc.id()) {
238− align_view(doc, view, Align::Center);
236+ align_view(doc, view, Align::Center);
239− }
240}
237}
241
238
242type SymbolPicker = Picker<SymbolInformationItem>;
239type SymbolPicker = Picker<SymbolInformationItem>;
243
240
244fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url>) -> SymbolPicker {
241fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url>) -> SymbolPicker {
245 // TODO: drop current_path comparison and instead use workspace: bool flag?
242 // TODO: drop current_path comparison and instead use workspace: bool flag?
246− Picker::new(symbols, current_path, move |cx, item, action| {
243+ Picker::new(symbols, current_path.clone(), move |cx, item, action| {
247− jump_to_location(
248− cx.editor,
244+ let (view, doc) = current!(cx.editor);
245+ push_jump(view, doc);
246+
247+ if current_path.as_ref() != Some(&item.symbol.location.uri) {
249− &item.symbol.location,
248+ let uri = &item.symbol.location.uri;
249+ let path = match uri.to_file_path() {
250+ Ok(path) => path,
251+ Err(_) => {
252+ let err = format!("unable to convert URI to filepath: {}", uri);
253+ cx.editor.set_error(err);
254+ return;
255+ }
256+ };
257+ if let Err(err) = cx.editor.open(&path, action) {
258+ let err = format!("failed to open document: {}: {}", uri, err);
259+ log::error!("{}", err);
260+ cx.editor.set_error(err);
261+ return;
262+ }
263+ }
264+
265+ let (view, doc) = current!(cx.editor);
266+
267+ if let Some(range) =
250− item.offset_encoding,
268+ lsp_range_to_range(doc.text(), item.symbol.location.range, item.offset_encoding)
251− action,
269+ {
252− );
270+ // we flip the range so that the cursor sits on the start of the symbol
271+ // (for example start of the function).
272+ doc.set_selection(view.id, Selection::single(range.head, range.anchor));
273+ align_view(doc, view, Align::Center);
274+ }
253 })
275 })
254 .with_preview(move |_editor, item| Some(location_to_file_location(&item.symbol.location)))
276 .with_preview(move |_editor, item| Some(location_to_file_location(&item.symbol.location)))
255 .truncate_start(false)
277 .truncate_start(false)
@@ -264,7 +286,7 @@ enum DiagnosticsFormat {
264fn diag_picker(
286fn diag_picker(
265 cx: &Context,
287 cx: &Context,
266 diagnostics: BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize)>>,
288 diagnostics: BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize)>>,
267− _current_path: Option<lsp::Url>,
289+ current_path: Option<lsp::Url>,
268 format: DiagnosticsFormat,
290 format: DiagnosticsFormat,
269) -> Picker<PickerDiagnostic> {
291) -> Picker<PickerDiagnostic> {
270 // TODO: drop current_path comparison and instead use workspace: bool flag?
292 // TODO: drop current_path comparison and instead use workspace: bool flag?
@@ -302,12 +324,22 @@ fn diag_picker(
302 offset_encoding,
324 offset_encoding,
303 },
325 },
304 action| {
326 action| {
305− jump_to_location(
327+ if current_path.as_ref() == Some(url) {
306− cx.editor,
328+ let (view, doc) = current!(cx.editor);
307− &lsp::Location::new(url.clone(), diag.range),
329+ push_jump(view, doc);
308− *offset_encoding,
330+ } else {
309− action,
331+ let path = url.to_file_path().unwrap();
310− )
332+ cx.editor.open(&path, action).expect("editor.open failed");
333+ }
334+
335+ let (view, doc) = current!(cx.editor);
336+
337+ if let Some(range) = lsp_range_to_range(doc.text(), diag.range, *offset_encoding) {
338+ // we flip the range so that the cursor sits on the start of the symbol
339+ // (for example start of the function).
340+ doc.set_selection(view.id, Selection::single(range.head, range.anchor));
341+ align_view(doc, view, Align::Center);
342+ }
311 },
343 },
312 )
344 )
313 .with_preview(move |_editor, PickerDiagnostic { url, diag, .. }| {
345 .with_preview(move |_editor, PickerDiagnostic { url, diag, .. }| {
@@ -745,16 +777,7 @@ pub fn code_action(cx: &mut Context) {
745 });
777 });
746 picker.move_down(); // pre-select the first item
778 picker.move_down(); // pre-select the first item
747
779
748− let margin = if editor.menu_border() {
749− Margin::vertical(1)
750− } else {
751− Margin::none()
752− };
753−
754− let popup = Popup::new("code-action", picker)
780+ let popup = Popup::new("code-action", picker).with_scrollbar(false);
755− .with_scrollbar(false)
756− .margin(margin);
757−
758 compositor.replace_or_push("code-action", popup);
781 compositor.replace_or_push("code-action", popup);
759 };
782 };
760
783
helix-term/src/commands/typed.rs+695−890
@@ -5,10 +5,7 @@ use crate::job::Job;
5
5
6use super::*;
6use super::*;
7
7
8−use helix_core::fuzzy::fuzzy_match;
9−use helix_core::indent::MAX_INDENT;
10−use helix_core::{encoding, line_ending, path::get_canonicalized_path, shellwords::Shellwords};
8+use helix_core::{encoding, shellwords::Shellwords};
11−use helix_lsp::{OffsetEncoding, Url};
12use helix_view::document::DEFAULT_LANGUAGE_NAME;
9use helix_view::document::DEFAULT_LANGUAGE_NAME;
13use helix_view::editor::{Action, CloseError, ConfigEvent};
10use helix_view::editor::{Action, CloseError, ConfigEvent};
14use serde_json::Value;
11use serde_json::Value;
@@ -332,16 +329,12 @@ fn write_impl(
332 path: Option<&Cow<str>>,
329 path: Option<&Cow<str>>,
333 force: bool,
330 force: bool,
334) -> anyhow::Result<()> {
331) -> anyhow::Result<()> {
335− let config = cx.editor.config();
332+ let editor_auto_fmt = cx.editor.config().auto_format;
336 let jobs = &mut cx.jobs;
333 let jobs = &mut cx.jobs;
337 let (view, doc) = current!(cx.editor);
334 let (view, doc) = current!(cx.editor);
338 let path = path.map(AsRef::as_ref);
335 let path = path.map(AsRef::as_ref);
339
336
340− if config.insert_final_newline {
337+ let fmt = if editor_auto_fmt {
341− insert_final_newline(doc, view);
342− }
343−
344− let fmt = if config.auto_format {
345 doc.auto_format().map(|fmt| {
338 doc.auto_format().map(|fmt| {
346 let callback = make_format_callback(
339 let callback = make_format_callback(
347 doc.id(),
340 doc.id(),
@@ -365,16 +358,6 @@ fn write_impl(
365 Ok(())
358 Ok(())
366}
359}
367
360
368−fn insert_final_newline(doc: &mut Document, view: &mut View) {
369− let text = doc.text();
370− if line_ending::get_line_ending(&text.slice(..)).is_none() {
371− let eof = Selection::point(text.len_chars());
372− let insert = Transaction::insert(text, &eof, doc.line_ending.as_str().into());
373− doc.apply(&insert, view.id);
374− doc.append_changes_to_history(view);
375− }
376−}
377−
378fn write(
361fn write(
379 cx: &mut compositor::Context,
362 cx: &mut compositor::Context,
380 args: &[Cow<str>],
363 args: &[Cow<str>],
@@ -477,7 +460,8 @@ fn set_indent_style(
477 cx.editor.set_status(match style {
460 cx.editor.set_status(match style {
478 Tabs => "tabs".to_owned(),
461 Tabs => "tabs".to_owned(),
479 Spaces(1) => "1 space".to_owned(),
462 Spaces(1) => "1 space".to_owned(),
480− Spaces(n) => format!("{} spaces", n),
463+ Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
464+ _ => unreachable!(), // Shouldn't happen.
481 });
465 });
482 return Ok(());
466 return Ok(());
483 }
467 }
@@ -489,7 +473,7 @@ fn set_indent_style(
489 Some(arg) => arg
473 Some(arg) => arg
490 .parse::<u8>()
474 .parse::<u8>()
491 .ok()
475 .ok()
492− .filter(|n| (1..=MAX_INDENT).contains(n))
476+ .filter(|n| (1..=8).contains(n))
493 .map(Spaces),
477 .map(Spaces),
494 _ => None,
478 _ => None,
495 };
479 };
@@ -574,6 +558,7 @@ fn set_line_ending(
574
558
575 Ok(())
559 Ok(())
576}
560}
561+
577fn earlier(
562fn earlier(
578 cx: &mut compositor::Context,
563 cx: &mut compositor::Context,
579 args: &[Cow<str>],
564 args: &[Cow<str>],
@@ -672,10 +657,11 @@ pub fn write_all_impl(
672 write_scratch: bool,
657 write_scratch: bool,
673) -> anyhow::Result<()> {
658) -> anyhow::Result<()> {
674 let mut errors: Vec<&'static str> = Vec::new();
659 let mut errors: Vec<&'static str> = Vec::new();
675− let config = cx.editor.config();
660+ let auto_format = cx.editor.config().auto_format;
676 let jobs = &mut cx.jobs;
661 let jobs = &mut cx.jobs;
677 let current_view = view!(cx.editor);
662 let current_view = view!(cx.editor);
678
663
664+ // save all documents
679 let saves: Vec<_> = cx
665 let saves: Vec<_> = cx
680 .editor
666 .editor
681 .documents
667 .documents
@@ -686,7 +672,7 @@ pub fn write_all_impl(
686 }
672 }
687 if doc.path().is_none() {
673 if doc.path().is_none() {
688 if write_scratch {
674 if write_scratch {
689− errors.push("cannot write a buffer without a filename");
675+ errors.push("cannot write a buffer without a filename\n");
690 }
676 }
691 return None;
677 return None;
692 }
678 }
@@ -706,35 +692,32 @@ pub fn write_all_impl(
706 current_view.id
692 current_view.id
707 };
693 };
708
694
709− Some((doc.id(), target_view))
695+ let fmt = if auto_format {
696+ doc.auto_format().map(|fmt| {
697+ let callback = make_format_callback(
698+ doc.id(),
699+ doc.version(),
700+ target_view,
701+ fmt,
702+ Some((None, force)),
710− })
703+ );
711− .collect();
704+ jobs.add(Job::with_callback(callback).wait_before_exiting());
712−
705+ })
713− for (doc_id, target_view) in saves {
706+ } else {
714− let doc = doc_mut!(cx.editor, &doc_id);
707+ None
708+ };
715
709
716− if config.insert_final_newline {
710+ if fmt.is_none() {
717− insert_final_newline(doc, view_mut!(cx.editor, target_view));
711+ return Some(doc.id());
718− }
712+ }
719
713
720− let fmt = if config.auto_format {
721− doc.auto_format().map(|fmt| {
722− let callback = make_format_callback(
723− doc_id,
724− doc.version(),
725− target_view,
726− fmt,
727− Some((None, force)),
728− );
729− jobs.add(Job::with_callback(callback).wait_before_exiting());
730− })
731− } else {
732 None
714 None
733− };
715+ })
716+ .collect();
734
717
735− if fmt.is_none() {
718+ // manually call save for the rest of docs that don't have a formatter
719+ for id in saves {
736− cx.editor.save::<PathBuf>(doc_id, None, force)?;
720+ cx.editor.save::<PathBuf>(id, None, force)?;
737− }
738 }
721 }
739
722
740 if !errors.is_empty() && !force {
723 if !errors.is_empty() && !force {
@@ -921,8 +904,7 @@ fn yank_main_selection_to_clipboard(
921 return Ok(());
904 return Ok(());
922 }
905 }
923
906
924− yank_primary_selection_impl(cx.editor, '+');
907+ yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard)
925− Ok(())
926}
908}
927
909
928fn yank_joined(
910fn yank_joined(
@@ -956,8 +938,7 @@ fn yank_joined_to_clipboard(
956 let doc = doc!(cx.editor);
938 let doc = doc!(cx.editor);
957 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
939 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
958 let separator = args.first().unwrap_or(&default_sep);
940 let separator = args.first().unwrap_or(&default_sep);
959− yank_joined_impl(cx.editor, separator, '+');
941+ yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
960− Ok(())
961}
942}
962
943
963fn yank_main_selection_to_primary_clipboard(
944fn yank_main_selection_to_primary_clipboard(
@@ -969,8 +950,7 @@ fn yank_main_selection_to_primary_clipboard(
969 return Ok(());
950 return Ok(());
970 }
951 }
971
952
972− yank_primary_selection_impl(cx.editor, '*');
953+ yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection)
973− Ok(())
974}
954}
975
955
976fn yank_joined_to_primary_clipboard(
956fn yank_joined_to_primary_clipboard(
@@ -985,8 +965,7 @@ fn yank_joined_to_primary_clipboard(
985 let doc = doc!(cx.editor);
965 let doc = doc!(cx.editor);
986 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
966 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
987 let separator = args.first().unwrap_or(&default_sep);
967 let separator = args.first().unwrap_or(&default_sep);
988− yank_joined_impl(cx.editor, separator, '*');
968+ yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
989− Ok(())
990}
969}
991
970
992fn paste_clipboard_after(
971fn paste_clipboard_after(
@@ -998,8 +977,7 @@ fn paste_clipboard_after(
998 return Ok(());
977 return Ok(());
999 }
978 }
1000
979
1001− paste(cx.editor, '+', Paste::After, 1);
980+ paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard, 1)
1002− Ok(())
1003}
981}
1004
982
1005fn paste_clipboard_before(
983fn paste_clipboard_before(
@@ -1011,8 +989,7 @@ fn paste_clipboard_before(
1011 return Ok(());
989 return Ok(());
1012 }
990 }
1013
991
1014− paste(cx.editor, '+', Paste::Before, 1);
992+ paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Clipboard, 1)
1015− Ok(())
1016}
993}
1017
994
1018fn paste_primary_clipboard_after(
995fn paste_primary_clipboard_after(
@@ -1024,8 +1001,7 @@ fn paste_primary_clipboard_after(
1024 return Ok(());
1001 return Ok(());
1025 }
1002 }
1026
1003
1027− paste(cx.editor, '*', Paste::After, 1);
1004+ paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection, 1)
1028− Ok(())
1029}
1005}
1030
1006
1031fn paste_primary_clipboard_before(
1007fn paste_primary_clipboard_before(
@@ -1037,8 +1013,30 @@ fn paste_primary_clipboard_before(
1037 return Ok(());
1013 return Ok(());
1038 }
1014 }
1039
1015
1040− paste(cx.editor, '*', Paste::Before, 1);
1016+ paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Selection, 1)
1041− Ok(())
1017+}
1018+
1019+fn replace_selections_with_clipboard_impl(
1020+ cx: &mut compositor::Context,
1021+ clipboard_type: ClipboardType,
1022+) -> anyhow::Result<()> {
1023+ let scrolloff = cx.editor.config().scrolloff;
1024+ let (view, doc) = current!(cx.editor);
1025+
1026+ match cx.editor.clipboard_provider.get_contents(clipboard_type) {
1027+ Ok(contents) => {
1028+ let selection = doc.selection(view.id);
1029+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
1030+ (range.from(), range.to(), Some(contents.as_str().into()))
1031+ });
1032+
1033+ doc.apply(&transaction, view.id);
1034+ doc.append_changes_to_history(view);
1035+ view.ensure_cursor_in_view(doc, scrolloff);
1036+ Ok(())
1037+ }
1038+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
1039+ }
1042}
1040}
1043
1041
1044fn replace_selections_with_clipboard(
1042fn replace_selections_with_clipboard(
@@ -1050,8 +1048,7 @@ fn replace_selections_with_clipboard(
1050 return Ok(());
1048 return Ok(());
1051 }
1049 }
1052
1050
1053− replace_with_yanked_impl(cx.editor, '+', 1);
1051+ replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
1054− Ok(())
1055}
1052}
1056
1053
1057fn replace_selections_with_primary_clipboard(
1054fn replace_selections_with_primary_clipboard(
@@ -1063,8 +1060,7 @@ fn replace_selections_with_primary_clipboard(
1063 return Ok(());
1060 return Ok(());
1064 }
1061 }
1065
1062
1066− replace_with_yanked_impl(cx.editor, '*', 1);
1063+ replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
1067− Ok(())
1068}
1064}
1069
1065
1070fn show_clipboard_provider(
1066fn show_clipboard_provider(
@@ -1077,7 +1073,7 @@ fn show_clipboard_provider(
1077 }
1073 }
1078
1074
1079 cx.editor
1075 cx.editor
1080− .set_status(cx.editor.registers.clipboard_provider_name().to_string());
1076+ .set_status(cx.editor.clipboard_provider.name().to_string());
1081 Ok(())
1077 Ok(())
1082}
1078}
1083
1079
@@ -1282,10 +1278,12 @@ fn reload(
1282 }
1278 }
1283
1279
1284 let scrolloff = cx.editor.config().scrolloff;
1280 let scrolloff = cx.editor.config().scrolloff;
1281+ let redraw_handle = cx.editor.redraw_handle.clone();
1285 let (view, doc) = current!(cx.editor);
1282 let (view, doc) = current!(cx.editor);
1286− doc.reload(view, &cx.editor.diff_providers).map(|_| {
1283+ doc.reload(view, &cx.editor.diff_providers, redraw_handle)
1284+ .map(|_| {
1287− view.ensure_cursor_in_view(doc, scrolloff);
1285+ view.ensure_cursor_in_view(doc, scrolloff);
1288− })?;
1286+ })?;
1289 if let Some(path) = doc.path() {
1287 if let Some(path) = doc.path() {
1290 cx.editor
1288 cx.editor
1291 .language_servers
1289 .language_servers
@@ -1331,7 +1329,8 @@ fn reload_all(
1331 // Ensure that the view is synced with the document's history.
1329 // Ensure that the view is synced with the document's history.
1332 view.sync_changes(doc);
1330 view.sync_changes(doc);
1333
1331
1332+ let redraw_handle = cx.editor.redraw_handle.clone();
1334− doc.reload(view, &cx.editor.diff_providers)?;
1333+ doc.reload(view, &cx.editor.diff_providers, redraw_handle)?;
1335 if let Some(path) = doc.path() {
1334 if let Some(path) = doc.path() {
1336 cx.editor
1335 cx.editor
1337 .language_servers
1336 .language_servers
@@ -1543,84 +1542,6 @@ fn tree_sitter_scopes(
1543 Ok(())
1542 Ok(())
1544}
1543}
1545
1544
1546−fn tree_sitter_highlight_name(
1547− cx: &mut compositor::Context,
1548− _args: &[Cow<str>],
1549− event: PromptEvent,
1550−) -> anyhow::Result<()> {
1551− fn find_highlight_at_cursor(
1552− cx: &mut compositor::Context<'_>,
1553− ) -> Option<helix_core::syntax::Highlight> {
1554− use helix_core::syntax::HighlightEvent;
1555−
1556− let (view, doc) = current!(cx.editor);
1557− let syntax = doc.syntax()?;
1558− let text = doc.text().slice(..);
1559− let cursor = doc.selection(view.id).primary().cursor(text);
1560− let byte = text.char_to_byte(cursor);
1561− let node = syntax
1562− .tree()
1563− .root_node()
1564− .descendant_for_byte_range(byte, byte)?;
1565− // Query the same range as the one used in syntax highlighting.
1566− let range = {
1567− // Calculate viewport byte ranges:
1568− let row = text.char_to_line(view.offset.anchor.min(text.len_chars()));
1569− // Saturating subs to make it inclusive zero indexing.
1570− let last_line = text.len_lines().saturating_sub(1);
1571− let height = view.inner_area(doc).height;
1572− let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
1573− let start = text.line_to_byte(row.min(last_line));
1574− let end = text.line_to_byte(last_visible_line + 1);
1575−
1576− start..end
1577− };
1578−
1579− let mut highlight = None;
1580−
1581− for event in syntax.highlight_iter(text, Some(range), None) {
1582− match event.unwrap() {
1583− HighlightEvent::Source { start, end }
1584− if start == node.start_byte() && end == node.end_byte() =>
1585− {
1586− return highlight;
1587− }
1588− HighlightEvent::HighlightStart(hl) => {
1589− highlight = Some(hl);
1590− }
1591− _ => (),
1592− }
1593− }
1594−
1595− None
1596− }
1597−
1598− if event != PromptEvent::Validate {
1599− return Ok(());
1600− }
1601−
1602− let Some(highlight) = find_highlight_at_cursor(cx) else {
1603− return Ok(());
1604− };
1605−
1606− let content = cx.editor.theme.scope(highlight.0).to_string();
1607−
1608− let callback = async move {
1609− let call: job::Callback = Callback::EditorCompositor(Box::new(
1610− move |editor: &mut Editor, compositor: &mut Compositor| {
1611− let content = ui::Markdown::new(content, editor.syn_loader.clone());
1612− let popup = Popup::new("hover", content).auto_close(true);
1613− compositor.replace_or_push("hover", popup);
1614− },
1615− ));
1616− Ok(call)
1617− };
1618−
1619− cx.jobs.callback(callback);
1620−
1621− Ok(())
1622−}
1623−
1624fn vsplit(
1545fn vsplit(
1625 cx: &mut compositor::Context,
1546 cx: &mut compositor::Context,
1626 args: &[Cow<str>],
1547 args: &[Cow<str>],
@@ -1630,8 +1551,10 @@ fn vsplit(
1630 return Ok(());
1551 return Ok(());
1631 }
1552 }
1632
1553
1554+ let id = view!(cx.editor).doc;
1555+
1633 if args.is_empty() {
1556 if args.is_empty() {
1634− split(cx.editor, Action::VerticalSplit);
1557+ cx.editor.switch(id, Action::VerticalSplit);
1635 } else {
1558 } else {
1636 for arg in args {
1559 for arg in args {
1637 cx.editor
1560 cx.editor
@@ -1651,8 +1574,10 @@ fn hsplit(
1651 return Ok(());
1574 return Ok(());
1652 }
1575 }
1653
1576
1577+ let id = view!(cx.editor).doc;
1578+
1654 if args.is_empty() {
1579 if args.is_empty() {
1655− split(cx.editor, Action::HorizontalSplit);
1580+ cx.editor.switch(id, Action::HorizontalSplit);
1656 } else {
1581 } else {
1657 for arg in args {
1582 for arg in args {
1658 cx.editor
1583 cx.editor
@@ -1767,7 +1692,7 @@ fn tutor(
1767 let path = helix_loader::runtime_file(Path::new("tutor"));
1692 let path = helix_loader::runtime_file(Path::new("tutor"));
1768 cx.editor.open(&path, Action::Replace)?;
1693 cx.editor.open(&path, Action::Replace)?;
1769 // Unset path to prevent accidentally saving to the original tutor file.
1694 // Unset path to prevent accidentally saving to the original tutor file.
1770− doc_mut!(cx.editor).set_path(None);
1695+ doc_mut!(cx.editor).set_path(None)?;
1771 Ok(())
1696 Ok(())
1772}
1697}
1773
1698
@@ -1944,29 +1869,14 @@ fn toggle_option(
1944 .to_string(),
1869 .to_string(),
1945 )
1870 )
1946 }
1871 }
1947− Value::Number(ref value) => {
1948− ensure!(
1949− args.len() > 2,
1950− "Bad arguments. For number configurations use: `:toggle key val1 val2 ...`",
1951− );
1952−
1953− Value::Number(
1954− args[1..]
1955− .iter()
1956− .skip_while(|&e| value.to_string() != *e.to_string())
1957− .nth(1)
1958− .unwrap_or_else(|| &args[1])
1959− .parse()?,
1960− )
1961− }
1962− Value::Null | Value::Object(_) | Value::Array(_) => {
1872+ Value::Null | Value::Object(_) | Value::Array(_) | Value::Number(_) => {
1963 anyhow::bail!("Configuration {key} does not support toggle yet")
1873 anyhow::bail!("Configuration {key} does not support toggle yet")
1964 }
1874 }
1965 };
1875 };
1966
1876
1967 let status = format!("'{key}' is now set to {value}");
1877 let status = format!("'{key}' is now set to {value}");
1968 let config = serde_json::from_value(config)
1878 let config = serde_json::from_value(config)
1969− .map_err(|err| anyhow::anyhow!("Cannot parse `{:?}`, {}", &args, err))?;
1879+ .map_err(|_| anyhow::anyhow!("Could not parse field: `{:?}`", &args))?;
1970
1880
1971 cx.editor
1881 cx.editor
1972 .config_events
1882 .config_events
@@ -2375,721 +2285,606 @@ fn clear_register(
2375 format!("Invalid register {}", args[0])
2285 format!("Invalid register {}", args[0])
2376 );
2286 );
2377 let register = args[0].chars().next().unwrap_or_default();
2287 let register = args[0].chars().next().unwrap_or_default();
2378− if cx.editor.registers.remove(register) {
2288+ match cx.editor.registers.remove(register) {
2289+ Some(_) => cx
2379− cx.editor
2290+ .editor
2380− .set_status(format!("Register {} cleared", register));
2291+ .set_status(format!("Register {} cleared", register)),
2381− } else {
2292+ None => cx
2382− cx.editor
2293+ .editor
2383− .set_error(format!("Register {} not found", register));
2294+ .set_error(format!("Register {} not found", register)),
2384 }
2295 }
2385 Ok(())
2296 Ok(())
2386}
2297}
2387
2298
2388−fn redraw(
2389− cx: &mut compositor::Context,
2390− _args: &[Cow<str>],
2391− event: PromptEvent,
2392−) -> anyhow::Result<()> {
2393− if event != PromptEvent::Validate {
2394− return Ok(());
2395− }
2396−
2397− let callback = Box::pin(async move {
2398− let call: job::Callback =
2399− job::Callback::EditorCompositor(Box::new(|_editor, compositor| {
2400− compositor.need_full_redraw();
2401− }));
2402−
2403− Ok(call)
2404− });
2405−
2406− cx.jobs.callback(callback);
2407−
2408− Ok(())
2409−}
2410−
2411−fn move_buffer(
2412− cx: &mut compositor::Context,
2413− args: &[Cow<str>],
2414− event: PromptEvent,
2415−) -> anyhow::Result<()> {
2416− if event != PromptEvent::Validate {
2417− return Ok(());
2418− }
2419−
2420− ensure!(args.len() == 1, format!(":move takes one argument"));
2421− let doc = doc!(cx.editor);
2422−
2423− let new_path = get_canonicalized_path(&PathBuf::from(args.first().unwrap().to_string()));
2424− let old_path = doc
2425− .path()
2426− .ok_or_else(|| anyhow!("Scratch buffer cannot be moved. Use :write instead"))?
2427− .clone();
2428− let old_path_as_url = doc.url().unwrap();
2429− let new_path_as_url = Url::from_file_path(&new_path).unwrap();
2430−
2431− let edits: Vec<(
2432− helix_lsp::Result<helix_lsp::lsp::WorkspaceEdit>,
2433− OffsetEncoding,
2434− String,
2435− )> = doc
2436− .language_servers()
2437− .map(|lsp| {
2438− (
2439− lsp.prepare_file_rename(&old_path_as_url, &new_path_as_url),
2440− lsp.offset_encoding(),
2441− lsp.name().to_owned(),
2442− )
2443− })
2444− .filter(|(f, _, _)| f.is_some())
2445− .map(|(f, encoding, name)| (helix_lsp::block_on(f.unwrap()), encoding, name))
2446− .collect();
2447−
2448− for (lsp_reply, encoding, name) in edits {
2449− match lsp_reply {
2450− Ok(edit) => {
2451− if let Err(e) = apply_workspace_edit(cx.editor, encoding, &edit) {
2452− log::error!(
2453− ":move command failed to apply edits from lsp {}: {:?}",
2454− name,
2455− e
2456− );
2457− };
2458− }
2459− Err(e) => {
2460− log::error!("LSP {} failed to treat willRename request: {:?}", name, e);
2461− }
2462− };
2463− }
2464−
2465− let doc = doc_mut!(cx.editor);
2466−
2467− doc.set_path(Some(new_path.as_path()));
2468− if let Err(e) = std::fs::rename(&old_path, &new_path) {
2469− doc.set_path(Some(old_path.as_path()));
2470− bail!("Could not move file: {}", e);
2471− };
2472−
2473− doc.language_servers().for_each(|lsp| {
2474− lsp.did_file_rename(&old_path_as_url, &new_path_as_url);
2475− });
2476−
2477− cx.editor
2478− .language_servers
2479− .file_event_handler
2480− .file_changed(new_path);
2481−
2482− Ok(())
2483−}
2484−
2485pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2299pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2486− TypableCommand {
2300+ TypableCommand {
2487− name: "quit",
2301+ name: "quit",
2488− aliases: &["q"],
2302+ aliases: &["q"],
2489− doc: "Close the current view.",
2303+ doc: "Close the current view.",
2490− fun: quit,
2304+ fun: quit,
2491− signature: CommandSignature::none(),
2305+ signature: CommandSignature::none(),
2492− },
2306+ },
2493− TypableCommand {
2307+ TypableCommand {
2494− name: "quit!",
2308+ name: "quit!",
2495− aliases: &["q!"],
2309+ aliases: &["q!"],
2496− doc: "Force close the current view, ignoring unsaved changes.",
2310+ doc: "Force close the current view, ignoring unsaved changes.",
2497− fun: force_quit,
2311+ fun: force_quit,
2498− signature: CommandSignature::none(),
2312+ signature: CommandSignature::none(),
2499− },
2313+ },
2500− TypableCommand {
2314+ TypableCommand {
2501− name: "open",
2315+ name: "open",
2502− aliases: &["o"],
2316+ aliases: &["o"],
2503− doc: "Open a file from disk into the current view.",
2317+ doc: "Open a file from disk into the current view.",
2504− fun: open,
2318+ fun: open,
2505− signature: CommandSignature::all(completers::filename),
2319+ signature: CommandSignature::all(completers::filename),
2506− },
2320+ },
2507− TypableCommand {
2321+ TypableCommand {
2508− name: "buffer-close",
2322+ name: "buffer-close",
2509− aliases: &["bc", "bclose"],
2323+ aliases: &["bc", "bclose"],
2510− doc: "Close the current buffer.",
2324+ doc: "Close the current buffer.",
2511− fun: buffer_close,
2325+ fun: buffer_close,
2512− signature: CommandSignature::all(completers::buffer),
2326+ signature: CommandSignature::all(completers::buffer),
2513− },
2327+ },
2514− TypableCommand {
2328+ TypableCommand {
2515− name: "buffer-close!",
2329+ name: "buffer-close!",
2516− aliases: &["bc!", "bclose!"],
2330+ aliases: &["bc!", "bclose!"],
2517− doc: "Close the current buffer forcefully, ignoring unsaved changes.",
2331+ doc: "Close the current buffer forcefully, ignoring unsaved changes.",
2518− fun: force_buffer_close,
2332+ fun: force_buffer_close,
2519− signature: CommandSignature::all(completers::buffer)
2333+ signature: CommandSignature::all(completers::buffer)
2520− },
2334+ },
2521− TypableCommand {
2335+ TypableCommand {
2522− name: "buffer-close-others",
2336+ name: "buffer-close-others",
2523− aliases: &["bco", "bcloseother"],
2337+ aliases: &["bco", "bcloseother"],
2524− doc: "Close all buffers but the currently focused one.",
2338+ doc: "Close all buffers but the currently focused one.",
2525− fun: buffer_close_others,
2339+ fun: buffer_close_others,
2526− signature: CommandSignature::none(),
2340+ signature: CommandSignature::none(),
2527− },
2341+ },
2528− TypableCommand {
2342+ TypableCommand {
2529− name: "buffer-close-others!",
2343+ name: "buffer-close-others!",
2530− aliases: &["bco!", "bcloseother!"],
2344+ aliases: &["bco!", "bcloseother!"],
2531− doc: "Force close all buffers but the currently focused one.",
2345+ doc: "Force close all buffers but the currently focused one.",
2532− fun: force_buffer_close_others,
2346+ fun: force_buffer_close_others,
2533− signature: CommandSignature::none(),
2347+ signature: CommandSignature::none(),
2534− },
2348+ },
2535− TypableCommand {
2349+ TypableCommand {
2536− name: "buffer-close-all",
2350+ name: "buffer-close-all",
2537− aliases: &["bca", "bcloseall"],
2351+ aliases: &["bca", "bcloseall"],
2538− doc: "Close all buffers without quitting.",
2352+ doc: "Close all buffers without quitting.",
2539− fun: buffer_close_all,
2353+ fun: buffer_close_all,
2540− signature: CommandSignature::none(),
2354+ signature: CommandSignature::none(),
2541− },
2355+ },
2542− TypableCommand {
2356+ TypableCommand {
2543− name: "buffer-close-all!",
2357+ name: "buffer-close-all!",
2544− aliases: &["bca!", "bcloseall!"],
2358+ aliases: &["bca!", "bcloseall!"],
2545− doc: "Force close all buffers ignoring unsaved changes without quitting.",
2359+ doc: "Force close all buffers ignoring unsaved changes without quitting.",
2546− fun: force_buffer_close_all,
2360+ fun: force_buffer_close_all,
2547− signature: CommandSignature::none(),
2361+ signature: CommandSignature::none(),
2548− },
2362+ },
2549− TypableCommand {
2363+ TypableCommand {
2550− name: "buffer-next",
2364+ name: "buffer-next",
2551− aliases: &["bn", "bnext"],
2365+ aliases: &["bn", "bnext"],
2552− doc: "Goto next buffer.",
2366+ doc: "Goto next buffer.",
2553− fun: buffer_next,
2367+ fun: buffer_next,
2554− signature: CommandSignature::none(),
2368+ signature: CommandSignature::none(),
2555− },
2369+ },
2556− TypableCommand {
2370+ TypableCommand {
2557− name: "buffer-previous",
2371+ name: "buffer-previous",
2558− aliases: &["bp", "bprev"],
2372+ aliases: &["bp", "bprev"],
2559− doc: "Goto previous buffer.",
2373+ doc: "Goto previous buffer.",
2560− fun: buffer_previous,
2374+ fun: buffer_previous,
2561− signature: CommandSignature::none(),
2375+ signature: CommandSignature::none(),
2562− },
2376+ },
2563− TypableCommand {
2377+ TypableCommand {
2564− name: "write",
2378+ name: "write",
2565− aliases: &["w"],
2379+ aliases: &["w"],
2566− doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
2380+ doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
2567− fun: write,
2381+ fun: write,
2568− signature: CommandSignature::positional(&[completers::filename]),
2382+ signature: CommandSignature::positional(&[completers::filename]),
2569− },
2383+ },
2570− TypableCommand {
2384+ TypableCommand {
2571− name: "write!",
2385+ name: "write!",
2572− aliases: &["w!"],
2386+ aliases: &["w!"],
2573− doc: "Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt)",
2387+ doc: "Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt)",
2574− fun: force_write,
2388+ fun: force_write,
2575− signature: CommandSignature::positional(&[completers::filename]),
2389+ signature: CommandSignature::positional(&[completers::filename]),
2576− },
2390+ },
2577− TypableCommand {
2391+ TypableCommand {
2578− name: "write-buffer-close",
2392+ name: "write-buffer-close",
2579− aliases: &["wbc"],
2393+ aliases: &["wbc"],
2580− doc: "Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt)",
2394+ doc: "Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt)",
2581− fun: write_buffer_close,
2395+ fun: write_buffer_close,
2582− signature: CommandSignature::positional(&[completers::filename]),
2396+ signature: CommandSignature::positional(&[completers::filename]),
2583− },
2397+ },
2584− TypableCommand {
2398+ TypableCommand {
2585− name: "write-buffer-close!",
2399+ name: "write-buffer-close!",
2586− aliases: &["wbc!"],
2400+ aliases: &["wbc!"],
2587− doc: "Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt)",
2401+ doc: "Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt)",
2588− fun: force_write_buffer_close,
2402+ fun: force_write_buffer_close,
2589− signature: CommandSignature::positional(&[completers::filename]),
2403+ signature: CommandSignature::positional(&[completers::filename]),
2590− },
2404+ },
2591− TypableCommand {
2405+ TypableCommand {
2592− name: "new",
2406+ name: "new",
2593− aliases: &["n"],
2407+ aliases: &["n"],
2594− doc: "Create a new scratch buffer.",
2408+ doc: "Create a new scratch buffer.",
2595− fun: new_file,
2409+ fun: new_file,
2596− signature: CommandSignature::none(),
2410+ // TODO: This seems to complete with a filename, but doesn't use that filename to
2597− },
2411+ // set the path of the newly created buffer.
2598− TypableCommand {
2412+ signature: CommandSignature::positional(&[completers::filename]),
2599− name: "format",
2413+ },
2600− aliases: &["fmt"],
2414+ TypableCommand {
2601− doc: "Format the file using the LSP formatter.",
2415+ name: "format",
2602− fun: format,
2416+ aliases: &["fmt"],
2603− signature: CommandSignature::none(),
2417+ doc: "Format the file using the LSP formatter.",
2604− },
2418+ fun: format,
2605− TypableCommand {
2419+ signature: CommandSignature::none(),
2606− name: "indent-style",
2420+ },
2607− aliases: &[],
2421+ TypableCommand {
2608− doc: "Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.)",
2422+ name: "indent-style",
2609− fun: set_indent_style,
2423+ aliases: &[],
2610− signature: CommandSignature::none(),
2424+ doc: "Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)",
2611− },
2425+ fun: set_indent_style,
2612− TypableCommand {
2426+ signature: CommandSignature::none(),
2613− name: "line-ending",
2427+ },
2614− aliases: &[],
2428+ TypableCommand {
2615− #[cfg(not(feature = "unicode-lines"))]
2429+ name: "line-ending",
2616− doc: "Set the document's default line ending. Options: crlf, lf.",
2430+ aliases: &[],
2617− #[cfg(feature = "unicode-lines")]
2431+ #[cfg(not(feature = "unicode-lines"))]
2618− doc: "Set the document's default line ending. Options: crlf, lf, cr, ff, nel.",
2432+ doc: "Set the document's default line ending. Options: crlf, lf.",
2619− fun: set_line_ending,
2433+ #[cfg(feature = "unicode-lines")]
2620− signature: CommandSignature::none(),
2434+ doc: "Set the document's default line ending. Options: crlf, lf, cr, ff, nel.",
2621− },
2435+ fun: set_line_ending,
2622− TypableCommand {
2436+ signature: CommandSignature::none(),
2623− name: "earlier",
2437+ },
2624− aliases: &["ear"],
2438+ TypableCommand {
2625− doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
2439+ name: "earlier",
2626− fun: earlier,
2440+ aliases: &["ear"],
2627− signature: CommandSignature::none(),
2441+ doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
2628− },
2442+ fun: earlier,
2629− TypableCommand {
2443+ signature: CommandSignature::none(),
2630− name: "later",
2444+ },
2631− aliases: &["lat"],
2445+ TypableCommand {
2632− doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
2446+ name: "later",
2633− fun: later,
2447+ aliases: &["lat"],
2634− signature: CommandSignature::none(),
2448+ doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
2635− },
2449+ fun: later,
2636− TypableCommand {
2450+ signature: CommandSignature::none(),
2637− name: "write-quit",
2451+ },
2638− aliases: &["wq", "x"],
2452+ TypableCommand {
2639− doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
2453+ name: "write-quit",
2640− fun: write_quit,
2454+ aliases: &["wq", "x"],
2641− signature: CommandSignature::positional(&[completers::filename]),
2455+ doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
2642− },
2456+ fun: write_quit,
2643− TypableCommand {
2457+ signature: CommandSignature::positional(&[completers::filename]),
2644− name: "write-quit!",
2458+ },
2645− aliases: &["wq!", "x!"],
2459+ TypableCommand {
2646− doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
2460+ name: "write-quit!",
2647− fun: force_write_quit,
2461+ aliases: &["wq!", "x!"],
2648− signature: CommandSignature::positional(&[completers::filename]),
2462+ doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
2649− },
2463+ fun: force_write_quit,
2650− TypableCommand {
2464+ signature: CommandSignature::positional(&[completers::filename]),
2651− name: "write-all",
2465+ },
2652− aliases: &["wa"],
2466+ TypableCommand {
2653− doc: "Write changes from all buffers to disk.",
2467+ name: "write-all",
2654− fun: write_all,
2468+ aliases: &["wa"],
2655− signature: CommandSignature::none(),
2469+ doc: "Write changes from all buffers to disk.",
2656− },
2470+ fun: write_all,
2657− TypableCommand {
2471+ signature: CommandSignature::none(),
2658− name: "write-all!",
2472+ },
2659− aliases: &["wa!"],
2473+ TypableCommand {
2660− doc: "Forcefully write changes from all buffers to disk creating necessary subdirectories.",
2474+ name: "write-all!",
2661− fun: force_write_all,
2475+ aliases: &["wa!"],
2662− signature: CommandSignature::none(),
2476+ doc: "Forcefully write changes from all buffers to disk creating necessary subdirectories.",
2663− },
2477+ fun: force_write_all,
2664− TypableCommand {
2478+ signature: CommandSignature::none(),
2665− name: "write-quit-all",
2479+ },
2666− aliases: &["wqa", "xa"],
2480+ TypableCommand {
2667− doc: "Write changes from all buffers to disk and close all views.",
2481+ name: "write-quit-all",
2668− fun: write_all_quit,
2482+ aliases: &["wqa", "xa"],
2669− signature: CommandSignature::none(),
2483+ doc: "Write changes from all buffers to disk and close all views.",
2670− },
2484+ fun: write_all_quit,
2671− TypableCommand {
2485+ signature: CommandSignature::none(),
2672− name: "write-quit-all!",
2486+ },
2673− aliases: &["wqa!", "xa!"],
2487+ TypableCommand {
2674− doc: "Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes).",
2488+ name: "write-quit-all!",
2675− fun: force_write_all_quit,
2489+ aliases: &["wqa!", "xa!"],
2676− signature: CommandSignature::none(),
2490+ doc: "Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes).",
2677− },
2491+ fun: force_write_all_quit,
2678− TypableCommand {
2492+ signature: CommandSignature::none(),
2679− name: "quit-all",
2493+ },
2680− aliases: &["qa"],
2494+ TypableCommand {
2681− doc: "Close all views.",
2495+ name: "quit-all",
2682− fun: quit_all,
2496+ aliases: &["qa"],
2683− signature: CommandSignature::none(),
2497+ doc: "Close all views.",
2684− },
2498+ fun: quit_all,
2685− TypableCommand {
2499+ signature: CommandSignature::none(),
2686− name: "quit-all!",
2500+ },
2687− aliases: &["qa!"],
2501+ TypableCommand {
2688− doc: "Force close all views ignoring unsaved changes.",
2502+ name: "quit-all!",
2689− fun: force_quit_all,
2503+ aliases: &["qa!"],
2690− signature: CommandSignature::none(),
2504+ doc: "Force close all views ignoring unsaved changes.",
2691− },
2505+ fun: force_quit_all,
2692− TypableCommand {
2506+ signature: CommandSignature::none(),
2693− name: "cquit",
2507+ },
2694− aliases: &["cq"],
2508+ TypableCommand {
2695− doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).",
2509+ name: "cquit",
2696− fun: cquit,
2510+ aliases: &["cq"],
2697− signature: CommandSignature::none(),
2511+ doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).",
2698− },
2512+ fun: cquit,
2699− TypableCommand {
2513+ signature: CommandSignature::none(),
2700− name: "cquit!",
2514+ },
2701− aliases: &["cq!"],
2515+ TypableCommand {
2702− doc: "Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).",
2516+ name: "cquit!",
2703− fun: force_cquit,
2517+ aliases: &["cq!"],
2704− signature: CommandSignature::none(),
2518+ doc: "Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).",
2705− },
2519+ fun: force_cquit,
2706− TypableCommand {
2520+ signature: CommandSignature::none(),
2707− name: "theme",
2521+ },
2708− aliases: &[],
2522+ TypableCommand {
2709− doc: "Change the editor theme (show current theme if no name specified).",
2523+ name: "theme",
2710− fun: theme,
2524+ aliases: &[],
2711− signature: CommandSignature::positional(&[completers::theme]),
2525+ doc: "Change the editor theme (show current theme if no name specified).",
2712− },
2526+ fun: theme,
2713− TypableCommand {
2527+ signature: CommandSignature::positional(&[completers::theme]),
2714− name: "yank-join",
2528+ },
2715− aliases: &[],
2529+ TypableCommand {
2716− doc: "Yank joined selections. A separator can be provided as first argument. Default value is newline.",
2530+ name: "yank-join",
2717− fun: yank_joined,
2531+ aliases: &[],
2718− signature: CommandSignature::none(),
2532+ doc: "Yank joined selections. A separator can be provided as first argument. Default value is newline.",
2719− },
2533+ fun: yank_joined,
2720− TypableCommand {
2534+ signature: CommandSignature::none(),
2721− name: "clipboard-yank",
2535+ },
2722− aliases: &[],
2536+ TypableCommand {
2723− doc: "Yank main selection into system clipboard.",
2537+ name: "clipboard-yank",
2724− fun: yank_main_selection_to_clipboard,
2538+ aliases: &[],
2725− signature: CommandSignature::none(),
2539+ doc: "Yank main selection into system clipboard.",
2726− },
2540+ fun: yank_main_selection_to_clipboard,
2727− TypableCommand {
2541+ signature: CommandSignature::none(),
2728− name: "clipboard-yank-join",
2542+ },
2729− aliases: &[],
2543+ TypableCommand {
2730− doc: "Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2544+ name: "clipboard-yank-join",
2731− fun: yank_joined_to_clipboard,
2545+ aliases: &[],
2732− signature: CommandSignature::none(),
2546+ doc: "Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2733− },
2547+ fun: yank_joined_to_clipboard,
2734− TypableCommand {
2548+ signature: CommandSignature::none(),
2735− name: "primary-clipboard-yank",
2549+ },
2736− aliases: &[],
2550+ TypableCommand {
2737− doc: "Yank main selection into system primary clipboard.",
2551+ name: "primary-clipboard-yank",
2738− fun: yank_main_selection_to_primary_clipboard,
2552+ aliases: &[],
2739− signature: CommandSignature::none(),
2553+ doc: "Yank main selection into system primary clipboard.",
2740− },
2554+ fun: yank_main_selection_to_primary_clipboard,
2741− TypableCommand {
2555+ signature: CommandSignature::none(),
2742− name: "primary-clipboard-yank-join",
2556+ },
2743− aliases: &[],
2557+ TypableCommand {
2744− doc: "Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2558+ name: "primary-clipboard-yank-join",
2745− fun: yank_joined_to_primary_clipboard,
2559+ aliases: &[],
2746− signature: CommandSignature::none(),
2560+ doc: "Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2747− },
2561+ fun: yank_joined_to_primary_clipboard,
2748− TypableCommand {
2562+ signature: CommandSignature::none(),
2749− name: "clipboard-paste-after",
2563+ },
2750− aliases: &[],
2564+ TypableCommand {
2751− doc: "Paste system clipboard after selections.",
2565+ name: "clipboard-paste-after",
2752− fun: paste_clipboard_after,
2566+ aliases: &[],
2753− signature: CommandSignature::none(),
2567+ doc: "Paste system clipboard after selections.",
2754− },
2568+ fun: paste_clipboard_after,
2755− TypableCommand {
2569+ signature: CommandSignature::none(),
2756− name: "clipboard-paste-before",
2570+ },
2757− aliases: &[],
2571+ TypableCommand {
2758− doc: "Paste system clipboard before selections.",
2572+ name: "clipboard-paste-before",
2759− fun: paste_clipboard_before,
2573+ aliases: &[],
2760− signature: CommandSignature::none(),
2574+ doc: "Paste system clipboard before selections.",
2761− },
2575+ fun: paste_clipboard_before,
2762− TypableCommand {
2576+ signature: CommandSignature::none(),
2763− name: "clipboard-paste-replace",
2577+ },
2764− aliases: &[],
2578+ TypableCommand {
2765− doc: "Replace selections with content of system clipboard.",
2579+ name: "clipboard-paste-replace",
2766− fun: replace_selections_with_clipboard,
2580+ aliases: &[],
2767− signature: CommandSignature::none(),
2581+ doc: "Replace selections with content of system clipboard.",
2768− },
2582+ fun: replace_selections_with_clipboard,
2769− TypableCommand {
2583+ signature: CommandSignature::none(),
2770− name: "primary-clipboard-paste-after",
2584+ },
2771− aliases: &[],
2585+ TypableCommand {
2772− doc: "Paste primary clipboard after selections.",
2586+ name: "primary-clipboard-paste-after",
2773− fun: paste_primary_clipboard_after,
2587+ aliases: &[],
2774− signature: CommandSignature::none(),
2588+ doc: "Paste primary clipboard after selections.",
2775− },
2589+ fun: paste_primary_clipboard_after,
2776− TypableCommand {
2590+ signature: CommandSignature::none(),
2777− name: "primary-clipboard-paste-before",
2591+ },
2778− aliases: &[],
2592+ TypableCommand {
2779− doc: "Paste primary clipboard before selections.",
2593+ name: "primary-clipboard-paste-before",
2780− fun: paste_primary_clipboard_before,
2594+ aliases: &[],
2781− signature: CommandSignature::none(),
2595+ doc: "Paste primary clipboard before selections.",
2782− },
2596+ fun: paste_primary_clipboard_before,
2783− TypableCommand {
2597+ signature: CommandSignature::none(),
2784− name: "primary-clipboard-paste-replace",
2598+ },
2785− aliases: &[],
2599+ TypableCommand {
2786− doc: "Replace selections with content of system primary clipboard.",
2600+ name: "primary-clipboard-paste-replace",
2787− fun: replace_selections_with_primary_clipboard,
2601+ aliases: &[],
2788− signature: CommandSignature::none(),
2602+ doc: "Replace selections with content of system primary clipboard.",
2789− },
2603+ fun: replace_selections_with_primary_clipboard,
2790− TypableCommand {
2604+ signature: CommandSignature::none(),
2791− name: "show-clipboard-provider",
2605+ },
2792− aliases: &[],
2606+ TypableCommand {
2793− doc: "Show clipboard provider name in status bar.",
2607+ name: "show-clipboard-provider",
2794− fun: show_clipboard_provider,
2608+ aliases: &[],
2795− signature: CommandSignature::none(),
2609+ doc: "Show clipboard provider name in status bar.",
2796− },
2610+ fun: show_clipboard_provider,
2797− TypableCommand {
2611+ signature: CommandSignature::none(),
2798− name: "change-current-directory",
2612+ },
2799− aliases: &["cd"],
2613+ TypableCommand {
2800− doc: "Change the current working directory.",
2614+ name: "change-current-directory",
2801− fun: change_current_directory,
2615+ aliases: &["cd"],
2802− signature: CommandSignature::positional(&[completers::directory]),
2616+ doc: "Change the current working directory.",
2803− },
2617+ fun: change_current_directory,
2804− TypableCommand {
2618+ signature: CommandSignature::positional(&[completers::directory]),
2805− name: "show-directory",
2619+ },
2806− aliases: &["pwd"],
2620+ TypableCommand {
2807− doc: "Show the current working directory.",
2621+ name: "show-directory",
2808− fun: show_current_directory,
2622+ aliases: &["pwd"],
2809− signature: CommandSignature::none(),
2623+ doc: "Show the current working directory.",
2810− },
2624+ fun: show_current_directory,
2811− TypableCommand {
2625+ signature: CommandSignature::none(),
2812− name: "encoding",
2626+ },
2813− aliases: &[],
2627+ TypableCommand {
2814− doc: "Set encoding. Based on `https://encoding.spec.whatwg.org`.",
2628+ name: "encoding",
2815− fun: set_encoding,
2629+ aliases: &[],
2816− signature: CommandSignature::none(),
2630+ doc: "Set encoding. Based on `https://encoding.spec.whatwg.org`.",
2817− },
2631+ fun: set_encoding,
2818− TypableCommand {
2632+ signature: CommandSignature::none(),
2819− name: "character-info",
2633+ },
2820− aliases: &["char"],
2634+ TypableCommand {
2821− doc: "Get info about the character under the primary cursor.",
2635+ name: "character-info",
2822− fun: get_character_info,
2636+ aliases: &["char"],
2823− signature: CommandSignature::none(),
2637+ doc: "Get info about the character under the primary cursor.",
2824− },
2638+ fun: get_character_info,
2825− TypableCommand {
2639+ signature: CommandSignature::none(),
2826− name: "reload",
2640+ },
2827− aliases: &["rl"],
2641+ TypableCommand {
2828− doc: "Discard changes and reload from the source file.",
2642+ name: "reload",
2829− fun: reload,
2643+ aliases: &["rl"],
2830− signature: CommandSignature::none(),
2644+ doc: "Discard changes and reload from the source file.",
2831− },
2645+ fun: reload,
2832− TypableCommand {
2646+ signature: CommandSignature::none(),
2833− name: "reload-all",
2647+ },
2834− aliases: &["rla"],
2648+ TypableCommand {
2835− doc: "Discard changes and reload all documents from the source files.",
2649+ name: "reload-all",
2836− fun: reload_all,
2650+ aliases: &["rla"],
2837− signature: CommandSignature::none(),
2651+ doc: "Discard changes and reload all documents from the source files.",
2838− },
2652+ fun: reload_all,
2839− TypableCommand {
2653+ signature: CommandSignature::none(),
2840− name: "update",
2654+ },
2841− aliases: &["u"],
2655+ TypableCommand {
2842− doc: "Write changes only if the file has been modified.",
2656+ name: "update",
2843− fun: update,
2657+ aliases: &["u"],
2844− signature: CommandSignature::none(),
2658+ doc: "Write changes only if the file has been modified.",
2845− },
2659+ fun: update,
2846− TypableCommand {
2660+ signature: CommandSignature::none(),
2847− name: "lsp-workspace-command",
2661+ },
2848− aliases: &[],
2662+ TypableCommand {
2849− doc: "Open workspace command picker",
2663+ name: "lsp-workspace-command",
2850− fun: lsp_workspace_command,
2664+ aliases: &[],
2851− signature: CommandSignature::positional(&[completers::lsp_workspace_command]),
2665+ doc: "Open workspace command picker",
2852− },
2666+ fun: lsp_workspace_command,
2853− TypableCommand {
2667+ signature: CommandSignature::positional(&[completers::lsp_workspace_command]),
2854− name: "lsp-restart",
2668+ },
2855− aliases: &[],
2669+ TypableCommand {
2856− doc: "Restarts the language servers used by the current doc",
2670+ name: "lsp-restart",
2857− fun: lsp_restart,
2671+ aliases: &[],
2858− signature: CommandSignature::none(),
2672+ doc: "Restarts the language servers used by the current doc",
2859− },
2673+ fun: lsp_restart,
2860− TypableCommand {
2674+ signature: CommandSignature::none(),
2861− name: "lsp-stop",
2675+ },
2862− aliases: &[],
2676+ TypableCommand {
2863− doc: "Stops the language servers that are used by the current doc",
2677+ name: "lsp-stop",
2864− fun: lsp_stop,
2678+ aliases: &[],
2865− signature: CommandSignature::none(),
2679+ doc: "Stops the language servers that are used by the current doc",
2866− },
2680+ fun: lsp_stop,
2867− TypableCommand {
2681+ signature: CommandSignature::none(),
2868− name: "tree-sitter-scopes",
2682+ },
2869− aliases: &[],
2683+ TypableCommand {
2870− doc: "Display tree sitter scopes, primarily for theming and development.",
2684+ name: "tree-sitter-scopes",
2871− fun: tree_sitter_scopes,
2685+ aliases: &[],
2872− signature: CommandSignature::none(),
2686+ doc: "Display tree sitter scopes, primarily for theming and development.",
2873− },
2687+ fun: tree_sitter_scopes,
2874− TypableCommand {
2688+ signature: CommandSignature::none(),
2875− name: "tree-sitter-highlight-name",
2689+ },
2876− aliases: &[],
2690+ TypableCommand {
2877− doc: "Display name of tree-sitter highlight scope under the cursor.",
2691+ name: "debug-start",
2878− fun: tree_sitter_highlight_name,
2692+ aliases: &["dbg"],
2879− signature: CommandSignature::none(),
2693+ doc: "Start a debug session from a given template with given parameters.",
2880− },
2694+ fun: debug_start,
2881− TypableCommand {
2695+ signature: CommandSignature::none(),
2882− name: "debug-start",
2696+ },
2883− aliases: &["dbg"],
2697+ TypableCommand {
2884− doc: "Start a debug session from a given template with given parameters.",
2698+ name: "debug-remote",
2885− fun: debug_start,
2699+ aliases: &["dbg-tcp"],
2886− signature: CommandSignature::none(),
2700+ doc: "Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.",
2887− },
2701+ fun: debug_remote,
2888− TypableCommand {
2702+ signature: CommandSignature::none(),
2889− name: "debug-remote",
2703+ },
2890− aliases: &["dbg-tcp"],
2704+ TypableCommand {
2891− doc: "Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.",
2705+ name: "debug-eval",
2892− fun: debug_remote,
2706+ aliases: &[],
2893− signature: CommandSignature::none(),
2707+ doc: "Evaluate expression in current debug context.",
2894− },
2708+ fun: debug_eval,
2895− TypableCommand {
2709+ signature: CommandSignature::none(),
2896− name: "debug-eval",
2710+ },
2897− aliases: &[],
2711+ TypableCommand {
2898− doc: "Evaluate expression in current debug context.",
2712+ name: "vsplit",
2899− fun: debug_eval,
2713+ aliases: &["vs"],
2900− signature: CommandSignature::none(),
2714+ doc: "Open the file in a vertical split.",
2901− },
2715+ fun: vsplit,
2902− TypableCommand {
2716+ signature: CommandSignature::all(completers::filename)
2903− name: "vsplit",
2717+ },
2904− aliases: &["vs"],
2718+ TypableCommand {
2905− doc: "Open the file in a vertical split.",
2719+ name: "vsplit-new",
2906− fun: vsplit,
2720+ aliases: &["vnew"],
2907− signature: CommandSignature::all(completers::filename)
2721+ doc: "Open a scratch buffer in a vertical split.",
2908− },
2722+ fun: vsplit_new,
2909− TypableCommand {
2723+ signature: CommandSignature::none(),
2910− name: "vsplit-new",
2724+ },
2911− aliases: &["vnew"],
2725+ TypableCommand {
2912− doc: "Open a scratch buffer in a vertical split.",
2726+ name: "hsplit",
2913− fun: vsplit_new,
2727+ aliases: &["hs", "sp"],
2914− signature: CommandSignature::none(),
2728+ doc: "Open the file in a horizontal split.",
2915− },
2729+ fun: hsplit,
2916− TypableCommand {
2730+ signature: CommandSignature::all(completers::filename)
2917− name: "hsplit",
2731+ },
2918− aliases: &["hs", "sp"],
2732+ TypableCommand {
2919− doc: "Open the file in a horizontal split.",
2733+ name: "hsplit-new",
2920− fun: hsplit,
2734+ aliases: &["hnew"],
2921− signature: CommandSignature::all(completers::filename)
2735+ doc: "Open a scratch buffer in a horizontal split.",
2922− },
2736+ fun: hsplit_new,
2923− TypableCommand {
2737+ signature: CommandSignature::none(),
2924− name: "hsplit-new",
2738+ },
2925− aliases: &["hnew"],
2739+ TypableCommand {
2926− doc: "Open a scratch buffer in a horizontal split.",
2740+ name: "tutor",
2927− fun: hsplit_new,
2741+ aliases: &[],
2928− signature: CommandSignature::none(),
2742+ doc: "Open the tutorial.",
2929− },
2743+ fun: tutor,
2930− TypableCommand {
2744+ signature: CommandSignature::none(),
2931− name: "tutor",
2745+ },
2932− aliases: &[],
2746+ TypableCommand {
2933− doc: "Open the tutorial.",
2747+ name: "goto",
2934− fun: tutor,
2748+ aliases: &["g"],
2935− signature: CommandSignature::none(),
2749+ doc: "Goto line number.",
2936− },
2750+ fun: goto_line_number,
2937− TypableCommand {
2751+ signature: CommandSignature::none(),
2938− name: "goto",
2752+ },
2939− aliases: &["g"],
2753+ TypableCommand {
2940− doc: "Goto line number.",
2754+ name: "set-language",
2941− fun: goto_line_number,
2755+ aliases: &["lang"],
2942− signature: CommandSignature::none(),
2756+ doc: "Set the language of current buffer (show current language if no value specified).",
2943− },
2757+ fun: language,
2944− TypableCommand {
2758+ signature: CommandSignature::positional(&[completers::language]),
2945− name: "set-language",
2759+ },
2946− aliases: &["lang"],
2760+ TypableCommand {
2947− doc: "Set the language of current buffer (show current language if no value specified).",
2761+ name: "set-option",
2948− fun: language,
2762+ aliases: &["set"],
2949− signature: CommandSignature::positional(&[completers::language]),
2763+ doc: "Set a config option at runtime.\nFor example to disable smart case search, use `:set search.smart-case false`.",
2950− },
2764+ fun: set_option,
2951− TypableCommand {
2765+ // TODO: Add support for completion of the options value(s), when appropriate.
2952− name: "set-option",
2766+ signature: CommandSignature::positional(&[completers::setting]),
2953− aliases: &["set"],
2767+ },
2954− doc: "Set a config option at runtime.\nFor example to disable smart case search, use `:set search.smart-case false`.",
2768+ TypableCommand {
2955− fun: set_option,
2769+ name: "toggle-option",
2956− // TODO: Add support for completion of the options value(s), when appropriate.
2770+ aliases: &["toggle"],
2957− signature: CommandSignature::positional(&[completers::setting]),
2771+ doc: "Toggle a boolean config option at runtime.\nFor example to toggle smart case search, use `:toggle search.smart-case`.",
2958− },
2772+ fun: toggle_option,
2959− TypableCommand {
2773+ signature: CommandSignature::positional(&[completers::setting]),
2960− name: "toggle-option",
2774+ },
2961− aliases: &["toggle"],
2775+ TypableCommand {
2962− doc: "Toggle a boolean config option at runtime.\nFor example to toggle smart case search, use `:toggle search.smart-case`.",
2776+ name: "get-option",
2963− fun: toggle_option,
2777+ aliases: &["get"],
2964− signature: CommandSignature::positional(&[completers::setting]),
2778+ doc: "Get the current value of a config option.",
2965− },
2779+ fun: get_option,
2966− TypableCommand {
2780+ signature: CommandSignature::positional(&[completers::setting]),
2967− name: "get-option",
2781+ },
2968− aliases: &["get"],
2782+ TypableCommand {
2969− doc: "Get the current value of a config option.",
2783+ name: "sort",
2970− fun: get_option,
2784+ aliases: &[],
2971− signature: CommandSignature::positional(&[completers::setting]),
2785+ doc: "Sort ranges in selection.",
2972− },
2786+ fun: sort,
2973− TypableCommand {
2787+ signature: CommandSignature::none(),
2974− name: "sort",
2788+ },
2975− aliases: &[],
2789+ TypableCommand {
2976− doc: "Sort ranges in selection.",
2790+ name: "rsort",
2977− fun: sort,
2791+ aliases: &[],
2978− signature: CommandSignature::none(),
2792+ doc: "Sort ranges in selection in reverse order.",
2979− },
2793+ fun: sort_reverse,
2980− TypableCommand {
2794+ signature: CommandSignature::none(),
2981− name: "rsort",
2795+ },
2982− aliases: &[],
2796+ TypableCommand {
2983− doc: "Sort ranges in selection in reverse order.",
2797+ name: "reflow",
2984− fun: sort_reverse,
2798+ aliases: &[],
2985− signature: CommandSignature::none(),
2799+ doc: "Hard-wrap the current selection of lines to a given width.",
2986− },
2800+ fun: reflow,
2987− TypableCommand {
2801+ signature: CommandSignature::none(),
2988− name: "reflow",
2802+ },
2989− aliases: &[],
2803+ TypableCommand {
2990− doc: "Hard-wrap the current selection of lines to a given width.",
2804+ name: "tree-sitter-subtree",
2991− fun: reflow,
2805+ aliases: &["ts-subtree"],
2992− signature: CommandSignature::none(),
2806+ doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
2993− },
2807+ fun: tree_sitter_subtree,
2994− TypableCommand {
2808+ signature: CommandSignature::none(),
2995− name: "tree-sitter-subtree",
2809+ },
2996− aliases: &["ts-subtree"],
2810+ TypableCommand {
2997− doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
2811+ name: "config-reload",
2998− fun: tree_sitter_subtree,
2812+ aliases: &[],
2999− signature: CommandSignature::none(),
2813+ doc: "Refresh user config.",
3000− },
2814+ fun: refresh_config,
3001− TypableCommand {
2815+ signature: CommandSignature::none(),
3002− name: "config-reload",
2816+ },
3003− aliases: &[],
2817+ TypableCommand {
3004− doc: "Refresh user config.",
2818+ name: "config-open",
3005− fun: refresh_config,
2819+ aliases: &[],
3006− signature: CommandSignature::none(),
2820+ doc: "Open the user config.toml file.",
3007− },
2821+ fun: open_config,
3008− TypableCommand {
2822+ signature: CommandSignature::none(),
3009− name: "config-open",
2823+ },
3010− aliases: &[],
2824+ TypableCommand {
3011− doc: "Open the user config.toml file.",
2825+ name: "config-open-workspace",
3012− fun: open_config,
2826+ aliases: &[],
3013− signature: CommandSignature::none(),
2827+ doc: "Open the workspace config.toml file.",
3014− },
2828+ fun: open_workspace_config,
3015− TypableCommand {
2829+ signature: CommandSignature::none(),
3016− name: "config-open-workspace",
2830+ },
3017− aliases: &[],
2831+ TypableCommand {
3018− doc: "Open the workspace config.toml file.",
2832+ name: "log-open",
3019− fun: open_workspace_config,
2833+ aliases: &[],
3020− signature: CommandSignature::none(),
2834+ doc: "Open the helix log file.",
3021− },
2835+ fun: open_log,
3022− TypableCommand {
2836+ signature: CommandSignature::none(),
3023− name: "log-open",
2837+ },
3024− aliases: &[],
2838+ TypableCommand {
3025− doc: "Open the helix log file.",
2839+ name: "insert-output",
3026− fun: open_log,
2840+ aliases: &[],
3027− signature: CommandSignature::none(),
2841+ doc: "Run shell command, inserting output before each selection.",
3028− },
2842+ fun: insert_output,
3029− TypableCommand {
2843+ signature: CommandSignature::none(),
3030− name: "insert-output",
2844+ },
3031− aliases: &[],
2845+ TypableCommand {
3032− doc: "Run shell command, inserting output before each selection.",
2846+ name: "append-output",
3033− fun: insert_output,
2847+ aliases: &[],
3034− signature: CommandSignature::none(),
2848+ doc: "Run shell command, appending output after each selection.",
3035− },
2849+ fun: append_output,
3036− TypableCommand {
2850+ signature: CommandSignature::none(),
3037− name: "append-output",
2851+ },
3038− aliases: &[],
2852+ TypableCommand {
3039− doc: "Run shell command, appending output after each selection.",
2853+ name: "pipe",
3040− fun: append_output,
2854+ aliases: &[],
3041− signature: CommandSignature::none(),
2855+ doc: "Pipe each selection to the shell command.",
3042− },
2856+ fun: pipe,
3043− TypableCommand {
2857+ signature: CommandSignature::none(),
3044− name: "pipe",
2858+ },
3045− aliases: &[],
2859+ TypableCommand {
3046− doc: "Pipe each selection to the shell command.",
2860+ name: "pipe-to",
3047− fun: pipe,
2861+ aliases: &[],
3048− signature: CommandSignature::none(),
2862+ doc: "Pipe each selection to the shell command, ignoring output.",
3049− },
2863+ fun: pipe_to,
3050− TypableCommand {
2864+ signature: CommandSignature::none(),
3051− name: "pipe-to",
2865+ },
3052− aliases: &[],
2866+ TypableCommand {
3053− doc: "Pipe each selection to the shell command, ignoring output.",
2867+ name: "run-shell-command",
3054− fun: pipe_to,
2868+ aliases: &["sh"],
3055− signature: CommandSignature::none(),
2869+ doc: "Run a shell command",
3056− },
2870+ fun: run_shell_command,
3057− TypableCommand {
2871+ signature: CommandSignature::all(completers::filename)
3058− name: "run-shell-command",
2872+ },
3059− aliases: &["sh"],
2873+ TypableCommand {
3060− doc: "Run a shell command",
2874+ name: "reset-diff-change",
3061− fun: run_shell_command,
2875+ aliases: &["diffget", "diffg"],
3062− signature: CommandSignature::all(completers::filename)
2876+ doc: "Reset the diff change at the cursor position.",
3063− },
2877+ fun: reset_diff_change,
3064− TypableCommand {
2878+ signature: CommandSignature::none(),
3065− name: "reset-diff-change",
2879+ },
3066− aliases: &["diffget", "diffg"],
2880+ TypableCommand {
3067− doc: "Reset the diff change at the cursor position.",
2881+ name: "clear-register",
3068− fun: reset_diff_change,
2882+ aliases: &[],
3069− signature: CommandSignature::none(),
2883+ doc: "Clear given register. If no argument is provided, clear all registers.",
3070− },
2884+ fun: clear_register,
3071− TypableCommand {
2885+ signature: CommandSignature::none(),
3072− name: "clear-register",
2886+ },
3073− aliases: &[],
2887+ ];
3074− doc: "Clear given register. If no argument is provided, clear all registers.",
3075− fun: clear_register,
3076− signature: CommandSignature::none(),
3077− },
3078− TypableCommand {
3079− name: "redraw",
3080− aliases: &[],
3081− doc: "Clear and re-render the whole UI",
3082− fun: redraw,
3083− signature: CommandSignature::none(),
3084− },
3085− TypableCommand {
3086− name: "move",
3087− aliases: &[],
3088− doc: "Move the current buffer and its corresponding file to a different path",
3089− fun: move_buffer,
3090− signature: CommandSignature::positional(&[completers::filename]),
3091− },
3092−];
3093
2888
3094pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
2889pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
3095 Lazy::new(|| {
2890 Lazy::new(|| {
@@ -3108,18 +2903,28 @@ pub(super) fn command_mode(cx: &mut Context) {
3108 ":".into(),
2903 ":".into(),
3109 Some(':'),
2904 Some(':'),
3110 |editor: &Editor, input: &str| {
2905 |editor: &Editor, input: &str| {
2906+ static FUZZY_MATCHER: Lazy<fuzzy_matcher::skim::SkimMatcherV2> =
2907+ Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default);
2908+
3111 let shellwords = Shellwords::from(input);
2909 let shellwords = Shellwords::from(input);
3112 let words = shellwords.words();
2910 let words = shellwords.words();
3113
2911
3114 if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) {
2912 if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) {
2913+ // If the command has not been finished yet, complete commands.
2914+ let mut matches: Vec<_> = typed::TYPABLE_COMMAND_LIST
2915+ .iter()
2916+ .filter_map(|command| {
2917+ FUZZY_MATCHER
3115− fuzzy_match(
2918+ .fuzzy_match(command.name, input)
3116− input,
3117− TYPABLE_COMMAND_LIST.iter().map(|command| command.name),
2919+ .map(|score| (command.name, score))
3118− false,
2920+ })
3119− )
2921+ .collect();
2922+
2923+ matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score));
2924+ matches
3120− .into_iter()
2925+ .into_iter()
3121− .map(|(name, _)| (0.., name.into()))
2926+ .map(|(name, _)| (0.., name.into()))
3122− .collect()
2927+ .collect()
3123 } else {
2928 } else {
3124 // Otherwise, use the command's completer and the last shellword
2929 // Otherwise, use the command's completer and the last shellword
3125 // as completion input.
2930 // as completion input.
helix-term/src/compositor.rs−12
@@ -16,7 +16,6 @@ pub enum EventResult {
16}
16}
17
17
18use crate::job::Jobs;
18use crate::job::Jobs;
19−use crate::ui::picker;
20use helix_view::Editor;
19use helix_view::Editor;
21
20
22pub use helix_view::input::Event;
21pub use helix_view::input::Event;
@@ -80,7 +79,6 @@ pub struct Compositor {
80 area: Rect,
79 area: Rect,
81
80
82 pub(crate) last_picker: Option<Box<dyn Component>>,
81 pub(crate) last_picker: Option<Box<dyn Component>>,
83− pub(crate) full_redraw: bool,
84}
82}
85
83
86impl Compositor {
84impl Compositor {
@@ -89,7 +87,6 @@ impl Compositor {
89 layers: Vec::new(),
87 layers: Vec::new(),
90 area,
88 area,
91 last_picker: None,
89 last_picker: None,
92− full_redraw: false,
93 }
90 }
94 }
91 }
95
92
@@ -103,11 +100,6 @@ impl Compositor {
103
100
104 /// Add a layer to be rendered in front of all existing layers.
101 /// Add a layer to be rendered in front of all existing layers.
105 pub fn push(&mut self, mut layer: Box<dyn Component>) {
102 pub fn push(&mut self, mut layer: Box<dyn Component>) {
106− // immediately clear last_picker field to avoid excessive memory
107− // consumption for picker with many items
108− if layer.id() == Some(picker::ID) {
109− self.last_picker = None;
110− }
111 let size = self.size();
103 let size = self.size();
112 // trigger required_size on init
104 // trigger required_size on init
113 layer.required_size((size.width, size.height));
105 layer.required_size((size.width, size.height));
@@ -208,10 +200,6 @@ impl Compositor {
208 .find(|component| component.id() == Some(id))
200 .find(|component| component.id() == Some(id))
209 .and_then(|component| component.as_any_mut().downcast_mut())
201 .and_then(|component| component.as_any_mut().downcast_mut())
210 }
202 }
211−
212− pub fn need_full_redraw(&mut self) {
213− self.full_redraw = true;
214− }
215}
203}
216
204
217// View casting, taken straight from Cursive
205// View casting, taken straight from Cursive
helix-term/src/config.rs−1
@@ -109,7 +109,6 @@ impl Config {
109 )?,
109 )?,
110 }
110 }
111 }
111 }
112−
113 // these are just two io errors return the one for the global config
112 // these are just two io errors return the one for the global config
114 (Err(err), Err(_)) => return Err(err),
113 (Err(err), Err(_)) => return Err(err),
115 };
114 };
helix-term/src/health.rs+16−44
@@ -181,8 +181,8 @@ pub fn languages_all() -> std::io::Result<()> {
181 .language
181 .language
182 .sort_unstable_by_key(|l| l.language_id.clone());
182 .sort_unstable_by_key(|l| l.language_id.clone());
183
183
184− let check_binary = |cmd: Option<&str>| match cmd {
184+ let check_binary = |cmd: Option<String>| match cmd {
185− Some(cmd) => match which::which(cmd) {
185+ Some(cmd) => match which::which(&cmd) {
186 Ok(_) => column(&format!("✓ {}", cmd), Color::Green),
186 Ok(_) => column(&format!("✓ {}", cmd), Color::Green),
187 Err(_) => column(&format!("✘ {}", cmd), Color::Red),
187 Err(_) => column(&format!("✘ {}", cmd), Color::Red),
188 },
188 },
@@ -192,15 +192,17 @@ pub fn languages_all() -> std::io::Result<()> {
192 for lang in &syn_loader_conf.language {
192 for lang in &syn_loader_conf.language {
193 column(&lang.language_id, Color::Reset);
193 column(&lang.language_id, Color::Reset);
194
194
195+ // TODO multiple language servers (check binary for each supported language server, not just the first)
196+
195− let mut cmds = lang.language_servers.iter().filter_map(|ls| {
197+ let lsp = lang.language_servers.first().and_then(|ls| {
196 syn_loader_conf
198 syn_loader_conf
197 .language_server
199 .language_server
198 .get(&ls.name)
200 .get(&ls.name)
199− .map(|config| config.command.as_str())
201+ .map(|config| config.command.clone())
200 });
202 });
201− check_binary(cmds.next());
203+ check_binary(lsp);
202
204
203− let dap = lang.debugger.as_ref().map(|dap| dap.command.as_str());
205+ let dap = lang.debugger.as_ref().map(|dap| dap.command.to_string());
204 check_binary(dap);
206 check_binary(dap);
205
207
206 for ts_feat in TsFeature::all() {
208 for ts_feat in TsFeature::all() {
@@ -211,12 +213,6 @@ pub fn languages_all() -> std::io::Result<()> {
211 }
213 }
212
214
213 writeln!(stdout)?;
215 writeln!(stdout)?;
214−
215− for cmd in cmds {
216− column("", Color::Reset);
217− check_binary(Some(cmd));
218− writeln!(stdout)?;
219− }
220 }
216 }
221
217
222 Ok(())
218 Ok(())
@@ -272,12 +268,15 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
272 }
268 }
273 };
269 };
274
270
275− probe_protocols(
271+ // TODO multiple language servers
272+ probe_protocol(
276 "language server",
273 "language server",
277− lang.language_servers
274+ lang.language_servers.first().and_then(|ls| {
278− .iter()
279− .filter_map(|ls| syn_loader_conf.language_server.get(&ls.name))
275+ syn_loader_conf
276+ .language_server
277+ .get(&ls.name)
280− .map(|config| config.command.as_str()),
278+ .map(|config| config.command.clone())
279+ }),
281 )?;
280 )?;
282
281
283 probe_protocol(
282 probe_protocol(
@@ -292,33 +291,6 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
292 Ok(())
291 Ok(())
293}
292}
294
293
295−/// Display diagnostics about multiple LSPs and DAPs.
296−fn probe_protocols<'a, I: Iterator<Item = &'a str> + 'a>(
297− protocol_name: &str,
298− server_cmds: I,
299−) -> std::io::Result<()> {
300− let stdout = std::io::stdout();
301− let mut stdout = stdout.lock();
302− let mut server_cmds = server_cmds.peekable();
303−
304− write!(stdout, "Configured {}s:", protocol_name)?;
305− if server_cmds.peek().is_none() {
306− writeln!(stdout, "{}", " None".yellow())?;
307− return Ok(());
308− }
309− writeln!(stdout)?;
310−
311− for cmd in server_cmds {
312− let (path, icon) = match which::which(cmd) {
313− Ok(path) => (path.display().to_string().green(), "✓".green()),
314− Err(_) => (format!("'{}' not found in $PATH", cmd).red(), "✘".red()),
315− };
316− writeln!(stdout, " {} {}: {}", icon, cmd, path)?;
317− }
318−
319− Ok(())
320−}
321−
322/// Display diagnostics about LSP and DAP.
294/// Display diagnostics about LSP and DAP.
323fn probe_protocol(protocol_name: &str, server_cmd: Option<String>) -> std::io::Result<()> {
295fn probe_protocol(protocol_name: &str, server_cmd: Option<String>) -> std::io::Result<()> {
324 let stdout = std::io::stdout();
296 let stdout = std::io::stdout();
helix-term/src/keymap.rs+14−14
@@ -401,14 +401,14 @@ mod tests {
401 fn merge_partial_keys() {
401 fn merge_partial_keys() {
402 let keymap = hashmap! {
402 let keymap = hashmap! {
403 Mode::Normal => keymap!({ "Normal mode"
403 Mode::Normal => keymap!({ "Normal mode"
404− "i" => normal_mode,
404+ "i" => normal_mode,
405− "无" => insert_mode,
405+ "无" => insert_mode,
406− "z" => jump_backward,
406+ "z" => jump_backward,
407− "g" => { "Merge into goto mode"
407+ "g" => { "Merge into goto mode"
408− "$" => goto_line_end,
408+ "$" => goto_line_end,
409− "g" => delete_char_forward,
409+ "g" => delete_char_forward,
410− },
410+ },
411− })
411+ })
412 };
412 };
413 let mut merged_keyamp = default();
413 let mut merged_keyamp = default();
414 merge_keys(&mut merged_keyamp, keymap.clone());
414 merge_keys(&mut merged_keyamp, keymap.clone());
@@ -474,13 +474,13 @@ mod tests {
474 fn order_should_be_set() {
474 fn order_should_be_set() {
475 let keymap = hashmap! {
475 let keymap = hashmap! {
476 Mode::Normal => keymap!({ "Normal mode"
476 Mode::Normal => keymap!({ "Normal mode"
477− "space" => { ""
477+ "space" => { ""
478− "s" => { ""
478+ "s" => { ""
479− "v" => vsplit,
479+ "v" => vsplit,
480− "c" => hsplit,
480+ "c" => hsplit,
481+ },
481 },
482 },
482− },
483+ })
483− })
484 };
484 };
485 let mut merged_keyamp = default();
485 let mut merged_keyamp = default();
486 merge_keys(&mut merged_keyamp, keymap.clone());
486 merge_keys(&mut merged_keyamp, keymap.clone());
helix-term/src/keymap/default.rs+2−8
@@ -88,8 +88,6 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
88 "A-i" | "A-down" => shrink_selection,
88 "A-i" | "A-down" => shrink_selection,
89 "A-p" | "A-left" => select_prev_sibling,
89 "A-p" | "A-left" => select_prev_sibling,
90 "A-n" | "A-right" => select_next_sibling,
90 "A-n" | "A-right" => select_next_sibling,
91− "A-e" => move_parent_node_end,
92− "A-b" => move_parent_node_start,
93
91
94 "%" => select_all,
92 "%" => select_all,
95 "x" => extend_line_below,
93 "x" => extend_line_below,
@@ -267,7 +265,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
267 "C-v" | "v" => vsplit_new,
265 "C-v" | "v" => vsplit_new,
268 },
266 },
269 },
267 },
270− "y" => yank_to_clipboard,
268+ "y" => yank_joined_to_clipboard,
271 "Y" => yank_main_selection_to_clipboard,
269 "Y" => yank_main_selection_to_clipboard,
272 "p" => paste_clipboard_after,
270 "p" => paste_clipboard_after,
273 "P" => paste_clipboard_before,
271 "P" => paste_clipboard_before,
@@ -338,9 +336,6 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
338 "B" => extend_prev_long_word_start,
336 "B" => extend_prev_long_word_start,
339 "E" => extend_next_long_word_end,
337 "E" => extend_next_long_word_end,
340
338
341− "A-e" => extend_parent_node_end,
342− "A-b" => extend_parent_node_start,
343−
344 "n" => extend_search_next,
339 "n" => extend_search_next,
345 "N" => extend_search_prev,
340 "N" => extend_search_prev,
346
341
@@ -373,8 +368,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
373 "C-h" | "backspace" | "S-backspace" => delete_char_backward,
368 "C-h" | "backspace" | "S-backspace" => delete_char_backward,
374 "C-d" | "del" => delete_char_forward,
369 "C-d" | "del" => delete_char_forward,
375 "C-j" | "ret" => insert_newline,
370 "C-j" | "ret" => insert_newline,
376− "tab" => smart_tab,
377− "S-tab" => insert_tab,
371+ "tab" => insert_tab,
378
372
379 "up" => move_visual_line_up,
373 "up" => move_visual_line_up,
380 "down" => move_visual_line_down,
374 "down" => move_visual_line_down,
helix-term/src/main.rs+1−17
@@ -66,8 +66,6 @@ FLAGS:
66 -V, --version Prints version information
66 -V, --version Prints version information
67 --vsplit Splits all given files vertically into different windows
67 --vsplit Splits all given files vertically into different windows
68 --hsplit Splits all given files horizontally into different windows
68 --hsplit Splits all given files horizontally into different windows
69− -w, --working-dir <path> Specify an initial working directory
70− +N Open the first given file at line number N
71",
69",
72 env!("CARGO_PKG_NAME"),
70 env!("CARGO_PKG_NAME"),
73 VERSION_AND_GIT_HASH,
71 VERSION_AND_GIT_HASH,
@@ -76,7 +74,7 @@ FLAGS:
76 helix_loader::default_log_file().display(),
74 helix_loader::default_log_file().display(),
77 );
75 );
78
76
79− let mut args = Args::parse_args().context("could not parse arguments")?;
77+ let args = Args::parse_args().context("could not parse arguments")?;
80
78
81 helix_loader::initialize_config_file(args.config_file.clone());
79 helix_loader::initialize_config_file(args.config_file.clone());
82 helix_loader::initialize_log_file(args.log_file.clone());
80 helix_loader::initialize_log_file(args.log_file.clone());
@@ -116,20 +114,6 @@ FLAGS:
116
114
117 setup_logging(args.verbosity).context("failed to initialize logging")?;
115 setup_logging(args.verbosity).context("failed to initialize logging")?;
118
116
119− // Before setting the working directory, resolve all the paths in args.files
120− for (path, _) in args.files.iter_mut() {
121− *path = helix_core::path::get_canonicalized_path(path);
122− }
123−
124− // NOTE: Set the working directory early so the correct configuration is loaded. Be aware that
125− // Application::new() depends on this logic so it must be updated if this changes.
126− if let Some(path) = &args.working_directory {
127− helix_loader::set_current_working_dir(path)?;
128− } else if let Some((path, _)) = args.files.first().filter(|p| p.0.is_dir()) {
129− // If the first file is a directory, it will be the working directory unless -w was specified
130− helix_loader::set_current_working_dir(path)?;
131− }
132−
133 let config = match Config::load_default() {
117 let config = match Config::load_default() {
134 Ok(config) => config,
118 Ok(config) => config,
135 Err(ConfigLoadError::Error(err)) if err.kind() == std::io::ErrorKind::NotFound => {
119 Err(ConfigLoadError::Error(err)) if err.kind() == std::io::ErrorKind::NotFound => {
helix-term/src/ui/completion.rs+1−17
@@ -2,7 +2,6 @@ use crate::compositor::{Component, Context, Event, EventResult};
2use helix_view::{
2use helix_view::{
3 document::SavePoint,
3 document::SavePoint,
4 editor::CompleteAction,
4 editor::CompleteAction,
5− graphics::Margin,
6 theme::{Modifier, Style},
5 theme::{Modifier, Style},
7 ViewId,
6 ViewId,
8};
7};
@@ -327,18 +326,9 @@ impl Completion {
327 }
326 }
328 };
327 };
329 });
328 });
330−
331− let margin = if editor.menu_border() {
332− Margin::vertical(1)
333− } else {
334− Margin::none()
335− };
336−
337 let popup = Popup::new(Self::ID, menu)
329 let popup = Popup::new(Self::ID, menu)
338 .with_scrollbar(false)
330 .with_scrollbar(false)
339− .ignore_escape_key(true)
331+ .ignore_escape_key(true);
340− .margin(margin);
341−
342 let mut completion = Self {
332 let mut completion = Self {
343 popup,
333 popup,
344 start_offset,
334 start_offset,
@@ -579,12 +569,6 @@ impl Component for Completion {
579 // clear area
569 // clear area
580 let background = cx.editor.theme.get("ui.popup");
570 let background = cx.editor.theme.get("ui.popup");
581 surface.clear_with(doc_area, background);
571 surface.clear_with(doc_area, background);
582−
583− if cx.editor.popup_border() {
584− use tui::widgets::{Block, Borders, Widget};
585− Widget::render(Block::default().borders(Borders::ALL), doc_area, surface);
586− }
587−
588 markdown_doc.render(doc_area, surface, cx);
572 markdown_doc.render(doc_area, surface, cx);
589 }
573 }
590}
574}
helix-term/src/ui/document.rs+24−60
@@ -97,8 +97,7 @@ pub fn render_document(
97 doc: &Document,
97 doc: &Document,
98 offset: ViewPosition,
98 offset: ViewPosition,
99 doc_annotations: &TextAnnotations,
99 doc_annotations: &TextAnnotations,
100− syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
101− overlay_highlight_iter: impl Iterator<Item = HighlightEvent>,
100+ highlight_iter: impl Iterator<Item = HighlightEvent>,
102 theme: &Theme,
101 theme: &Theme,
103 line_decoration: &mut [Box<dyn LineDecoration + '_>],
102 line_decoration: &mut [Box<dyn LineDecoration + '_>],
104 translated_positions: &mut [TranslatedPosition],
103 translated_positions: &mut [TranslatedPosition],
@@ -110,8 +109,7 @@ pub fn render_document(
110 offset,
109 offset,
111 &doc.text_format(viewport.width, Some(theme)),
110 &doc.text_format(viewport.width, Some(theme)),
112 doc_annotations,
111 doc_annotations,
113− syntax_highlight_iter,
112+ highlight_iter,
114− overlay_highlight_iter,
115 theme,
113 theme,
116 line_decoration,
114 line_decoration,
117 translated_positions,
115 translated_positions,
@@ -159,8 +157,7 @@ pub fn render_text<'t>(
159 offset: ViewPosition,
157 offset: ViewPosition,
160 text_fmt: &TextFormat,
158 text_fmt: &TextFormat,
161 text_annotations: &TextAnnotations,
159 text_annotations: &TextAnnotations,
162− syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
163− overlay_highlight_iter: impl Iterator<Item = HighlightEvent>,
160+ highlight_iter: impl Iterator<Item = HighlightEvent>,
164 theme: &Theme,
161 theme: &Theme,
165 line_decorations: &mut [Box<dyn LineDecoration + '_>],
162 line_decorations: &mut [Box<dyn LineDecoration + '_>],
166 translated_positions: &mut [TranslatedPosition],
163 translated_positions: &mut [TranslatedPosition],
@@ -181,16 +178,10 @@ pub fn render_text<'t>(
181
178
182 let (mut formatter, mut first_visible_char_idx) =
179 let (mut formatter, mut first_visible_char_idx) =
183 DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, offset.anchor);
180 DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, offset.anchor);
184− let mut syntax_styles = StyleIter {
181+ let mut styles = StyleIter {
185 text_style: renderer.text_style,
182 text_style: renderer.text_style,
186 active_highlights: Vec::with_capacity(64),
183 active_highlights: Vec::with_capacity(64),
187− highlight_iter: syntax_highlight_iter,
184+ highlight_iter,
188− theme,
189− };
190− let mut overlay_styles = StyleIter {
191− text_style: Style::default(),
192− active_highlights: Vec::with_capacity(64),
193− highlight_iter: overlay_highlight_iter,
194 theme,
185 theme,
195 };
186 };
196
187
@@ -202,10 +193,7 @@ pub fn render_text<'t>(
202 };
193 };
203 let mut is_in_indent_area = true;
194 let mut is_in_indent_area = true;
204 let mut last_line_indent_level = 0;
195 let mut last_line_indent_level = 0;
205− let mut syntax_style_span = syntax_styles
196+ let mut style_span = styles
206− .next()
207− .unwrap_or_else(|| (Style::default(), usize::MAX));
208− let mut overlay_style_span = overlay_styles
209 .next()
197 .next()
210 .unwrap_or_else(|| (Style::default(), usize::MAX));
198 .unwrap_or_else(|| (Style::default(), usize::MAX));
211
199
@@ -233,16 +221,9 @@ pub fn render_text<'t>(
233
221
234 // skip any graphemes on visual lines before the block start
222 // skip any graphemes on visual lines before the block start
235 if pos.row < row_off {
223 if pos.row < row_off {
236− if char_pos >= syntax_style_span.1 {
224+ if char_pos >= style_span.1 {
237− syntax_style_span = if let Some(syntax_style_span) = syntax_styles.next() {
225+ style_span = if let Some(style_span) = styles.next() {
238− syntax_style_span
226+ style_span
239− } else {
240− break;
241− }
242− }
243− if char_pos >= overlay_style_span.1 {
244− overlay_style_span = if let Some(overlay_style_span) = overlay_styles.next() {
245− overlay_style_span
246 } else {
227 } else {
247 break;
228 break;
248 }
229 }
@@ -279,15 +260,8 @@ pub fn render_text<'t>(
279 }
260 }
280
261
281 // acquire the correct grapheme style
262 // acquire the correct grapheme style
282− if char_pos >= syntax_style_span.1 {
263+ if char_pos >= style_span.1 {
283− syntax_style_span = syntax_styles
284− .next()
285− .unwrap_or((Style::default(), usize::MAX));
286− }
287− if char_pos >= overlay_style_span.1 {
288− overlay_style_span = overlay_styles
289− .next()
290− .unwrap_or((Style::default(), usize::MAX));
264+ style_span = styles.next().unwrap_or((Style::default(), usize::MAX));
291 }
265 }
292 char_pos += grapheme.doc_chars();
266 char_pos += grapheme.doc_chars();
293
267
@@ -301,25 +275,22 @@ pub fn render_text<'t>(
301 pos,
275 pos,
302 );
276 );
303
277
304− let (syntax_style, overlay_style) =
305− if let GraphemeSource::VirtualText { highlight } = grapheme.source {
278+ let grapheme_style = if let GraphemeSource::VirtualText { highlight } = grapheme.source {
306− let mut style = renderer.text_style;
279+ let style = renderer.text_style;
307− if let Some(highlight) = highlight {
280+ if let Some(highlight) = highlight {
308− style = style.patch(theme.highlight(highlight.0))
281+ style.patch(theme.highlight(highlight.0))
309− }
310− (style, Style::default())
311 } else {
282 } else {
312− (syntax_style_span.0, overlay_style_span.0)
283+ style
284+ }
285+ } else {
286+ style_span.0
313− };
287+ };
314
288
315− let is_virtual = grapheme.is_virtual();
289+ let virt = grapheme.is_virtual();
316 renderer.draw_grapheme(
290 renderer.draw_grapheme(
317 grapheme.grapheme,
291 grapheme.grapheme,
318− GraphemeStyle {
292+ grapheme_style,
319− syntax_style,
293+ virt,
320− overlay_style,
321− },
322− is_virtual,
323 &mut last_line_indent_level,
294 &mut last_line_indent_level,
324 &mut is_in_indent_area,
295 &mut is_in_indent_area,
325 pos,
296 pos,
@@ -351,11 +322,6 @@ pub struct TextRenderer<'a> {
351 pub viewport: Rect,
322 pub viewport: Rect,
352}
323}
353
324
354−pub struct GraphemeStyle {
355− syntax_style: Style,
356− overlay_style: Style,
357−}
358−
359impl<'a> TextRenderer<'a> {
325impl<'a> TextRenderer<'a> {
360 pub fn new(
326 pub fn new(
361 surface: &'a mut Surface,
327 surface: &'a mut Surface,
@@ -429,7 +395,7 @@ impl<'a> TextRenderer<'a> {
429 pub fn draw_grapheme(
395 pub fn draw_grapheme(
430 &mut self,
396 &mut self,
431 grapheme: Grapheme,
397 grapheme: Grapheme,
432− grapheme_style: GraphemeStyle,
398+ mut style: Style,
433 is_virtual: bool,
399 is_virtual: bool,
434 last_indent_level: &mut usize,
400 last_indent_level: &mut usize,
435 is_in_indent_area: &mut bool,
401 is_in_indent_area: &mut bool,
@@ -439,11 +405,9 @@ impl<'a> TextRenderer<'a> {
439 let is_whitespace = grapheme.is_whitespace();
405 let is_whitespace = grapheme.is_whitespace();
440
406
441 // TODO is it correct to apply the whitespace style to all unicode white spaces?
407 // TODO is it correct to apply the whitespace style to all unicode white spaces?
442− let mut style = grapheme_style.syntax_style;
443 if is_whitespace {
408 if is_whitespace {
444 style = style.patch(self.whitespace_style);
409 style = style.patch(self.whitespace_style);
445 }
410 }
446− style = style.patch(grapheme_style.overlay_style);
447
411
448 let width = grapheme.width();
412 let width = grapheme.width();
449 let space = if is_virtual { " " } else { &self.space };
413 let space = if is_virtual { " " } else { &self.space };
helix-term/src/ui/editor.rs+54−102
@@ -43,8 +43,6 @@ pub struct EditorView {
43 pub(crate) last_insert: (commands::MappableCommand, Vec<InsertEvent>),
43 pub(crate) last_insert: (commands::MappableCommand, Vec<InsertEvent>),
44 pub(crate) completion: Option<Completion>,
44 pub(crate) completion: Option<Completion>,
45 spinners: ProgressSpinners,
45 spinners: ProgressSpinners,
46− /// Tracks if the terminal window is focused by reaction to terminal focus events
47− terminal_focused: bool,
48}
46}
49
47
50#[derive(Debug, Clone)]
48#[derive(Debug, Clone)]
@@ -73,7 +71,6 @@ impl EditorView {
73 last_insert: (commands::MappableCommand::normal_mode, Vec::new()),
71 last_insert: (commands::MappableCommand::normal_mode, Vec::new()),
74 completion: None,
72 completion: None,
75 spinners: ProgressSpinners::default(),
73 spinners: ProgressSpinners::default(),
76− terminal_focused: true,
77 }
74 }
78 }
75 }
79
76
@@ -124,20 +121,16 @@ impl EditorView {
124 line_decorations.push(Box::new(line_decoration));
121 line_decorations.push(Box::new(line_decoration));
125 }
122 }
126
123
127− let syntax_highlights =
124+ let mut highlights =
128 Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
125 Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
129−
130− let mut overlay_highlights =
126+ let overlay_highlights = Self::overlay_syntax_highlights(
131− Self::empty_highlight_iter(doc, view.offset.anchor, inner.height);
132− let overlay_syntax_highlights = Self::overlay_syntax_highlights(
133 doc,
127 doc,
134 view.offset.anchor,
128 view.offset.anchor,
135 inner.height,
129 inner.height,
136 &text_annotations,
130 &text_annotations,
137 );
131 );
138− if !overlay_syntax_highlights.is_empty() {
132+ if !overlay_highlights.is_empty() {
139− overlay_highlights =
133+ highlights = Box::new(syntax::merge(highlights, overlay_highlights));
140− Box::new(syntax::merge(overlay_highlights, overlay_syntax_highlights));
141 }
134 }
142
135
143 for diagnostic in Self::doc_diagnostics_highlights(doc, theme) {
136 for diagnostic in Self::doc_diagnostics_highlights(doc, theme) {
@@ -146,41 +139,39 @@ impl EditorView {
146 if diagnostic.is_empty() {
139 if diagnostic.is_empty() {
147 continue;
140 continue;
148 }
141 }
149− overlay_highlights = Box::new(syntax::merge(overlay_highlights, diagnostic));
142+ highlights = Box::new(syntax::merge(highlights, diagnostic));
150 }
143 }
151
144
152− if is_focused {
145+ let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
153 let highlights = syntax::merge(
146 let highlights = syntax::merge(
154− overlay_highlights,
147+ highlights,
155 Self::doc_selection_highlights(
148 Self::doc_selection_highlights(
156 editor.mode(),
149 editor.mode(),
157 doc,
150 doc,
158 view,
151 view,
159 theme,
152 theme,
160 &config.cursor_shape,
153 &config.cursor_shape,
161− self.terminal_focused,
162 ),
154 ),
163 );
155 );
164 let focused_view_elements = Self::highlight_focused_view_elements(view, doc, theme);
156 let focused_view_elements = Self::highlight_focused_view_elements(view, doc, theme);
165 if focused_view_elements.is_empty() {
157 if focused_view_elements.is_empty() {
166− overlay_highlights = Box::new(highlights)
158+ Box::new(highlights)
167 } else {
159 } else {
168− overlay_highlights = Box::new(syntax::merge(highlights, focused_view_elements))
160+ Box::new(syntax::merge(highlights, focused_view_elements))
169 }
161 }
170− }
162+ } else {
163+ Box::new(highlights)
164+ };
171
165
172− let gutter_overflow = view.gutter_offset(doc) == 0;
173− if !gutter_overflow {
174− Self::render_gutter(
166+ Self::render_gutter(
175− editor,
167+ editor,
176− doc,
168+ doc,
177− view,
169+ view,
178− view.area,
170+ view.area,
179− theme,
171+ theme,
180− is_focused & self.terminal_focused,
172+ is_focused,
181− &mut line_decorations,
173+ &mut line_decorations,
182− );
174+ );
183− }
184
175
185 if is_focused {
176 if is_focused {
186 let cursor = doc
177 let cursor = doc
@@ -200,8 +191,7 @@ impl EditorView {
200 doc,
191 doc,
201 view.offset,
192 view.offset,
202 &text_annotations,
193 &text_annotations,
203− syntax_highlights,
194+ highlights,
204− overlay_highlights,
205 theme,
195 theme,
206 &mut line_decorations,
196 &mut line_decorations,
207 &mut translated_positions,
197 &mut translated_positions,
@@ -261,39 +251,27 @@ impl EditorView {
261 .for_each(|area| surface.set_style(area, ruler_theme))
251 .for_each(|area| surface.set_style(area, ruler_theme))
262 }
252 }
263
253
264− fn viewport_byte_range(
254+ pub fn overlay_syntax_highlights(
265− text: helix_core::RopeSlice,
266− row: usize,
267− height: u16,
268− ) -> std::ops::Range<usize> {
269− // Calculate viewport byte ranges:
270− // Saturating subs to make it inclusive zero indexing.
271− let last_line = text.len_lines().saturating_sub(1);
272− let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
273− let start = text.line_to_byte(row.min(last_line));
274− let end = text.line_to_byte(last_visible_line + 1);
275−
276− start..end
277− }
278−
279− pub fn empty_highlight_iter(
280 doc: &Document,
255 doc: &Document,
281 anchor: usize,
256 anchor: usize,
282 height: u16,
257 height: u16,
283− ) -> Box<dyn Iterator<Item = HighlightEvent>> {
258+ text_annotations: &TextAnnotations,
259+ ) -> Vec<(usize, std::ops::Range<usize>)> {
284 let text = doc.text().slice(..);
260 let text = doc.text().slice(..);
285 let row = text.char_to_line(anchor.min(text.len_chars()));
261 let row = text.char_to_line(anchor.min(text.len_chars()));
286
262
263+ let range = {
287− // Calculate viewport byte ranges:
264+ // Calculate viewport byte ranges:
288− // Saturating subs to make it inclusive zero indexing.
265+ // Saturating subs to make it inclusive zero indexing.
289− let range = Self::viewport_byte_range(text, row, height);
266+ let last_line = text.len_lines().saturating_sub(1);
290− Box::new(
267+ let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
291− [HighlightEvent::Source {
268+ let start = text.line_to_byte(row.min(last_line));
292− start: text.byte_to_char(range.start),
269+ let end = text.line_to_byte(last_visible_line + 1);
293− end: text.byte_to_char(range.end),
270+
271+ start..end
294− }]
272+ };
295− .into_iter(),
273+
296− )
274+ text_annotations.collect_overlay_highlights(range)
297 }
275 }
298
276
299 /// Get syntax highlights for a document in a view represented by the first line
277 /// Get syntax highlights for a document in a view represented by the first line
@@ -308,7 +286,16 @@ impl EditorView {
308 let text = doc.text().slice(..);
286 let text = doc.text().slice(..);
309 let row = text.char_to_line(anchor.min(text.len_chars()));
287 let row = text.char_to_line(anchor.min(text.len_chars()));
310
288
311− let range = Self::viewport_byte_range(text, row, height);
289+ let range = {
290+ // Calculate viewport byte ranges:
291+ // Saturating subs to make it inclusive zero indexing.
292+ let last_line = text.len_lines().saturating_sub(1);
293+ let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
294+ let start = text.line_to_byte(row.min(last_line));
295+ let end = text.line_to_byte(last_visible_line + 1);
296+
297+ start..end
298+ };
312
299
313 match doc.syntax() {
300 match doc.syntax() {
314 Some(syntax) => {
301 Some(syntax) => {
@@ -341,20 +328,6 @@ impl EditorView {
341 }
328 }
342 }
329 }
343
330
344− pub fn overlay_syntax_highlights(
345− doc: &Document,
346− anchor: usize,
347− height: u16,
348− text_annotations: &TextAnnotations,
349− ) -> Vec<(usize, std::ops::Range<usize>)> {
350− let text = doc.text().slice(..);
351− let row = text.char_to_line(anchor.min(text.len_chars()));
352−
353− let range = Self::viewport_byte_range(text, row, height);
354−
355− text_annotations.collect_overlay_highlights(range)
356− }
357−
358 /// Get highlight spans for document diagnostics
331 /// Get highlight spans for document diagnostics
359 pub fn doc_diagnostics_highlights(
332 pub fn doc_diagnostics_highlights(
360 doc: &Document,
333 doc: &Document,
@@ -386,7 +359,7 @@ impl EditorView {
386 let mut warning_vec = Vec::new();
359 let mut warning_vec = Vec::new();
387 let mut error_vec = Vec::new();
360 let mut error_vec = Vec::new();
388
361
389− for diagnostic in doc.shown_diagnostics() {
362+ for diagnostic in doc.diagnostics() {
390 // Separate diagnostics into different Vecs by severity.
363 // Separate diagnostics into different Vecs by severity.
391 let (vec, scope) = match diagnostic.severity {
364 let (vec, scope) = match diagnostic.severity {
392 Some(Severity::Info) => (&mut info_vec, info),
365 Some(Severity::Info) => (&mut info_vec, info),
@@ -421,7 +394,6 @@ impl EditorView {
421 view: &View,
394 view: &View,
422 theme: &Theme,
395 theme: &Theme,
423 cursor_shape_config: &CursorShapeConfig,
396 cursor_shape_config: &CursorShapeConfig,
424− is_terminal_focused: bool,
425 ) -> Vec<(usize, std::ops::Range<usize>)> {
397 ) -> Vec<(usize, std::ops::Range<usize>)> {
426 let text = doc.text().slice(..);
398 let text = doc.text().slice(..);
427 let selection = doc.selection(view.id);
399 let selection = doc.selection(view.id);
@@ -469,7 +441,7 @@ impl EditorView {
469
441
470 // Special-case: cursor at end of the rope.
442 // Special-case: cursor at end of the rope.
471 if range.head == range.anchor && range.head == text.len_chars() {
443 if range.head == range.anchor && range.head == text.len_chars() {
472− if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
444+ if !selection_is_primary || cursor_is_block {
473 // Bar and underline cursors are drawn by the terminal
445 // Bar and underline cursors are drawn by the terminal
474 // BUG: If the editor area loses focus while having a bar or
446 // BUG: If the editor area loses focus while having a bar or
475 // underline cursor (eg. when a regex prompt has focus) then
447 // underline cursor (eg. when a regex prompt has focus) then
@@ -492,17 +464,13 @@ impl EditorView {
492 cursor_start
464 cursor_start
493 };
465 };
494 spans.push((selection_scope, range.anchor..selection_end));
466 spans.push((selection_scope, range.anchor..selection_end));
495− // add block cursors
496− // skip primary cursor if terminal is unfocused - crossterm cursor is used in that case
497− if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
467+ if !selection_is_primary || cursor_is_block {
498 spans.push((cursor_scope, cursor_start..range.head));
468 spans.push((cursor_scope, cursor_start..range.head));
499 }
469 }
500 } else {
470 } else {
501 // Reverse case.
471 // Reverse case.
502 let cursor_end = next_grapheme_boundary(text, range.head);
472 let cursor_end = next_grapheme_boundary(text, range.head);
503− // add block cursors
504− // skip primary cursor if terminal is unfocused - crossterm cursor is used in that case
505− if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
473+ if !selection_is_primary || cursor_is_block {
506 spans.push((cursor_scope, range.head..cursor_end));
474 spans.push((cursor_scope, range.head..cursor_end));
507 }
475 }
508 // non block cursors look like they exclude the cursor
476 // non block cursors look like they exclude the cursor
@@ -1099,7 +1067,6 @@ impl EditorView {
1099 let editor = &mut cxt.editor;
1067 let editor = &mut cxt.editor;
1100
1068
1101 if let Some((pos, view_id)) = pos_and_view(editor, row, column, true) {
1069 if let Some((pos, view_id)) = pos_and_view(editor, row, column, true) {
1102− let prev_view_id = view!(editor).id;
1103 let doc = doc_mut!(editor, &view!(editor, view_id).doc);
1070 let doc = doc_mut!(editor, &view!(editor, view_id).doc);
1104
1071
1105 if modifiers == KeyModifiers::ALT {
1072 if modifiers == KeyModifiers::ALT {
@@ -1109,10 +1076,6 @@ impl EditorView {
1109 doc.set_selection(view_id, Selection::point(pos));
1076 doc.set_selection(view_id, Selection::point(pos));
1110 }
1077 }
1111
1078
1112− if view_id != prev_view_id {
1113− self.clear_completion(editor);
1114− }
1115−
1116 editor.focus(view_id);
1079 editor.focus(view_id);
1117 editor.ensure_cursor_in_view(view_id);
1080 editor.ensure_cursor_in_view(view_id);
1118
1081
@@ -1406,17 +1369,13 @@ impl Component for EditorView {
1406
1369
1407 Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
1370 Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
1408 Event::IdleTimeout => self.handle_idle_timeout(&mut cx),
1371 Event::IdleTimeout => self.handle_idle_timeout(&mut cx),
1409− Event::FocusGained => {
1372+ Event::FocusGained => EventResult::Ignored(None),
1410− self.terminal_focused = true;
1411− EventResult::Consumed(None)
1412− }
1413 Event::FocusLost => {
1373 Event::FocusLost => {
1414 if context.editor.config().auto_save {
1374 if context.editor.config().auto_save {
1415 if let Err(e) = commands::typed::write_all_impl(context, false, false) {
1375 if let Err(e) = commands::typed::write_all_impl(context, false, false) {
1416 context.editor.set_error(format!("{}", e));
1376 context.editor.set_error(format!("{}", e));
1417 }
1377 }
1418 }
1378 }
1419− self.terminal_focused = false;
1420 EventResult::Consumed(None)
1379 EventResult::Consumed(None)
1421 }
1380 }
1422 }
1381 }
@@ -1526,15 +1485,8 @@ impl Component for EditorView {
1526
1485
1527 fn cursor(&self, _area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
1486 fn cursor(&self, _area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
1528 match editor.cursor() {
1487 match editor.cursor() {
1529− // all block cursors are drawn manually
1488+ // All block cursors are drawn manually
1530− (pos, CursorKind::Block) => {
1531− if self.terminal_focused {
1532− (pos, CursorKind::Hidden)
1489+ (pos, CursorKind::Block) => (pos, CursorKind::Hidden),
1533− } else {
1534− // use crossterm cursor when terminal loses focus
1535− (pos, CursorKind::Underline)
1536− }
1537− }
1538 cursor => cursor,
1490 cursor => cursor,
1539 }
1491 }
1540 }
1492 }
helix-term/src/ui/fuzzy_match.rsnew file+239
@@ -0,0 +1,239 @@
1+use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
2+use fuzzy_matcher::FuzzyMatcher;
3+
4+#[cfg(test)]
5+mod test;
6+
7+struct QueryAtom {
8+ kind: QueryAtomKind,
9+ atom: String,
10+ ignore_case: bool,
11+ inverse: bool,
12+}
13+impl QueryAtom {
14+ fn new(atom: &str) -> Option<QueryAtom> {
15+ let mut atom = atom.to_string();
16+ let inverse = atom.starts_with('!');
17+ if inverse {
18+ atom.remove(0);
19+ }
20+
21+ let mut kind = match atom.chars().next() {
22+ Some('^') => QueryAtomKind::Prefix,
23+ Some('\'') => QueryAtomKind::Substring,
24+ _ if inverse => QueryAtomKind::Substring,
25+ _ => QueryAtomKind::Fuzzy,
26+ };
27+
28+ if atom.starts_with(['^', '\'']) {
29+ atom.remove(0);
30+ }
31+
32+ if atom.is_empty() {
33+ return None;
34+ }
35+
36+ if atom.ends_with('$') && !atom.ends_with("\\$") {
37+ atom.pop();
38+ kind = if kind == QueryAtomKind::Prefix {
39+ QueryAtomKind::Exact
40+ } else {
41+ QueryAtomKind::Postfix
42+ }
43+ }
44+
45+ Some(QueryAtom {
46+ kind,
47+ atom: atom.replace('\\', ""),
48+ // not ideal but fuzzy_matches only knows ascii uppercase so more consistent
49+ // to behave the same
50+ ignore_case: kind != QueryAtomKind::Fuzzy
51+ && atom.chars().all(|c| c.is_ascii_lowercase()),
52+ inverse,
53+ })
54+ }
55+
56+ fn indices(&self, matcher: &Matcher, item: &str, indices: &mut Vec<usize>) -> bool {
57+ // for inverse there are no indices to return
58+ // just return whether we matched
59+ if self.inverse {
60+ return self.matches(matcher, item);
61+ }
62+ let buf;
63+ let item = if self.ignore_case {
64+ buf = item.to_ascii_lowercase();
65+ &buf
66+ } else {
67+ item
68+ };
69+ let off = match self.kind {
70+ QueryAtomKind::Fuzzy => {
71+ if let Some((_, fuzzy_indices)) = matcher.fuzzy_indices(item, &self.atom) {
72+ indices.extend_from_slice(&fuzzy_indices);
73+ return true;
74+ } else {
75+ return false;
76+ }
77+ }
78+ QueryAtomKind::Substring => {
79+ if let Some(off) = item.find(&self.atom) {
80+ off
81+ } else {
82+ return false;
83+ }
84+ }
85+ QueryAtomKind::Prefix if item.starts_with(&self.atom) => 0,
86+ QueryAtomKind::Postfix if item.ends_with(&self.atom) => item.len() - self.atom.len(),
87+ QueryAtomKind::Exact if item == self.atom => 0,
88+ _ => return false,
89+ };
90+
91+ indices.extend(off..(off + self.atom.len()));
92+ true
93+ }
94+
95+ fn matches(&self, matcher: &Matcher, item: &str) -> bool {
96+ let buf;
97+ let item = if self.ignore_case {
98+ buf = item.to_ascii_lowercase();
99+ &buf
100+ } else {
101+ item
102+ };
103+ let mut res = match self.kind {
104+ QueryAtomKind::Fuzzy => matcher.fuzzy_match(item, &self.atom).is_some(),
105+ QueryAtomKind::Substring => item.contains(&self.atom),
106+ QueryAtomKind::Prefix => item.starts_with(&self.atom),
107+ QueryAtomKind::Postfix => item.ends_with(&self.atom),
108+ QueryAtomKind::Exact => item == self.atom,
109+ };
110+ if self.inverse {
111+ res = !res;
112+ }
113+ res
114+ }
115+}
116+
117+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
118+enum QueryAtomKind {
119+ /// Item is a fuzzy match of this behaviour
120+ ///
121+ /// Usage: `foo`
122+ Fuzzy,
123+ /// Item contains query atom as a continuous substring
124+ ///
125+ /// Usage `'foo`
126+ Substring,
127+ /// Item starts with query atom
128+ ///
129+ /// Usage: `^foo`
130+ Prefix,
131+ /// Item ends with query atom
132+ ///
133+ /// Usage: `foo$`
134+ Postfix,
135+ /// Item is equal to query atom
136+ ///
137+ /// Usage `^foo$`
138+ Exact,
139+}
140+
141+#[derive(Default)]
142+pub struct FuzzyQuery {
143+ first_fuzzy_atom: Option<String>,
144+ query_atoms: Vec<QueryAtom>,
145+}
146+
147+fn query_atoms(query: &str) -> impl Iterator<Item = &str> + '_ {
148+ let mut saw_backslash = false;
149+ query.split(move |c| {
150+ saw_backslash = match c {
151+ ' ' if !saw_backslash => return true,
152+ '\\' => true,
153+ _ => false,
154+ };
155+ false
156+ })
157+}
158+
159+impl FuzzyQuery {
160+ pub fn refine(&self, query: &str, old_query: &str) -> (FuzzyQuery, bool) {
161+ // TODO: we could be a lot smarter about this
162+ let new_query = Self::new(query);
163+ let mut is_refinement = query.starts_with(old_query);
164+
165+ // if the last atom is an inverse atom adding more text to it
166+ // will actually increase the number of matches and we can not refine
167+ // the matches.
168+ if is_refinement && !self.query_atoms.is_empty() {
169+ let last_idx = self.query_atoms.len() - 1;
170+ if self.query_atoms[last_idx].inverse
171+ && self.query_atoms[last_idx].atom != new_query.query_atoms[last_idx].atom
172+ {
173+ is_refinement = false;
174+ }
175+ }
176+
177+ (new_query, is_refinement)
178+ }
179+
180+ pub fn new(query: &str) -> FuzzyQuery {
181+ let mut first_fuzzy_query = None;
182+ let query_atoms = query_atoms(query)
183+ .filter_map(|atom| {
184+ let atom = QueryAtom::new(atom)?;
185+ if atom.kind == QueryAtomKind::Fuzzy && first_fuzzy_query.is_none() {
186+ first_fuzzy_query = Some(atom.atom);
187+ None
188+ } else {
189+ Some(atom)
190+ }
191+ })
192+ .collect();
193+ FuzzyQuery {
194+ first_fuzzy_atom: first_fuzzy_query,
195+ query_atoms,
196+ }
197+ }
198+
199+ pub fn fuzzy_match(&self, item: &str, matcher: &Matcher) -> Option<i64> {
200+ // use the rank of the first fuzzzy query for the rank, because merging ranks is not really possible
201+ // this behaviour matches fzf and skim
202+ let score = self
203+ .first_fuzzy_atom
204+ .as_ref()
205+ .map_or(Some(0), |atom| matcher.fuzzy_match(item, atom))?;
206+ if self
207+ .query_atoms
208+ .iter()
209+ .any(|atom| !atom.matches(matcher, item))
210+ {
211+ return None;
212+ }
213+ Some(score)
214+ }
215+
216+ pub fn fuzzy_indices(&self, item: &str, matcher: &Matcher) -> Option<(i64, Vec<usize>)> {
217+ let (score, mut indices) = self.first_fuzzy_atom.as_ref().map_or_else(
218+ || Some((0, Vec::new())),
219+ |atom| matcher.fuzzy_indices(item, atom),
220+ )?;
221+
222+ // fast path for the common case of just a single atom
223+ if self.query_atoms.is_empty() {
224+ return Some((score, indices));
225+ }
226+
227+ for atom in &self.query_atoms {
228+ if !atom.indices(matcher, item, &mut indices) {
229+ return None;
230+ }
231+ }
232+
233+ // deadup and remove duplicate matches
234+ indices.sort_unstable();
235+ indices.dedup();
236+
237+ Some((score, indices))
238+ }
239+}
helix-term/src/ui/fuzzy_match/test.rsnew file+47
@@ -0,0 +1,47 @@
1+use crate::ui::fuzzy_match::FuzzyQuery;
2+use crate::ui::fuzzy_match::Matcher;
3+
4+fn run_test<'a>(query: &str, items: &'a [&'a str]) -> Vec<String> {
5+ let query = FuzzyQuery::new(query);
6+ let matcher = Matcher::default();
7+ items
8+ .iter()
9+ .filter_map(|item| {
10+ let (_, indices) = query.fuzzy_indices(item, &matcher)?;
11+ let matched_string = indices
12+ .iter()
13+ .map(|&pos| item.chars().nth(pos).unwrap())
14+ .collect();
15+ Some(matched_string)
16+ })
17+ .collect()
18+}
19+
20+#[test]
21+fn match_single_value() {
22+ let matches = run_test("foo", &["foobar", "foo", "bar"]);
23+ assert_eq!(matches, &["foo", "foo"])
24+}
25+
26+#[test]
27+fn match_multiple_values() {
28+ let matches = run_test(
29+ "foo bar",
30+ &["foo bar", "foo bar", "bar foo", "bar", "foo"],
31+ );
32+ assert_eq!(matches, &["foobar", "foobar", "barfoo"])
33+}
34+
35+#[test]
36+fn space_escape() {
37+ let matches = run_test(r"foo\ bar", &["bar foo", "foo bar", "foobar"]);
38+ assert_eq!(matches, &["foo bar"])
39+}
40+
41+#[test]
42+fn trim() {
43+ let matches = run_test(r" foo bar ", &["bar foo", "foo bar", "foobar"]);
44+ assert_eq!(matches, &["barfoo", "foobar", "foobar"]);
45+ let matches = run_test(r" foo bar\ ", &["bar foo", "foo bar", "foobar"]);
46+ assert_eq!(matches, &["bar foo"])
47+}
helix-term/src/ui/lsp.rs+1−3
@@ -92,9 +92,7 @@ impl Component for SignatureHelp {
92 Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
92 Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
93 };
93 };
94 let sig_doc = sig_doc.parse(Some(&cx.editor.theme));
94 let sig_doc = sig_doc.parse(Some(&cx.editor.theme));
95− let sig_doc_area = area
96− .clip_top(sig_text_area.height + 2)
95+ let sig_doc_area = area.clip_top(sig_text_area.height + 2);
97− .clip_bottom(u16::from(cx.editor.popup_border()));
98 let sig_doc_para = Paragraph::new(sig_doc)
96 let sig_doc_para = Paragraph::new(sig_doc)
99 .wrap(Wrap { trim: false })
97 .wrap(Wrap { trim: false })
100 .scroll((cx.scroll.unwrap_or_default() as u16, 0));
98 .scroll((cx.scroll.unwrap_or_default() as u16, 0));
helix-term/src/ui/markdown.rs+5−12
@@ -14,7 +14,6 @@ use helix_core::{
14};
14};
15use helix_view::{
15use helix_view::{
16 graphics::{Margin, Rect, Style},
16 graphics::{Margin, Rect, Style},
17− theme::Modifier,
18 Theme,
17 Theme,
19};
18};
20
19
@@ -184,9 +183,7 @@ impl Markdown {
184 // Transform text in `<code>` blocks into `Event::Code`
183 // Transform text in `<code>` blocks into `Event::Code`
185 let mut in_code = false;
184 let mut in_code = false;
186 let parser = parser.filter_map(|event| match event {
185 let parser = parser.filter_map(|event| match event {
187− Event::Html(tag)
186+ Event::Html(tag) if *tag == *"<code>" => {
188− if tag.starts_with("<code") && matches!(tag.chars().nth(5), Some(' ' | '>')) =>
189− {
190 in_code = true;
187 in_code = true;
191 None
188 None
192 }
189 }
@@ -278,21 +275,17 @@ impl Markdown {
278 );
275 );
279 lines.extend(tui_text.lines.into_iter());
276 lines.extend(tui_text.lines.into_iter());
280 } else {
277 } else {
281− let style = match tags.last() {
282− Some(Tag::Heading(level, ..)) => match level {
278+ let style = if let Some(Tag::Heading(level, ..)) = tags.last() {
279+ match level {
283 HeadingLevel::H1 => heading_styles[0],
280 HeadingLevel::H1 => heading_styles[0],
284 HeadingLevel::H2 => heading_styles[1],
281 HeadingLevel::H2 => heading_styles[1],
285 HeadingLevel::H3 => heading_styles[2],
282 HeadingLevel::H3 => heading_styles[2],
286 HeadingLevel::H4 => heading_styles[3],
283 HeadingLevel::H4 => heading_styles[3],
287 HeadingLevel::H5 => heading_styles[4],
284 HeadingLevel::H5 => heading_styles[4],
288 HeadingLevel::H6 => heading_styles[5],
285 HeadingLevel::H6 => heading_styles[5],
289− },
290− Some(Tag::Emphasis) => text_style.add_modifier(Modifier::ITALIC),
291− Some(Tag::Strong) => text_style.add_modifier(Modifier::BOLD),
292− Some(Tag::Strikethrough) => {
293− text_style.add_modifier(Modifier::CROSSED_OUT)
294 }
286 }
287+ } else {
295− _ => text_style,
288+ text_style
296 };
289 };
297 spans.push(Span::styled(text, style));
290 spans.push(Span::styled(text, style));
298 }
291 }
helix-term/src/ui/menu.rs+40−73
@@ -1,29 +1,22 @@
1−use std::{borrow::Cow, cmp::Reverse, path::PathBuf};
1+use std::{borrow::Cow, path::PathBuf};
2
2
3use crate::{
3use crate::{
4 compositor::{Callback, Component, Compositor, Context, Event, EventResult},
4 compositor::{Callback, Component, Compositor, Context, Event, EventResult},
5 ctrl, key, shift,
5 ctrl, key, shift,
6};
6};
7−use helix_core::fuzzy::MATCHER;
8−use nucleo::pattern::{Atom, AtomKind, CaseMatching};
9−use nucleo::{Config, Utf32Str};
10−use tui::{
11− buffer::Buffer as Surface,
7+use tui::{buffer::Buffer as Surface, widgets::Table};
12− widgets::{Block, Borders, Table, Widget},
13−};
14
8
15pub use tui::widgets::{Cell, Row};
9pub use tui::widgets::{Cell, Row};
16
10
17−use helix_view::{
11+use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
18− editor::SmartTabConfig,
12+use fuzzy_matcher::FuzzyMatcher;
13+
19− graphics::{Margin, Rect},
14+use helix_view::{graphics::Rect, Editor};
20− Editor,
21−};
22use tui::layout::Constraint;
15use tui::layout::Constraint;
23
16
24−pub trait Item: Sync + Send + 'static {
17+pub trait Item {
25 /// Additional editor state that is used for label calculation.
18 /// Additional editor state that is used for label calculation.
26− type Data: Sync + Send + 'static;
19+ type Data;
27
20
28 fn format(&self, data: &Self::Data) -> Row;
21 fn format(&self, data: &Self::Data) -> Row;
29
22
@@ -58,8 +51,9 @@ pub struct Menu<T: Item> {
58
51
59 cursor: Option<usize>,
52 cursor: Option<usize>,
60
53
54+ matcher: Box<Matcher>,
61 /// (index, score)
55 /// (index, score)
62− matches: Vec<(u32, u32)>,
56+ matches: Vec<(usize, i64)>,
63
57
64 widths: Vec<Constraint>,
58 widths: Vec<Constraint>,
65
59
@@ -81,10 +75,11 @@ impl<T: Item> Menu<T> {
81 editor_data: <T as Item>::Data,
75 editor_data: <T as Item>::Data,
82 callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
76 callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
83 ) -> Self {
77 ) -> Self {
84− let matches = (0..options.len() as u32).map(|i| (i, 0)).collect();
78+ let matches = (0..options.len()).map(|i| (i, 0)).collect();
85 Self {
79 Self {
86 options,
80 options,
87 editor_data,
81 editor_data,
82+ matcher: Box::new(Matcher::default().ignore_case()),
88 matches,
83 matches,
89 cursor: None,
84 cursor: None,
90 widths: Vec::new(),
85 widths: Vec::new(),
@@ -99,19 +94,20 @@ impl<T: Item> Menu<T> {
99 pub fn score(&mut self, pattern: &str) {
94 pub fn score(&mut self, pattern: &str) {
100 // reuse the matches allocation
95 // reuse the matches allocation
101 self.matches.clear();
96 self.matches.clear();
102− let mut matcher = MATCHER.lock();
97+ self.matches.extend(
103− matcher.config = Config::DEFAULT;
98+ self.options
104− let pattern = Atom::new(pattern, CaseMatching::Ignore, AtomKind::Fuzzy, false);
99+ .iter()
105− let mut buf = Vec::new();
100+ .enumerate()
106− let matches = self.options.iter().enumerate().filter_map(|(i, option)| {
101+ .filter_map(|(index, option)| {
107− let text = option.filter_text(&self.editor_data);
102+ let text = option.filter_text(&self.editor_data);
108− pattern
103+ // TODO: using fuzzy_indices could give us the char idx for match highlighting
109− .score(Utf32Str::new(&text, &mut buf), &mut matcher)
104+ self.matcher
105+ .fuzzy_match(&text, pattern)
110− .map(|score| (i as u32, score as u32))
106+ .map(|score| (index, score))
111− });
107+ }),
112− self.matches.extend(matches);
108+ );
109+ // Order of equal elements needs to be preserved as LSP preselected items come in order of high to low priority
113− self.matches
110+ self.matches.sort_by_key(|(_, score)| -score);
114− .sort_unstable_by_key(|&(i, score)| (Reverse(score), i));
115
111
116 // reset cursor position
112 // reset cursor position
117 self.cursor = None;
113 self.cursor = None;
@@ -205,7 +201,7 @@ impl<T: Item> Menu<T> {
205 self.cursor.and_then(|cursor| {
201 self.cursor.and_then(|cursor| {
206 self.matches
202 self.matches
207 .get(cursor)
203 .get(cursor)
208− .map(|(index, _score)| &self.options[*index as usize])
204+ .map(|(index, _score)| &self.options[*index])
209 })
205 })
210 }
206 }
211
207
@@ -213,7 +209,7 @@ impl<T: Item> Menu<T> {
213 self.cursor.and_then(|cursor| {
209 self.cursor.and_then(|cursor| {
214 self.matches
210 self.matches
215 .get(cursor)
211 .get(cursor)
216− .map(|(index, _score)| &mut self.options[*index as usize])
212+ .map(|(index, _score)| &mut self.options[*index])
217 })
213 })
218 }
214 }
219
215
@@ -251,21 +247,6 @@ impl<T: Item + 'static> Component for Menu<T> {
251 compositor.pop();
247 compositor.pop();
252 }));
248 }));
253
249
254− // Ignore tab key when supertab is turned on in order not to interfere
255− // with it. (Is there a better way to do this?)
256− if (event == key!(Tab) || event == shift!(Tab))
257− && cx.editor.config().auto_completion
258− && matches!(
259− cx.editor.config().smart_tab,
260− Some(SmartTabConfig {
261− enable: true,
262− supersede_menu: true,
263− })
264− )
265− {
266− return EventResult::Ignored(None);
267− }
268−
269 match event {
250 match event {
270 // esc or ctrl-c aborts the completion and closes the menu
251 // esc or ctrl-c aborts the completion and closes the menu
271 key!(Esc) | ctrl!('c') => {
252 key!(Esc) | ctrl!('c') => {
@@ -329,15 +310,6 @@ impl<T: Item + 'static> Component for Menu<T> {
329 let selected = theme.get("ui.menu.selected");
310 let selected = theme.get("ui.menu.selected");
330 surface.clear_with(area, style);
311 surface.clear_with(area, style);
331
312
332− let render_borders = cx.editor.menu_border();
333−
334− let area = if render_borders {
335− Widget::render(Block::default().borders(Borders::ALL), area, surface);
336− area.inner(&Margin::vertical(1))
337− } else {
338− area
339− };
340−
341 let scroll = self.scroll;
313 let scroll = self.scroll;
342
314
343 let options: Vec<_> = self
315 let options: Vec<_> = self
@@ -345,7 +317,7 @@ impl<T: Item + 'static> Component for Menu<T> {
345 .iter()
317 .iter()
346 .map(|(index, _score)| {
318 .map(|(index, _score)| {
347 // (index, self.options.get(*index).unwrap()) // get_unchecked
319 // (index, self.options.get(*index).unwrap()) // get_unchecked
348− &self.options[*index as usize] // get_unchecked
320+ &self.options[*index] // get_unchecked
349 })
321 })
350 .collect();
322 .collect();
351
323
@@ -378,19 +350,15 @@ impl<T: Item + 'static> Component for Menu<T> {
378 false,
350 false,
379 );
351 );
380
352
381− let render_borders = cx.editor.menu_border();
382−
383− if !render_borders {
384− if let Some(cursor) = self.cursor {
353+ if let Some(cursor) = self.cursor {
385− let offset_from_top = cursor - scroll;
354+ let offset_from_top = cursor - scroll;
386− let left = &mut surface[(area.left(), area.y + offset_from_top as u16)];
355+ let left = &mut surface[(area.left(), area.y + offset_from_top as u16)];
387− left.set_style(selected);
356+ left.set_style(selected);
388− let right = &mut surface[(
357+ let right = &mut surface[(
389− area.right().saturating_sub(1),
358+ area.right().saturating_sub(1),
390− area.y + offset_from_top as u16,
359+ area.y + offset_from_top as u16,
391− )];
360+ )];
392− right.set_style(selected);
361+ right.set_style(selected);
393− }
394 }
362 }
395
363
396 let fits = len <= win_height;
364 let fits = len <= win_height;
@@ -405,13 +373,12 @@ impl<T: Item + 'static> Component for Menu<T> {
405 for i in 0..win_height {
373 for i in 0..win_height {
406 cell = &mut surface[(area.right() - 1, area.top() + i as u16)];
374 cell = &mut surface[(area.right() - 1, area.top() + i as u16)];
407
375
408− let half_block = if render_borders { "▌" } else { "▐" };
376+ cell.set_symbol("▐"); // right half block
409
377
410 if scroll_line <= i && i < scroll_line + scroll_height {
378 if scroll_line <= i && i < scroll_line + scroll_height {
411 // Draw scroll thumb
379 // Draw scroll thumb
412− cell.set_symbol(half_block);
413 cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
380 cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
414− } else if !render_borders {
381+ } else {
415 // Draw scroll track
382 // Draw scroll track
416 cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
383 cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
417 }
384 }
helix-term/src/ui/mod.rs+145−88
@@ -1,12 +1,13 @@
1mod completion;
1mod completion;
2mod document;
2mod document;
3pub(crate) mod editor;
3pub(crate) mod editor;
4+mod fuzzy_match;
4mod info;
5mod info;
5pub mod lsp;
6pub mod lsp;
6mod markdown;
7mod markdown;
7pub mod menu;
8pub mod menu;
8pub mod overlay;
9pub mod overlay;
9−pub mod picker;
10+mod picker;
10pub mod popup;
11pub mod popup;
11mod prompt;
12mod prompt;
12mod spinner;
13mod spinner;
@@ -63,7 +64,7 @@ pub fn regex_prompt(
63 prompt: std::borrow::Cow<'static, str>,
64 prompt: std::borrow::Cow<'static, str>,
64 history_register: Option<char>,
65 history_register: Option<char>,
65 completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
66 completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
66− fun: impl Fn(&mut crate::compositor::Context, Regex, PromptEvent) + 'static,
67+ fun: impl Fn(&mut Editor, Regex, PromptEvent) + 'static,
67) {
68) {
68 let (view, doc) = current!(cx.editor);
69 let (view, doc) = current!(cx.editor);
69 let doc_id = view.doc;
70 let doc_id = view.doc;
@@ -110,7 +111,7 @@ pub fn regex_prompt(
110 view.jumps.push((doc_id, snapshot.clone()));
111 view.jumps.push((doc_id, snapshot.clone()));
111 }
112 }
112
113
113− fun(cx, regex, event);
114+ fun(cx.editor, regex, event);
114
115
115 let (view, doc) = current!(cx.editor);
116 let (view, doc) = current!(cx.editor);
116 view.ensure_cursor_in_view(doc, config.scrolloff);
117 view.ensure_cursor_in_view(doc, config.scrolloff);
@@ -173,13 +174,9 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
173 .git_ignore(config.file_picker.git_ignore)
174 .git_ignore(config.file_picker.git_ignore)
174 .git_global(config.file_picker.git_global)
175 .git_global(config.file_picker.git_global)
175 .git_exclude(config.file_picker.git_exclude)
176 .git_exclude(config.file_picker.git_exclude)
176− .sort_by_file_name(|name1, name2| name1.cmp(name2))
177 .max_depth(config.file_picker.max_depth)
177 .max_depth(config.file_picker.max_depth)
178 .filter_entry(move |entry| filter_picker_entry(entry, &absolute_root, dedup_symlinks));
178 .filter_entry(move |entry| filter_picker_entry(entry, &absolute_root, dedup_symlinks));
179
179
180− walk_builder.add_custom_ignore_filename(helix_loader::config_dir().join("ignore"));
181− walk_builder.add_custom_ignore_filename(".helix/ignore");
182−
183 // We want to exclude files that the editor can't handle yet
180 // We want to exclude files that the editor can't handle yet
184 let mut type_builder = TypesBuilder::new();
181 let mut type_builder = TypesBuilder::new();
185 type_builder
182 type_builder
@@ -193,16 +190,32 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
193 .build()
190 .build()
194 .expect("failed to build excluded_types");
191 .expect("failed to build excluded_types");
195 walk_builder.types(excluded_types);
192 walk_builder.types(excluded_types);
193+
194+ // We want files along with their modification date for sorting
196− let mut files = walk_builder.build().filter_map(|entry| {
195+ let files = walk_builder.build().filter_map(|entry| {
197 let entry = entry.ok()?;
196 let entry = entry.ok()?;
197+ // This is faster than entry.path().is_dir() since it uses cached fs::Metadata fetched by ignore/walkdir
198− if !entry.file_type()?.is_file() {
198+ if entry.file_type()?.is_file() {
199+ Some(entry.into_path())
200+ } else {
199− return None;
201+ None
200 }
202 }
201− Some(entry.into_path())
202 });
203 });
204+
205+ // Cap the number of files if we aren't in a git project, preventing
206+ // hangs when using the picker in your home directory
207+ let mut files: Vec<PathBuf> = if root.join(".git").exists() {
208+ files.collect()
209+ } else {
210+ // const MAX: usize = 8192;
211+ const MAX: usize = 100_000;
212+ files.take(MAX).collect()
213+ };
214+ files.sort();
215+
203 log::debug!("file_picker init {:?}", Instant::now().duration_since(now));
216 log::debug!("file_picker init {:?}", Instant::now().duration_since(now));
204
217
205− let picker = Picker::new(Vec::new(), root, move |cx, path: &PathBuf, action| {
218+ Picker::new(files, root, move |cx, path: &PathBuf, action| {
206 if let Err(e) = cx.editor.open(path, action) {
219 if let Err(e) = cx.editor.open(path, action) {
207 let err = if let Some(err) = e.source() {
220 let err = if let Some(err) = e.source() {
208 format!("{}", err)
221 format!("{}", err)
@@ -212,41 +225,20 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
212 cx.editor.set_error(err);
225 cx.editor.set_error(err);
213 }
226 }
214 })
227 })
215− .with_preview(|_editor, path| Some((path.clone().into(), None)));
228+ .with_preview(|_editor, path| Some((path.clone().into(), None)))
216− let injector = picker.injector();
217− let timeout = std::time::Instant::now() + std::time::Duration::from_millis(30);
218−
219− let mut hit_timeout = false;
220− for file in &mut files {
221− if injector.push(file).is_err() {
222− break;
223− }
224− if std::time::Instant::now() >= timeout {
225− hit_timeout = true;
226− break;
227− }
228− }
229− if hit_timeout {
230− std::thread::spawn(move || {
231− for file in files {
232− if injector.push(file).is_err() {
233− break;
234− }
235− }
236− });
237− }
238− picker
239}
229}
240
230
241pub mod completers {
231pub mod completers {
242 use crate::ui::prompt::Completion;
232 use crate::ui::prompt::Completion;
243− use helix_core::fuzzy::fuzzy_match;
233+ use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
234+ use fuzzy_matcher::FuzzyMatcher;
244 use helix_core::syntax::LanguageServerFeature;
235 use helix_core::syntax::LanguageServerFeature;
245 use helix_view::document::SCRATCH_BUFFER_NAME;
236 use helix_view::document::SCRATCH_BUFFER_NAME;
246 use helix_view::theme;
237 use helix_view::theme;
247 use helix_view::{editor::Config, Editor};
238 use helix_view::{editor::Config, Editor};
248 use once_cell::sync::Lazy;
239 use once_cell::sync::Lazy;
249 use std::borrow::Cow;
240 use std::borrow::Cow;
241+ use std::cmp::Reverse;
250
242
251 pub type Completer = fn(&Editor, &str) -> Vec<Completion>;
243 pub type Completer = fn(&Editor, &str) -> Vec<Completion>;
252
244
@@ -255,16 +247,31 @@ pub mod completers {
255 }
247 }
256
248
257 pub fn buffer(editor: &Editor, input: &str) -> Vec<Completion> {
249 pub fn buffer(editor: &Editor, input: &str) -> Vec<Completion> {
258− let names = editor.documents.values().map(|doc| {
250+ let mut names: Vec<_> = editor
251+ .documents
252+ .values()
253+ .map(|doc| {
254+ let name = doc
259− doc.relative_path()
255+ .relative_path()
260− .map(|p| p.display().to_string().into())
256+ .map(|p| p.display().to_string())
261− .unwrap_or_else(|| Cow::from(SCRATCH_BUFFER_NAME))
257+ .unwrap_or_else(|| String::from(SCRATCH_BUFFER_NAME));
258+ ((0..), Cow::from(name))
262− });
259+ })
263−
260+ .collect();
264− fuzzy_match(input, names, true)
261+
262+ let matcher = Matcher::default();
263+
264+ let mut matches: Vec<_> = names
265 .into_iter()
265 .into_iter()
266+ .filter_map(|(_range, name)| {
267+ matcher.fuzzy_match(&name, input).map(|score| (name, score))
268+ })
269+ .collect();
270+
271+ matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
266− .map(|(name, _)| ((0..), name))
272+ names = matches.into_iter().map(|(name, _)| ((0..), name)).collect();
267− .collect()
273+
274+ names
268 }
275 }
269
276
270 pub fn theme(_editor: &Editor, input: &str) -> Vec<Completion> {
277 pub fn theme(_editor: &Editor, input: &str) -> Vec<Completion> {
@@ -277,10 +284,26 @@ pub mod completers {
277 names.sort();
284 names.sort();
278 names.dedup();
285 names.dedup();
279
286
280− fuzzy_match(input, names, false)
287+ let mut names: Vec<_> = names
281 .into_iter()
288 .into_iter()
282− .map(|(name, _)| ((0..), name.into()))
289+ .map(|name| ((0..), Cow::from(name)))
283− .collect()
290+ .collect();
291+
292+ let matcher = Matcher::default();
293+
294+ let mut matches: Vec<_> = names
295+ .into_iter()
296+ .filter_map(|(_range, name)| {
297+ matcher.fuzzy_match(&name, input).map(|score| (name, score))
298+ })
299+ .collect();
300+
301+ matches.sort_unstable_by(|(name1, score1), (name2, score2)| {
302+ (Reverse(*score1), name1).cmp(&(Reverse(*score2), name2))
303+ });
304+ names = matches.into_iter().map(|(name, _)| ((0..), name)).collect();
305+
306+ names
284 }
307 }
285
308
286 /// Recursive function to get all keys from this value and add them to vec
309 /// Recursive function to get all keys from this value and add them to vec
@@ -307,22 +330,22 @@ pub mod completers {
307 keys
330 keys
308 });
331 });
309
332
310− fuzzy_match(input, &*KEYS, false)
333+ let matcher = Matcher::default();
334+
335+ let mut matches: Vec<_> = KEYS
336+ .iter()
337+ .filter_map(|name| matcher.fuzzy_match(name, input).map(|score| (name, score)))
338+ .collect();
339+
340+ matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
341+ matches
311 .into_iter()
342 .into_iter()
312 .map(|(name, _)| ((0..), name.into()))
343 .map(|(name, _)| ((0..), name.into()))
313 .collect()
344 .collect()
314 }
345 }
315
346
316 pub fn filename(editor: &Editor, input: &str) -> Vec<Completion> {
347 pub fn filename(editor: &Editor, input: &str) -> Vec<Completion> {
317− filename_with_git_ignore(editor, input, true)
318− }
319−
320− pub fn filename_with_git_ignore(
321− editor: &Editor,
322− input: &str,
323− git_ignore: bool,
324− ) -> Vec<Completion> {
325− filename_impl(editor, input, git_ignore, |entry| {
348+ filename_impl(editor, input, |entry| {
326 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
349 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
327
350
328 if is_dir {
351 if is_dir {
@@ -334,6 +357,8 @@ pub mod completers {
334 }
357 }
335
358
336 pub fn language(editor: &Editor, input: &str) -> Vec<Completion> {
359 pub fn language(editor: &Editor, input: &str) -> Vec<Completion> {
360+ let matcher = Matcher::default();
361+
337 let text: String = "text".into();
362 let text: String = "text".into();
338
363
339 let language_ids = editor
364 let language_ids = editor
@@ -342,13 +367,27 @@ pub mod completers {
342 .map(|config| &config.language_id)
367 .map(|config| &config.language_id)
343 .chain(std::iter::once(&text));
368 .chain(std::iter::once(&text));
344
369
370+ let mut matches: Vec<_> = language_ids
371+ .filter_map(|language_id| {
372+ matcher
345− fuzzy_match(input, language_ids, false)
373+ .fuzzy_match(language_id, input)
374+ .map(|score| (language_id, score))
375+ })
376+ .collect();
377+
378+ matches.sort_unstable_by(|(language1, score1), (language2, score2)| {
379+ (Reverse(*score1), language1).cmp(&(Reverse(*score2), language2))
380+ });
381+
382+ matches
346 .into_iter()
383 .into_iter()
347− .map(|(name, _)| ((0..), name.to_owned().into()))
384+ .map(|(language, _score)| ((0..), language.clone().into()))
348 .collect()
385 .collect()
349 }
386 }
350
387
351 pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
388 pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
389+ let matcher = Matcher::default();
390+
352 let Some(options) = doc!(editor)
391 let Some(options) = doc!(editor)
353 .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
392 .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
354 .find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
393 .find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
@@ -356,22 +395,28 @@ pub mod completers {
356 return vec![];
395 return vec![];
357 };
396 };
358
397
398+ let mut matches: Vec<_> = options
399+ .commands
400+ .iter()
401+ .filter_map(|command| {
402+ matcher
359− fuzzy_match(input, &options.commands, false)
403+ .fuzzy_match(command, input)
404+ .map(|score| (command, score))
405+ })
406+ .collect();
407+
408+ matches.sort_unstable_by(|(command1, score1), (command2, score2)| {
409+ (Reverse(*score1), command1).cmp(&(Reverse(*score2), command2))
410+ });
411+
412+ matches
360 .into_iter()
413 .into_iter()
361− .map(|(name, _)| ((0..), name.to_owned().into()))
414+ .map(|(command, _score)| ((0..), command.clone().into()))
362 .collect()
415 .collect()
363 }
416 }
364
417
365 pub fn directory(editor: &Editor, input: &str) -> Vec<Completion> {
418 pub fn directory(editor: &Editor, input: &str) -> Vec<Completion> {
366− directory_with_git_ignore(editor, input, true)
367− }
368−
369− pub fn directory_with_git_ignore(
370− editor: &Editor,
371− input: &str,
372− git_ignore: bool,
373− ) -> Vec<Completion> {
374− filename_impl(editor, input, git_ignore, |entry| {
419+ filename_impl(editor, input, |entry| {
375 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
420 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
376
421
377 if is_dir {
422 if is_dir {
@@ -394,12 +439,7 @@ pub mod completers {
394 }
439 }
395
440
396 // TODO: we could return an iter/lazy thing so it can fetch as many as it needs.
441 // TODO: we could return an iter/lazy thing so it can fetch as many as it needs.
397− fn filename_impl<F>(
442+ fn filename_impl<F>(_editor: &Editor, input: &str, filter_fn: F) -> Vec<Completion>
398− _editor: &Editor,
399− input: &str,
400− git_ignore: bool,
401− filter_fn: F,
402− ) -> Vec<Completion>
403 where
443 where
404 F: Fn(&ignore::DirEntry) -> FileMatch,
444 F: Fn(&ignore::DirEntry) -> FileMatch,
405 {
445 {
@@ -439,10 +479,9 @@ pub mod completers {
439
479
440 let end = input.len()..;
480 let end = input.len()..;
441
481
442− let files = WalkBuilder::new(&dir)
482+ let mut files: Vec<_> = WalkBuilder::new(&dir)
443 .hidden(false)
483 .hidden(false)
444 .follow_links(false) // We're scanning over depth 1
484 .follow_links(false) // We're scanning over depth 1
445− .git_ignore(git_ignore)
446 .max_depth(Some(1))
485 .max_depth(Some(1))
447 .build()
486 .build()
448 .filter_map(|file| {
487 .filter_map(|file| {
@@ -471,25 +510,43 @@ pub mod completers {
471 path.push("");
510 path.push("");
472 }
511 }
473
512
474− let path = path.into_os_string().into_string().ok()?;
513+ let path = path.to_str()?.to_owned();
475− Some(Cow::from(path))
514+ Some((end.clone(), Cow::from(path)))
476 })
515 })
477 }) // TODO: unwrap or skip
516 }) // TODO: unwrap or skip
478− .filter(|path| !path.is_empty());
517+ .filter(|(_, path)| !path.is_empty()) // TODO
518+ .collect();
479
519
480 // if empty, return a list of dirs and files in current dir
520 // if empty, return a list of dirs and files in current dir
481 if let Some(file_name) = file_name {
521 if let Some(file_name) = file_name {
522+ let matcher = Matcher::default();
523+
524+ // inefficient, but we need to calculate the scores, filter out None, then sort.
525+ let mut matches: Vec<_> = files
526+ .into_iter()
527+ .filter_map(|(_range, file)| {
528+ matcher
529+ .fuzzy_match(&file, &file_name)
530+ .map(|score| (file, score))
531+ })
532+ .collect();
533+
482 let range = (input.len().saturating_sub(file_name.len()))..;
534 let range = (input.len().saturating_sub(file_name.len()))..;
483− fuzzy_match(&file_name, files, true)
535+
536+ matches.sort_unstable_by(|(file1, score1), (file2, score2)| {
537+ (Reverse(*score1), file1).cmp(&(Reverse(*score2), file2))
538+ });
539+
540+ files = matches
484 .into_iter()
541 .into_iter()
485− .map(|(name, _)| (range.clone(), name))
542+ .map(|(file, _)| (range.clone(), file))
486− .collect()
543+ .collect();
487
544
488 // TODO: complete to longest common match
545 // TODO: complete to longest common match
489 } else {
546 } else {
490− let mut files: Vec<_> = files.map(|file| (end.clone(), file)).collect();
491 files.sort_unstable_by(|(_, path1), (_, path2)| path1.cmp(path2));
547 files.sort_unstable_by(|(_, path1), (_, path2)| path1.cmp(path2));
492− files
493 }
548 }
549+
550+ files
494 }
551 }
495}
552}
helix-term/src/ui/picker.rs+321−355
@@ -7,12 +7,11 @@ use crate::{
7 ui::{
7 ui::{
8 self,
8 self,
9 document::{render_document, LineDecoration, LinePos, TextRenderer},
9 document::{render_document, LineDecoration, LinePos, TextRenderer},
10+ fuzzy_match::FuzzyQuery,
10 EditorView,
11 EditorView,
11 },
12 },
12};
13};
13use futures_util::{future::BoxFuture, FutureExt};
14use futures_util::{future::BoxFuture, FutureExt};
14−use nucleo::pattern::CaseMatching;
15−use nucleo::{Config, Nucleo, Utf32String};
16use tui::{
15use tui::{
17 buffer::Buffer as Surface,
16 buffer::Buffer as Surface,
18 layout::Constraint,
17 layout::Constraint,
@@ -20,23 +19,16 @@ use tui::{
20 widgets::{Block, BorderType, Borders, Cell, Table},
19 widgets::{Block, BorderType, Borders, Cell, Table},
21};
20};
22
21
22+use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
23use tui::widgets::Widget;
23use tui::widgets::Widget;
24
24
25−use std::{
25+use std::cmp::{self, Ordering};
26− collections::HashMap,
26+use std::{collections::HashMap, io::Read, path::PathBuf};
27− io::Read,
28− path::PathBuf,
29− sync::{
30− atomic::{self, AtomicBool},
31− Arc,
32− },
33−};
34
27
35use crate::ui::{Prompt, PromptEvent};
28use crate::ui::{Prompt, PromptEvent};
36use helix_core::{
29use helix_core::{
37− char_idx_at_visual_offset, fuzzy::MATCHER, movement::Direction,
30+ movement::Direction, text_annotations::TextAnnotations,
38− text_annotations::TextAnnotations, unicode::segmentation::UnicodeSegmentation, Position,
31+ unicode::segmentation::UnicodeSegmentation, Position, Syntax,
39− Syntax,
40};
32};
41use helix_view::{
33use helix_view::{
42 editor::Action,
34 editor::Action,
@@ -46,7 +38,6 @@ use helix_view::{
46 Document, DocumentId, Editor,
38 Document, DocumentId, Editor,
47};
39};
48
40
49−pub const ID: &str = "picker";
50use super::{menu::Item, overlay::Overlay};
41use super::{menu::Item, overlay::Overlay};
51
42
52pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72;
43pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72;
@@ -60,12 +51,12 @@ pub enum PathOrId {
60}
51}
61
52
62impl PathOrId {
53impl PathOrId {
63− fn get_canonicalized(self) -> Self {
54+ fn get_canonicalized(self) -> std::io::Result<Self> {
64 use PathOrId::*;
55 use PathOrId::*;
65− match self {
56+ Ok(match self {
66− Path(path) => Path(helix_core::path::get_canonicalized_path(&path)),
57+ Path(path) => Path(helix_core::path::get_canonicalized_path(&path)?),
67 Id(id) => Id(id),
58 Id(id) => Id(id),
68− }
59+ })
69 }
60 }
70}
61}
71
62
@@ -112,9 +103,9 @@ impl Preview<'_, '_> {
112 /// Alternate text to show for the preview.
103 /// Alternate text to show for the preview.
113 fn placeholder(&self) -> &str {
104 fn placeholder(&self) -> &str {
114 match *self {
105 match *self {
115− Self::EditorDocument(_) => "<Invalid file location>",
106+ Self::EditorDocument(_) => "<File preview>",
116 Self::Cached(preview) => match preview {
107 Self::Cached(preview) => match preview {
117− CachedPreview::Document(_) => "<Invalid file location>",
108+ CachedPreview::Document(_) => "<File preview>",
118 CachedPreview::Binary => "<Binary file>",
109 CachedPreview::Binary => "<Binary file>",
119 CachedPreview::LargeFile => "<File too large to preview>",
110 CachedPreview::LargeFile => "<File too large to preview>",
120 CachedPreview::NotFound => "<File not found>",
111 CachedPreview::NotFound => "<File not found>",
@@ -123,71 +114,20 @@ impl Preview<'_, '_> {
123 }
114 }
124}
115}
125
116
126−fn item_to_nucleo<T: Item>(item: T, editor_data: &T::Data) -> Option<(T, Utf32String)> {
127− let row = item.format(editor_data);
128− let mut cells = row.cells.iter();
129− let mut text = String::with_capacity(row.cell_text().map(|cell| cell.len()).sum());
130− let cell = cells.next()?;
131− if let Some(cell) = cell.content.lines.first() {
132− for span in &cell.0 {
133− text.push_str(&span.content);
134− }
135− }
136−
137− for cell in cells {
138− text.push(' ');
139− if let Some(cell) = cell.content.lines.first() {
140− for span in &cell.0 {
141− text.push_str(&span.content);
142− }
143− }
144− }
145− Some((item, text.into()))
146−}
147−
148−pub struct Injector<T: Item> {
149− dst: nucleo::Injector<T>,
150− editor_data: Arc<T::Data>,
151− shutown: Arc<AtomicBool>,
152−}
153−
154−impl<T: Item> Clone for Injector<T> {
155− fn clone(&self) -> Self {
156− Injector {
157− dst: self.dst.clone(),
158− editor_data: self.editor_data.clone(),
159− shutown: self.shutown.clone(),
160− }
161− }
162−}
163−
164−pub struct InjectorShutdown;
165−
166−impl<T: Item> Injector<T> {
167− pub fn push(&self, item: T) -> Result<(), InjectorShutdown> {
168− if self.shutown.load(atomic::Ordering::Relaxed) {
169− return Err(InjectorShutdown);
170− }
171−
172− if let Some((item, matcher_text)) = item_to_nucleo(item, &self.editor_data) {
173− self.dst.push(item, |dst| dst[0] = matcher_text);
174− }
175− Ok(())
176− }
177−}
178−
179pub struct Picker<T: Item> {
117pub struct Picker<T: Item> {
118+ options: Vec<T>,
180− editor_data: Arc<T::Data>,
119+ editor_data: T::Data,
181− shutdown: Arc<AtomicBool>,
120+ // filter: String,
182− matcher: Nucleo<T>,
121+ matcher: Box<Matcher>,
122+ matches: Vec<PickerMatch>,
183
123
184 /// Current height of the completions box
124 /// Current height of the completions box
185 completion_height: u16,
125 completion_height: u16,
186
126
187− cursor: u32,
127+ cursor: usize,
128+ // pattern: String,
188 prompt: Prompt,
129 prompt: Prompt,
189− previous_pattern: String,
130+ previous_pattern: (String, FuzzyQuery),
190−
191 /// Whether to show the preview panel (default true)
131 /// Whether to show the preview panel (default true)
192 show_preview: bool,
132 show_preview: bool,
193 /// Constraints for tabular formatting
133 /// Constraints for tabular formatting
@@ -204,59 +144,10 @@ pub struct Picker<T: Item> {
204}
144}
205
145
206impl<T: Item + 'static> Picker<T> {
146impl<T: Item + 'static> Picker<T> {
207− pub fn stream(editor_data: T::Data) -> (Nucleo<T>, Injector<T>) {
208− let matcher = Nucleo::new(
209− Config::DEFAULT,
210− Arc::new(helix_event::request_redraw),
211− None,
212− 1,
213− );
214− let streamer = Injector {
215− dst: matcher.injector(),
216− editor_data: Arc::new(editor_data),
217− shutown: Arc::new(AtomicBool::new(false)),
218− };
219− (matcher, streamer)
220− }
221−
222 pub fn new(
147 pub fn new(
223 options: Vec<T>,
148 options: Vec<T>,
224 editor_data: T::Data,
149 editor_data: T::Data,
225 callback_fn: impl Fn(&mut Context, &T, Action) + 'static,
150 callback_fn: impl Fn(&mut Context, &T, Action) + 'static,
226− ) -> Self {
227− let matcher = Nucleo::new(
228− Config::DEFAULT,
229− Arc::new(helix_event::request_redraw),
230− None,
231− 1,
232− );
233− let injector = matcher.injector();
234− for item in options {
235− if let Some((item, matcher_text)) = item_to_nucleo(item, &editor_data) {
236− injector.push(item, |dst| dst[0] = matcher_text);
237− }
238− }
239− Self::with(
240− matcher,
241− Arc::new(editor_data),
242− Arc::new(AtomicBool::new(false)),
243− callback_fn,
244− )
245− }
246−
247− pub fn with_stream(
248− matcher: Nucleo<T>,
249− injector: Injector<T>,
250− callback_fn: impl Fn(&mut Context, &T, Action) + 'static,
251− ) -> Self {
252− Self::with(matcher, injector.editor_data, injector.shutown, callback_fn)
253− }
254−
255− fn with(
256− matcher: Nucleo<T>,
257− editor_data: Arc<T::Data>,
258− shutdown: Arc<AtomicBool>,
259− callback_fn: impl Fn(&mut Context, &T, Action) + 'static,
260 ) -> Self {
151 ) -> Self {
261 let prompt = Prompt::new(
152 let prompt = Prompt::new(
262 "".into(),
153 "".into(),
@@ -265,13 +156,14 @@ impl<T: Item + 'static> Picker<T> {
265 |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {},
156 |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {},
266 );
157 );
267
158
268− Self {
159+ let mut picker = Self {
269− matcher,
160+ options,
270 editor_data,
161 editor_data,
271− shutdown,
162+ matcher: Box::default(),
163+ matches: Vec::new(),
272 cursor: 0,
164 cursor: 0,
273 prompt,
165 prompt,
274− previous_pattern: String::new(),
166+ previous_pattern: (String::new(), FuzzyQuery::default()),
275 truncate_start: true,
167 truncate_start: true,
276 show_preview: true,
168 show_preview: true,
277 callback_fn: Box::new(callback_fn),
169 callback_fn: Box::new(callback_fn),
@@ -280,15 +172,24 @@ impl<T: Item + 'static> Picker<T> {
280 preview_cache: HashMap::new(),
172 preview_cache: HashMap::new(),
281 read_buffer: Vec::with_capacity(1024),
173 read_buffer: Vec::with_capacity(1024),
282 file_fn: None,
174 file_fn: None,
283− }
175+ };
284− }
285
176
286− pub fn injector(&self) -> Injector<T> {
177+ picker.calculate_column_widths();
287− Injector {
178+
288− dst: self.matcher.injector(),
179+ // scoring on empty input
289− editor_data: self.editor_data.clone(),
180+ // TODO: just reuse score()
290− shutown: self.shutdown.clone(),
181+ picker
291− }
182+ .matches
183+ .extend(picker.options.iter().enumerate().map(|(index, option)| {
184+ let text = option.filter_text(&picker.editor_data);
185+ PickerMatch {
186+ index,
187+ score: 0,
188+ len: text.chars().count(),
189+ }
190+ }));
191+
192+ picker
292 }
193 }
293
194
294 pub fn truncate_start(mut self, truncate_start: bool) -> Self {
195 pub fn truncate_start(mut self, truncate_start: bool) -> Self {
@@ -301,25 +202,122 @@ impl<T: Item + 'static> Picker<T> {
301 preview_fn: impl Fn(&Editor, &T) -> Option<FileLocation> + 'static,
202 preview_fn: impl Fn(&Editor, &T) -> Option<FileLocation> + 'static,
302 ) -> Self {
203 ) -> Self {
303 self.file_fn = Some(Box::new(preview_fn));
204 self.file_fn = Some(Box::new(preview_fn));
304− // assumption: if we have a preview we are matching paths... If this is ever
305− // not true this could be a separate builder function
306− self.matcher.update_config(Config::DEFAULT.match_paths());
307 self
205 self
308 }
206 }
309
207
310 pub fn set_options(&mut self, new_options: Vec<T>) {
208 pub fn set_options(&mut self, new_options: Vec<T>) {
311− self.matcher.restart(false);
312− let injector = self.matcher.injector();
313− for item in new_options {
209+ self.options = new_options;
314− if let Some((item, matcher_text)) = item_to_nucleo(item, &self.editor_data) {
210+ self.cursor = 0;
315− injector.push(item, |dst| dst[0] = matcher_text);
211+ self.force_score();
212+ self.calculate_column_widths();
213+ }
214+
215+ /// Calculate the width constraints using the maximum widths of each column
216+ /// for the current options.
217+ fn calculate_column_widths(&mut self) {
218+ let n = self
219+ .options
220+ .first()
221+ .map(|option| option.format(&self.editor_data).cells.len())
222+ .unwrap_or_default();
223+ let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| {
224+ let row = option.format(&self.editor_data);
225+ // maintain max for each column
226+ for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) {
227+ let width = cell.content.width();
228+ if width > *acc {
229+ *acc = width;
230+ }
316 }
231 }
232+ acc
233+ });
234+ self.widths = max_lens
235+ .into_iter()
236+ .map(|len| Constraint::Length(len as u16))
237+ .collect();
238+ }
239+
240+ pub fn score(&mut self) {
241+ let pattern = self.prompt.line();
242+
243+ if pattern == &self.previous_pattern.0 {
244+ return;
317 }
245 }
246+
247+ let (query, is_refined) = self
248+ .previous_pattern
249+ .1
250+ .refine(pattern, &self.previous_pattern.0);
251+
252+ if pattern.is_empty() {
253+ // Fast path for no pattern.
254+ self.matches.clear();
255+ self.matches
256+ .extend(self.options.iter().enumerate().map(|(index, option)| {
257+ let text = option.filter_text(&self.editor_data);
258+ PickerMatch {
259+ index,
260+ score: 0,
261+ len: text.chars().count(),
262+ }
263+ }));
264+ } else if is_refined {
265+ // optimization: if the pattern is a more specific version of the previous one
266+ // then we can score the filtered set.
267+ self.matches.retain_mut(|pmatch| {
268+ let option = &self.options[pmatch.index];
269+ let text = option.sort_text(&self.editor_data);
270+
271+ match query.fuzzy_match(&text, &self.matcher) {
272+ Some(s) => {
273+ // Update the score
274+ pmatch.score = s;
275+ true
276+ }
277+ None => false,
278+ }
279+ });
280+
281+ self.matches.sort_unstable();
282+ } else {
283+ self.force_score();
284+ }
285+
286+ // reset cursor position
287+ self.cursor = 0;
288+ let pattern = self.prompt.line();
289+ self.previous_pattern.0.clone_from(pattern);
290+ self.previous_pattern.1 = query;
291+ }
292+
293+ pub fn force_score(&mut self) {
294+ let pattern = self.prompt.line();
295+
296+ let query = FuzzyQuery::new(pattern);
297+ self.matches.clear();
298+ self.matches.extend(
299+ self.options
300+ .iter()
301+ .enumerate()
302+ .filter_map(|(index, option)| {
303+ let text = option.filter_text(&self.editor_data);
304+
305+ query
306+ .fuzzy_match(&text, &self.matcher)
307+ .map(|score| PickerMatch {
308+ index,
309+ score,
310+ len: text.chars().count(),
311+ })
312+ }),
313+ );
314+
315+ self.matches.sort_unstable();
318 }
316 }
319
317
320 /// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`)
318 /// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`)
321− pub fn move_by(&mut self, amount: u32, direction: Direction) {
319+ pub fn move_by(&mut self, amount: usize, direction: Direction) {
322− let len = self.matcher.snapshot().matched_item_count();
320+ let len = self.matches.len();
323
321
324 if len == 0 {
322 if len == 0 {
325 // No results, can't move.
323 // No results, can't move.
@@ -338,12 +336,12 @@ impl<T: Item + 'static> Picker<T> {
338
336
339 /// Move the cursor down by exactly one page. After the last page comes the first page.
337 /// Move the cursor down by exactly one page. After the last page comes the first page.
340 pub fn page_up(&mut self) {
338 pub fn page_up(&mut self) {
341− self.move_by(self.completion_height as u32, Direction::Backward);
339+ self.move_by(self.completion_height as usize, Direction::Backward);
342 }
340 }
343
341
344 /// Move the cursor up by exactly one page. After the first page comes the last page.
342 /// Move the cursor up by exactly one page. After the first page comes the last page.
345 pub fn page_down(&mut self) {
343 pub fn page_down(&mut self) {
346− self.move_by(self.completion_height as u32, Direction::Forward);
344+ self.move_by(self.completion_height as usize, Direction::Forward);
347 }
345 }
348
346
349 /// Move the cursor to the first entry
347 /// Move the cursor to the first entry
@@ -353,18 +351,13 @@ impl<T: Item + 'static> Picker<T> {
353
351
354 /// Move the cursor to the last entry
352 /// Move the cursor to the last entry
355 pub fn to_end(&mut self) {
353 pub fn to_end(&mut self) {
356− self.cursor = self
357− .matcher
358− .snapshot()
359− .matched_item_count()
360− .saturating_sub(1);
354+ self.cursor = self.matches.len().saturating_sub(1);
361 }
355 }
362
356
363 pub fn selection(&self) -> Option<&T> {
357 pub fn selection(&self) -> Option<&T> {
364− self.matcher
358+ self.matches
365− .snapshot()
366− .get_matched_item(self.cursor)
359+ .get(self.cursor)
367− .map(|item| item.data)
360+ .map(|pmatch| &self.options[pmatch.index])
368 }
361 }
369
362
370 pub fn toggle_preview(&mut self) {
363 pub fn toggle_preview(&mut self) {
@@ -373,17 +366,8 @@ impl<T: Item + 'static> Picker<T> {
373
366
374 fn prompt_handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
367 fn prompt_handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
375 if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) {
368 if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) {
376− let pattern = self.prompt.line();
369+ // TODO: recalculate only if pattern changed
377− // TODO: better track how the pattern has changed
370+ self.score();
378− if pattern != &self.previous_pattern {
379− self.matcher.pattern.reparse(
380− 0,
381− pattern,
382− CaseMatching::Smart,
383− pattern.starts_with(&self.previous_pattern),
384− );
385− self.previous_pattern = pattern.clone();
386− }
387 }
371 }
388 EventResult::Consumed(None)
372 EventResult::Consumed(None)
389 }
373 }
@@ -391,7 +375,7 @@ impl<T: Item + 'static> Picker<T> {
391 fn current_file(&self, editor: &Editor) -> Option<FileLocation> {
375 fn current_file(&self, editor: &Editor) -> Option<FileLocation> {
392 self.selection()
376 self.selection()
393 .and_then(|current| (self.file_fn.as_ref()?)(editor, current))
377 .and_then(|current| (self.file_fn.as_ref()?)(editor, current))
394− .map(|(path_or_id, line)| (path_or_id.get_canonicalized(), line))
378+ .and_then(|(path_or_id, line)| path_or_id.get_canonicalized().ok().zip(Some(line)))
395 }
379 }
396
380
397 /// Get (cached) preview for a given path. If a document corresponding
381 /// Get (cached) preview for a given path. If a document corresponding
@@ -427,9 +411,12 @@ impl<T: Item + 'static> Picker<T> {
427 (size, _) if size > MAX_FILE_SIZE_FOR_PREVIEW => {
411 (size, _) if size > MAX_FILE_SIZE_FOR_PREVIEW => {
428 CachedPreview::LargeFile
412 CachedPreview::LargeFile
429 }
413 }
414+ _ => {
415+ // TODO: enable syntax highlighting; blocked by async rendering
430− _ => Document::open(path, None, None, editor.config.clone())
416+ Document::open(path, None, None, editor.config.clone())
431− .map(|doc| CachedPreview::Document(Box::new(doc)))
417+ .map(|doc| CachedPreview::Document(Box::new(doc)))
432− .unwrap_or(CachedPreview::NotFound),
418+ .unwrap_or(CachedPreview::NotFound)
419+ }
433 },
420 },
434 )
421 )
435 .unwrap_or(CachedPreview::NotFound);
422 .unwrap_or(CachedPreview::NotFound);
@@ -474,13 +461,9 @@ impl<T: Item + 'static> Picker<T> {
474 log::info!("highlighting picker item failed");
461 log::info!("highlighting picker item failed");
475 return;
462 return;
476 };
463 };
464+ let Some(Overlay {
465+ content: picker, ..
477− let picker = match compositor.find::<Overlay<Self>>() {
466+ }) = compositor.find::<Overlay<Self>>()
478− Some(Overlay { content, .. }) => Some(content),
479− None => compositor
480− .find::<Overlay<DynamicPicker<T>>>()
481− .map(|overlay| &mut overlay.content.file_picker),
482− };
483− let Some(picker) = picker
484 else {
467 else {
485 log::info!("picker closed before syntax highlighting finished");
468 log::info!("picker closed before syntax highlighting finished");
486 return;
469 return;
@@ -512,14 +495,6 @@ impl<T: Item + 'static> Picker<T> {
512 }
495 }
513
496
514 fn render_picker(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
497 fn render_picker(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
515− let status = self.matcher.tick(10);
516− let snapshot = self.matcher.snapshot();
517− if status.changed {
518− self.cursor = self
519− .cursor
520− .min(snapshot.matched_item_count().saturating_sub(1))
521− }
522−
523 let text_style = cx.editor.theme.get("ui.text");
498 let text_style = cx.editor.theme.get("ui.text");
524 let selected = cx.editor.theme.get("ui.text.focus");
499 let selected = cx.editor.theme.get("ui.text.focus");
525 let highlight_style = cx.editor.theme.get("special").add_modifier(Modifier::BOLD);
500 let highlight_style = cx.editor.theme.get("special").add_modifier(Modifier::BOLD);
@@ -540,15 +515,8 @@ impl<T: Item + 'static> Picker<T> {
540 // -- Render the input bar:
515 // -- Render the input bar:
541
516
542 let area = inner.clip_left(1).with_height(1);
517 let area = inner.clip_left(1).with_height(1);
543− // render the prompt first since it will clear its background
544− self.prompt.render(area, surface, cx);
545
518
546− let count = format!(
519+ let count = format!("{}/{}", self.matches.len(), self.options.len());
547− "{}{}/{}",
548− if status.running { "(running) " } else { "" },
549− snapshot.matched_item_count(),
550− snapshot.item_count(),
551− );
552 surface.set_stringn(
520 surface.set_stringn(
553 (area.x + area.width).saturating_sub(count.len() as u16 + 1),
521 (area.x + area.width).saturating_sub(count.len() as u16 + 1),
554 area.y,
522 area.y,
@@ -557,6 +525,8 @@ impl<T: Item + 'static> Picker<T> {
557 text_style,
525 text_style,
558 );
526 );
559
527
528+ self.prompt.render(area, surface, cx);
529+
560 // -- Separator
530 // -- Separator
561 let sep_style = cx.editor.theme.get("ui.background.separator");
531 let sep_style = cx.editor.theme.get("ui.background.separator");
562 let borders = BorderType::line_symbols(BorderType::Plain);
532 let borders = BorderType::line_symbols(BorderType::Plain);
@@ -569,89 +539,106 @@ impl<T: Item + 'static> Picker<T> {
569 // -- Render the contents:
539 // -- Render the contents:
570 // subtract area of prompt from top
540 // subtract area of prompt from top
571 let inner = inner.clip_top(2);
541 let inner = inner.clip_top(2);
542+
572− let rows = inner.height as u32;
543+ let rows = inner.height;
573− let offset = self.cursor - (self.cursor % std::cmp::max(1, rows));
544+ let offset = self.cursor - (self.cursor % std::cmp::max(1, rows as usize));
574 let cursor = self.cursor.saturating_sub(offset);
545 let cursor = self.cursor.saturating_sub(offset);
575− let end = offset
576− .saturating_add(rows)
577− .min(snapshot.matched_item_count());
578− let mut indices = Vec::new();
579− let mut matcher = MATCHER.lock();
580− matcher.config = Config::DEFAULT;
581− if self.file_fn.is_some() {
582− matcher.config.set_match_paths()
583− }
584
546
585− let options = snapshot.matched_items(offset..end).map(|item| {
547+ let options = self
586− snapshot.pattern().column_pattern(0).indices(
548+ .matches
587− item.matcher_columns[0].slice(..),
549+ .iter()
588− &mut matcher,
550+ .skip(offset)
589− &mut indices,
551+ .take(rows as usize)
590− );
552+ .map(|pmatch| &self.options[pmatch.index])
591− indices.sort_unstable();
592− indices.dedup();
593− let mut row = item.data.format(&self.editor_data);
553+ .map(|option| option.format(&self.editor_data))
594−
554+ .map(|mut row| {
595− let mut grapheme_idx = 0u32;
555+ const TEMP_CELL_SEP: &str = " ";
596− let mut indices = indices.drain(..);
556+
597− let mut next_highlight_idx = indices.next().unwrap_or(u32::MAX);
557+ let line = row.cell_text().fold(String::new(), |mut s, frag| {
598− if self.widths.len() < row.cells.len() {
558+ s.push_str(&frag);
599− self.widths.resize(row.cells.len(), Constraint::Length(0));
559+ s.push_str(TEMP_CELL_SEP);
600− }
560+ s
601− let mut widths = self.widths.iter_mut();
602− for cell in &mut row.cells {
603− let Some(Constraint::Length(max_width)) = widths.next() else {
604− unreachable!();
605− };
561+ });
606
562
607− // merge index highlights on top of existing hightlights
563+ // Items are filtered by using the text returned by menu::Item::filter_text
608− let mut span_list = Vec::new();
564+ // but we do highlighting here using the text in Row and therefore there
609− let mut current_span = String::new();
565+ // might be inconsistencies. This is the best we can do since only the
610− let mut current_style = Style::default();
566+ // text in Row is displayed to the end user.
611− let mut width = 0;
567+ let (_score, highlights) = FuzzyQuery::new(self.prompt.line())
612−
568+ .fuzzy_indices(&line, &self.matcher)
613− let spans: &[Span] = cell.content.lines.first().map_or(&[], |it| it.0.as_slice());
569+ .unwrap_or_default();
614− for span in spans {
570+
615− // this looks like a bug on first glance, we are iterating
571+ let highlight_byte_ranges: Vec<_> = line
616− // graphemes but treating them as char indices. The reason that
572+ .char_indices()
617− // this is correct is that nucleo will only ever consider the first char
573+ .enumerate()
618− // of a grapheme (and discard the rest of the grapheme) so the indices
574+ .filter_map(|(char_idx, (byte_offset, ch))| {
619− // returned by nucleo are essentially grapheme indecies
575+ highlights
620− for grapheme in span.content.graphemes(true) {
576+ .contains(&char_idx)
621− let style = if grapheme_idx == next_highlight_idx {
577+ .then(|| byte_offset..byte_offset + ch.len_utf8())
622− next_highlight_idx = indices.next().unwrap_or(u32::MAX);
578+ })
579+ .collect();
580+
581+ // The starting byte index of the current (iterating) cell
582+ let mut cell_start_byte_offset = 0;
583+ for cell in row.cells.iter_mut() {
584+ let spans = match cell.content.lines.get(0) {
585+ Some(s) => s,
586+ None => {
587+ cell_start_byte_offset += TEMP_CELL_SEP.len();
588+ continue;
589+ }
590+ };
591+
592+ let mut cell_len = 0;
593+
594+ let graphemes_with_style: Vec<_> = spans
595+ .0
596+ .iter()
597+ .flat_map(|span| {
598+ span.content
599+ .grapheme_indices(true)
600+ .zip(std::iter::repeat(span.style))
601+ })
602+ .map(|((grapheme_byte_offset, grapheme), style)| {
603+ cell_len += grapheme.len();
604+ let start = cell_start_byte_offset;
605+
606+ let grapheme_byte_range =
607+ grapheme_byte_offset..grapheme_byte_offset + grapheme.len();
608+
609+ if highlight_byte_ranges.iter().any(|hl_rng| {
610+ hl_rng.start >= start + grapheme_byte_range.start
611+ && hl_rng.end <= start + grapheme_byte_range.end
612+ }) {
623− span.style.patch(highlight_style)
613+ (grapheme, style.patch(highlight_style))
624− } else {
614+ } else {
625− span.style
615+ (grapheme, style)
626− };
627− if style != current_style {
628− if !current_span.is_empty() {
629− span_list.push(Span::styled(current_span, current_style))
630 }
616 }
631− current_span = String::new();
617+ })
632− current_style = style;
618+ .collect();
619+
620+ let mut span_list: Vec<(String, Style)> = Vec::new();
621+ for (grapheme, style) in graphemes_with_style {
622+ if span_list.last().map(|(_, sty)| sty) == Some(&style) {
623+ let (string, _) = span_list.last_mut().unwrap();
624+ string.push_str(grapheme);
625+ } else {
626+ span_list.push((String::from(grapheme), style))
633 }
627 }
634− current_span.push_str(grapheme);
635− grapheme_idx += 1;
636 }
628 }
637− width += span.width();
638− }
639
629
640− span_list.push(Span::styled(current_span, current_style));
630+ let spans: Vec<Span> = span_list
641− if width as u16 > *max_width {
631+ .into_iter()
642− *max_width = width as u16;
632+ .map(|(string, style)| Span::styled(string, style))
643− }
633+ .collect();
634+ let spans: Spans = spans.into();
644− *cell = Cell::from(Spans::from(span_list));
635+ *cell = Cell::from(spans);
645
636
646− // spacer
637+ cell_start_byte_offset += cell_len + TEMP_CELL_SEP.len();
647− if grapheme_idx == next_highlight_idx {
648− next_highlight_idx = indices.next().unwrap_or(u32::MAX);
649 }
638 }
650− grapheme_idx += 1;
651− }
652
639
653− row
640+ row
654− });
641+ });
655
642
656 let table = Table::new(options)
643 let table = Table::new(options)
657 .style(text_style)
644 .style(text_style)
@@ -667,7 +654,7 @@ impl<T: Item + 'static> Picker<T> {
667 surface,
654 surface,
668 &mut TableState {
655 &mut TableState {
669 offset: 0,
656 offset: 0,
670− selected: Some(cursor as usize),
657+ selected: Some(cursor),
671 },
658 },
672 self.truncate_start,
659 self.truncate_start,
673 );
660 );
@@ -693,14 +680,8 @@ impl<T: Item + 'static> Picker<T> {
693 if let Some((path, range)) = self.current_file(cx.editor) {
680 if let Some((path, range)) = self.current_file(cx.editor) {
694 let preview = self.get_preview(path, cx.editor);
681 let preview = self.get_preview(path, cx.editor);
695 let doc = match preview.document() {
682 let doc = match preview.document() {
696− Some(doc)
683+ Some(doc) => doc,
697− if range.map_or(true, |(start, end)| {
684+ None => {
698− start <= end && end <= doc.text().len_lines()
699− }) =>
700− {
701− doc
702− }
703− _ => {
704 let alt_text = preview.placeholder();
685 let alt_text = preview.placeholder();
705 let x = inner.x + inner.width.saturating_sub(alt_text.len() as u16) / 2;
686 let x = inner.x + inner.width.saturating_sub(alt_text.len() as u16) / 2;
706 let y = inner.y + inner.height / 2;
687 let y = inner.y + inner.height / 2;
@@ -709,47 +690,32 @@ impl<T: Item + 'static> Picker<T> {
709 }
690 }
710 };
691 };
711
692
712− let mut offset = ViewPosition::default();
713− if let Some((start_line, end_line)) = range {
714− let height = end_line - start_line;
715− let text = doc.text().slice(..);
716− let start = text.line_to_char(start_line);
717− let middle = text.line_to_char(start_line + height / 2);
718− if height < inner.height as usize {
719− let text_fmt = doc.text_format(inner.width, None);
720− let annotations = TextAnnotations::default();
721− (offset.anchor, offset.vertical_offset) = char_idx_at_visual_offset(
722− text,
723− middle,
724− // align to middle
693+ // align to middle
725− -(inner.height as isize / 2),
694+ let first_line = range
726− 0,
695+ .map(|(start, end)| {
727− &text_fmt,
696+ let height = end.saturating_sub(start) + 1;
728− &annotations,
697+ let middle = start + (height.saturating_sub(1) / 2);
729− );
698+ middle.saturating_sub(inner.height as usize / 2).min(start)
730− if start < offset.anchor {
731− offset.anchor = start;
732− offset.vertical_offset = 0;
733− }
734− } else {
735− offset.anchor = start;
736− }
699+ })
737− }
700+ .unwrap_or(0);
738
701
702+ let offset = ViewPosition {
703+ anchor: doc.text().line_to_char(first_line),
704+ horizontal_offset: 0,
705+ vertical_offset: 0,
706+ };
707+
739− let syntax_highlights = EditorView::doc_syntax_highlights(
708+ let mut highlights = EditorView::doc_syntax_highlights(
740 doc,
709 doc,
741 offset.anchor,
710 offset.anchor,
742 area.height,
711 area.height,
743 &cx.editor.theme,
712 &cx.editor.theme,
744 );
713 );
745−
746− let mut overlay_highlights =
747− EditorView::empty_highlight_iter(doc, offset.anchor, area.height);
748 for spans in EditorView::doc_diagnostics_highlights(doc, &cx.editor.theme) {
714 for spans in EditorView::doc_diagnostics_highlights(doc, &cx.editor.theme) {
749 if spans.is_empty() {
715 if spans.is_empty() {
750 continue;
716 continue;
751 }
717 }
752− overlay_highlights = Box::new(helix_core::syntax::merge(overlay_highlights, spans));
718+ highlights = Box::new(helix_core::syntax::merge(highlights, spans));
753 }
719 }
754 let mut decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
720 let mut decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
755
721
@@ -780,8 +746,7 @@ impl<T: Item + 'static> Picker<T> {
780 offset,
746 offset,
781 // TODO: compute text annotations asynchronously here (like inlay hints)
747 // TODO: compute text annotations asynchronously here (like inlay hints)
782 &TextAnnotations::default(),
748 &TextAnnotations::default(),
783− syntax_highlights,
749+ highlights,
784− overlay_highlights,
785 &cx.editor.theme,
750 &cx.editor.theme,
786 &mut decorations,
751 &mut decorations,
787 &mut [],
752 &mut [],
@@ -790,7 +755,7 @@ impl<T: Item + 'static> Picker<T> {
790 }
755 }
791}
756}
792
757
793−impl<T: Item + 'static + Send + Sync> Component for Picker<T> {
758+impl<T: Item + 'static> Component for Picker<T> {
794 fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
759 fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
795 // +---------+ +---------+
760 // +---------+ +---------+
796 // |prompt | |preview |
761 // |prompt | |preview |
@@ -799,8 +764,7 @@ impl<T: Item + 'static + Send + Sync> Component for Picker<T> {
799 // | | | |
764 // | | | |
800 // +---------+ +---------+
765 // +---------+ +---------+
801
766
802− let render_preview =
803− self.show_preview && self.file_fn.is_some() && area.width > MIN_AREA_WIDTH_FOR_PREVIEW;
767+ let render_preview = self.show_preview && area.width > MIN_AREA_WIDTH_FOR_PREVIEW;
804
768
805 let picker_width = if render_preview {
769 let picker_width = if render_preview {
806 area.width / 2
770 area.width / 2
@@ -830,28 +794,11 @@ impl<T: Item + 'static + Send + Sync> Component for Picker<T> {
830 _ => return EventResult::Ignored(None),
794 _ => return EventResult::Ignored(None),
831 };
795 };
832
796
833− let close_fn = |picker: &mut Self| {
797+ let close_fn =
834− // if the picker is very large don't store it as last_picker to avoid
835− // excessive memory consumption
836− let callback: compositor::Callback = if picker.matcher.snapshot().item_count() > 100_000
837− {
838− Box::new(|compositor: &mut Compositor, _ctx| {
839− // remove the layer
840− compositor.pop();
841− })
842− } else {
843− // stop streaming in new items in the background, really we should
844− // be restarting the stream somehow once the picker gets
845− // reopened instead (like for an FS crawl) that would also remove the
846− // need for the special case above but that is pretty tricky
847− picker.shutdown.store(true, atomic::Ordering::Relaxed);
848− Box::new(|compositor: &mut Compositor, _ctx| {
849− // remove the layer
850− compositor.last_picker = compositor.pop();
851− })
852− };
853− EventResult::Consumed(Some(callback))
798+ EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _ctx| {
799+ // remove the layer
800+ compositor.last_picker = compositor.pop();
854− };
801+ })));
855
802
856 // So that idle timeout retriggers
803 // So that idle timeout retriggers
857 ctx.editor.reset_idle_timer();
804 ctx.editor.reset_idle_timer();
@@ -875,7 +822,9 @@ impl<T: Item + 'static + Send + Sync> Component for Picker<T> {
875 key!(End) => {
822 key!(End) => {
876 self.to_end();
823 self.to_end();
877 }
824 }
825+ key!(Esc) | ctrl!('c') => {
878− key!(Esc) | ctrl!('c') => return close_fn(self),
826+ return close_fn;
827+ }
879 alt!(Enter) => {
828 alt!(Enter) => {
880 if let Some(option) = self.selection() {
829 if let Some(option) = self.selection() {
881 (self.callback_fn)(ctx, option, Action::Load);
830 (self.callback_fn)(ctx, option, Action::Load);
@@ -885,19 +834,19 @@ impl<T: Item + 'static + Send + Sync> Component for Picker<T> {
885 if let Some(option) = self.selection() {
834 if let Some(option) = self.selection() {
886 (self.callback_fn)(ctx, option, Action::Replace);
835 (self.callback_fn)(ctx, option, Action::Replace);
887 }
836 }
888− return close_fn(self);
837+ return close_fn;
889 }
838 }
890 ctrl!('s') => {
839 ctrl!('s') => {
891 if let Some(option) = self.selection() {
840 if let Some(option) = self.selection() {
892 (self.callback_fn)(ctx, option, Action::HorizontalSplit);
841 (self.callback_fn)(ctx, option, Action::HorizontalSplit);
893 }
842 }
894− return close_fn(self);
843+ return close_fn;
895 }
844 }
896 ctrl!('v') => {
845 ctrl!('v') => {
897 if let Some(option) = self.selection() {
846 if let Some(option) = self.selection() {
898 (self.callback_fn)(ctx, option, Action::VerticalSplit);
847 (self.callback_fn)(ctx, option, Action::VerticalSplit);
899 }
848 }
900− return close_fn(self);
849+ return close_fn;
901 }
850 }
902 ctrl!('t') => {
851 ctrl!('t') => {
903 self.toggle_preview();
852 self.toggle_preview();
@@ -925,15 +874,30 @@ impl<T: Item + 'static + Send + Sync> Component for Picker<T> {
925 self.completion_height = height.saturating_sub(4);
874 self.completion_height = height.saturating_sub(4);
926 Some((width, height))
875 Some((width, height))
927 }
876 }
877+}
928
878
929− fn id(&self) -> Option<&'static str> {
879+#[derive(PartialEq, Eq, Debug)]
930− Some(ID)
880+struct PickerMatch {
881+ score: i64,
882+ index: usize,
883+ len: usize,
884+}
885+
886+impl PickerMatch {
887+ fn key(&self) -> impl Ord {
888+ (cmp::Reverse(self.score), self.len, self.index)
931 }
889 }
932}
890}
933−impl<T: Item> Drop for Picker<T> {
891+
934− fn drop(&mut self) {
892+impl PartialOrd for PickerMatch {
935− // ensure we cancel any ongoing background threads streaming into the picker
893+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
936− self.shutdown.store(true, atomic::Ordering::Relaxed)
894+ Some(self.cmp(other))
895+ }
896+}
897+
898+impl Ord for PickerMatch {
899+ fn cmp(&self, other: &Self) -> Ordering {
900+ self.key().cmp(&other.key())
937 }
901 }
938}
902}
939
903
@@ -946,13 +910,15 @@ pub type DynQueryCallback<T> =
946
910
947/// A picker that updates its contents via a callback whenever the
911/// A picker that updates its contents via a callback whenever the
948/// query string changes. Useful for live grep, workspace symbols, etc.
912/// query string changes. Useful for live grep, workspace symbols, etc.
949−pub struct DynamicPicker<T: ui::menu::Item + Send + Sync> {
913+pub struct DynamicPicker<T: ui::menu::Item + Send> {
950 file_picker: Picker<T>,
914 file_picker: Picker<T>,
951 query_callback: DynQueryCallback<T>,
915 query_callback: DynQueryCallback<T>,
952 query: String,
916 query: String,
953}
917}
954
918
955−impl<T: ui::menu::Item + Send + Sync> DynamicPicker<T> {
919+impl<T: ui::menu::Item + Send> DynamicPicker<T> {
920+ pub const ID: &'static str = "dynamic-picker";
921+
956 pub fn new(file_picker: Picker<T>, query_callback: DynQueryCallback<T>) -> Self {
922 pub fn new(file_picker: Picker<T>, query_callback: DynQueryCallback<T>) -> Self {
957 Self {
923 Self {
958 file_picker,
924 file_picker,
@@ -962,7 +928,7 @@ impl<T: ui::menu::Item + Send + Sync> DynamicPicker<T> {
962 }
928 }
963}
929}
964
930
965−impl<T: Item + Send + Sync + 'static> Component for DynamicPicker<T> {
931+impl<T: Item + Send + 'static> Component for DynamicPicker<T> {
966 fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
932 fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
967 self.file_picker.render(area, surface, cx);
933 self.file_picker.render(area, surface, cx);
968 }
934 }
@@ -984,7 +950,7 @@ impl<T: Item + Send + Sync + 'static> Component for DynamicPicker<T> {
984 let callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
950 let callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
985 // Wrapping of pickers in overlay is done outside the picker code,
951 // Wrapping of pickers in overlay is done outside the picker code,
986 // so this is fragile and will break if wrapped in some other widget.
952 // so this is fragile and will break if wrapped in some other widget.
987− let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(ID) {
953+ let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(Self::ID) {
988 Some(overlay) => &mut overlay.content.file_picker,
954 Some(overlay) => &mut overlay.content.file_picker,
989 None => return,
955 None => return,
990 };
956 };
@@ -1005,6 +971,6 @@ impl<T: Item + Send + Sync + 'static> Component for DynamicPicker<T> {
1005 }
971 }
1006
972
1007 fn id(&self) -> Option<&'static str> {
973 fn id(&self) -> Option<&'static str> {
1008− Some(ID)
974+ Some(Self::ID)
1009 }
975 }
1010}
976}
helix-term/src/ui/popup.rs+7−27
@@ -3,10 +3,7 @@ use crate::{
3 compositor::{Callback, Component, Context, Event, EventResult},
3 compositor::{Callback, Component, Context, Event, EventResult},
4 ctrl, key,
4 ctrl, key,
5};
5};
6−use tui::{
7− buffer::Buffer as Surface,
6+use tui::buffer::Buffer as Surface;
8− widgets::{Block, Borders, Widget},
9−};
10
7
11use helix_core::Position;
8use helix_core::Position;
12use helix_view::{
9use helix_view::{
@@ -255,29 +252,13 @@ impl<T: Component> Component for Popup<T> {
255 let background = cx.editor.theme.get("ui.popup");
252 let background = cx.editor.theme.get("ui.popup");
256 surface.clear_with(area, background);
253 surface.clear_with(area, background);
257
254
258− let render_borders = cx.editor.popup_border();
259−
260− let inner = if self
261− .contents
262− .type_name()
263− .starts_with("helix_term::ui::menu::Menu")
264− {
265− area
266− } else {
267− area.inner(&self.margin)
255+ let inner = area.inner(&self.margin);
268− };
269−
270− let border = usize::from(render_borders);
271− if render_borders {
272− Widget::render(Block::default().borders(Borders::ALL), area, surface);
273− }
274−
275 self.contents.render(inner, surface, cx);
256 self.contents.render(inner, surface, cx);
276
257
277 // render scrollbar if contents do not fit
258 // render scrollbar if contents do not fit
278 if self.has_scrollbar {
259 if self.has_scrollbar {
279− let win_height = (inner.height as usize).saturating_sub(2 * border);
260+ let win_height = inner.height as usize;
280− let len = (self.child_size.1 as usize).saturating_sub(2 * border);
261+ let len = self.child_size.1 as usize;
281 let fits = len <= win_height;
262 let fits = len <= win_height;
282 let scroll = self.scroll;
263 let scroll = self.scroll;
283 let scroll_style = cx.editor.theme.get("ui.menu.scroll");
264 let scroll_style = cx.editor.theme.get("ui.menu.scroll");
@@ -293,15 +274,14 @@ impl<T: Component> Component for Popup<T> {
293
274
294 let mut cell;
275 let mut cell;
295 for i in 0..win_height {
276 for i in 0..win_height {
296− cell = &mut surface[(inner.right() - 1, inner.top() + (border + i) as u16)];
277+ cell = &mut surface[(inner.right() - 1, inner.top() + i as u16)];
297
278
298− let half_block = if render_borders { "▌" } else { "▐" };
279+ cell.set_symbol("▐"); // right half block
299
280
300 if scroll_line <= i && i < scroll_line + scroll_height {
281 if scroll_line <= i && i < scroll_line + scroll_height {
301 // Draw scroll thumb
282 // Draw scroll thumb
302− cell.set_symbol(half_block);
303 cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
283 cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
304− } else if !render_borders {
284+ } else {
305 // Draw scroll track
285 // Draw scroll track
306 cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
286 cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
307 }
287 }
helix-term/src/ui/prompt.rs+29−27
@@ -306,8 +306,8 @@ impl Prompt {
306 direction: CompletionDirection,
306 direction: CompletionDirection,
307 ) {
307 ) {
308 (self.callback_fn)(cx, &self.line, PromptEvent::Abort);
308 (self.callback_fn)(cx, &self.line, PromptEvent::Abort);
309− let mut values = match cx.editor.registers.read(register, cx.editor) {
309+ let values = match cx.editor.registers.read(register) {
310− Some(values) if values.len() > 0 => values.rev(),
310+ Some(values) if !values.is_empty() => values,
311 _ => return,
311 _ => return,
312 };
312 };
313
313
@@ -315,16 +315,13 @@ impl Prompt {
315
315
316 let index = match direction {
316 let index = match direction {
317 CompletionDirection::Forward => self.history_pos.map_or(0, |i| i + 1),
317 CompletionDirection::Forward => self.history_pos.map_or(0, |i| i + 1),
318− CompletionDirection::Backward => self
318+ CompletionDirection::Backward => {
319− .history_pos
320− .unwrap_or_else(|| values.len())
321− .saturating_sub(1),
319+ self.history_pos.unwrap_or(values.len()).saturating_sub(1)
320+ }
322 }
321 }
323 .min(end);
322 .min(end);
324
323
325− self.line = values.nth(index).unwrap().to_string();
324+ self.line = values[index].clone();
326− // Appease the borrow checker.
327− drop(values);
328
325
329 self.history_pos = Some(index);
326 self.history_pos = Some(index);
330
327
@@ -473,7 +470,7 @@ impl Prompt {
473 // Show the most recently entered value as a suggestion.
470 // Show the most recently entered value as a suggestion.
474 if let Some(suggestion) = self
471 if let Some(suggestion) = self
475 .history_register
472 .history_register
476− .and_then(|reg| cx.editor.registers.first(reg, cx.editor))
473+ .and_then(|reg| cx.editor.registers.last(reg))
477 {
474 {
478 surface.set_string(line_area.x, line_area.y, suggestion, suggestion_color);
475 surface.set_string(line_area.x, line_area.y, suggestion, suggestion_color);
479 }
476 }
@@ -570,29 +567,25 @@ impl Component for Prompt {
570 } else {
567 } else {
571 let last_item = self
568 let last_item = self
572 .history_register
569 .history_register
573− .and_then(|reg| cx.editor.registers.first(reg, cx.editor))
570+ .and_then(|reg| cx.editor.registers.last(reg).cloned())
574− .map(|entry| entry.to_string())
571+ .map(|entry| entry.into())
575− .unwrap_or_else(|| String::from(""));
572+ .unwrap_or_else(|| Cow::from(""));
576
573
577 // handle executing with last command in history if nothing entered
574 // handle executing with last command in history if nothing entered
578− let input = if self.line.is_empty() {
575+ let input: Cow<str> = if self.line.is_empty() {
579− &last_item
576+ last_item
580 } else {
577 } else {
581 if last_item != self.line {
578 if last_item != self.line {
582 // store in history
579 // store in history
583 if let Some(register) = self.history_register {
580 if let Some(register) = self.history_register {
584− if let Err(err) =
585− cx.editor.registers.push(register, self.line.clone())
581+ cx.editor.registers.push(register, self.line.clone());
586− {
587− cx.editor.set_error(err.to_string());
588− }
589 };
582 };
590 }
583 }
591
584
592− &self.line
585+ self.line.as_str().into()
593 };
586 };
594
587
595− (self.callback_fn)(cx, input, PromptEvent::Validate);
588+ (self.callback_fn)(cx, &input, PromptEvent::Validate);
596
589
597 return close_fn;
590 return close_fn;
598 }
591 }
@@ -624,16 +617,25 @@ impl Component for Prompt {
624 self.completion = cx
617 self.completion = cx
625 .editor
618 .editor
626 .registers
619 .registers
627− .iter_preview()
620+ .inner()
621+ .iter()
622+ .map(|(ch, reg)| {
623+ let content = reg
624+ .read()
625+ .get(0)
626+ .and_then(|s| s.lines().next().to_owned())
627+ .unwrap_or_default();
628− .map(|(ch, preview)| (0.., format!("{} {}", ch, &preview).into()))
628+ (0.., format!("{} {}", ch, &content).into())
629+ })
629 .collect();
630 .collect();
630 self.next_char_handler = Some(Box::new(|prompt, c, context| {
631 self.next_char_handler = Some(Box::new(|prompt, c, context| {
631 prompt.insert_str(
632 prompt.insert_str(
632− &context
633+ context
633 .editor
634 .editor
634 .registers
635 .registers
635− .first(c, context.editor)
636+ .read(c)
636− .unwrap_or_default(),
637+ .and_then(|r| r.first())
638+ .map_or("", |r| r.as_str()),
637 context.editor,
639 context.editor,
638 );
640 );
639 }));
641 }));
helix-term/src/ui/statusline.rs−14
@@ -145,7 +145,6 @@ where
145 helix_view::editor::StatusLineElement::FileModificationIndicator => {
145 helix_view::editor::StatusLineElement::FileModificationIndicator => {
146 render_file_modification_indicator
146 render_file_modification_indicator
147 }
147 }
148− helix_view::editor::StatusLineElement::ReadOnlyIndicator => render_read_only_indicator,
149 helix_view::editor::StatusLineElement::FileEncoding => render_file_encoding,
148 helix_view::editor::StatusLineElement::FileEncoding => render_file_encoding,
150 helix_view::editor::StatusLineElement::FileLineEnding => render_file_line_ending,
149 helix_view::editor::StatusLineElement::FileLineEnding => render_file_line_ending,
151 helix_view::editor::StatusLineElement::FileType => render_file_type,
150 helix_view::editor::StatusLineElement::FileType => render_file_type,
@@ -443,19 +442,6 @@ where
443 write(context, title, None);
442 write(context, title, None);
444}
443}
445
444
446−fn render_read_only_indicator<F>(context: &mut RenderContext, write: F)
447−where
448− F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
449−{
450− let title = if context.doc.readonly {
451− " [readonly] "
452− } else {
453− ""
454− }
455− .to_string();
456− write(context, title, None);
457−}
458−
459fn render_file_base_name<F>(context: &mut RenderContext, write: F)
445fn render_file_base_name<F>(context: &mut RenderContext, write: F)
460where
446where
461 F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
447 F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
helix-term/tests/integration.rs−1
@@ -18,7 +18,6 @@ mod test {
18 mod auto_indent;
18 mod auto_indent;
19 mod auto_pairs;
19 mod auto_pairs;
20 mod commands;
20 mod commands;
21− mod languages;
22 mod movement;
21 mod movement;
23 mod prompt;
22 mod prompt;
24 mod splits;
23 mod splits;
helix-term/tests/test/commands.rs−47
@@ -2,7 +2,6 @@ use helix_term::application::Application;
2
2
3use super::*;
3use super::*;
4
4
5−mod movement;
6mod write;
5mod write;
7
6
8#[tokio::test(flavor = "multi_thread")]
7#[tokio::test(flavor = "multi_thread")]
@@ -480,49 +479,3 @@ fn bar() {#(\n|)#\
480
479
481 Ok(())
480 Ok(())
482}
481}
483−
484−#[tokio::test(flavor = "multi_thread")]
485−async fn test_join_selections() -> anyhow::Result<()> {
486− // normal join
487− test((
488− platform_line(indoc! {"\
489− #[a|]#bc
490− def
491− "}),
492− "J",
493− platform_line(indoc! {"\
494− #[a|]#bc def
495− "}),
496− ))
497− .await?;
498−
499− // join with empty line
500− test((
501− platform_line(indoc! {"\
502− #[a|]#bc
503−
504− def
505− "}),
506− "JJ",
507− platform_line(indoc! {"\
508− #[a|]#bc def
509− "}),
510− ))
511− .await?;
512−
513− // join with additional space in non-empty line
514− test((
515− platform_line(indoc! {"\
516− #[a|]#bc
517−
518− def
519− "}),
520− "JJ",
521− platform_line(indoc! {"\
522− #[a|]#bc def
523− "}),
524− ))
525− .await?;
526−
527− Ok(())
528−}
helix-term/tests/test/commands/movement.rsdeleted−452
@@ -1,452 +0,0 @@
1−use super::*;
2−
3−#[tokio::test(flavor = "multi_thread")]
4−async fn test_move_parent_node_end() -> anyhow::Result<()> {
5− let tests = vec![
6− // single cursor stays single cursor, first goes to end of current
7− // node, then parent
8− (
9− helpers::platform_line(indoc! {r##"
10− fn foo() {
11− let result = if true {
12− "yes"
13− } else {
14− "no#["|]#
15− }
16− }
17− "##}),
18− "<A-e>",
19− helpers::platform_line(indoc! {"\
20− fn foo() {
21− let result = if true {
22− \"yes\"
23− } else {
24− \"no\"#[\n|]#
25− }
26− }
27− "}),
28− ),
29− (
30− helpers::platform_line(indoc! {"\
31− fn foo() {
32− let result = if true {
33− \"yes\"
34− } else {
35− \"no\"#[\n|]#
36− }
37− }
38− "}),
39− "<A-e>",
40− helpers::platform_line(indoc! {"\
41− fn foo() {
42− let result = if true {
43− \"yes\"
44− } else {
45− \"no\"
46− }#[\n|]#
47− }
48− "}),
49− ),
50− // select mode extends
51− (
52− helpers::platform_line(indoc! {r##"
53− fn foo() {
54− let result = if true {
55− "yes"
56− } else {
57− #["no"|]#
58− }
59− }
60− "##}),
61− "v<A-e><A-e>",
62− helpers::platform_line(indoc! {"\
63− fn foo() {
64− let result = if true {
65− \"yes\"
66− } else {
67− #[\"no\"
68− }\n|]#
69− }
70− "}),
71− ),
72− ];
73−
74− for test in tests {
75− test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?;
76− }
77−
78− Ok(())
79−}
80−
81−#[tokio::test(flavor = "multi_thread")]
82−async fn test_move_parent_node_start() -> anyhow::Result<()> {
83− let tests = vec![
84− // single cursor stays single cursor, first goes to end of current
85− // node, then parent
86− (
87− helpers::platform_line(indoc! {r##"
88− fn foo() {
89− let result = if true {
90− "yes"
91− } else {
92− "no#["|]#
93− }
94− }
95− "##}),
96− "<A-b>",
97− helpers::platform_line(indoc! {"\
98− fn foo() {
99− let result = if true {
100− \"yes\"
101− } else {
102− #[\"|]#no\"
103− }
104− }
105− "}),
106− ),
107− (
108− helpers::platform_line(indoc! {"\
109− fn foo() {
110− let result = if true {
111− \"yes\"
112− } else {
113− \"no\"#[\n|]#
114− }
115− }
116− "}),
117− "<A-b>",
118− helpers::platform_line(indoc! {"\
119− fn foo() {
120− let result = if true {
121− \"yes\"
122− } else #[{|]#
123− \"no\"
124− }
125− }
126− "}),
127− ),
128− (
129− helpers::platform_line(indoc! {"\
130− fn foo() {
131− let result = if true {
132− \"yes\"
133− } else #[{|]#
134− \"no\"
135− }
136− }
137− "}),
138− "<A-b>",
139− helpers::platform_line(indoc! {"\
140− fn foo() {
141− let result = if true {
142− \"yes\"
143− } #[e|]#lse {
144− \"no\"
145− }
146− }
147− "}),
148− ),
149− // select mode extends
150− (
151− helpers::platform_line(indoc! {r##"
152− fn foo() {
153− let result = if true {
154− "yes"
155− } else {
156− #["no"|]#
157− }
158− }
159− "##}),
160− "v<A-b><A-b>",
161− helpers::platform_line(indoc! {"\
162− fn foo() {
163− let result = if true {
164− \"yes\"
165− } else #[|{
166− ]#\"no\"
167− }
168− }
169− "}),
170− ),
171− (
172− helpers::platform_line(indoc! {r##"
173− fn foo() {
174− let result = if true {
175− "yes"
176− } else {
177− #["no"|]#
178− }
179− }
180− "##}),
181− "v<A-b><A-b><A-b>",
182− helpers::platform_line(indoc! {"\
183− fn foo() {
184− let result = if true {
185− \"yes\"
186− } #[|else {
187− ]#\"no\"
188− }
189− }
190− "}),
191− ),
192− ];
193−
194− for test in tests {
195− test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?;
196− }
197−
198− Ok(())
199−}
200−
201−#[tokio::test(flavor = "multi_thread")]
202−async fn test_smart_tab_move_parent_node_end() -> anyhow::Result<()> {
203− let tests = vec![
204− // single cursor stays single cursor, first goes to end of current
205− // node, then parent
206− (
207− helpers::platform_line(indoc! {r##"
208− fn foo() {
209− let result = if true {
210− "yes"
211− } else {
212− "no#["|]#
213− }
214− }
215− "##}),
216− "i<tab>",
217− helpers::platform_line(indoc! {"\
218− fn foo() {
219− let result = if true {
220− \"yes\"
221− } else {
222− \"no\"#[|\n]#
223− }
224− }
225− "}),
226− ),
227− (
228− helpers::platform_line(indoc! {"\
229− fn foo() {
230− let result = if true {
231− \"yes\"
232− } else {
233− \"no\"#[\n|]#
234− }
235− }
236− "}),
237− "i<tab>",
238− helpers::platform_line(indoc! {"\
239− fn foo() {
240− let result = if true {
241− \"yes\"
242− } else {
243− \"no\"
244− }#[|\n]#
245− }
246− "}),
247− ),
248− // appending to the end of a line should still look at the current
249− // line, not the next one
250− (
251− helpers::platform_line(indoc! {"\
252− fn foo() {
253− let result = if true {
254− \"yes\"
255− } else {
256− \"no#[\"|]#
257− }
258− }
259− "}),
260− "a<tab>",
261− helpers::platform_line(indoc! {"\
262− fn foo() {
263− let result = if true {
264− \"yes\"
265− } else {
266− \"no\"
267− }#[\n|]#
268− }
269− "}),
270− ),
271− // before cursor is all whitespace, so insert tab
272− (
273− helpers::platform_line(indoc! {"\
274− fn foo() {
275− let result = if true {
276− \"yes\"
277− } else {
278− #[\"no\"|]#
279− }
280− }
281− "}),
282− "i<tab>",
283− helpers::platform_line(indoc! {"\
284− fn foo() {
285− let result = if true {
286− \"yes\"
287− } else {
288− #[|\"no\"]#
289− }
290− }
291− "}),
292− ),
293− // if selection spans multiple lines, it should still only look at the
294− // line on which the head is
295− (
296− helpers::platform_line(indoc! {"\
297− fn foo() {
298− let result = if true {
299− #[\"yes\"
300− } else {
301− \"no\"|]#
302− }
303− }
304− "}),
305− "a<tab>",
306− helpers::platform_line(indoc! {"\
307− fn foo() {
308− let result = if true {
309− \"yes\"
310− } else {
311− \"no\"
312− }#[\n|]#
313− }
314− "}),
315− ),
316− (
317− helpers::platform_line(indoc! {"\
318− fn foo() {
319− let result = if true {
320− #[\"yes\"
321− } else {
322− \"no\"|]#
323− }
324− }
325− "}),
326− "i<tab>",
327− helpers::platform_line(indoc! {"\
328− fn foo() {
329− let result = if true {
330− #[|\"yes\"
331− } else {
332− \"no\"]#
333− }
334− }
335− "}),
336− ),
337− (
338− helpers::platform_line(indoc! {"\
339− fn foo() {
340− #[l|]#et result = if true {
341− #(\"yes\"
342− } else {
343− \"no\"|)#
344− }
345− }
346− "}),
347− "i<tab>",
348− helpers::platform_line(indoc! {"\
349− fn foo() {
350− #[|l]#et result = if true {
351− #(|\"yes\"
352− } else {
353− \"no\")#
354− }
355− }
356− "}),
357− ),
358− (
359− helpers::platform_line(indoc! {"\
360− fn foo() {
361− let result = if true {
362− \"yes\"#[\n|]#
363− } else {
364− \"no\"#(\n|)#
365− }
366− }
367− "}),
368− "i<tab>",
369− helpers::platform_line(indoc! {"\
370− fn foo() {
371− let result = if true {
372− \"yes\"
373− }#[| ]#else {
374− \"no\"
375− }#(|\n)#
376− }
377− "}),
378− ),
379− (
380− helpers::platform_line(indoc! {"\
381− fn foo() {
382− let result = if true {
383− #[\"yes\"|]#
384− } else {
385− #(\"no\"|)#
386− }
387− }
388− "}),
389− "i<tab>",
390− helpers::platform_line(indoc! {"\
391− fn foo() {
392− let result = if true {
393− #[|\"yes\"]#
394− } else {
395− #(|\"no\")#
396− }
397− }
398− "}),
399− ),
400− // if any cursors are not preceded by all whitespace, then do the
401− // smart_tab action
402− (
403− helpers::platform_line(indoc! {"\
404− fn foo() {
405− let result = if true {
406− #[\"yes\"\n|]#
407− } else {
408− \"no#(\"\n|)#
409− }
410− }
411− "}),
412− "i<tab>",
413− helpers::platform_line(indoc! {"\
414− fn foo() {
415− let result = if true {
416− \"yes\"
417− }#[| ]#else {
418− \"no\"
419− }#(|\n)#
420− }
421− "}),
422− ),
423− // Ctrl-tab always inserts a tab
424− (
425− helpers::platform_line(indoc! {"\
426− fn foo() {
427− let result = if true {
428− #[\"yes\"\n|]#
429− } else {
430− \"no#(\"\n|)#
431− }
432− }
433− "}),
434− "i<S-tab>",
435− helpers::platform_line(indoc! {"\
436− fn foo() {
437− let result = if true {
438− #[|\"yes\"\n]#
439− } else {
440− \"no #(|\"\n)#
441− }
442− }
443− "}),
444− ),
445− ];
446−
447− for test in tests {
448− test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?;
449− }
450−
451− Ok(())
452−}
helix-term/tests/test/commands/write.rs+3−122
@@ -93,7 +93,7 @@ async fn test_buffer_close_concurrent() -> anyhow::Result<()> {
93 )
93 )
94 .await?;
94 .await?;
95
95
96− helpers::assert_file_has_content(file.as_file_mut(), &platform_line(&RANGE.end().to_string()))?;
96+ helpers::assert_file_has_content(file.as_file_mut(), &RANGE.end().to_string())?;
97
97
98 Ok(())
98 Ok(())
99}
99}
@@ -209,7 +209,7 @@ async fn test_write_concurrent() -> anyhow::Result<()> {
209
209
210 let mut file_content = String::new();
210 let mut file_content = String::new();
211 file.as_file_mut().read_to_string(&mut file_content)?;
211 file.as_file_mut().read_to_string(&mut file_content)?;
212− assert_eq!(platform_line(&RANGE.end().to_string()), file_content);
212+ assert_eq!(RANGE.end().to_string(), file_content);
213
213
214 Ok(())
214 Ok(())
215}
215}
@@ -424,132 +424,13 @@ async fn test_write_utf_bom_file() -> anyhow::Result<()> {
424 Ok(())
424 Ok(())
425}
425}
426
426
427−#[tokio::test(flavor = "multi_thread")]
428−async fn test_write_insert_final_newline_added_if_missing() -> anyhow::Result<()> {
429− let mut file = tempfile::NamedTempFile::new()?;
430− let mut app = helpers::AppBuilder::new()
431− .with_file(file.path(), None)
432− .with_input_text("#[h|]#ave you tried chamomile tea?")
433− .build()?;
434−
435− test_key_sequence(&mut app, Some(":w<ret>"), None, false).await?;
436−
437− helpers::assert_file_has_content(
438− file.as_file_mut(),
439− &helpers::platform_line("have you tried chamomile tea?\n"),
440− )?;
441−
442− Ok(())
443−}
444−
445−#[tokio::test(flavor = "multi_thread")]
446−async fn test_write_insert_final_newline_unchanged_if_not_missing() -> anyhow::Result<()> {
447− let mut file = tempfile::NamedTempFile::new()?;
448− let mut app = helpers::AppBuilder::new()
449− .with_file(file.path(), None)
450− .with_input_text(&helpers::platform_line("#[t|]#en minutes, please\n"))
451− .build()?;
452−
453− test_key_sequence(&mut app, Some(":w<ret>"), None, false).await?;
454−
455− helpers::assert_file_has_content(
456− file.as_file_mut(),
457− &helpers::platform_line("ten minutes, please\n"),
458− )?;
459−
460− Ok(())
461−}
462−
463−#[tokio::test(flavor = "multi_thread")]
464−async fn test_write_insert_final_newline_unchanged_if_missing_and_false() -> anyhow::Result<()> {
465− let mut file = tempfile::NamedTempFile::new()?;
466− let mut app = helpers::AppBuilder::new()
467− .with_config(Config {
468− editor: helix_view::editor::Config {
469− insert_final_newline: false,
470− ..Default::default()
471− },
472− ..Default::default()
473− })
474− .with_file(file.path(), None)
475− .with_input_text("#[t|]#he quiet rain continued through the night")
476− .build()?;
477−
478− test_key_sequence(&mut app, Some(":w<ret>"), None, false).await?;
479−
480− helpers::assert_file_has_content(
481− file.as_file_mut(),
482− "the quiet rain continued through the night",
483− )?;
484−
485− Ok(())
486−}
487−
488−#[tokio::test(flavor = "multi_thread")]
489−async fn test_write_all_insert_final_newline_add_if_missing_and_modified() -> anyhow::Result<()> {
490− let mut file1 = tempfile::NamedTempFile::new()?;
491− let mut file2 = tempfile::NamedTempFile::new()?;
492− let mut app = helpers::AppBuilder::new()
493− .with_file(file1.path(), None)
494− .with_input_text("#[w|]#e don't serve time travelers here")
495− .build()?;
496−
497− test_key_sequence(
498− &mut app,
499− Some(&format!(
500− ":o {}<ret>ia time traveler walks into a bar<esc>:wa<ret>",
501− file2.path().to_string_lossy()
502− )),
503− None,
504− false,
505− )
506− .await?;
507−
508− helpers::assert_file_has_content(
509− file1.as_file_mut(),
510− &helpers::platform_line("we don't serve time travelers here\n"),
511− )?;
512−
513− helpers::assert_file_has_content(
514− file2.as_file_mut(),
515− &helpers::platform_line("a time traveler walks into a bar\n"),
516− )?;
517−
518− Ok(())
519−}
520−
521−#[tokio::test(flavor = "multi_thread")]
522−async fn test_write_all_insert_final_newline_do_not_add_if_unmodified() -> anyhow::Result<()> {
523− let mut file = tempfile::NamedTempFile::new()?;
524− let mut app = helpers::AppBuilder::new()
525− .with_file(file.path(), None)
526− .build()?;
527−
528− file.write_all(b"i lost on Jeopardy!")?;
529− file.rewind()?;
530−
531− test_key_sequence(&mut app, Some(":wa<ret>"), None, false).await?;
532−
533− helpers::assert_file_has_content(file.as_file_mut(), "i lost on Jeopardy!")?;
534−
535− Ok(())
536−}
537−
538async fn edit_file_with_content(file_content: &[u8]) -> anyhow::Result<()> {
427async fn edit_file_with_content(file_content: &[u8]) -> anyhow::Result<()> {
539 let mut file = tempfile::NamedTempFile::new()?;
428 let mut file = tempfile::NamedTempFile::new()?;
540
429
541 file.as_file_mut().write_all(&file_content)?;
430 file.as_file_mut().write_all(&file_content)?;
542
431
543 helpers::test_key_sequence(
432 helpers::test_key_sequence(
544− &mut helpers::AppBuilder::new()
433+ &mut helpers::AppBuilder::new().build()?,
545− .with_config(Config {
546− editor: helix_view::editor::Config {
547− insert_final_newline: false,
548− ..Default::default()
549− },
550− ..Default::default()
551− })
552− .build()?,
553 Some(&format!(":o {}<ret>:x<ret>", file.path().to_string_lossy())),
434 Some(&format!(":o {}<ret>:x<ret>", file.path().to_string_lossy())),
554 None,
435 None,
555 true,
436 true,
helix-term/tests/test/helpers.rs+1−9
@@ -320,14 +320,6 @@ impl AppBuilder {
320 }
320 }
321
321
322 pub fn build(self) -> anyhow::Result<Application> {
322 pub fn build(self) -> anyhow::Result<Application> {
323− if let Some(path) = &self.args.working_directory {
324− bail!("Changing the working directory to {path:?} is not yet supported for integration tests");
325− }
326−
327− if let Some((path, _)) = self.args.files.first().filter(|p| p.0.is_dir()) {
328− bail!("Having the directory {path:?} in args.files[0] is not yet supported for integration tests");
329− }
330−
331 let mut app = Application::new(self.args, self.config, self.syn_conf)?;
323 let mut app = Application::new(self.args, self.config, self.syn_conf)?;
332
324
333 if let Some((text, selection)) = self.input {
325 if let Some((text, selection)) = self.input {
@@ -358,7 +350,7 @@ pub fn assert_file_has_content(file: &mut File, content: &str) -> anyhow::Result
358
350
359 let mut file_content = String::new();
351 let mut file_content = String::new();
360 file.read_to_string(&mut file_content)?;
352 file.read_to_string(&mut file_content)?;
361− assert_eq!(file_content, content);
353+ assert_eq!(content, file_content);
362
354
363 Ok(())
355 Ok(())
364}
356}
helix-term/tests/test/languages/go.rsdeleted−41
@@ -1,41 +0,0 @@
1−use super::*;
2−
3−#[tokio::test(flavor = "multi_thread")]
4−async fn auto_indent() -> anyhow::Result<()> {
5− let app = || AppBuilder::new().with_file("foo.go", None);
6−
7− let enter_tests = [
8− (
9− helpers::platform_line(indoc! {r##"
10− type Test struct {#[}|]#
11− "##}),
12− "i<ret>",
13− helpers::platform_line(indoc! {"\
14− type Test struct {
15− \t#[|\n]#
16− }
17− "}),
18− ),
19− (
20− helpers::platform_line(indoc! {"\
21− func main() {
22− \tswitch nil {#[}|]#
23− }
24− "}),
25− "i<ret>",
26− helpers::platform_line(indoc! {"\
27− func main() {
28− \tswitch nil {
29− \t\t#[|\n]#
30− \t}
31− }
32− "}),
33− ),
34− ];
35−
36− for test in enter_tests {
37− test_with_config(app(), test).await?;
38− }
39−
40− Ok(())
41−}
helix-term/tests/test/languages/mod.rsdeleted−4
@@ -1,4 +0,0 @@
1−use super::*;
2−
3−mod go;
4−mod yaml;
helix-term/tests/test/languages/yaml.rsdeleted−819
@@ -1,819 +0,0 @@
1−use super::*;
2−
3−#[tokio::test(flavor = "multi_thread")]
4−async fn auto_indent() -> anyhow::Result<()> {
5− let app = || AppBuilder::new().with_file("foo.yaml", None);
6−
7− let below_tests = [
8− (
9− helpers::platform_line(indoc! {r##"
10− #[t|]#op:
11− baz: foo
12− bazi:
13− more: yes
14− why: because
15− quux:
16− - 1
17− - 2
18− bax: foox
19− fook:
20− "##}),
21− "o",
22− helpers::platform_line(indoc! {"\
23− top:
24− #[\n|]#
25− baz: foo
26− bazi:
27− more: yes
28− why: because
29− quux:
30− - 1
31− - 2
32− bax: foox
33− fook:
34− "}),
35− ),
36− (
37− helpers::platform_line(indoc! {r##"
38− top:
39− b#[a|]#z: foo
40− bazi:
41− more: yes
42− why: because
43− quux:
44− - 1
45− - 2
46− bax: foox
47− fook:
48− "##}),
49− "o",
50− helpers::platform_line(indoc! {"\
51− top:
52− baz: foo
53− #[\n|]#
54− bazi:
55− more: yes
56− why: because
57− quux:
58− - 1
59− - 2
60− bax: foox
61− fook:
62− "}),
63− ),
64− (
65− helpers::platform_line(indoc! {r##"
66− top:
67− baz: foo
68− bazi#[:|]#
69− more: yes
70− why: because
71− quux:
72− - 1
73− - 2
74− bax: foox
75− fook:
76− "##}),
77− "o",
78− helpers::platform_line(indoc! {"\
79− top:
80− baz: foo
81− bazi:
82− #[\n|]#
83− more: yes
84− why: because
85− quux:
86− - 1
87− - 2
88− bax: foox
89− fook:
90− "}),
91− ),
92− (
93− helpers::platform_line(indoc! {r##"
94− top:
95− baz: foo
96− bazi:
97− more: #[yes|]#
98− why: because
99− quux:
100− - 1
101− - 2
102− bax: foox
103− fook:
104− "##}),
105− "o",
106− helpers::platform_line(indoc! {"\
107− top:
108− baz: foo
109− bazi:
110− more: yes
111− #[\n|]#
112− why: because
113− quux:
114− - 1
115− - 2
116− bax: foox
117− fook:
118− "}),
119− ),
120− (
121− helpers::platform_line(indoc! {r##"
122− top:
123− baz: foo
124− bazi:
125− more: yes
126− why: becaus#[e|]#
127− quux:
128− - 1
129− - 2
130− bax: foox
131− fook:
132− "##}),
133− "o",
134− helpers::platform_line(indoc! {"\
135− top:
136− baz: foo
137− bazi:
138− more: yes
139− why: because
140− #[\n|]#
141− quux:
142− - 1
143− - 2
144− bax: foox
145− fook:
146− "}),
147− ),
148− (
149− helpers::platform_line(indoc! {"\
150− top:
151− baz: foo
152− bazi:
153− more: yes
154− why: because
155− quux:#[\n|]#
156− - 1
157− - 2
158− bax: foox
159− fook:
160− "}),
161− "o",
162− helpers::platform_line(indoc! {"\
163− top:
164− baz: foo
165− bazi:
166− more: yes
167− why: because
168− quux:
169− #[\n|]#
170− - 1
171− - 2
172− bax: foox
173− fook:
174− "}),
175− ),
176− (
177− helpers::platform_line(indoc! {"\
178− top:
179− baz: foo
180− bazi:
181− more: yes
182− why: because
183− quux:
184− - 1#[\n|]#
185− - 2
186− bax: foox
187− fook:
188− "}),
189− "o",
190− helpers::platform_line(indoc! {"\
191− top:
192− baz: foo
193− bazi:
194− more: yes
195− why: because
196− quux:
197− - 1
198− #[\n|]#
199− - 2
200− bax: foox
201− fook:
202− "}),
203− ),
204− (
205− helpers::platform_line(indoc! {"\
206− top:
207− baz: foo
208− bazi:
209− more: yes
210− why: because
211− quux:
212− - 1
213− - 2
214− bax: foox
215− fook:#[\n|]#
216− "}),
217− "o",
218− helpers::platform_line(indoc! {"\
219− top:
220− baz: foo
221− bazi:
222− more: yes
223− why: because
224− quux:
225− - 1
226− - 2
227− bax: foox
228− fook:
229− #[\n|]#
230− "}),
231− ),
232− (
233− helpers::platform_line(indoc! {"\
234− top:
235− baz: foo
236− bax: |
237− some
238− multi
239− line
240− string#[\n|]#
241− fook:
242− "}),
243− "o",
244− helpers::platform_line(indoc! {"\
245− top:
246− baz: foo
247− bax: |
248− some
249− multi
250− line
251− string
252− #[\n|]#
253− fook:
254− "}),
255− ),
256− (
257− helpers::platform_line(indoc! {"\
258− top:
259− baz: foo
260− bax: >
261− some
262− multi
263− line#[\n|]#
264− string
265− fook:
266− "}),
267− "o",
268− helpers::platform_line(indoc! {"\
269− top:
270− baz: foo
271− bax: >
272− some
273− multi
274− line
275− #[\n|]#
276− string
277− fook:
278− "}),
279− ),
280− (
281− helpers::platform_line(indoc! {"\
282− top:
283− baz: foo
284− bax: >#[\n|]#
285− fook:
286− "}),
287− "o",
288− helpers::platform_line(indoc! {"\
289− top:
290− baz: foo
291− bax: >
292− #[\n|]#
293− fook:
294− "}),
295− ),
296− (
297− helpers::platform_line(indoc! {"\
298− - top:#[\n|]#
299− baz: foo
300− bax: foox
301− fook:
302− "}),
303− "o",
304− helpers::platform_line(indoc! {"\
305− - top:
306− #[\n|]#
307− baz: foo
308− bax: foox
309− fook:
310− "}),
311− ),
312− (
313− helpers::platform_line(indoc! {"\
314− - top:
315− baz: foo#[\n|]#
316− bax: foox
317− fook:
318− "}),
319− "o",
320− helpers::platform_line(indoc! {"\
321− - top:
322− baz: foo
323− #[\n|]#
324− bax: foox
325− fook:
326− "}),
327− ),
328− (
329− helpers::platform_line(indoc! {"\
330− - top:
331− baz: foo
332− bax: foox#[\n|]#
333− fook:
334− "}),
335− "o",
336− helpers::platform_line(indoc! {"\
337− - top:
338− baz: foo
339− bax: foox
340− #[\n|]#
341− fook:
342− "}),
343− ),
344− (
345− helpers::platform_line(indoc! {"\
346− top:
347− baz:
348− - one: two#[\n|]#
349− three: four
350− - top:
351− baz: foo
352− bax: foox
353− "}),
354− "o",
355− helpers::platform_line(indoc! {"\
356− top:
357− baz:
358− - one: two
359− #[\n|]#
360− three: four
361− - top:
362− baz: foo
363− bax: foox
364− "}),
365− ),
366− // yaml map without a key
367− (
368− helpers::platform_line(indoc! {"\
369− top:#[\n|]#
370− "}),
371− "o",
372− helpers::platform_line(indoc! {"\
373− top:
374− #[\n|]#
375− "}),
376− ),
377− (
378− helpers::platform_line(indoc! {"\
379− top#[:|]#
380− bottom: withvalue
381− "}),
382− "o",
383− helpers::platform_line(indoc! {"\
384− top:
385− #[\n|]#
386− bottom: withvalue
387− "}),
388− ),
389− (
390− helpers::platform_line(indoc! {"\
391− bottom: withvalue
392− top#[:|]#
393− "}),
394− "o",
395− helpers::platform_line(indoc! {"\
396− bottom: withvalue
397− top:
398− #[\n|]#
399− "}),
400− ),
401− ];
402−
403− for test in below_tests {
404− test_with_config(app(), test).await?;
405− }
406−
407− let above_tests = [
408− (
409− helpers::platform_line(indoc! {r##"
410− #[t|]#op:
411− baz: foo
412− bazi:
413− more: yes
414− why: because
415− quux:
416− - 1
417− - 2
418− bax: foox
419− fook:
420− "##}),
421− "O",
422− helpers::platform_line(indoc! {"\
423− #[\n|]#
424− top:
425− baz: foo
426− bazi:
427− more: yes
428− why: because
429− quux:
430− - 1
431− - 2
432− bax: foox
433− fook:
434− "}),
435− ),
436− (
437− helpers::platform_line(indoc! {r##"
438− top:
439− b#[a|]#z: foo
440− bazi:
441− more: yes
442− why: because
443− quux:
444− - 1
445− - 2
446− bax: foox
447− fook:
448− "##}),
449− "O",
450− helpers::platform_line(indoc! {"\
451− top:
452− #[\n|]#
453− baz: foo
454− bazi:
455− more: yes
456− why: because
457− quux:
458− - 1
459− - 2
460− bax: foox
461− fook:
462− "}),
463− ),
464− (
465− helpers::platform_line(indoc! {r##"
466− top:
467− baz: foo
468− bazi#[:|]#
469− more: yes
470− why: because
471− quux:
472− - 1
473− - 2
474− bax: foox
475− fook:
476− "##}),
477− "O",
478− helpers::platform_line(indoc! {"\
479− top:
480− baz: foo
481− #[\n|]#
482− bazi:
483− more: yes
484− why: because
485− quux:
486− - 1
487− - 2
488− bax: foox
489− fook:
490− "}),
491− ),
492− (
493− helpers::platform_line(indoc! {r##"
494− top:
495− baz: foo
496− bazi:
497− more: #[yes|]#
498− why: because
499− quux:
500− - 1
501− - 2
502− bax: foox
503− fook:
504− "##}),
505− "O",
506− helpers::platform_line(indoc! {"\
507− top:
508− baz: foo
509− bazi:
510− #[\n|]#
511− more: yes
512− why: because
513− quux:
514− - 1
515− - 2
516− bax: foox
517− fook:
518− "}),
519− ),
520− (
521− helpers::platform_line(indoc! {r##"
522− top:
523− baz: foo
524− bazi:
525− more: yes
526− why: becaus#[e|]#
527− quux:
528− - 1
529− - 2
530− bax: foox
531− fook:
532− "##}),
533− "O",
534− helpers::platform_line(indoc! {"\
535− top:
536− baz: foo
537− bazi:
538− more: yes
539− #[\n|]#
540− why: because
541− quux:
542− - 1
543− - 2
544− bax: foox
545− fook:
546− "}),
547− ),
548− (
549− helpers::platform_line(indoc! {"\
550− top:
551− baz: foo
552− bazi:
553− more: yes
554− why: because
555− quux:#[\n|]#
556− - 1
557− - 2
558− bax: foox
559− fook:
560− "}),
561− "O",
562− helpers::platform_line(indoc! {"\
563− top:
564− baz: foo
565− bazi:
566− more: yes
567− why: because
568− #[\n|]#
569− quux:
570− - 1
571− - 2
572− bax: foox
573− fook:
574− "}),
575− ),
576− (
577− helpers::platform_line(indoc! {"\
578− top:
579− baz: foo
580− bazi:
581− more: yes
582− why: because
583− quux:
584− - 1#[\n|]#
585− - 2
586− bax: foox
587− fook:
588− "}),
589− "O",
590− helpers::platform_line(indoc! {"\
591− top:
592− baz: foo
593− bazi:
594− more: yes
595− why: because
596− quux:
597− #[\n|]#
598− - 1
599− - 2
600− bax: foox
601− fook:
602− "}),
603− ),
604− (
605− helpers::platform_line(indoc! {"\
606− top:
607− baz: foo
608− bazi:
609− more: yes
610− why: because
611− quux:
612− - 1
613− - 2
614− bax: foox
615− fook:#[\n|]#
616− "}),
617− "O",
618− helpers::platform_line(indoc! {"\
619− top:
620− baz: foo
621− bazi:
622− more: yes
623− why: because
624− quux:
625− - 1
626− - 2
627− bax: foox
628− #[\n|]#
629− fook:
630− "}),
631− ),
632− (
633− helpers::platform_line(indoc! {"\
634− top:
635− baz: foo
636− bax: |
637− some
638− multi
639− line
640− string#[\n|]#
641− fook:
642− "}),
643− "O",
644− helpers::platform_line(indoc! {"\
645− top:
646− baz: foo
647− bax: |
648− some
649− multi
650− line
651− #[\n|]#
652− string
653− fook:
654− "}),
655− ),
656− (
657− helpers::platform_line(indoc! {"\
658− top:
659− baz: foo
660− bax: >
661− some#[\n|]#
662− multi
663− line
664− string
665− fook:
666− "}),
667− "O",
668− helpers::platform_line(indoc! {"\
669− top:
670− baz: foo
671− bax: >
672− #[\n|]#
673− some
674− multi
675− line
676− string
677− fook:
678− "}),
679− ),
680− (
681− helpers::platform_line(indoc! {"\
682− top:
683− baz: foo
684− bax: >
685− fook:#[\n|]#
686− "}),
687− "O",
688− helpers::platform_line(indoc! {"\
689− top:
690− baz: foo
691− bax: >
692− #[\n|]#
693− fook:
694− "}),
695− ),
696− (
697− helpers::platform_line(indoc! {"\
698− - top:
699− baz: foo#[\n|]#
700− bax: foox
701− fook:
702− "}),
703− "O",
704− helpers::platform_line(indoc! {"\
705− - top:
706− #[\n|]#
707− baz: foo
708− bax: foox
709− fook:
710− "}),
711− ),
712− (
713− helpers::platform_line(indoc! {"\
714− - top:
715− baz: foo
716− bax: foox
717− fook:#[\n|]#
718− "}),
719− "O",
720− helpers::platform_line(indoc! {"\
721− - top:
722− baz: foo
723− bax: foox
724− #[\n|]#
725− fook:
726− "}),
727− ),
728− (
729− helpers::platform_line(indoc! {"\
730− top:
731− baz:
732− - one: two#[\n|]#
733− three: four
734− - top:
735− baz: foo
736− bax: foox
737− "}),
738− "O",
739− helpers::platform_line(indoc! {"\
740− top:
741− baz:
742− #[\n|]#
743− - one: two
744− three: four
745− - top:
746− baz: foo
747− bax: foox
748− "}),
749− ),
750− // yaml map without a key
751− (
752− helpers::platform_line(indoc! {"\
753− top:#[\n|]#
754− "}),
755− "O",
756− helpers::platform_line(indoc! {"\
757− #[\n|]#
758− top:
759− "}),
760− ),
761− (
762− helpers::platform_line(indoc! {"\
763− bottom: withvalue
764− top#[:|]#
765− "}),
766− "O",
767− helpers::platform_line(indoc! {"\
768− bottom: withvalue
769− #[\n|]#
770− top:
771− "}),
772− ),
773− (
774− helpers::platform_line(indoc! {"\
775− top:
776− bottom:#[ |]#withvalue
777− "}),
778− "O",
779− helpers::platform_line(indoc! {"\
780− top:
781− #[\n|]#
782− bottom: withvalue
783− "}),
784− ),
785− ];
786−
787− for test in above_tests {
788− test_with_config(app(), test).await?;
789− }
790−
791− let enter_tests = [
792− (
793− helpers::platform_line(indoc! {r##"
794− foo: #[b|]#ar
795− "##}),
796− "i<ret>",
797− helpers::platform_line(indoc! {"\
798− foo:
799− #[|b]#ar
800− "}),
801− ),
802− (
803− helpers::platform_line(indoc! {"\
804− foo:#[\n|]#
805− "}),
806− "i<ret>",
807− helpers::platform_line(indoc! {"\
808− foo:
809− #[|\n]#
810− "}),
811− ),
812− ];
813−
814− for test in enter_tests {
815− test_with_config(app(), test).await?;
816− }
817−
818− Ok(())
819−}
helix-term/tests/test/movement.rs−39
@@ -513,42 +513,3 @@ async fn select_mode_tree_sitter_prev_function_goes_backwards_to_object() -> any
513
513
514 Ok(())
514 Ok(())
515}
515}
516−
517−#[tokio::test(flavor = "multi_thread")]
518−async fn find_char_line_ending() -> anyhow::Result<()> {
519− test((
520− helpers::platform_line(indoc! {
521− "\
522− one
523− #[|t]#wo
524− three"
525− }),
526− "T<ret>gll2f<ret>",
527− helpers::platform_line(indoc! {
528− "\
529− one
530− two#[
531− |]#three"
532− }),
533− ))
534− .await?;
535−
536− test((
537− helpers::platform_line(indoc! {
538− "\
539− #[|o]#ne
540− two
541− three"
542− }),
543− "f<ret>2t<ret>ghT<ret>F<ret>",
544− helpers::platform_line(indoc! {
545− "\
546− one#[|
547− t]#wo
548− three"
549− }),
550− ))
551− .await?;
552−
553− Ok(())
554−}
helix-term/tests/test/splits.rs+3−3
@@ -62,9 +62,9 @@ async fn test_split_write_quit_all() -> anyhow::Result<()> {
62 )
62 )
63 .await?;
63 .await?;
64
64
65− helpers::assert_file_has_content(file1.as_file_mut(), &platform_line("hello1"))?;
65+ helpers::assert_file_has_content(file1.as_file_mut(), "hello1")?;
66− helpers::assert_file_has_content(file2.as_file_mut(), &platform_line("hello2"))?;
66+ helpers::assert_file_has_content(file2.as_file_mut(), "hello2")?;
67− helpers::assert_file_has_content(file3.as_file_mut(), &platform_line("hello3"))?;
67+ helpers::assert_file_has_content(file3.as_file_mut(), "hello3")?;
68
68
69 Ok(())
69 Ok(())
70}
70}
helix-tui/Cargo.toml+15−15
@@ -1,28 +1,28 @@
1[package]
1[package]
2name = "helix-tui"
2name = "helix-tui"
3+version = "0.6.0"
4+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5+description = """
3−description = """A library to build rich terminal user interfaces or dashboards"""
6+A library to build rich terminal user interfaces or dashboards
7+"""
8+edition = "2021"
9+license = "MPL-2.0"
10+categories = ["editor"]
11+repository = "https://github.com/helix-editor/helix"
12+homepage = "https://helix-editor.com"
4include = ["src/**/*", "README.md"]
13include = ["src/**/*", "README.md"]
5−version.workspace = true
6−authors.workspace = true
7−edition.workspace = true
8−license.workspace = true
9−rust-version.workspace = true
10−categories.workspace = true
11−repository.workspace = true
12−homepage.workspace = true
13
14
14[features]
15[features]
15default = ["crossterm"]
16default = ["crossterm"]
16
17
17[dependencies]
18[dependencies]
18−helix-view = { path = "../helix-view", features = ["term"] }
19−helix-core = { path = "../helix-core" }
20−
21−bitflags = "2.4"
19+bitflags = "2.3"
22cassowary = "0.3"
20cassowary = "0.3"
23unicode-segmentation = "1.10"
21unicode-segmentation = "1.10"
24−crossterm = { version = "0.27", optional = true }
22+crossterm = { version = "0.26", optional = true }
25termini = "1.0"
23termini = "1.0"
26serde = { version = "1", "optional" = true, features = ["derive"]}
24serde = { version = "1", "optional" = true, features = ["derive"]}
27−once_cell = "1.19"
25+once_cell = "1.18"
28log = "~0.4"
26log = "~0.4"
27+helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
28+helix-core = { version = "0.6", path = "../helix-core" }
helix-tui/src/backend/crossterm.rs+33−51
@@ -32,21 +32,10 @@ fn vte_version() -> Option<usize> {
32}
32}
33
33
34/// Describes terminal capabilities like extended underline, truecolor, etc.
34/// Describes terminal capabilities like extended underline, truecolor, etc.
35−#[derive(Clone, Debug)]
35+#[derive(Copy, Clone, Debug, Default)]
36struct Capabilities {
36struct Capabilities {
37 /// Support for undercurled, underdashed, etc.
37 /// Support for undercurled, underdashed, etc.
38 has_extended_underlines: bool,
38 has_extended_underlines: bool,
39− /// Support for resetting the cursor style back to normal.
40− reset_cursor_command: String,
41−}
42−
43−impl Default for Capabilities {
44− fn default() -> Self {
45− Self {
46− has_extended_underlines: false,
47− reset_cursor_command: "\x1B[0 q".to_string(),
48− }
49− }
50}
39}
51
40
52impl Capabilities {
41impl Capabilities {
@@ -65,10 +54,6 @@ impl Capabilities {
65 || t.extended_cap("Su").is_some()
54 || t.extended_cap("Su").is_some()
66 || vte_version() >= Some(5102)
55 || vte_version() >= Some(5102)
67 || matches!(term_program().as_deref(), Some("WezTerm")),
56 || matches!(term_program().as_deref(), Some("WezTerm")),
68− reset_cursor_command: t
69− .utf8_string_cap(termini::StringCapability::CursorNormal)
70− .unwrap_or("\x1B[0 q")
71− .to_string(),
72 },
57 },
73 }
58 }
74 }
59 }
@@ -169,8 +154,7 @@ where
169
154
170 fn restore(&mut self, config: Config) -> io::Result<()> {
155 fn restore(&mut self, config: Config) -> io::Result<()> {
171 // reset cursor shape
156 // reset cursor shape
172− self.buffer
157+ write!(self.buffer, "\x1B[0 q")?;
173− .write_all(self.capabilities.reset_cursor_command.as_bytes())?;
174 if config.enable_mouse_capture {
158 if config.enable_mouse_capture {
175 execute!(self.buffer, DisableMouseCapture)?;
159 execute!(self.buffer, DisableMouseCapture)?;
176 }
160 }
@@ -217,7 +201,7 @@ where
217 for (x, y, cell) in content {
201 for (x, y, cell) in content {
218 // Move the cursor if the previous location was not (x - 1, y)
202 // Move the cursor if the previous location was not (x - 1, y)
219 if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
203 if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
220− queue!(self.buffer, MoveTo(x, y))?;
204+ map_error(queue!(self.buffer, MoveTo(x, y)))?;
221 }
205 }
222 last_pos = Some((x, y));
206 last_pos = Some((x, y));
223 if cell.modifier != modifier {
207 if cell.modifier != modifier {
@@ -230,12 +214,12 @@ where
230 }
214 }
231 if cell.fg != fg {
215 if cell.fg != fg {
232 let color = CColor::from(cell.fg);
216 let color = CColor::from(cell.fg);
233− queue!(self.buffer, SetForegroundColor(color))?;
217+ map_error(queue!(self.buffer, SetForegroundColor(color)))?;
234 fg = cell.fg;
218 fg = cell.fg;
235 }
219 }
236 if cell.bg != bg {
220 if cell.bg != bg {
237 let color = CColor::from(cell.bg);
221 let color = CColor::from(cell.bg);
238− queue!(self.buffer, SetBackgroundColor(color))?;
222+ map_error(queue!(self.buffer, SetBackgroundColor(color)))?;
239 bg = cell.bg;
223 bg = cell.bg;
240 }
224 }
241
225
@@ -243,7 +227,7 @@ where
243 if self.capabilities.has_extended_underlines {
227 if self.capabilities.has_extended_underlines {
244 if cell.underline_color != underline_color {
228 if cell.underline_color != underline_color {
245 let color = CColor::from(cell.underline_color);
229 let color = CColor::from(cell.underline_color);
246− queue!(self.buffer, SetUnderlineColor(color))?;
230+ map_error(queue!(self.buffer, SetUnderlineColor(color)))?;
247 underline_color = cell.underline_color;
231 underline_color = cell.underline_color;
248 }
232 }
249 } else {
233 } else {
@@ -255,24 +239,24 @@ where
255
239
256 if new_underline_style != underline_style {
240 if new_underline_style != underline_style {
257 let attr = CAttribute::from(new_underline_style);
241 let attr = CAttribute::from(new_underline_style);
258− queue!(self.buffer, SetAttribute(attr))?;
242+ map_error(queue!(self.buffer, SetAttribute(attr)))?;
259 underline_style = new_underline_style;
243 underline_style = new_underline_style;
260 }
244 }
261
245
262− queue!(self.buffer, Print(&cell.symbol))?;
246+ map_error(queue!(self.buffer, Print(&cell.symbol)))?;
263 }
247 }
264
248
265− queue!(
249+ map_error(queue!(
266 self.buffer,
250 self.buffer,
267 SetUnderlineColor(CColor::Reset),
251 SetUnderlineColor(CColor::Reset),
268 SetForegroundColor(CColor::Reset),
252 SetForegroundColor(CColor::Reset),
269 SetBackgroundColor(CColor::Reset),
253 SetBackgroundColor(CColor::Reset),
270 SetAttribute(CAttribute::Reset)
254 SetAttribute(CAttribute::Reset)
271− )
255+ ))
272 }
256 }
273
257
274 fn hide_cursor(&mut self) -> io::Result<()> {
258 fn hide_cursor(&mut self) -> io::Result<()> {
275− execute!(self.buffer, Hide)
259+ map_error(execute!(self.buffer, Hide))
276 }
260 }
277
261
278 fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> {
262 fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> {
@@ -282,7 +266,7 @@ where
282 CursorKind::Underline => SetCursorStyle::SteadyUnderScore,
266 CursorKind::Underline => SetCursorStyle::SteadyUnderScore,
283 CursorKind::Hidden => unreachable!(),
267 CursorKind::Hidden => unreachable!(),
284 };
268 };
285− execute!(self.buffer, Show, shape)
269+ map_error(execute!(self.buffer, Show, shape))
286 }
270 }
287
271
288 fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
272 fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
@@ -291,11 +275,11 @@ where
291 }
275 }
292
276
293 fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
277 fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
294− execute!(self.buffer, MoveTo(x, y))
278+ map_error(execute!(self.buffer, MoveTo(x, y)))
295 }
279 }
296
280
297 fn clear(&mut self) -> io::Result<()> {
281 fn clear(&mut self) -> io::Result<()> {
298− execute!(self.buffer, Clear(ClearType::All))
282+ map_error(execute!(self.buffer, Clear(ClearType::All)))
299 }
283 }
300
284
301 fn size(&self) -> io::Result<Rect> {
285 fn size(&self) -> io::Result<Rect> {
@@ -310,6 +294,10 @@ where
310 }
294 }
311}
295}
312
296
297+fn map_error(error: crossterm::Result<()>) -> io::Result<()> {
298+ error.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
299+}
300+
313#[derive(Debug)]
301#[derive(Debug)]
314struct ModifierDiff {
302struct ModifierDiff {
315 pub from: Modifier,
303 pub from: Modifier,
@@ -324,54 +312,48 @@ impl ModifierDiff {
324 //use crossterm::Attribute;
312 //use crossterm::Attribute;
325 let removed = self.from - self.to;
313 let removed = self.from - self.to;
326 if removed.contains(Modifier::REVERSED) {
314 if removed.contains(Modifier::REVERSED) {
327− queue!(w, SetAttribute(CAttribute::NoReverse))?;
315+ map_error(queue!(w, SetAttribute(CAttribute::NoReverse)))?;
328 }
316 }
329 if removed.contains(Modifier::BOLD) {
317 if removed.contains(Modifier::BOLD) {
330− queue!(w, SetAttribute(CAttribute::NormalIntensity))?;
318+ map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
331 if self.to.contains(Modifier::DIM) {
319 if self.to.contains(Modifier::DIM) {
332− queue!(w, SetAttribute(CAttribute::Dim))?;
320+ map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
333 }
321 }
334 }
322 }
335 if removed.contains(Modifier::ITALIC) {
323 if removed.contains(Modifier::ITALIC) {
336− queue!(w, SetAttribute(CAttribute::NoItalic))?;
324+ map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?;
337 }
325 }
338 if removed.contains(Modifier::DIM) {
326 if removed.contains(Modifier::DIM) {
339− queue!(w, SetAttribute(CAttribute::NormalIntensity))?;
327+ map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
340 }
328 }
341 if removed.contains(Modifier::CROSSED_OUT) {
329 if removed.contains(Modifier::CROSSED_OUT) {
342− queue!(w, SetAttribute(CAttribute::NotCrossedOut))?;
330+ map_error(queue!(w, SetAttribute(CAttribute::NotCrossedOut)))?;
343 }
331 }
344 if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
332 if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
345− queue!(w, SetAttribute(CAttribute::NoBlink))?;
333+ map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
346− }
347− if removed.contains(Modifier::HIDDEN) {
348− queue!(w, SetAttribute(CAttribute::NoHidden))?;
349 }
334 }
350
335
351 let added = self.to - self.from;
336 let added = self.to - self.from;
352 if added.contains(Modifier::REVERSED) {
337 if added.contains(Modifier::REVERSED) {
353− queue!(w, SetAttribute(CAttribute::Reverse))?;
338+ map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
354 }
339 }
355 if added.contains(Modifier::BOLD) {
340 if added.contains(Modifier::BOLD) {
356− queue!(w, SetAttribute(CAttribute::Bold))?;
341+ map_error(queue!(w, SetAttribute(CAttribute::Bold)))?;
357 }
342 }
358 if added.contains(Modifier::ITALIC) {
343 if added.contains(Modifier::ITALIC) {
359− queue!(w, SetAttribute(CAttribute::Italic))?;
344+ map_error(queue!(w, SetAttribute(CAttribute::Italic)))?;
360 }
345 }
361 if added.contains(Modifier::DIM) {
346 if added.contains(Modifier::DIM) {
362− queue!(w, SetAttribute(CAttribute::Dim))?;
347+ map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
363 }
348 }
364 if added.contains(Modifier::CROSSED_OUT) {
349 if added.contains(Modifier::CROSSED_OUT) {
365− queue!(w, SetAttribute(CAttribute::CrossedOut))?;
350+ map_error(queue!(w, SetAttribute(CAttribute::CrossedOut)))?;
366 }
351 }
367 if added.contains(Modifier::SLOW_BLINK) {
352 if added.contains(Modifier::SLOW_BLINK) {
368− queue!(w, SetAttribute(CAttribute::SlowBlink))?;
353+ map_error(queue!(w, SetAttribute(CAttribute::SlowBlink)))?;
369 }
354 }
370 if added.contains(Modifier::RAPID_BLINK) {
355 if added.contains(Modifier::RAPID_BLINK) {
371− queue!(w, SetAttribute(CAttribute::RapidBlink))?;
356+ map_error(queue!(w, SetAttribute(CAttribute::RapidBlink)))?;
372− }
373− if added.contains(Modifier::HIDDEN) {
374− queue!(w, SetAttribute(CAttribute::Hidden))?;
375 }
357 }
376
358
377 Ok(())
359 Ok(())
@@ -425,7 +407,7 @@ impl Command for SetUnderlineColor {
425 }
407 }
426
408
427 #[cfg(windows)]
409 #[cfg(windows)]
428− fn execute_winapi(&self) -> io::Result<()> {
410+ fn execute_winapi(&self) -> crossterm::Result<()> {
429 Err(std::io::Error::new(
411 Err(std::io::Error::new(
430 std::io::ErrorKind::Other,
412 std::io::ErrorKind::Other,
431 "SetUnderlineColor not supported by winapi.",
413 "SetUnderlineColor not supported by winapi.",
helix-tui/src/buffer.rs+2−2
@@ -213,7 +213,7 @@ impl Buffer {
213 && y < self.area.bottom()
213 && y < self.area.bottom()
214 }
214 }
215
215
216− /// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates.
216+ /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates.
217 ///
217 ///
218 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
218 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
219 ///
219 ///
@@ -242,7 +242,7 @@ impl Buffer {
242 ((y - self.area.y) as usize) * (self.area.width as usize) + ((x - self.area.x) as usize)
242 ((y - self.area.y) as usize) * (self.area.width as usize) + ((x - self.area.x) as usize)
243 }
243 }
244
244
245− /// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates,
245+ /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates,
246 /// or `None` if the coordinates are outside the buffer's area.
246 /// or `None` if the coordinates are outside the buffer's area.
247 fn index_of_opt(&self, x: u16, y: u16) -> Option<usize> {
247 fn index_of_opt(&self, x: u16, y: u16) -> Option<usize> {
248 if self.in_bounds(x, y) {
248 if self.in_bounds(x, y) {
helix-tui/src/terminal.rs+14
@@ -65,6 +65,20 @@ where
65 viewport: Viewport,
65 viewport: Viewport,
66}
66}
67
67
68+impl<B> Drop for Terminal<B>
69+where
70+ B: Backend,
71+{
72+ fn drop(&mut self) {
73+ // Attempt to restore the cursor state
74+ if self.cursor_kind == CursorKind::Hidden {
75+ if let Err(err) = self.show_cursor(CursorKind::Block) {
76+ eprintln!("Failed to show the cursor: {}", err);
77+ }
78+ }
79+ }
80+}
81+
68impl<B> Terminal<B>
82impl<B> Terminal<B>
69where
83where
70 B: Backend,
84 B: Backend,
helix-vcs/Cargo.toml+10−12
@@ -1,25 +1,23 @@
1[package]
1[package]
2name = "helix-vcs"
2name = "helix-vcs"
3−version.workspace = true
3+version = "0.6.0"
4−authors.workspace = true
4+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition.workspace = true
5+edition = "2021"
6−license.workspace = true
6+license = "MPL-2.0"
7−rust-version.workspace = true
8−categories.workspace = true
7+categories = ["editor"]
9−repository.workspace = true
8+repository = "https://github.com/helix-editor/helix"
10−homepage.workspace = true
9+homepage = "https://helix-editor.com"
11
10
12# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13
12
14[dependencies]
13[dependencies]
15−helix-core = { path = "../helix-core" }
14+helix-core = { version = "0.6", path = "../helix-core" }
16−helix-event = { path = "../helix-event" }
17
15
18tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] }
16tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] }
19parking_lot = "0.12"
17parking_lot = "0.12"
20arc-swap = { version = "1.6.0" }
18arc-swap = { version = "1.6.0" }
21
19
22−gix = { version = "0.57.0", default-features = false , optional = true }
20+gix = { version = "0.48.0", default-features = false , optional = true }
23imara-diff = "0.1.5"
21imara-diff = "0.1.5"
24anyhow = "1"
22anyhow = "1"
25
23
@@ -29,4 +27,4 @@ log = "0.4"
29git = ["gix"]
27git = ["gix"]
30
28
31[dev-dependencies]
29[dev-dependencies]
32−tempfile = "3.8"
30+tempfile = "3.7"
helix-vcs/src/diff.rs+19−6
@@ -2,10 +2,10 @@ use std::ops::Range;
2use std::sync::Arc;
2use std::sync::Arc;
3
3
4use helix_core::Rope;
4use helix_core::Rope;
5−use helix_event::RenderLockGuard;
6use imara_diff::Algorithm;
5use imara_diff::Algorithm;
7use parking_lot::{Mutex, MutexGuard};
6use parking_lot::{Mutex, MutexGuard};
8use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
7use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
8+use tokio::sync::{Notify, OwnedRwLockReadGuard, RwLock};
9use tokio::task::JoinHandle;
9use tokio::task::JoinHandle;
10use tokio::time::Instant;
10use tokio::time::Instant;
11
11
@@ -14,9 +14,11 @@ use crate::diff::worker::DiffWorker;
14mod line_cache;
14mod line_cache;
15mod worker;
15mod worker;
16
16
17+type RedrawHandle = (Arc<Notify>, Arc<RwLock<()>>);
18+
17/// A rendering lock passed to the differ the prevents redraws from occurring
19/// A rendering lock passed to the differ the prevents redraws from occurring
18struct RenderLock {
20struct RenderLock {
19− pub lock: RenderLockGuard,
21+ pub lock: OwnedRwLockReadGuard<()>,
20 pub timeout: Option<Instant>,
22 pub timeout: Option<Instant>,
21}
23}
22
24
@@ -36,22 +38,28 @@ struct DiffInner {
36#[derive(Clone, Debug)]
38#[derive(Clone, Debug)]
37pub struct DiffHandle {
39pub struct DiffHandle {
38 channel: UnboundedSender<Event>,
40 channel: UnboundedSender<Event>,
41+ render_lock: Arc<RwLock<()>>,
39 diff: Arc<Mutex<DiffInner>>,
42 diff: Arc<Mutex<DiffInner>>,
40 inverted: bool,
43 inverted: bool,
41}
44}
42
45
43impl DiffHandle {
46impl DiffHandle {
44− pub fn new(diff_base: Rope, doc: Rope) -> DiffHandle {
47+ pub fn new(diff_base: Rope, doc: Rope, redraw_handle: RedrawHandle) -> DiffHandle {
45− DiffHandle::new_with_handle(diff_base, doc).0
48+ DiffHandle::new_with_handle(diff_base, doc, redraw_handle).0
46 }
49 }
47
50
51+ fn new_with_handle(
52+ diff_base: Rope,
53+ doc: Rope,
54+ redraw_handle: RedrawHandle,
48− fn new_with_handle(diff_base: Rope, doc: Rope) -> (DiffHandle, JoinHandle<()>) {
55+ ) -> (DiffHandle, JoinHandle<()>) {
49 let (sender, receiver) = unbounded_channel();
56 let (sender, receiver) = unbounded_channel();
50 let diff: Arc<Mutex<DiffInner>> = Arc::default();
57 let diff: Arc<Mutex<DiffInner>> = Arc::default();
51 let worker = DiffWorker {
58 let worker = DiffWorker {
52 channel: receiver,
59 channel: receiver,
53 diff: diff.clone(),
60 diff: diff.clone(),
54 new_hunks: Vec::default(),
61 new_hunks: Vec::default(),
62+ redraw_notify: redraw_handle.0,
55 diff_finished_notify: Arc::default(),
63 diff_finished_notify: Arc::default(),
56 };
64 };
57 let handle = tokio::spawn(worker.run(diff_base, doc));
65 let handle = tokio::spawn(worker.run(diff_base, doc));
@@ -59,6 +67,7 @@ impl DiffHandle {
59 channel: sender,
67 channel: sender,
60 diff,
68 diff,
61 inverted: false,
69 inverted: false,
70+ render_lock: redraw_handle.1,
62 };
71 };
63 (differ, handle)
72 (differ, handle)
64 }
73 }
@@ -78,7 +87,11 @@ impl DiffHandle {
78 /// This function is only intended to be called from within the rendering loop
87 /// This function is only intended to be called from within the rendering loop
79 /// if called from elsewhere it may fail to acquire the render lock and panic
88 /// if called from elsewhere it may fail to acquire the render lock and panic
80 pub fn update_document(&self, doc: Rope, block: bool) -> bool {
89 pub fn update_document(&self, doc: Rope, block: bool) -> bool {
81− let lock = helix_event::lock_frame();
90+ // unwrap is ok here because the rendering lock is
91+ // only exclusively locked during redraw.
92+ // This function is only intended to be called
93+ // from the core rendering loop where no redraw can happen in parallel
94+ let lock = self.render_lock.clone().try_read_owned().unwrap();
82 let timeout = if block {
95 let timeout = if block {
83 None
96 None
84 } else {
97 } else {
helix-vcs/src/diff/worker.rs+9−3
@@ -23,6 +23,7 @@ pub(super) struct DiffWorker {
23 pub channel: UnboundedReceiver<Event>,
23 pub channel: UnboundedReceiver<Event>,
24 pub diff: Arc<Mutex<DiffInner>>,
24 pub diff: Arc<Mutex<DiffInner>>,
25 pub new_hunks: Vec<Hunk>,
25 pub new_hunks: Vec<Hunk>,
26+ pub redraw_notify: Arc<Notify>,
26 pub diff_finished_notify: Arc<Notify>,
27 pub diff_finished_notify: Arc<Notify>,
27}
28}
28
29
@@ -31,7 +32,11 @@ impl DiffWorker {
31 let mut accumulator = EventAccumulator::new();
32 let mut accumulator = EventAccumulator::new();
32 accumulator.handle_event(event).await;
33 accumulator.handle_event(event).await;
33 accumulator
34 accumulator
35+ .accumulate_debounced_events(
36+ &mut self.channel,
37+ self.redraw_notify.clone(),
34− .accumulate_debounced_events(&mut self.channel, self.diff_finished_notify.clone())
38+ self.diff_finished_notify.clone(),
39+ )
35 .await;
40 .await;
36 (accumulator.doc, accumulator.diff_base)
41 (accumulator.doc, accumulator.diff_base)
37 }
42 }
@@ -132,6 +137,7 @@ impl<'a> EventAccumulator {
132 async fn accumulate_debounced_events(
137 async fn accumulate_debounced_events(
133 &mut self,
138 &mut self,
134 channel: &mut UnboundedReceiver<Event>,
139 channel: &mut UnboundedReceiver<Event>,
140+ redraw_notify: Arc<Notify>,
135 diff_finished_notify: Arc<Notify>,
141 diff_finished_notify: Arc<Notify>,
136 ) {
142 ) {
137 let async_debounce = Duration::from_millis(DIFF_DEBOUNCE_TIME_ASYNC);
143 let async_debounce = Duration::from_millis(DIFF_DEBOUNCE_TIME_ASYNC);
@@ -158,7 +164,7 @@ impl<'a> EventAccumulator {
158 None => {
164 None => {
159 tokio::spawn(async move {
165 tokio::spawn(async move {
160 diff_finished_notify.notified().await;
166 diff_finished_notify.notified().await;
161− helix_event::request_redraw();
167+ redraw_notify.notify_one();
162 });
168 });
163 }
169 }
164 // diff is performed inside the rendering loop
170 // diff is performed inside the rendering loop
@@ -184,7 +190,7 @@ impl<'a> EventAccumulator {
184 // and wait until the diff occurs to trigger an async redraw
190 // and wait until the diff occurs to trigger an async redraw
185 log::info!("Diff computation timed out, update of diffs might appear delayed");
191 log::info!("Diff computation timed out, update of diffs might appear delayed");
186 diff_finished_notify.notified().await;
192 diff_finished_notify.notified().await;
187− helix_event::request_redraw()
193+ redraw_notify.notify_one();
188 });
194 });
189 }
195 }
190 // a blocking diff is performed inside the rendering loop
196 // a blocking diff is performed inside the rendering loop
helix-vcs/src/diff/worker/test.rs+5−1
@@ -5,7 +5,11 @@ use crate::diff::{DiffHandle, Hunk};
5
5
6impl DiffHandle {
6impl DiffHandle {
7 fn new_test(diff_base: &str, doc: &str) -> (DiffHandle, JoinHandle<()>) {
7 fn new_test(diff_base: &str, doc: &str) -> (DiffHandle, JoinHandle<()>) {
8− DiffHandle::new_with_handle(Rope::from_str(diff_base), Rope::from_str(doc))
8+ DiffHandle::new_with_handle(
9+ Rope::from_str(diff_base),
10+ Rope::from_str(doc),
11+ Default::default(),
12+ )
9 }
13 }
10 async fn into_diff(self, handle: JoinHandle<()>) -> Vec<Hunk> {
14 async fn into_diff(self, handle: JoinHandle<()>) -> Vec<Hunk> {
11 let diff = self.diff;
15 let diff = self.diff;
helix-vcs/src/git.rs+5−5
@@ -3,7 +3,7 @@ use arc_swap::ArcSwap;
3use std::path::Path;
3use std::path::Path;
4use std::sync::Arc;
4use std::sync::Arc;
5
5
6−use gix::objs::tree::EntryKind;
6+use gix::objs::tree::EntryMode;
7use gix::sec::trust::DefaultForLevel;
7use gix::sec::trust::DefaultForLevel;
8use gix::{Commit, ObjectId, Repository, ThreadSafeRepository};
8use gix::{Commit, ObjectId, Repository, ThreadSafeRepository};
9
9
@@ -126,14 +126,14 @@ fn find_file_in_commit(repo: &Repository, commit: &Commit, file: &Path) -> Resul
126 let rel_path = file.strip_prefix(repo_dir)?;
126 let rel_path = file.strip_prefix(repo_dir)?;
127 let tree = commit.tree()?;
127 let tree = commit.tree()?;
128 let tree_entry = tree
128 let tree_entry = tree
129− .lookup_entry_by_path(rel_path, &mut Vec::new())?
129+ .lookup_entry_by_path(rel_path)?
130 .context("file is untracked")?;
130 .context("file is untracked")?;
131− match tree_entry.mode().kind() {
131+ match tree_entry.mode() {
132 // not a file, everything is new, do not show diff
132 // not a file, everything is new, do not show diff
133− mode @ (EntryKind::Tree | EntryKind::Commit | EntryKind::Link) => {
133+ mode @ (EntryMode::Tree | EntryMode::Commit | EntryMode::Link) => {
134 bail!("entry at {} is not a file but a {mode:?}", file.display())
134 bail!("entry at {} is not a file but a {mode:?}", file.display())
135 }
135 }
136 // found a file
136 // found a file
137− EntryKind::Blob | EntryKind::BlobExecutable => Ok(tree_entry.object_id()),
137+ EntryMode::Blob | EntryMode::BlobExecutable => Ok(tree_entry.object_id()),
138 }
138 }
139}
139}
helix-view/Cargo.toml+18−22
@@ -1,34 +1,31 @@
1[package]
1[package]
2name = "helix-view"
2name = "helix-view"
3+version = "0.6.0"
4+authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5+edition = "2021"
6+license = "MPL-2.0"
3description = "UI abstractions for use in backends"
7description = "UI abstractions for use in backends"
4−version.workspace = true
5−authors.workspace = true
6−edition.workspace = true
7−license.workspace = true
8−rust-version.workspace = true
9−categories.workspace = true
8+categories = ["editor"]
10−repository.workspace = true
9+repository = "https://github.com/helix-editor/helix"
11−homepage.workspace = true
10+homepage = "https://helix-editor.com"
12
11
13[features]
12[features]
14default = []
13default = []
15term = ["crossterm"]
14term = ["crossterm"]
16
15
17[dependencies]
16[dependencies]
18−helix-core = { path = "../helix-core" }
19−helix-event = { path = "../helix-event" }
20−helix-loader = { path = "../helix-loader" }
21−helix-lsp = { path = "../helix-lsp" }
22−helix-dap = { path = "../helix-dap" }
23−helix-vcs = { path = "../helix-vcs" }
24−
25−bitflags = "2.4"
17+bitflags = "2.3"
26anyhow = "1"
18anyhow = "1"
19+helix-core = { version = "0.6", path = "../helix-core" }
20+helix-loader = { version = "0.6", path = "../helix-loader" }
21+helix-lsp = { version = "0.6", path = "../helix-lsp" }
22+helix-dap = { version = "0.6", path = "../helix-dap" }
27−crossterm = { version = "0.27", optional = true }
23+crossterm = { version = "0.26", optional = true }
24+helix-vcs = { version = "0.6", path = "../helix-vcs" }
28
25
29# Conversion traits
26# Conversion traits
30−once_cell = "1.19"
27+once_cell = "1.18"
31−url = "2.5.0"
28+url = "2"
32
29
33arc-swap = { version = "1.6.0" }
30arc-swap = { version = "1.6.0" }
34
31
@@ -45,16 +42,15 @@ serde_json = "1.0"
45toml = "0.7"
42toml = "0.7"
46log = "~0.4"
43log = "~0.4"
47
44
48−which = "5.0.0"
45+which = "4.4"
49parking_lot = "0.12.1"
46parking_lot = "0.12.1"
50
47
51
48
52[target.'cfg(windows)'.dependencies]
49[target.'cfg(windows)'.dependencies]
53−clipboard-win = { version = "5.0", features = ["std"] }
50+clipboard-win = { version = "4.5", features = ["std"] }
54
51
55[target.'cfg(unix)'.dependencies]
52[target.'cfg(unix)'.dependencies]
56libc = "0.2"
53libc = "0.2"
57−rustix = { version = "0.38", features = ["fs"] }
58
54
59[dev-dependencies]
55[dev-dependencies]
60helix-tui = { path = "../helix-tui" }
56helix-tui = { path = "../helix-tui" }
helix-view/src/clipboard.rs+2−7
@@ -73,14 +73,9 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
73
73
74#[cfg(target_os = "macos")]
74#[cfg(target_os = "macos")]
75pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
75pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
76− use crate::env::{binary_exists, env_var_is_set};
76+ use crate::env::binary_exists;
77
77
78− if env_var_is_set("TMUX") && binary_exists("tmux") {
79− command_provider! {
80− paste => "tmux", "save-buffer", "-";
81− copy => "tmux", "load-buffer", "-w", "-";
82− }
83− } else if binary_exists("pbcopy") && binary_exists("pbpaste") {
78+ if binary_exists("pbcopy") && binary_exists("pbpaste") {
84 command_provider! {
79 command_provider! {
85 paste => "pbpaste";
80 paste => "pbpaste";
86 copy => "pbcopy";
81 copy => "pbcopy";
helix-view/src/document.rs+39−111
@@ -33,7 +33,7 @@ use helix_core::{
33 ChangeSet, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction,
33 ChangeSet, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction,
34};
34};
35
35
36−use crate::editor::Config;
36+use crate::editor::{Config, RedrawHandle};
37use crate::{DocumentId, Editor, Theme, View, ViewId};
37use crate::{DocumentId, Editor, Theme, View, ViewId};
38
38
39/// 8kB of buffer space for encoding and decoding `Rope`s.
39/// 8kB of buffer space for encoding and decoding `Rope`s.
@@ -185,8 +185,6 @@ pub struct Document {
185
185
186 // when document was used for most-recent-used buffer picker
186 // when document was used for most-recent-used buffer picker
187 pub focused_at: std::time::Instant,
187 pub focused_at: std::time::Instant,
188−
189− pub readonly: bool,
190}
188}
191
189
192/// Inlay hints for a single `(Document, View)` combo.
190/// Inlay hints for a single `(Document, View)` combo.
@@ -675,7 +673,6 @@ impl Document {
675 config,
673 config,
676 version_control_head: None,
674 version_control_head: None,
677 focused_at: std::time::Instant::now(),
675 focused_at: std::time::Instant::now(),
678− readonly: false,
679 }
676 }
680 }
677 }
681
678
@@ -708,7 +705,7 @@ impl Document {
708 let mut doc = Self::from(rope, Some((encoding, has_bom)), config);
705 let mut doc = Self::from(rope, Some((encoding, has_bom)), config);
709
706
710 // set the path and try detecting the language
707 // set the path and try detecting the language
711− doc.set_path(Some(path));
708+ doc.set_path(Some(path))?;
712 if let Some(loader) = config_loader {
709 if let Some(loader) = config_loader {
713 doc.detect_language(loader);
710 doc.detect_language(loader);
714 }
711 }
@@ -733,16 +730,16 @@ impl Document {
733 // We can't use anyhow::Result here since the output of the future has to be
730 // We can't use anyhow::Result here since the output of the future has to be
734 // clonable to be used as shared future. So use a custom error type.
731 // clonable to be used as shared future. So use a custom error type.
735 pub fn format(&self) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
732 pub fn format(&self) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
736− if let Some((fmt_cmd, fmt_args)) = self
733+ if let Some(formatter) = self
737 .language_config()
734 .language_config()
738− .and_then(|c| c.formatter.as_ref())
735+ .and_then(|c| c.formatter.clone())
739− .and_then(|formatter| Some((which::which(&formatter.command).ok()?, &formatter.args)))
736+ .filter(|formatter| which::which(&formatter.command).is_ok())
740 {
737 {
741 use std::process::Stdio;
738 use std::process::Stdio;
742 let text = self.text().clone();
739 let text = self.text().clone();
743− let mut process = tokio::process::Command::new(&fmt_cmd);
740+ let mut process = tokio::process::Command::new(&formatter.command);
744 process
741 process
745− .args(fmt_args)
742+ .args(&formatter.args)
746 .stdin(Stdio::piped())
743 .stdin(Stdio::piped())
747 .stdout(Stdio::piped())
744 .stdout(Stdio::piped())
748 .stderr(Stdio::piped());
745 .stderr(Stdio::piped());
@@ -751,7 +748,7 @@ impl Document {
751 let mut process = process
748 let mut process = process
752 .spawn()
749 .spawn()
753 .map_err(|e| FormatterError::SpawningFailed {
750 .map_err(|e| FormatterError::SpawningFailed {
754− command: fmt_cmd.to_string_lossy().into(),
751+ command: formatter.command.clone(),
755 error: e.kind(),
752 error: e.kind(),
756 })?;
753 })?;
757 {
754 {
@@ -853,7 +850,7 @@ impl Document {
853 let text = self.text().clone();
850 let text = self.text().clone();
854
851
855 let path = match path {
852 let path = match path {
856− Some(path) => helix_core::path::get_canonicalized_path(&path),
853+ Some(path) => helix_core::path::get_canonicalized_path(&path)?,
857 None => {
854 None => {
858 if self.path.is_none() {
855 if self.path.is_none() {
859 bail!("Can't save with no path set!");
856 bail!("Can't save with no path set!");
@@ -958,43 +955,12 @@ impl Document {
958 }
955 }
959 }
956 }
960
957
961− #[cfg(unix)]
962− // Detect if the file is readonly and change the readonly field if necessary (unix only)
963− pub fn detect_readonly(&mut self) {
964− use rustix::fs::{access, Access};
965− // Allows setting the flag for files the user cannot modify, like root files
966− self.readonly = match &self.path {
967− None => false,
968− Some(p) => match access(p, Access::WRITE_OK) {
969− Ok(_) => false,
970− Err(err) if err.kind() == std::io::ErrorKind::NotFound => false,
971− Err(_) => true,
972− },
973− };
974− }
975−
976− #[cfg(not(unix))]
977− // Detect if the file is readonly and change the readonly field if necessary (non-unix os)
978− pub fn detect_readonly(&mut self) {
979− // TODO Use the Windows' function `CreateFileW` to check if a file is readonly
980− // Discussion: https://github.com/helix-editor/helix/pull/7740#issuecomment-1656806459
981− // Vim implementation: https://github.com/vim/vim/blob/4c0089d696b8d1d5dc40568f25ea5738fa5bbffb/src/os_win32.c#L7665
982− // Windows binding: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/Storage/FileSystem/fn.CreateFileW.html
983− self.readonly = match &self.path {
984− None => false,
985− Some(p) => match std::fs::metadata(p) {
986− Err(err) if err.kind() == std::io::ErrorKind::NotFound => false,
987− Err(_) => false,
988− Ok(metadata) => metadata.permissions().readonly(),
989− },
990− };
991− }
992−
993 /// Reload the document from its path.
958 /// Reload the document from its path.
994 pub fn reload(
959 pub fn reload(
995 &mut self,
960 &mut self,
996 view: &mut View,
961 view: &mut View,
997 provider_registry: &DiffProviderRegistry,
962 provider_registry: &DiffProviderRegistry,
963+ redraw_handle: RedrawHandle,
998 ) -> Result<(), Error> {
964 ) -> Result<(), Error> {
999 let encoding = self.encoding;
965 let encoding = self.encoding;
1000 let path = self
966 let path = self
@@ -1003,9 +969,6 @@ impl Document {
1003 .ok_or_else(|| anyhow!("can't find file to reload from {:?}", self.display_name()))?
969 .ok_or_else(|| anyhow!("can't find file to reload from {:?}", self.display_name()))?
1004 .to_owned();
970 .to_owned();
1005
971
1006− // Once we have a valid path we check if its readonly status has changed
1007− self.detect_readonly();
1008−
1009 let mut file = std::fs::File::open(&path)?;
972 let mut file = std::fs::File::open(&path)?;
1010 let (rope, ..) = from_reader(&mut file, Some(encoding))?;
973 let (rope, ..) = from_reader(&mut file, Some(encoding))?;
1011
974
@@ -1022,7 +985,7 @@ impl Document {
1022 self.detect_indent_and_line_ending();
985 self.detect_indent_and_line_ending();
1023
986
1024 match provider_registry.get_diff_base(&path) {
987 match provider_registry.get_diff_base(&path) {
1025− Some(diff_base) => self.set_diff_base(diff_base),
988+ Some(diff_base) => self.set_diff_base(diff_base, redraw_handle),
1026 None => self.diff_handle = None,
989 None => self.diff_handle = None,
1027 }
990 }
1028
991
@@ -1046,14 +1009,16 @@ impl Document {
1046 self.encoding
1009 self.encoding
1047 }
1010 }
1048
1011
1049− pub fn set_path(&mut self, path: Option<&Path>) {
1012+ pub fn set_path(&mut self, path: Option<&Path>) -> Result<(), std::io::Error> {
1013+ let path = path
1050− let path = path.map(helix_core::path::get_canonicalized_path);
1014+ .map(helix_core::path::get_canonicalized_path)
1015+ .transpose()?;
1051
1016
1052 // if parent doesn't exist we still want to open the document
1017 // if parent doesn't exist we still want to open the document
1053 // and error out when document is saved
1018 // and error out when document is saved
1054 self.path = path;
1019 self.path = path;
1055
1020
1056− self.detect_readonly();
1021+ Ok(())
1057 }
1022 }
1058
1023
1059 /// Set the programming language for the file and load associated data (e.g. highlighting)
1024 /// Set the programming language for the file and load associated data (e.g. highlighting)
@@ -1206,47 +1171,30 @@ impl Document {
1206 transaction.changes(),
1171 transaction.changes(),
1207 );
1172 );
1208 if res.is_err() {
1173 if res.is_err() {
1209− log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
1174+ log::error!("TS parser failed, disabeling TS for the current buffer: {res:?}");
1210 self.syntax = None;
1175 self.syntax = None;
1211 }
1176 }
1212 }
1177 }
1213
1178
1214 let changes = transaction.changes();
1179 let changes = transaction.changes();
1215
1180
1181+ changes.update_positions(
1182+ self.diagnostics
1183+ .iter_mut()
1184+ .map(|diagnostic| (&mut diagnostic.range.start, Assoc::After)),
1185+ );
1186+ changes.update_positions(
1187+ self.diagnostics
1188+ .iter_mut()
1189+ .map(|diagnostic| (&mut diagnostic.range.end, Assoc::After)),
1190+ );
1216− // map diagnostics over changes too
1191+ // map state.diagnostics over changes::map_pos too
1217− changes.update_positions(self.diagnostics.iter_mut().map(|diagnostic| {
1218− let assoc = if diagnostic.starts_at_word {
1219− Assoc::BeforeWord
1220− } else {
1221− Assoc::After
1222− };
1223− (&mut diagnostic.range.start, assoc)
1224− }));
1225− changes.update_positions(self.diagnostics.iter_mut().map(|diagnostic| {
1226− let assoc = if diagnostic.ends_at_word {
1227− Assoc::AfterWord
1228− } else {
1229− Assoc::Before
1230− };
1231− (&mut diagnostic.range.end, assoc)
1232− }));
1233− self.diagnostics.retain_mut(|diagnostic| {
1192+ for diagnostic in &mut self.diagnostics {
1234− if diagnostic.range.start > diagnostic.range.end
1235− || (!diagnostic.zero_width && diagnostic.range.start == diagnostic.range.end)
1236− {
1237− return false;
1238− }
1239 diagnostic.line = self.text.char_to_line(diagnostic.range.start);
1193 diagnostic.line = self.text.char_to_line(diagnostic.range.start);
1240− true
1241− });
1194+ }
1242
1195
1196+ self.diagnostics
1243− self.diagnostics.sort_unstable_by_key(|diagnostic| {
1197+ .sort_unstable_by_key(|diagnostic| diagnostic.range);
1244− (
1245− diagnostic.range,
1246− diagnostic.severity,
1247− diagnostic.language_server_id,
1248− )
1249− });
1250
1198
1251 // Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place
1199 // Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place
1252 let apply_inlay_hint_changes = |annotations: &mut Rc<[InlineAnnotation]>| {
1200 let apply_inlay_hint_changes = |annotations: &mut Rc<[InlineAnnotation]>| {
@@ -1599,13 +1547,13 @@ impl Document {
1599 }
1547 }
1600
1548
1601 /// Intialize/updates the differ for this document with a new base.
1549 /// Intialize/updates the differ for this document with a new base.
1602− pub fn set_diff_base(&mut self, diff_base: Vec<u8>) {
1550+ pub fn set_diff_base(&mut self, diff_base: Vec<u8>, redraw_handle: RedrawHandle) {
1603 if let Ok((diff_base, ..)) = from_reader(&mut diff_base.as_slice(), Some(self.encoding)) {
1551 if let Ok((diff_base, ..)) = from_reader(&mut diff_base.as_slice(), Some(self.encoding)) {
1604 if let Some(differ) = &self.diff_handle {
1552 if let Some(differ) = &self.diff_handle {
1605 differ.update_diff_base(diff_base);
1553 differ.update_diff_base(diff_base);
1606 return;
1554 return;
1607 }
1555 }
1608− self.diff_handle = Some(DiffHandle::new(diff_base, self.text.clone()))
1556+ self.diff_handle = Some(DiffHandle::new(diff_base, self.text.clone(), redraw_handle))
1609 } else {
1557 } else {
1610 self.diff_handle = None;
1558 self.diff_handle = None;
1611 }
1559 }
@@ -1723,33 +1671,13 @@ impl Document {
1723
1671
1724 pub fn replace_diagnostics(
1672 pub fn replace_diagnostics(
1725 &mut self,
1673 &mut self,
1726− diagnostics: impl IntoIterator<Item = Diagnostic>,
1674+ mut diagnostics: Vec<Diagnostic>,
1727− unchanged_sources: &[String],
1728 language_server_id: usize,
1675 language_server_id: usize,
1729 ) {
1676 ) {
1730− if unchanged_sources.is_empty() {
1731− self.clear_diagnostics(language_server_id);
1677+ self.clear_diagnostics(language_server_id);
1732− } else {
1733− self.diagnostics.retain(|d| {
1678+ self.diagnostics.append(&mut diagnostics);
1734− if d.language_server_id != language_server_id {
1735− return true;
1736− }
1737−
1738− if let Some(source) = &d.source {
1739− unchanged_sources.contains(source)
1740− } else {
1741− false
1742− }
1743− });
1744− }
1745− self.diagnostics.extend(diagnostics);
1679+ self.diagnostics
1746− self.diagnostics.sort_unstable_by_key(|diagnostic| {
1680+ .sort_unstable_by_key(|diagnostic| diagnostic.range);
1747− (
1748− diagnostic.range,
1749− diagnostic.severity,
1750− diagnostic.language_server_id,
1751− )
1752− });
1753 }
1681 }
1754
1682
1755 pub fn clear_diagnostics(&mut self, language_server_id: usize) {
1683 pub fn clear_diagnostics(&mut self, language_server_id: usize) {
helix-view/src/editor.rs+76−134
@@ -1,10 +1,10 @@
1use crate::{
1use crate::{
2 align_view,
2 align_view,
3+ clipboard::{get_clipboard_provider, ClipboardProvider},
3 document::{DocumentSavedEventFuture, DocumentSavedEventResult, Mode, SavePoint},
4 document::{DocumentSavedEventFuture, DocumentSavedEventResult, Mode, SavePoint},
4 graphics::{CursorKind, Rect},
5 graphics::{CursorKind, Rect},
5 info::Info,
6 info::Info,
6 input::KeyEvent,
7 input::KeyEvent,
7− register::Registers,
8 theme::{self, Theme},
8 theme::{self, Theme},
9 tree::{self, Tree},
9 tree::{self, Tree},
10 view::ViewPosition,
10 view::ViewPosition,
@@ -32,7 +32,7 @@ use std::{
32use tokio::{
32use tokio::{
33 sync::{
33 sync::{
34 mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
34 mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
35− oneshot,
35+ oneshot, Notify, RwLock,
36 },
36 },
37 time::{sleep, Duration, Instant, Sleep},
37 time::{sleep, Duration, Instant, Sleep},
38};
38};
@@ -40,11 +40,13 @@ use tokio::{
40use anyhow::{anyhow, bail, Error};
40use anyhow::{anyhow, bail, Error};
41
41
42pub use helix_core::diagnostic::Severity;
42pub use helix_core::diagnostic::Severity;
43+pub use helix_core::register::Registers;
43use helix_core::{
44use helix_core::{
44 auto_pairs::AutoPairs,
45 auto_pairs::AutoPairs,
45− syntax::{self, AutoPairConfig, IndentationHeuristic, SoftWrap},
46+ syntax::{self, AutoPairConfig, SoftWrap},
46− Change, LineEnding, Position, Selection, NATIVE_LINE_ENDING,
47+ Change, LineEnding, NATIVE_LINE_ENDING,
47};
48};
49+use helix_core::{Position, Selection};
48use helix_dap as dap;
50use helix_dap as dap;
49use helix_lsp::lsp;
51use helix_lsp::lsp;
50
52
@@ -243,7 +245,7 @@ pub struct Config {
243 /// Set a global text_width
245 /// Set a global text_width
244 pub text_width: usize,
246 pub text_width: usize,
245 /// Time in milliseconds since last keypress before idle timers trigger.
247 /// Time in milliseconds since last keypress before idle timers trigger.
246− /// Used for autocompletion, set to 0 for instant. Defaults to 250ms.
248+ /// Used for autocompletion, set to 0 for instant. Defaults to 400ms.
247 #[serde(
249 #[serde(
248 serialize_with = "serialize_duration_millis",
250 serialize_with = "serialize_duration_millis",
249 deserialize_with = "deserialize_duration_millis"
251 deserialize_with = "deserialize_duration_millis"
@@ -286,31 +288,6 @@ pub struct Config {
286 pub workspace_lsp_roots: Vec<PathBuf>,
288 pub workspace_lsp_roots: Vec<PathBuf>,
287 /// Which line ending to choose for new documents. Defaults to `native`. i.e. `crlf` on Windows, otherwise `lf`.
289 /// Which line ending to choose for new documents. Defaults to `native`. i.e. `crlf` on Windows, otherwise `lf`.
288 pub default_line_ending: LineEndingConfig,
290 pub default_line_ending: LineEndingConfig,
289− /// Whether to automatically insert a trailing line-ending on write if missing. Defaults to `true`.
290− pub insert_final_newline: bool,
291− /// Enables smart tab
292− pub smart_tab: Option<SmartTabConfig>,
293− /// Draw border around popups.
294− pub popup_border: PopupBorderConfig,
295− /// Which indent heuristic to use when a new line is inserted
296− #[serde(default)]
297− pub indent_heuristic: IndentationHeuristic,
298−}
299−
300−#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)]
301−#[serde(rename_all = "kebab-case", default)]
302−pub struct SmartTabConfig {
303− pub enable: bool,
304− pub supersede_menu: bool,
305−}
306−
307−impl Default for SmartTabConfig {
308− fn default() -> Self {
309− SmartTabConfig {
310− enable: true,
311− supersede_menu: false,
312− }
313− }
314}
291}
315
292
316#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
293#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -427,7 +404,6 @@ impl Default for StatusLineConfig {
427 E::Mode,
404 E::Mode,
428 E::Spinner,
405 E::Spinner,
429 E::FileName,
406 E::FileName,
430− E::ReadOnlyIndicator,
431 E::FileModificationIndicator,
407 E::FileModificationIndicator,
432 ],
408 ],
433 center: vec![],
409 center: vec![],
@@ -471,18 +447,15 @@ pub enum StatusLineElement {
471 /// The LSP activity spinner
447 /// The LSP activity spinner
472 Spinner,
448 Spinner,
473
449
474− /// The file basename (the leaf of the open file's path)
450+ /// The base file name, including a dirty flag if it's unsaved
475 FileBaseName,
451 FileBaseName,
476
452
477− /// The relative file path
453+ /// The relative file path, including a dirty flag if it's unsaved
478 FileName,
454 FileName,
479
455
480 // The file modification indicator
456 // The file modification indicator
481 FileModificationIndicator,
457 FileModificationIndicator,
482
458
483− /// An indicator that shows `"[readonly]"` when a file cannot be written
484− ReadOnlyIndicator,
485−
486 /// The file encoding
459 /// The file encoding
487 FileEncoding,
460 FileEncoding,
488
461
@@ -731,7 +704,7 @@ pub struct WhitespaceCharacters {
731impl Default for WhitespaceCharacters {
704impl Default for WhitespaceCharacters {
732 fn default() -> Self {
705 fn default() -> Self {
733 Self {
706 Self {
734− space: '·', // U+00B7
707+ space: '·', // U+00B7
735 nbsp: '⍽', // U+237D
708 nbsp: '⍽', // U+237D
736 tab: '→', // U+2192
709 tab: '→', // U+2192
737 newline: '⏎', // U+23CE
710 newline: '⏎', // U+23CE
@@ -759,13 +732,12 @@ impl Default for IndentGuidesConfig {
759}
732}
760
733
761/// Line ending configuration.
734/// Line ending configuration.
762−#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
735+#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
763#[serde(rename_all = "lowercase")]
736#[serde(rename_all = "lowercase")]
764pub enum LineEndingConfig {
737pub enum LineEndingConfig {
765 /// The platform's native line ending.
738 /// The platform's native line ending.
766 ///
739 ///
767 /// `crlf` on Windows, otherwise `lf`.
740 /// `crlf` on Windows, otherwise `lf`.
768− #[default]
769 Native,
741 Native,
770 /// Line feed.
742 /// Line feed.
771 LF,
743 LF,
@@ -782,6 +754,12 @@ pub enum LineEndingConfig {
782 Nel,
754 Nel,
783}
755}
784
756
757+impl Default for LineEndingConfig {
758+ fn default() -> Self {
759+ LineEndingConfig::Native
760+ }
761+}
762+
785impl From<LineEndingConfig> for LineEnding {
763impl From<LineEndingConfig> for LineEnding {
786 fn from(line_ending: LineEndingConfig) -> Self {
764 fn from(line_ending: LineEndingConfig) -> Self {
787 match line_ending {
765 match line_ending {
@@ -798,15 +776,6 @@ impl From<LineEndingConfig> for LineEnding {
798 }
776 }
799}
777}
800
778
801−#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
802−#[serde(rename_all = "kebab-case")]
803−pub enum PopupBorderConfig {
804− None,
805− All,
806− Popup,
807− Menu,
808−}
809−
810impl Default for Config {
779impl Default for Config {
811 fn default() -> Self {
780 fn default() -> Self {
812 Self {
781 Self {
@@ -827,7 +796,7 @@ impl Default for Config {
827 auto_completion: true,
796 auto_completion: true,
828 auto_format: true,
797 auto_format: true,
829 auto_save: false,
798 auto_save: false,
830− idle_timeout: Duration::from_millis(250),
799+ idle_timeout: Duration::from_millis(400),
831 preview_completion_insert: true,
800 preview_completion_insert: true,
832 completion_trigger_len: 2,
801 completion_trigger_len: 2,
833 auto_info: true,
802 auto_info: true,
@@ -852,10 +821,6 @@ impl Default for Config {
852 completion_replace: false,
821 completion_replace: false,
853 workspace_lsp_roots: Vec::new(),
822 workspace_lsp_roots: Vec::new(),
854 default_line_ending: LineEndingConfig::default(),
823 default_line_ending: LineEndingConfig::default(),
855− insert_final_newline: true,
856− smart_tab: Some(SmartTabConfig::default()),
857− popup_border: PopupBorderConfig::None,
858− indent_heuristic: IndentationHeuristic::default(),
859 }
824 }
860 }
825 }
861}
826}
@@ -910,6 +875,8 @@ pub struct Editor {
910 pub debugger_events: SelectAll<UnboundedReceiverStream<dap::Payload>>,
875 pub debugger_events: SelectAll<UnboundedReceiverStream<dap::Payload>>,
911 pub breakpoints: HashMap<PathBuf, Vec<Breakpoint>>,
876 pub breakpoints: HashMap<PathBuf, Vec<Breakpoint>>,
912
877
878+ pub clipboard_provider: Box<dyn ClipboardProvider>,
879+
913 pub syn_loader: Arc<syntax::Loader>,
880 pub syn_loader: Arc<syntax::Loader>,
914 pub theme_loader: Arc<theme::Loader>,
881 pub theme_loader: Arc<theme::Loader>,
915 /// last_theme is used for theme previews. We store the current theme here,
882 /// last_theme is used for theme previews. We store the current theme here,
@@ -931,13 +898,16 @@ pub struct Editor {
931 pub auto_pairs: Option<AutoPairs>,
898 pub auto_pairs: Option<AutoPairs>,
932
899
933 pub idle_timer: Pin<Box<Sleep>>,
900 pub idle_timer: Pin<Box<Sleep>>,
934− redraw_timer: Pin<Box<Sleep>>,
935 last_motion: Option<Motion>,
901 last_motion: Option<Motion>,
936 pub last_completion: Option<CompleteAction>,
902 pub last_completion: Option<CompleteAction>,
937
903
938 pub exit_code: i32,
904 pub exit_code: i32,
939
905
940 pub config_events: (UnboundedSender<ConfigEvent>, UnboundedReceiver<ConfigEvent>),
906 pub config_events: (UnboundedSender<ConfigEvent>, UnboundedReceiver<ConfigEvent>),
907+ /// Allows asynchronous tasks to control the rendering
908+ /// The `Notify` allows asynchronous tasks to request the editor to perform a redraw
909+ /// The `RwLock` blocks the editor from performing the render until an exclusive lock can be acquired
910+ pub redraw_handle: RedrawHandle,
941 pub needs_redraw: bool,
911 pub needs_redraw: bool,
942 /// Cached position of the cursor calculated during rendering.
912 /// Cached position of the cursor calculated during rendering.
943 /// The content of `cursor_cache` is returned by `Editor::cursor` if
913 /// The content of `cursor_cache` is returned by `Editor::cursor` if
@@ -964,6 +934,8 @@ pub struct Editor {
964
934
965pub type Motion = Box<dyn Fn(&mut Editor)>;
935pub type Motion = Box<dyn Fn(&mut Editor)>;
966
936
937+pub type RedrawHandle = (Arc<Notify>, Arc<RwLock<()>>);
938+
967#[derive(Debug)]
939#[derive(Debug)]
968pub enum EditorEvent {
940pub enum EditorEvent {
969 DocumentSaved(DocumentSavedEventResult),
941 DocumentSaved(DocumentSavedEventResult),
@@ -971,7 +943,6 @@ pub enum EditorEvent {
971 LanguageServerMessage((usize, Call)),
943 LanguageServerMessage((usize, Call)),
972 DebuggerEvent(dap::Payload),
944 DebuggerEvent(dap::Payload),
973 IdleTimer,
945 IdleTimer,
974− Redraw,
975}
946}
976
947
977#[derive(Debug, Clone)]
948#[derive(Debug, Clone)]
@@ -1004,13 +975,6 @@ pub enum Action {
1004 VerticalSplit,
975 VerticalSplit,
1005}
976}
1006
977
1007−impl Action {
1008− /// Whether to align the view to the cursor after executing this action
1009− pub fn align_view(&self, view: &View, new_doc: DocumentId) -> bool {
1010− !matches!((self, view.doc == new_doc), (Action::Load, false))
1011− }
1012−}
1013−
1014/// Error thrown on failed document closed
978/// Error thrown on failed document closed
1015pub enum CloseError {
979pub enum CloseError {
1016 /// Document doesn't exist
980 /// Document doesn't exist
@@ -1059,32 +1023,23 @@ impl Editor {
1059 last_theme: None,
1023 last_theme: None,
1060 last_selection: None,
1024 last_selection: None,
1061 registers: Registers::default(),
1025 registers: Registers::default(),
1026+ clipboard_provider: get_clipboard_provider(),
1062 status_msg: None,
1027 status_msg: None,
1063 autoinfo: None,
1028 autoinfo: None,
1064 idle_timer: Box::pin(sleep(conf.idle_timeout)),
1029 idle_timer: Box::pin(sleep(conf.idle_timeout)),
1065− redraw_timer: Box::pin(sleep(Duration::MAX)),
1066 last_motion: None,
1030 last_motion: None,
1067 last_completion: None,
1031 last_completion: None,
1068 config,
1032 config,
1069 auto_pairs,
1033 auto_pairs,
1070 exit_code: 0,
1034 exit_code: 0,
1071 config_events: unbounded_channel(),
1035 config_events: unbounded_channel(),
1036+ redraw_handle: Default::default(),
1072 needs_redraw: false,
1037 needs_redraw: false,
1073 cursor_cache: Cell::new(None),
1038 cursor_cache: Cell::new(None),
1074 completion_request_handle: None,
1039 completion_request_handle: None,
1075 }
1040 }
1076 }
1041 }
1077
1042
1078− pub fn popup_border(&self) -> bool {
1079− self.config().popup_border == PopupBorderConfig::All
1080− || self.config().popup_border == PopupBorderConfig::Popup
1081− }
1082−
1083− pub fn menu_border(&self) -> bool {
1084− self.config().popup_border == PopupBorderConfig::All
1085− || self.config().popup_border == PopupBorderConfig::Menu
1086− }
1087−
1088 pub fn apply_motion<F: Fn(&mut Self) + 'static>(&mut self, motion: F) {
1043 pub fn apply_motion<F: Fn(&mut Self) + 'static>(&mut self, motion: F) {
1089 motion(self);
1044 motion(self);
1090 self.last_motion = Some(Box::new(motion));
1045 self.last_motion = Some(Box::new(motion));
@@ -1208,80 +1163,71 @@ impl Editor {
1208 }
1163 }
1209
1164
1210 /// Refreshes the language server for a given document
1165 /// Refreshes the language server for a given document
1211− pub fn refresh_language_servers(&mut self, doc_id: DocumentId) {
1166+ pub fn refresh_language_servers(&mut self, doc_id: DocumentId) -> Option<()> {
1212 self.launch_language_servers(doc_id)
1167 self.launch_language_servers(doc_id)
1213 }
1168 }
1214
1169
1215 /// Launch a language server for a given document
1170 /// Launch a language server for a given document
1216− fn launch_language_servers(&mut self, doc_id: DocumentId) {
1171+ fn launch_language_servers(&mut self, doc_id: DocumentId) -> Option<()> {
1217 if !self.config().lsp.enable {
1172 if !self.config().lsp.enable {
1218− return;
1173+ return None;
1219 }
1174 }
1220 // if doc doesn't have a URL it's a scratch buffer, ignore it
1175 // if doc doesn't have a URL it's a scratch buffer, ignore it
1221− let Some(doc) = self.documents.get_mut(&doc_id) else {
1176+ let doc = self.documents.get_mut(&doc_id)?;
1222− return;
1223− };
1224− let Some(doc_url) = doc.url() else {
1177+ let doc_url = doc.url()?;
1225− return;
1226− };
1227 let (lang, path) = (doc.language.clone(), doc.path().cloned());
1178 let (lang, path) = (doc.language.clone(), doc.path().cloned());
1228 let config = doc.config.load();
1179 let config = doc.config.load();
1229 let root_dirs = &config.workspace_lsp_roots;
1180 let root_dirs = &config.workspace_lsp_roots;
1230
1181
1231− // store only successfully started language servers
1182+ // try to find language servers based on the language name
1232− let language_servers = lang.as_ref().map_or_else(HashMap::default, |language| {
1183+ let language_servers = lang.as_ref().and_then(|language| {
1233 self.language_servers
1184 self.language_servers
1234 .get(language, path.as_ref(), root_dirs, config.lsp.snippets)
1185 .get(language, path.as_ref(), root_dirs, config.lsp.snippets)
1235− .filter_map(|(lang, client)| match client {
1186+ .map_err(|e| {
1236− Ok(client) => Some((lang, client)),
1237− Err(err) => {
1238− log::error!(
1187+ log::error!(
1239− "Failed to initialize the language servers for `{}` - `{}` {{ {} }}",
1188+ "Failed to initialize the language servers for `{}` {{ {} }}",
1240− language.scope(),
1189+ language.scope(),
1241− lang,
1190+ e
1242− err
1191+ )
1243− );
1244− None
1245− }
1246 })
1192 })
1247− .collect::<HashMap<_, _>>()
1193+ .ok()
1248 });
1194 });
1249
1195
1250− if language_servers.is_empty() {
1196+ if let Some(language_servers) = language_servers {
1251− return;
1197+ let language_id = doc.language_id().map(ToOwned::to_owned).unwrap_or_default();
1252− }
1198+
1199+ // only spawn new language servers if the servers aren't the same
1253
1200
1254− let language_id = doc.language_id().map(ToOwned::to_owned).unwrap_or_default();
1201+ let doc_language_servers_not_in_registry =
1202+ doc.language_servers.iter().filter(|(name, doc_ls)| {
1203+ language_servers
1204+ .get(*name)
1205+ .map_or(true, |ls| ls.id() != doc_ls.id())
1206+ });
1255
1207
1256− // only spawn new language servers if the servers aren't the same
1208+ for (_, language_server) in doc_language_servers_not_in_registry {
1257− let doc_language_servers_not_in_registry =
1209+ tokio::spawn(language_server.text_document_did_close(doc.identifier()));
1210+ }
1211+
1258− doc.language_servers.iter().filter(|(name, doc_ls)| {
1212+ let language_servers_not_in_doc = language_servers.iter().filter(|(name, ls)| {
1259− language_servers
1213+ doc.language_servers
1260 .get(*name)
1214 .get(*name)
1261− .map_or(true, |ls| ls.id() != doc_ls.id())
1215+ .map_or(true, |doc_ls| ls.id() != doc_ls.id())
1262 });
1216 });
1263
1217
1264− for (_, language_server) in doc_language_servers_not_in_registry {
1218+ for (_, language_server) in language_servers_not_in_doc {
1219+ // TODO: this now races with on_init code if the init happens too quickly
1265− tokio::spawn(language_server.text_document_did_close(doc.identifier()));
1220+ tokio::spawn(language_server.text_document_did_open(
1266− }
1221+ doc_url.clone(),
1267−
1222+ doc.version(),
1268− let language_servers_not_in_doc = language_servers.iter().filter(|(name, ls)| {
1223+ doc.text(),
1269− doc.language_servers
1224+ language_id.clone(),
1270− .get(*name)
1225+ ));
1271− .map_or(true, |doc_ls| ls.id() != doc_ls.id())
1272− });
1226+ }
1273
1227
1274− for (_, language_server) in language_servers_not_in_doc {
1228+ doc.language_servers = language_servers;
1275− // TODO: this now races with on_init code if the init happens too quickly
1276− tokio::spawn(language_server.text_document_did_open(
1277− doc_url.clone(),
1278− doc.version(),
1279− doc.text(),
1280− language_id.clone(),
1281− ));
1282 }
1229 }
1283−
1230+ Some(())
1284− doc.language_servers = language_servers;
1285 }
1231 }
1286
1232
1287 fn _refresh(&mut self) {
1233 fn _refresh(&mut self) {
@@ -1464,7 +1410,7 @@ impl Editor {
1464
1410
1465 // ??? possible use for integration tests
1411 // ??? possible use for integration tests
1466 pub fn open(&mut self, path: &Path, action: Action) -> Result<DocumentId, Error> {
1412 pub fn open(&mut self, path: &Path, action: Action) -> Result<DocumentId, Error> {
1467− let path = helix_core::path::get_canonicalized_path(path);
1413+ let path = helix_core::path::get_canonicalized_path(path)?;
1468 let id = self.document_by_path(&path).map(|doc| doc.id);
1414 let id = self.document_by_path(&path).map(|doc| doc.id);
1469
1415
1470 let id = if let Some(id) = id {
1416 let id = if let Some(id) = id {
@@ -1478,12 +1424,12 @@ impl Editor {
1478 )?;
1424 )?;
1479
1425
1480 if let Some(diff_base) = self.diff_providers.get_diff_base(&path) {
1426 if let Some(diff_base) = self.diff_providers.get_diff_base(&path) {
1481− doc.set_diff_base(diff_base);
1427+ doc.set_diff_base(diff_base, self.redraw_handle.clone());
1482 }
1428 }
1483 doc.set_version_control_head(self.diff_providers.get_current_head_name(&path));
1429 doc.set_version_control_head(self.diff_providers.get_current_head_name(&path));
1484
1430
1485 let id = self.new_document(doc);
1431 let id = self.new_document(doc);
1486− self.launch_language_servers(id);
1432+ let _ = self.launch_language_servers(id);
1487
1433
1488 id
1434 id
1489 };
1435 };
@@ -1777,20 +1723,16 @@ impl Editor {
1777 return EditorEvent::DebuggerEvent(event)
1723 return EditorEvent::DebuggerEvent(event)
1778 }
1724 }
1779
1725
1780− _ = helix_event::redraw_requested() => {
1726+ _ = self.redraw_handle.0.notified() => {
1781 if !self.needs_redraw{
1727 if !self.needs_redraw{
1782 self.needs_redraw = true;
1728 self.needs_redraw = true;
1783 let timeout = Instant::now() + Duration::from_millis(33);
1729 let timeout = Instant::now() + Duration::from_millis(33);
1784− if timeout < self.idle_timer.deadline() && timeout < self.redraw_timer.deadline(){
1730+ if timeout < self.idle_timer.deadline(){
1785− self.redraw_timer.as_mut().reset(timeout)
1731+ self.idle_timer.as_mut().reset(timeout)
1786 }
1732 }
1787 }
1733 }
1788 }
1734 }
1789
1735
1790− _ = &mut self.redraw_timer => {
1791− self.redraw_timer.as_mut().reset(Instant::now() + Duration::from_secs(86400 * 365 * 30));
1792− return EditorEvent::Redraw
1793− }
1794 _ = &mut self.idle_timer => {
1736 _ = &mut self.idle_timer => {
1795 return EditorEvent::IdleTimer
1737 return EditorEvent::IdleTimer
1796 }
1738 }
helix-view/src/gutter.rs+5−4
@@ -9,7 +9,8 @@ use crate::{
9};
9};
10
10
11fn count_digits(n: usize) -> usize {
11fn count_digits(n: usize) -> usize {
12− (usize::checked_ilog10(n).unwrap_or(0) + 1) as usize
12+ // TODO: use checked_log10 when MSRV reaches 1.67
13+ std::iter::successors(Some(n), |&n| (n >= 10).then_some(n / 10)).count()
13}
14}
14
15
15pub type GutterFn<'doc> = Box<dyn FnMut(usize, bool, bool, &mut String) -> Option<Style> + 'doc>;
16pub type GutterFn<'doc> = Box<dyn FnMut(usize, bool, bool, &mut String) -> Option<Style> + 'doc>;
@@ -93,9 +94,9 @@ pub fn diff<'doc>(
93 theme: &Theme,
94 theme: &Theme,
94 _is_focused: bool,
95 _is_focused: bool,
95) -> GutterFn<'doc> {
96) -> GutterFn<'doc> {
96− let added = theme.get("diff.plus.gutter");
97+ let added = theme.get("diff.plus");
97− let deleted = theme.get("diff.minus.gutter");
98+ let deleted = theme.get("diff.minus");
98− let modified = theme.get("diff.delta.gutter");
99+ let modified = theme.get("diff.delta");
99 if let Some(diff_handle) = doc.diff_handle() {
100 if let Some(diff_handle) = doc.diff_handle() {
100 let hunks = diff_handle.load();
101 let hunks = diff_handle.load();
101 let mut hunk_i = 0;
102 let mut hunk_i = 0;
helix-view/src/handlers/dap.rs+3−1
@@ -369,7 +369,9 @@ impl Editor {
369 {
369 {
370 Ok(process) => process,
370 Ok(process) => process,
371 Err(err) => {
371 Err(err) => {
372+ // TODO replace the pretty print {:?} with a regular format {}
373+ // when the MSRV is raised to 1.60.0
372− self.set_error(format!("Error starting external terminal: {}", err));
374+ self.set_error(format!("Error starting external terminal: {:?}", err));
373 return true;
375 return true;
374 }
376 }
375 };
377 };
helix-view/src/info.rs+11−4
@@ -1,5 +1,4 @@
1−use crate::register::Registers;
2−use helix_core::unicode::width::UnicodeWidthStr;
1+use helix_core::{register::Registers, unicode::width::UnicodeWidthStr};
3use std::fmt::Write;
2use std::fmt::Write;
4
3
5#[derive(Debug)]
4#[derive(Debug)]
@@ -57,8 +56,16 @@ impl Info {
57
56
58 pub fn from_registers(registers: &Registers) -> Self {
57 pub fn from_registers(registers: &Registers) -> Self {
59 let body: Vec<_> = registers
58 let body: Vec<_> = registers
60− .iter_preview()
59+ .inner()
60+ .iter()
61+ .map(|(ch, reg)| {
62+ let content = reg
63+ .read()
64+ .get(0)
65+ .and_then(|s| s.lines().next())
66+ .unwrap_or_default();
61− .map(|(ch, preview)| (ch.to_string(), preview))
67+ (ch.to_string(), content)
68+ })
62 .collect();
69 .collect();
63
70
64 let mut infobox = Self::new("Registers", &body);
71 let mut infobox = Self::new("Registers", &body);
helix-view/src/input.rs+1−65
@@ -43,10 +43,6 @@ pub enum MouseEventKind {
43 ScrollDown,
43 ScrollDown,
44 /// Scrolled mouse wheel upwards (away from the user).
44 /// Scrolled mouse wheel upwards (away from the user).
45 ScrollUp,
45 ScrollUp,
46− /// Scrolled mouse wheel leftwards.
47− ScrollLeft,
48− /// Scrolled mouse wheel rightwards.
49− ScrollRight,
50}
46}
51
47
52/// Represents a mouse button.
48/// Represents a mouse button.
@@ -462,8 +458,6 @@ impl From<crossterm::event::MouseEventKind> for MouseEventKind {
462 crossterm::event::MouseEventKind::Moved => Self::Moved,
458 crossterm::event::MouseEventKind::Moved => Self::Moved,
463 crossterm::event::MouseEventKind::ScrollDown => Self::ScrollDown,
459 crossterm::event::MouseEventKind::ScrollDown => Self::ScrollDown,
464 crossterm::event::MouseEventKind::ScrollUp => Self::ScrollUp,
460 crossterm::event::MouseEventKind::ScrollUp => Self::ScrollUp,
465− crossterm::event::MouseEventKind::ScrollLeft => Self::ScrollLeft,
466− crossterm::event::MouseEventKind::ScrollRight => Self::ScrollRight,
467 }
461 }
468 }
462 }
469}
463}
@@ -549,7 +543,7 @@ pub fn parse_macro(keys_str: &str) -> anyhow::Result<Vec<KeyEvent>> {
549 if c == ">" {
543 if c == ">" {
550 keys_res = Err(anyhow!("Unmatched '>'"));
544 keys_res = Err(anyhow!("Unmatched '>'"));
551 } else if c != "<" {
545 } else if c != "<" {
552− keys.push(if c == "-" { keys::MINUS } else { c });
546+ keys.push(c);
553 i += end_i;
547 i += end_i;
554 } else {
548 } else {
555 match s.find('>').context("'>' expected") {
549 match s.find('>').context("'>' expected") {
@@ -813,64 +807,6 @@ mod test {
813 },
807 },
814 ])
808 ])
815 );
809 );
816−
817− assert_eq!(
818− parse_macro(":w aa-bb.txt<ret>").ok(),
819− Some(vec![
820− KeyEvent {
821− code: KeyCode::Char(':'),
822− modifiers: KeyModifiers::NONE,
823− },
824− KeyEvent {
825− code: KeyCode::Char('w'),
826− modifiers: KeyModifiers::NONE,
827− },
828− KeyEvent {
829− code: KeyCode::Char(' '),
830− modifiers: KeyModifiers::NONE,
831− },
832− KeyEvent {
833− code: KeyCode::Char('a'),
834− modifiers: KeyModifiers::NONE,
835− },
836− KeyEvent {
837− code: KeyCode::Char('a'),
838− modifiers: KeyModifiers::NONE,
839− },
840− KeyEvent {
841− code: KeyCode::Char('-'),
842− modifiers: KeyModifiers::NONE,
843− },
844− KeyEvent {
845− code: KeyCode::Char('b'),
846− modifiers: KeyModifiers::NONE,
847− },
848− KeyEvent {
849− code: KeyCode::Char('b'),
850− modifiers: KeyModifiers::NONE,
851− },
852− KeyEvent {
853− code: KeyCode::Char('.'),
854− modifiers: KeyModifiers::NONE,
855− },
856− KeyEvent {
857− code: KeyCode::Char('t'),
858− modifiers: KeyModifiers::NONE,
859− },
860− KeyEvent {
861− code: KeyCode::Char('x'),
862− modifiers: KeyModifiers::NONE,
863− },
864− KeyEvent {
865− code: KeyCode::Char('t'),
866− modifiers: KeyModifiers::NONE,
867− },
868− KeyEvent {
869− code: KeyCode::Enter,
870− modifiers: KeyModifiers::NONE,
871− },
872− ])
873− );
874 }
810 }
875
811
876 #[test]
812 #[test]
helix-view/src/register.rs+9−206
@@ -1,13 +1,8 @@
1use std::{borrow::Cow, collections::HashMap, iter};
1use std::{borrow::Cow, collections::HashMap, iter};
2
2
3use anyhow::Result;
3use anyhow::Result;
4−use helix_core::NATIVE_LINE_ENDING;
5
4
6−use crate::{
5+use crate::Editor;
7− clipboard::{get_clipboard_provider, ClipboardProvider, ClipboardType},
8− document::SCRATCH_BUFFER_NAME,
9− Editor,
10−};
11
6
12/// A key-value store for saving sets of values.
7/// A key-value store for saving sets of values.
13///
8///
@@ -16,129 +11,35 @@ use crate::{
16/// behaviors when read or written to:
11/// behaviors when read or written to:
17///
12///
18/// * Black hole (`_`): all values read and written are discarded
13/// * Black hole (`_`): all values read and written are discarded
19−/// * Selection indices (`#`): index number of each selection starting at 1
20−/// * Selection contents (`.`)
21−/// * Document path (`%`): filename of the current buffer
22−/// * System clipboard (`*`)
23−/// * Primary clipboard (`+`)
24−#[derive(Debug)]
14+#[derive(Debug, Default)]
25pub struct Registers {
15pub struct Registers {
26− /// The mapping of register to values.
27− /// Values are stored in reverse order when inserted with `Registers::write`.
28− /// The order is reversed again in `Registers::read`. This allows us to
29− /// efficiently prepend new values in `Registers::push`.
30 inner: HashMap<char, Vec<String>>,
16 inner: HashMap<char, Vec<String>>,
31− clipboard_provider: Box<dyn ClipboardProvider>,
32− pub last_search_register: char,
33−}
34−
35−impl Default for Registers {
36− fn default() -> Self {
37− Self {
38− inner: Default::default(),
39− clipboard_provider: get_clipboard_provider(),
40− last_search_register: '/',
41− }
42− }
43}
17}
44
18
45impl Registers {
19impl Registers {
46− pub fn read<'a>(&'a self, name: char, editor: &'a Editor) -> Option<RegisterValues<'a>> {
20+ pub fn read<'a>(&'a self, name: char, _editor: &'a Editor) -> Option<RegisterValues<'a>> {
47 match name {
21 match name {
48 '_' => Some(RegisterValues::new(iter::empty())),
22 '_' => Some(RegisterValues::new(iter::empty())),
49− '#' => {
50− let (view, doc) = current_ref!(editor);
51− let selections = doc.selection(view.id).len();
52− // ExactSizeIterator is implemented for Range<usize> but
53− // not RangeInclusive<usize>.
54− Some(RegisterValues::new(
55− (0..selections).map(|i| (i + 1).to_string().into()),
56− ))
57− }
58− '.' => {
59− let (view, doc) = current_ref!(editor);
60− let text = doc.text().slice(..);
61− Some(RegisterValues::new(doc.selection(view.id).fragments(text)))
62− }
63− '%' => {
64− let doc = doc!(editor);
65−
66− let path = doc
67− .path()
68− .as_ref()
69− .map(|p| p.to_string_lossy())
70− .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into());
71−
72− Some(RegisterValues::new(iter::once(path)))
73− }
74− '*' | '+' => Some(read_from_clipboard(
75− self.clipboard_provider.as_ref(),
76− self.inner.get(&name),
77− match name {
78− '+' => ClipboardType::Clipboard,
79− '*' => ClipboardType::Selection,
80− _ => unreachable!(),
81− },
82− )),
83 _ => self
23 _ => self
84 .inner
24 .inner
85 .get(&name)
25 .get(&name)
86− .map(|values| RegisterValues::new(values.iter().map(Cow::from).rev())),
26+ .map(|values| RegisterValues::new(values.iter().map(Cow::from))),
87 }
27 }
88 }
28 }
89
29
90− pub fn write(&mut self, name: char, mut values: Vec<String>) -> Result<()> {
30+ pub fn write(&mut self, name: char, values: Vec<String>) -> Result<()> {
91 match name {
31 match name {
92 '_' => Ok(()),
32 '_' => Ok(()),
93− '#' | '.' | '%' => Err(anyhow::anyhow!("Register {name} does not support writing")),
94− '*' | '+' => {
95− self.clipboard_provider.set_contents(
96− values.join(NATIVE_LINE_ENDING.as_str()),
97− match name {
98− '+' => ClipboardType::Clipboard,
99− '*' => ClipboardType::Selection,
100− _ => unreachable!(),
101− },
102− )?;
103− values.reverse();
104− self.inner.insert(name, values);
105− Ok(())
106− }
107 _ => {
33 _ => {
108− values.reverse();
109 self.inner.insert(name, values);
34 self.inner.insert(name, values);
110 Ok(())
35 Ok(())
111 }
36 }
112 }
37 }
113 }
38 }
114
39
115− pub fn push(&mut self, name: char, mut value: String) -> Result<()> {
40+ pub fn push(&mut self, name: char, value: String) -> Result<()> {
116 match name {
41 match name {
117 '_' => Ok(()),
42 '_' => Ok(()),
118− '#' | '.' | '%' => Err(anyhow::anyhow!("Register {name} does not support pushing")),
119− '*' | '+' => {
120− let clipboard_type = match name {
121− '+' => ClipboardType::Clipboard,
122− '*' => ClipboardType::Selection,
123− _ => unreachable!(),
124− };
125− let contents = self.clipboard_provider.get_contents(clipboard_type)?;
126− let saved_values = self.inner.entry(name).or_insert_with(Vec::new);
127−
128− if !contents_are_saved(saved_values, &contents) {
129− anyhow::bail!("Failed to push to register {name}: clipboard does not match register contents");
130− }
131−
132− saved_values.push(value.clone());
133− if !contents.is_empty() {
134− value.push_str(NATIVE_LINE_ENDING.as_str());
135− }
136− value.push_str(&contents);
137− self.clipboard_provider
138− .set_contents(value, clipboard_type)?;
139−
140− Ok(())
141− }
142 _ => {
43 _ => {
143 self.inner.entry(name).or_insert_with(Vec::new).push(value);
44 self.inner.entry(name).or_insert_with(Vec::new).push(value);
144 Ok(())
45 Ok(())
@@ -157,125 +58,27 @@ impl Registers {
157 pub fn iter_preview(&self) -> impl Iterator<Item = (char, &str)> {
58 pub fn iter_preview(&self) -> impl Iterator<Item = (char, &str)> {
158 self.inner
59 self.inner
159 .iter()
60 .iter()
160− .filter(|(name, _)| !matches!(name, '*' | '+'))
161 .map(|(name, values)| {
61 .map(|(name, values)| {
162 let preview = values
62 let preview = values
163− .last()
63+ .first()
164 .and_then(|s| s.lines().next())
64 .and_then(|s| s.lines().next())
165 .unwrap_or("<empty>");
65 .unwrap_or("<empty>");
166
66
167 (*name, preview)
67 (*name, preview)
168 })
68 })
169− .chain(
170− [
171− ('_', "<empty>"),
69+ .chain([('_', "<empty>")].iter().copied())
172− ('#', "<selection indices>"),
173− ('.', "<selection contents>"),
174− ('%', "<document path>"),
175− ('+', "<system clipboard>"),
176− ('*', "<primary clipboard>"),
177− ]
178− .iter()
179− .copied(),
180− )
181 }
70 }
182
71
183 pub fn clear(&mut self) {
72 pub fn clear(&mut self) {
184− self.clear_clipboard(ClipboardType::Clipboard);
185− self.clear_clipboard(ClipboardType::Selection);
186 self.inner.clear()
73 self.inner.clear()
187 }
74 }
188
75
189 pub fn remove(&mut self, name: char) -> bool {
76 pub fn remove(&mut self, name: char) -> bool {
190 match name {
77 match name {
191− '*' | '+' => {
192− self.clear_clipboard(match name {
193− '+' => ClipboardType::Clipboard,
194− '*' => ClipboardType::Selection,
195− _ => unreachable!(),
196− });
197− self.inner.remove(&name);
198−
199− true
200− }
201− '_' | '#' | '.' | '%' => false,
78+ '_' => false,
202 _ => self.inner.remove(&name).is_some(),
79 _ => self.inner.remove(&name).is_some(),
203 }
80 }
204 }
81 }
205−
206− fn clear_clipboard(&mut self, clipboard_type: ClipboardType) {
207− if let Err(err) = self
208− .clipboard_provider
209− .set_contents("".into(), clipboard_type)
210− {
211− log::error!(
212− "Failed to clear {} clipboard: {err}",
213− match clipboard_type {
214− ClipboardType::Clipboard => "system",
215− ClipboardType::Selection => "primary",
216− }
217− )
218− }
219− }
220−
221− pub fn clipboard_provider_name(&self) -> Cow<str> {
222− self.clipboard_provider.name()
223− }
224−}
225−
226−fn read_from_clipboard<'a>(
227− provider: &dyn ClipboardProvider,
228− saved_values: Option<&'a Vec<String>>,
229− clipboard_type: ClipboardType,
230−) -> RegisterValues<'a> {
231− match provider.get_contents(clipboard_type) {
232− Ok(contents) => {
233− // If we're pasting the same values that we just yanked, re-use
234− // the saved values. This allows pasting multiple selections
235− // even when yanked to a clipboard.
236− let Some(values) = saved_values else { return RegisterValues::new(iter::once(contents.into())) };
237−
238− if contents_are_saved(values, &contents) {
239− RegisterValues::new(values.iter().map(Cow::from).rev())
240− } else {
241− RegisterValues::new(iter::once(contents.into()))
242− }
243− }
244− Err(err) => {
245− log::error!(
246− "Failed to read {} clipboard: {err}",
247− match clipboard_type {
248− ClipboardType::Clipboard => "system",
249− ClipboardType::Selection => "primary",
250− }
251− );
252−
253− RegisterValues::new(iter::empty())
254− }
255− }
256−}
257−
258−fn contents_are_saved(saved_values: &[String], mut contents: &str) -> bool {
259− let line_ending = NATIVE_LINE_ENDING.as_str();
260− let mut values = saved_values.iter().rev();
261−
262− match values.next() {
263− Some(first) if contents.starts_with(first) => {
264− contents = &contents[first.len()..];
265− }
266− None if contents.is_empty() => return true,
267− _ => return false,
268− }
269−
270− for value in values {
271− if contents.starts_with(line_ending) && contents[line_ending.len()..].starts_with(value) {
272− contents = &contents[line_ending.len() + value.len()..];
273− } else {
274− return false;
275− }
276− }
277−
278− true
279}
82}
280
83
281// This is a wrapper of an iterator that is both double ended and exact size,
84// This is a wrapper of an iterator that is both double ended and exact size,
helix-view/src/theme.rs−6
@@ -297,11 +297,6 @@ impl Theme {
297 self.highlights[index]
297 self.highlights[index]
298 }
298 }
299
299
300− #[inline]
301− pub fn scope(&self, index: usize) -> &str {
302− &self.scopes[index]
303− }
304−
305 pub fn name(&self) -> &str {
300 pub fn name(&self) -> &str {
306 &self.name
301 &self.name
307 }
302 }
@@ -364,7 +359,6 @@ impl Default for ThemePalette {
364 fn default() -> Self {
359 fn default() -> Self {
365 Self {
360 Self {
366 palette: hashmap! {
361 palette: hashmap! {
367− "default".to_string() => Color::Reset,
368 "black".to_string() => Color::Black,
362 "black".to_string() => Color::Black,
369 "red".to_string() => Color::Red,
363 "red".to_string() => Color::Red,
370 "green".to_string() => Color::Green,
364 "green".to_string() => Color::Green,
helix-view/src/view.rs+2−8
@@ -187,17 +187,11 @@ impl View {
187 }
187 }
188
188
189 pub fn gutter_offset(&self, doc: &Document) -> u16 {
189 pub fn gutter_offset(&self, doc: &Document) -> u16 {
190− let total_width = self
191− .gutters
190+ self.gutters
192 .layout
191 .layout
193 .iter()
192 .iter()
194 .map(|gutter| gutter.width(self, doc) as u16)
193 .map(|gutter| gutter.width(self, doc) as u16)
195− .sum();
194+ .sum()
196− if total_width < self.area.width {
197− total_width
198− } else {
199− 0
200− }
201 }
195 }
202
196
203 //
197 //
languages.toml+205−501
@@ -3,12 +3,10 @@
3
3
4[language-server]
4[language-server]
5
5
6−als = { command = "als" }
7awk-language-server = { command = "awk-language-server" }
6awk-language-server = { command = "awk-language-server" }
8bash-language-server = { command = "bash-language-server", args = ["start"] }
7bash-language-server = { command = "bash-language-server", args = ["start"] }
9bass = { command = "bass", args = ["--lsp"] }
8bass = { command = "bass", args = ["--lsp"] }
10bicep-langserver = { command = "bicep-langserver" }
9bicep-langserver = { command = "bicep-langserver" }
11−bufls = { command = "bufls", args = ["serve"] }
12cairo-language-server = { command = "cairo-language-server", args = [] }
10cairo-language-server = { command = "cairo-language-server", args = [] }
13cl-lsp = { command = "cl-lsp", args = [ "stdio" ] }
11cl-lsp = { command = "cl-lsp", args = [ "stdio" ] }
14clangd = { command = "clangd" }
12clangd = { command = "clangd" }
@@ -16,7 +14,6 @@ clojure-lsp = { command = "clojure-lsp" }
16cmake-language-server = { command = "cmake-language-server" }
14cmake-language-server = { command = "cmake-language-server" }
17crystalline = { command = "crystalline", args = ["--stdio"] }
15crystalline = { command = "crystalline", args = ["--stdio"] }
18cs = { command = "cs", args = ["launch", "--contrib", "smithy-language-server", "--", "0"] }
16cs = { command = "cs", args = ["launch", "--contrib", "smithy-language-server", "--", "0"] }
19−csharp-ls = { command = "csharp-ls" }
20cuelsp = { command = "cuelsp" }
17cuelsp = { command = "cuelsp" }
21dart = { command = "dart", args = ["language-server", "--client-id=helix"] }
18dart = { command = "dart", args = ["language-server", "--client-id=helix"] }
22dhall-lsp-server = { command = "dhall-lsp-server" }
19dhall-lsp-server = { command = "dhall-lsp-server" }
@@ -31,7 +28,6 @@ forth-lsp = { command = "forth-lsp" }
31fortls = { command = "fortls", args = ["--lowercase_intrinsics"] }
28fortls = { command = "fortls", args = ["--lowercase_intrinsics"] }
32fsharp-ls = { command = "fsautocomplete", config = { AutomaticWorkspaceInit = true } }
29fsharp-ls = { command = "fsautocomplete", config = { AutomaticWorkspaceInit = true } }
33gleam = { command = "gleam", args = ["lsp"] }
30gleam = { command = "gleam", args = ["lsp"] }
34−graphql-language-service = { command = "graphql-lsp", args = ["server", "-m", "stream"] }
35haskell-language-server = { command = "haskell-language-server-wrapper", args = ["--lsp"] }
31haskell-language-server = { command = "haskell-language-server-wrapper", args = ["--lsp"] }
36idris2-lsp = { command = "idris2-lsp" }
32idris2-lsp = { command = "idris2-lsp" }
37intelephense = { command = "intelephense", args = ["--stdio"] }
33intelephense = { command = "intelephense", args = ["--stdio"] }
@@ -40,7 +36,6 @@ jsonnet-language-server = { command = "jsonnet-language-server", args= ["-t", "-
40julia = { command = "julia", timeout = 60, args = [ "--startup-file=no", "--history-file=no", "--quiet", "-e", "using LanguageServer; runserver()", ] }
36julia = { command = "julia", timeout = 60, args = [ "--startup-file=no", "--history-file=no", "--quiet", "-e", "using LanguageServer; runserver()", ] }
41kotlin-language-server = { command = "kotlin-language-server" }
37kotlin-language-server = { command = "kotlin-language-server" }
42lean = { command = "lean", args = [ "--server" ] }
38lean = { command = "lean", args = [ "--server" ] }
43−ltex-ls = { command = "ltex-ls" }
44markdoc-ls = { command = "markdoc-ls", args = ["--stdio"] }
39markdoc-ls = { command = "markdoc-ls", args = ["--stdio"] }
45marksman = { command = "marksman", args = ["server"] }
40marksman = { command = "marksman", args = ["server"] }
46metals = { command = "metals", config = { "isHttpEnabled" = true } }
41metals = { command = "metals", config = { "isHttpEnabled" = true } }
@@ -48,19 +43,15 @@ mint = { command = "mint", args = ["ls"] }
48nil = { command = "nil" }
43nil = { command = "nil" }
49nimlangserver = { command = "nimlangserver" }
44nimlangserver = { command = "nimlangserver" }
50nls = { command = "nls" }
45nls = { command = "nls" }
51−nu-lsp = { command = "nu", args = [ "--lsp" ] }
52ocamllsp = { command = "ocamllsp" }
46ocamllsp = { command = "ocamllsp" }
53ols = { command = "ols", args = [] }
47ols = { command = "ols", args = [] }
54omnisharp = { command = "OmniSharp", args = [ "--languageserver" ] }
48omnisharp = { command = "OmniSharp", args = [ "--languageserver" ] }
55openscad-lsp = { command = "openscad-lsp", args = ["--stdio"] }
49openscad-lsp = { command = "openscad-lsp", args = ["--stdio"] }
56pasls = { command = "pasls", args = [] }
50pasls = { command = "pasls", args = [] }
57−pbkit = { command = "pb", args = [ "lsp" ] }
58perlnavigator = { command = "perlnavigator", args= ["--stdio"] }
51perlnavigator = { command = "perlnavigator", args= ["--stdio"] }
59prisma-language-server = { command = "prisma-language-server", args = ["--stdio"] }
52prisma-language-server = { command = "prisma-language-server", args = ["--stdio"] }
60purescript-language-server = { command = "purescript-language-server", args = ["--stdio"] }
53purescript-language-server = { command = "purescript-language-server", args = ["--stdio"] }
61pylsp = { command = "pylsp" }
54pylsp = { command = "pylsp" }
62−pyright = { command = "pyright-langserver", args = ["--stdio"], config = {} }
63−pylyzer = { command = "pylyzer", args = ["--server"] }
64qmlls = { command = "qmlls" }
55qmlls = { command = "qmlls" }
65r = { command = "R", args = ["--no-echo", "-e", "languageserver::run()"] }
56r = { command = "R", args = ["--no-echo", "-e", "languageserver::run()"] }
66racket = { command = "racket", args = ["-l", "racket-langserver"] }
57racket = { command = "racket", args = ["-l", "racket-langserver"] }
@@ -74,30 +65,24 @@ solc = { command = "solc", args = ["--lsp"] }
74sourcekit-lsp = { command = "sourcekit-lsp" }
65sourcekit-lsp = { command = "sourcekit-lsp" }
75svlangserver = { command = "svlangserver", args = [] }
66svlangserver = { command = "svlangserver", args = [] }
76swipl = { command = "swipl", args = [ "-g", "use_module(library(lsp_server))", "-g", "lsp_server:main", "-t", "halt", "--", "stdio" ] }
67swipl = { command = "swipl", args = [ "-g", "use_module(library(lsp_server))", "-g", "lsp_server:main", "-t", "halt", "--", "stdio" ] }
77−tailwindcss-ls = { command = "tailwindcss-language-server", args = ["--stdio"] }
78taplo = { command = "taplo", args = ["lsp", "stdio"] }
68taplo = { command = "taplo", args = ["lsp", "stdio"] }
79−templ = { command = "templ", args = ["lsp"] }
80terraform-ls = { command = "terraform-ls", args = ["serve"] }
69terraform-ls = { command = "terraform-ls", args = ["serve"] }
81texlab = { command = "texlab" }
70texlab = { command = "texlab" }
82vala-language-server = { command = "vala-language-server" }
71vala-language-server = { command = "vala-language-server" }
83vhdl_ls = { command = "vhdl_ls", args = [] }
72vhdl_ls = { command = "vhdl_ls", args = [] }
84vlang-language-server = { command = "v-analyzer" }
73vlang-language-server = { command = "v-analyzer" }
85−vscode-css-language-server = { command = "vscode-css-language-server", args = ["--stdio"], config = { provideFormatter = true, css = { validate = { enable = true } } } }
74+vscode-css-language-server = { command = "vscode-css-language-server", args = ["--stdio"], config = { "provideFormatter" = true }}
86vscode-html-language-server = { command = "vscode-html-language-server", args = ["--stdio"], config = { provideFormatter = true } }
75vscode-html-language-server = { command = "vscode-html-language-server", args = ["--stdio"], config = { provideFormatter = true } }
87−vscode-json-language-server = { command = "vscode-json-language-server", args = ["--stdio"], config = { provideFormatter = true, json = { validate = { enable = true } } } }
76+vscode-json-language-server = { command = "vscode-json-language-server", args = ["--stdio"], config = { provideFormatter = true } }
88vuels = { command = "vue-language-server", args = ["--stdio"], config = { typescript = { tsdk = "node_modules/typescript/lib/" } } }
77vuels = { command = "vue-language-server", args = ["--stdio"], config = { typescript = { tsdk = "node_modules/typescript/lib/" } } }
89wgsl_analyzer = { command = "wgsl_analyzer" }
78wgsl_analyzer = { command = "wgsl_analyzer" }
90yaml-language-server = { command = "yaml-language-server", args = ["--stdio"] }
79yaml-language-server = { command = "yaml-language-server", args = ["--stdio"] }
91zls = { command = "zls" }
80zls = { command = "zls" }
92blueprint-compiler = { command = "blueprint-compiler", args = ["lsp"] }
81blueprint-compiler = { command = "blueprint-compiler", args = ["lsp"] }
93−typst-lsp = { command = "typst-lsp" }
94
82
95−[language-server.ansible-language-server]
96−command = "ansible-language-server"
97−args = ["--stdio"]
98
83
99[language-server.lua-language-server]
84[language-server.lua-language-server]
100−command = "lua-language-server"
85+command = "lua-language-server"
101
86
102[language-server.lua-language-server.config.Lua.hint]
87[language-server.lua-language-server.config.Lua.hint]
103enable = true
88enable = true
@@ -119,12 +104,6 @@ functionTypeParameters = true
119parameterNames = true
104parameterNames = true
120rangeVariableTypes = true
105rangeVariableTypes = true
121
106
122−[language-server.golangci-lint-lsp]
123−command = "golangci-lint-langserver"
124−
125−[language-server.golangci-lint-lsp.config]
126−command = ["golangci-lint", "run", "--out-format", "json", "--issues-exit-code=1"]
127−
128
107
129[language-server.rust-analyzer]
108[language-server.rust-analyzer]
130command = "rust-analyzer"
109command = "rust-analyzer"
@@ -191,7 +170,6 @@ auto-format = true
191comment-token = "//"
170comment-token = "//"
192language-servers = [ "rust-analyzer" ]
171language-servers = [ "rust-analyzer" ]
193indent = { tab-width = 4, unit = " " }
172indent = { tab-width = 4, unit = " " }
194−persistent-diagnostic-sources = ["rustc", "clippy"]
195
173
196[language.auto-pairs]
174[language.auto-pairs]
197'(' = ')'
175'(' = ')'
@@ -252,6 +230,7 @@ name = "toml"
252scope = "source.toml"
230scope = "source.toml"
253injection-regex = "toml"
231injection-regex = "toml"
254file-types = ["toml", "poetry.lock", "Cargo.lock"]
232file-types = ["toml", "poetry.lock", "Cargo.lock"]
233+roots = []
255comment-token = "#"
234comment-token = "#"
256language-servers = [ "taplo" ]
235language-servers = [ "taplo" ]
257indent = { tab-width = 2, unit = " " }
236indent = { tab-width = 2, unit = " " }
@@ -265,6 +244,7 @@ name = "awk"
265scope = "source.awk"
244scope = "source.awk"
266injection-regex = "awk"
245injection-regex = "awk"
267file-types = ["awk", "gawk", "nawk", "mawk"]
246file-types = ["awk", "gawk", "nawk", "mawk"]
247+roots = []
268comment-token = "#"
248comment-token = "#"
269language-servers = [ "awk-language-server" ]
249language-servers = [ "awk-language-server" ]
270indent = { tab-width = 2, unit = " " }
250indent = { tab-width = 2, unit = " " }
@@ -278,7 +258,7 @@ name = "protobuf"
278scope = "source.proto"
258scope = "source.proto"
279injection-regex = "protobuf"
259injection-regex = "protobuf"
280file-types = ["proto"]
260file-types = ["proto"]
281−language-servers = [ "bufls", "pbkit" ]
261+roots = []
282comment-token = "//"
262comment-token = "//"
283indent = { tab-width = 2, unit = " " }
263indent = { tab-width = 2, unit = " " }
284
264
@@ -307,6 +287,7 @@ scope = "source.fish"
307injection-regex = "fish"
287injection-regex = "fish"
308file-types = ["fish"]
288file-types = ["fish"]
309shebangs = ["fish"]
289shebangs = ["fish"]
290+roots = []
310comment-token = "#"
291comment-token = "#"
311indent = { tab-width = 4, unit = " " }
292indent = { tab-width = 4, unit = " " }
312
293
@@ -320,6 +301,7 @@ scope = "source.mint"
320injection-regex = "mint"
301injection-regex = "mint"
321file-types = ["mint"]
302file-types = ["mint"]
322shebangs = []
303shebangs = []
304+roots = []
323comment-token = "//"
305comment-token = "//"
324language-servers = [ "mint" ]
306language-servers = [ "mint" ]
325indent = { tab-width = 2, unit = " " }
307indent = { tab-width = 2, unit = " " }
@@ -328,28 +310,8 @@ indent = { tab-width = 2, unit = " " }
328name = "json"
310name = "json"
329scope = "source.json"
311scope = "source.json"
330injection-regex = "json"
312injection-regex = "json"
331−file-types = [
313+file-types = ["json", "jsonc", "arb", "ipynb", "geojson", "gltf"]
332− "json",
314+roots = []
333− "jsonc",
334− "arb",
335− "ipynb",
336− "geojson",
337− "gltf",
338− "webmanifest",
339− "flake.lock",
340− ".babelrc",
341− ".bowerrc",
342− ".jscrc",
343− "js.map",
344− "ts.map",
345− "css.map",
346− ".jslintrc",
347− "jsonld",
348− ".vuerc",
349− "composer.lock",
350− ".watchmanconfig",
351− "avsc"
352−]
353language-servers = [ "vscode-json-language-server" ]
315language-servers = [ "vscode-json-language-server" ]
354auto-format = true
316auto-format = true
355indent = { tab-width = 2, unit = " " }
317indent = { tab-width = 2, unit = " " }
@@ -358,26 +320,12 @@ indent = { tab-width = 2, unit = " " }
358name = "json"
320name = "json"
359source = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "73076754005a460947cafe8e03a8cf5fa4fa2938" }
321source = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "73076754005a460947cafe8e03a8cf5fa4fa2938" }
360
322
361−
362−[[language]]
363−name = "json5"
364−scope = "source.json5"
365−injection-regex = "json5"
366−file-types = ["json5"]
367−language-servers = []
368−comment-token = "//"
369−indent = { tab-width = 4, unit = " " }
370−# https://json5.org
371−
372−[[grammar]]
373−name = "json5"
374−source = { git = "https://github.com/Joakker/tree-sitter-json5", rev = "c23f7a9b1ee7d45f516496b1e0e4be067264fa0d" }
375−
376[[language]]
323[[language]]
377name = "c"
324name = "c"
378scope = "source.c"
325scope = "source.c"
379injection-regex = "c"
326injection-regex = "c"
380file-types = ["c"] # TODO: ["h"]
327file-types = ["c"] # TODO: ["h"]
328+roots = []
381comment-token = "//"
329comment-token = "//"
382language-servers = [ "clangd" ]
330language-servers = [ "clangd" ]
383indent = { tab-width = 2, unit = " " }
331indent = { tab-width = 2, unit = " " }
@@ -413,7 +361,8 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-c", rev = "7175a6dd
413name = "cpp"
361name = "cpp"
414scope = "source.cpp"
362scope = "source.cpp"
415injection-regex = "cpp"
363injection-regex = "cpp"
416−file-types = ["cc", "hh", "c++", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino", "C", "H", "cu", "cuh", "cppm", "h++", "ii", "inl", { suffix = ".hpp.in" }, { suffix = ".h.in" }]
364+file-types = ["cc", "hh", "c++", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino", "C", "H", "cu", "cuh", "cppm"]
365+roots = []
417comment-token = "//"
366comment-token = "//"
418language-servers = [ "clangd" ]
367language-servers = [ "clangd" ]
419indent = { tab-width = 2, unit = " " }
368indent = { tab-width = 2, unit = " " }
@@ -459,7 +408,7 @@ language-servers = [ "crystalline" ]
459name = "c-sharp"
408name = "c-sharp"
460scope = "source.csharp"
409scope = "source.csharp"
461injection-regex = "c-?sharp"
410injection-regex = "c-?sharp"
462−file-types = ["cs", "csx", "cake"]
411+file-types = ["cs"]
463roots = ["sln", "csproj"]
412roots = ["sln", "csproj"]
464comment-token = "//"
413comment-token = "//"
465indent = { tab-width = 4, unit = "\t" }
414indent = { tab-width = 4, unit = "\t" }
@@ -496,7 +445,7 @@ file-types = ["go"]
496roots = ["go.work", "go.mod"]
445roots = ["go.work", "go.mod"]
497auto-format = true
446auto-format = true
498comment-token = "//"
447comment-token = "//"
499−language-servers = [ "gopls", "golangci-lint-lsp" ]
448+language-servers = [ "gopls" ]
500# TODO: gopls needs utf-8 offsets?
449# TODO: gopls needs utf-8 offsets?
501indent = { tab-width = 4, unit = "\t" }
450indent = { tab-width = 4, unit = "\t" }
502
451
@@ -546,6 +495,7 @@ name = "gomod"
546scope = "source.gomod"
495scope = "source.gomod"
547injection-regex = "gomod"
496injection-regex = "gomod"
548file-types = ["go.mod"]
497file-types = ["go.mod"]
498+roots = []
549auto-format = true
499auto-format = true
550comment-token = "//"
500comment-token = "//"
551language-servers = [ "gopls" ]
501language-servers = [ "gopls" ]
@@ -560,6 +510,7 @@ name = "gotmpl"
560scope = "source.gotmpl"
510scope = "source.gotmpl"
561injection-regex = "gotmpl"
511injection-regex = "gotmpl"
562file-types = ["gotmpl"]
512file-types = ["gotmpl"]
513+roots = []
563comment-token = "//"
514comment-token = "//"
564language-servers = [ "gopls" ]
515language-servers = [ "gopls" ]
565indent = { tab-width = 2, unit = " " }
516indent = { tab-width = 2, unit = " " }
@@ -573,6 +524,7 @@ name = "gowork"
573scope = "source.gowork"
524scope = "source.gowork"
574injection-regex = "gowork"
525injection-regex = "gowork"
575file-types = ["go.work"]
526file-types = ["go.work"]
527+roots = []
576auto-format = true
528auto-format = true
577comment-token = "//"
529comment-token = "//"
578language-servers = [ "gopls" ]
530language-servers = [ "gopls" ]
@@ -587,8 +539,9 @@ name = "javascript"
587scope = "source.js"
539scope = "source.js"
588injection-regex = "(js|javascript)"
540injection-regex = "(js|javascript)"
589language-id = "javascript"
541language-id = "javascript"
590−file-types = ["js", "mjs", "cjs", "rules", "es6", "pac", "jakefile"]
542+file-types = ["js", "mjs", "cjs"]
591shebangs = ["node"]
543shebangs = ["node"]
544+roots = []
592comment-token = "//"
545comment-token = "//"
593language-servers = [ "typescript-language-server" ]
546language-servers = [ "typescript-language-server" ]
594indent = { tab-width = 2, unit = " " }
547indent = { tab-width = 2, unit = " " }
@@ -607,7 +560,7 @@ args = { program = "{0}" }
607
560
608[[grammar]]
561[[grammar]]
609name = "javascript"
562name = "javascript"
610−source = { git = "https://github.com/tree-sitter/tree-sitter-javascript", rev = "f772967f7b7bc7c28f845be2420a38472b16a8ee" }
563+source = { git = "https://github.com/tree-sitter/tree-sitter-javascript", rev = "4a95461c4761c624f2263725aca79eeaefd36cad" }
611
564
612[[language]]
565[[language]]
613name = "jsx"
566name = "jsx"
@@ -615,6 +568,7 @@ scope = "source.jsx"
615injection-regex = "jsx"
568injection-regex = "jsx"
616language-id = "javascriptreact"
569language-id = "javascriptreact"
617file-types = ["jsx"]
570file-types = ["jsx"]
571+roots = []
618comment-token = "//"
572comment-token = "//"
619language-servers = [ "typescript-language-server" ]
573language-servers = [ "typescript-language-server" ]
620indent = { tab-width = 2, unit = " " }
574indent = { tab-width = 2, unit = " " }
@@ -626,13 +580,14 @@ scope = "source.ts"
626injection-regex = "(ts|typescript)"
580injection-regex = "(ts|typescript)"
627file-types = ["ts", "mts", "cts"]
581file-types = ["ts", "mts", "cts"]
628language-id = "typescript"
582language-id = "typescript"
629−shebangs = ["deno", "ts-node"]
583+shebangs = []
584+roots = []
630language-servers = [ "typescript-language-server" ]
585language-servers = [ "typescript-language-server" ]
631indent = { tab-width = 2, unit = " " }
586indent = { tab-width = 2, unit = " " }
632
587
633[[grammar]]
588[[grammar]]
634name = "typescript"
589name = "typescript"
635−source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "b1bf4825d9eaa0f3bdeb1e52f099533328acfbdf", subpath = "typescript" }
590+source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259", subpath = "typescript" }
636
591
637[[language]]
592[[language]]
638name = "tsx"
593name = "tsx"
@@ -640,18 +595,20 @@ scope = "source.tsx"
640injection-regex = "(tsx)" # |typescript
595injection-regex = "(tsx)" # |typescript
641language-id = "typescriptreact"
596language-id = "typescriptreact"
642file-types = ["tsx"]
597file-types = ["tsx"]
598+roots = []
643language-servers = [ "typescript-language-server" ]
599language-servers = [ "typescript-language-server" ]
644indent = { tab-width = 2, unit = " " }
600indent = { tab-width = 2, unit = " " }
645
601
646[[grammar]]
602[[grammar]]
647name = "tsx"
603name = "tsx"
648−source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "b1bf4825d9eaa0f3bdeb1e52f099533328acfbdf", subpath = "tsx" }
604+source = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259", subpath = "tsx" }
649
605
650[[language]]
606[[language]]
651name = "css"
607name = "css"
652scope = "source.css"
608scope = "source.css"
653injection-regex = "css"
609injection-regex = "css"
654file-types = ["css", "scss"]
610file-types = ["css", "scss"]
611+roots = []
655language-servers = [ "vscode-css-language-server" ]
612language-servers = [ "vscode-css-language-server" ]
656auto-format = true
613auto-format = true
657indent = { tab-width = 2, unit = " " }
614indent = { tab-width = 2, unit = " " }
@@ -665,6 +622,7 @@ name = "scss"
665scope = "source.scss"
622scope = "source.scss"
666injection-regex = "scss"
623injection-regex = "scss"
667file-types = ["scss"]
624file-types = ["scss"]
625+roots = []
668language-servers = [ "vscode-css-language-server" ]
626language-servers = [ "vscode-css-language-server" ]
669auto-format = true
627auto-format = true
670indent = { tab-width = 2, unit = " " }
628indent = { tab-width = 2, unit = " " }
@@ -677,7 +635,8 @@ source = { git = "https://github.com/serenadeai/tree-sitter-scss", rev = "c478c6
677name = "html"
635name = "html"
678scope = "text.html.basic"
636scope = "text.html.basic"
679injection-regex = "html"
637injection-regex = "html"
680−file-types = ["html", "htm", "shtml", "xhtml", "xht", "jsp", "asp", "aspx", "jshtm", "volt", "rhtml"]
638+file-types = ["html"]
639+roots = []
681language-servers = [ "vscode-html-language-server" ]
640language-servers = [ "vscode-html-language-server" ]
682auto-format = true
641auto-format = true
683indent = { tab-width = 2, unit = " " }
642indent = { tab-width = 2, unit = " " }
@@ -690,9 +649,9 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-html", rev = "29f53
690name = "python"
649name = "python"
691scope = "source.python"
650scope = "source.python"
692injection-regex = "python"
651injection-regex = "python"
693−file-types = ["py","pyi","py3","pyw","ptl",".pythonstartup",".pythonrc","SConstruct", "rpy", "cpy", "ipy", "pyt", "SConscript"]
652+file-types = ["py","pyi","py3","pyw","ptl",".pythonstartup",".pythonrc","SConstruct"]
694shebangs = ["python"]
653shebangs = ["python"]
695−roots = ["pyproject.toml", "setup.py", "poetry.lock", "pyrightconfig.json"]
654+roots = []
696comment-token = "#"
655comment-token = "#"
697language-servers = [ "pylsp" ]
656language-servers = [ "pylsp" ]
698# TODO: pyls needs utf-8 offsets
657# TODO: pyls needs utf-8 offsets
@@ -700,7 +659,7 @@ indent = { tab-width = 4, unit = " " }
700
659
701[[grammar]]
660[[grammar]]
702name = "python"
661name = "python"
703−source = { git = "https://github.com/tree-sitter/tree-sitter-python", rev = "4bfdd9033a2225cc95032ce77066b7aeca9e2efc" }
662+source = { git = "https://github.com/tree-sitter/tree-sitter-python", rev = "de221eccf9a221f5b85474a553474a69b4b5784d" }
704
663
705[[language]]
664[[language]]
706name = "nickel"
665name = "nickel"
@@ -708,6 +667,7 @@ scope = "source.nickel"
708injection-regex = "nickel"
667injection-regex = "nickel"
709file-types = ["ncl"]
668file-types = ["ncl"]
710shebangs = []
669shebangs = []
670+roots = []
711comment-token = "#"
671comment-token = "#"
712language-servers = [ "nls" ]
672language-servers = [ "nls" ]
713indent = { tab-width = 2, unit = " " }
673indent = { tab-width = 2, unit = " " }
@@ -728,6 +688,7 @@ scope = "source.nix"
728injection-regex = "nix"
688injection-regex = "nix"
729file-types = ["nix"]
689file-types = ["nix"]
730shebangs = []
690shebangs = []
691+roots = []
731comment-token = "#"
692comment-token = "#"
732language-servers = [ "nil" ]
693language-servers = [ "nil" ]
733indent = { tab-width = 2, unit = " " }
694indent = { tab-width = 2, unit = " " }
@@ -740,43 +701,9 @@ source = { git = "https://github.com/nix-community/tree-sitter-nix", rev = "1b69
740name = "ruby"
701name = "ruby"
741scope = "source.ruby"
702scope = "source.ruby"
742injection-regex = "ruby"
703injection-regex = "ruby"
743−file-types = [
704+file-types = ["rb", "rake", "rakefile", "irb", "gemfile", "gemspec", "Rakefile", "Gemfile", "rabl", "jbuilder", "jb", "Podfile", "podspec", "Vagrantfile", "Brewfile"]
744− "rb",
745− "rake",
746− "rakefile",
747− "irb",
748− "gemfile",
749− "gemspec",
750− "Rakefile",
751− "Gemfile",
752− "rabl",
753− "jbuilder",
754− "jb",
755− "Podfile",
756− "podspec",
757− "Vagrantfile",
758− "Brewfile",
759− "rjs",
760− "rbi",
761− "Guardfile",
762− "Capfile",
763− "Cheffile",
764− "Hobofile",
765− "Appraisals",
766− "Rantfile",
767− "Berksfile",
768− "Berksfile.lock",
769− "Thorfile",
770− "Puppetfile",
771− "Fastfile",
772− "Appfile",
773− "Deliverfile",
774− "Matchfile",
775− "Scanfile",
776− "Snapfile",
777− "Gymfile"
778−]
779shebangs = ["ruby"]
705shebangs = ["ruby"]
706+roots = []
780comment-token = "#"
707comment-token = "#"
781language-servers = [ "solargraph" ]
708language-servers = [ "solargraph" ]
782indent = { tab-width = 2, unit = " " }
709indent = { tab-width = 2, unit = " " }
@@ -789,48 +716,9 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-ruby", rev = "206c7
789name = "bash"
716name = "bash"
790scope = "source.bash"
717scope = "source.bash"
791injection-regex = "(shell|bash|zsh|sh)"
718injection-regex = "(shell|bash|zsh|sh)"
792−file-types = [
719+file-types = ["sh", "bash", "zsh", ".bash_login", ".bash_logout", ".bash_profile", ".bashrc", ".profile", ".zshenv", ".zlogin", ".zlogout", ".zprofile", ".zshrc", ".zimrc", "APKBUILD", "PKGBUILD", "eclass", "ebuild", "bazelrc", ".bash_aliases", "Renviron", ".Renviron"]
793− "sh",
794− "bash",
795− "zsh",
796− ".bash_login",
797− ".bash_logout",
798− ".bash_profile",
799− ".bashrc",
800− ".profile",
801− ".zshenv",
802− "zshenv",
803− ".zlogin",
804− "zlogin",
805− ".zlogout",
806− "zlogout",
807− ".zprofile",
808− "zprofile",
809− ".zshrc",
810− "zshrc",
811− ".zimrc",
812− "APKBUILD",
813− "PKGBUILD",
814− "eclass",
815− "ebuild",
816− "bazelrc",
817− ".bash_aliases",
818− "Renviron",
819− ".Renviron",
820− ".xprofile",
821− ".xsession",
822− ".xsessionrc",
823− "zsh-theme",
824− "ksh",
825− "cshrc",
826− "tcshrc",
827− ".yashrc",
828− ".yash_profile",
829− ".hushlogin",
830− "bashrc_Apple_Terminal",
831− "zshrc_Apple_Terminal"
832−]
833shebangs = ["sh", "bash", "dash", "zsh"]
720shebangs = ["sh", "bash", "dash", "zsh"]
721+roots = []
834comment-token = "#"
722comment-token = "#"
835language-servers = [ "bash-language-server" ]
723language-servers = [ "bash-language-server" ]
836indent = { tab-width = 2, unit = " " }
724indent = { tab-width = 2, unit = " " }
@@ -843,7 +731,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "275ef
843name = "php"
731name = "php"
844scope = "source.php"
732scope = "source.php"
845injection-regex = "php"
733injection-regex = "php"
846−file-types = ["php", "inc", "php4", "php5", "phtml", "ctp"]
734+file-types = ["php", "inc"]
847shebangs = ["php"]
735shebangs = ["php"]
848roots = ["composer.json", "index.php"]
736roots = ["composer.json", "index.php"]
849language-servers = [ "intelephense" ]
737language-servers = [ "intelephense" ]
@@ -858,6 +746,7 @@ name = "twig"
858scope = "source.twig"
746scope = "source.twig"
859injection-regex = "twig"
747injection-regex = "twig"
860file-types = ["twig"]
748file-types = ["twig"]
749+roots = []
861indent = { tab-width = 2, unit = " " }
750indent = { tab-width = 2, unit = " " }
862
751
863[[grammar]]
752[[grammar]]
@@ -868,7 +757,8 @@ source = { git = "https://github.com/gbprod/tree-sitter-twig", rev = "807b293fec
868name = "latex"
757name = "latex"
869scope = "source.tex"
758scope = "source.tex"
870injection-regex = "tex"
759injection-regex = "tex"
871−file-types = ["tex", "sty", "cls", "Rd", "bbx", "cbx"]
760+file-types = ["tex", "sty", "cls", "Rd"]
761+roots = []
872comment-token = "%"
762comment-token = "%"
873language-servers = [ "texlab" ]
763language-servers = [ "texlab" ]
874indent = { tab-width = 4, unit = "\t" }
764indent = { tab-width = 4, unit = "\t" }
@@ -882,6 +772,7 @@ name = "bibtex"
882scope = "source.bib"
772scope = "source.bib"
883injection-regex = "bib"
773injection-regex = "bib"
884file-types = ["bib"]
774file-types = ["bib"]
775+roots = []
885comment-token = "%"
776comment-token = "%"
886language-servers = [ "texlab" ]
777language-servers = [ "texlab" ]
887indent = { tab-width = 4, unit = "\t" }
778indent = { tab-width = 4, unit = "\t" }
@@ -918,23 +809,11 @@ indent = { tab-width = 2, unit = " " }
918name = "lean"
809name = "lean"
919source = { git = "https://github.com/Julian/tree-sitter-lean", rev = "d98426109258b266e1e92358c5f11716d2e8f638" }
810source = { git = "https://github.com/Julian/tree-sitter-lean", rev = "d98426109258b266e1e92358c5f11716d2e8f638" }
920
811
921−
922−[[language]]
923−name = "lpf"
924−comment-token = "#"
925−scope = "source.lpf"
926−file-types = ["lpf"]
927−
928−[[grammar]]
929−name = "lpf"
930−source = { git = "https://gitlab.com/TheZoq2/tree-sitter-lpf", rev = "db7372e60c722ca7f12ab359e57e6bf7611ab126" }
931−
932[[language]]
812[[language]]
933name = "julia"
813name = "julia"
934scope = "source.julia"
814scope = "source.julia"
935injection-regex = "julia"
815injection-regex = "julia"
936file-types = ["jl"]
816file-types = ["jl"]
937−shebangs = ["julia"]
938roots = ["Manifest.toml", "Project.toml"]
817roots = ["Manifest.toml", "Project.toml"]
939comment-token = "#"
818comment-token = "#"
940language-servers = [ "julia" ]
819language-servers = [ "julia" ]
@@ -948,33 +827,21 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-julia", rev = "8fb3
948name = "java"
827name = "java"
949scope = "source.java"
828scope = "source.java"
950injection-regex = "java"
829injection-regex = "java"
951−file-types = ["java", "jav"]
830+file-types = ["java"]
952roots = ["pom.xml", "build.gradle", "build.gradle.kts"]
831roots = ["pom.xml", "build.gradle", "build.gradle.kts"]
953language-servers = [ "jdtls" ]
832language-servers = [ "jdtls" ]
954−indent = { tab-width = 2, unit = " " }
833+indent = { tab-width = 4, unit = " " }
955
834
956[[grammar]]
835[[grammar]]
957name = "java"
836name = "java"
958source = { git = "https://github.com/tree-sitter/tree-sitter-java", rev = "09d650def6cdf7f479f4b78f595e9ef5b58ce31e" }
837source = { git = "https://github.com/tree-sitter/tree-sitter-java", rev = "09d650def6cdf7f479f4b78f595e9ef5b58ce31e" }
959
838
960−[[language]]
961−name = "smali"
962−scope = "source.smali"
963−injection-regex = "smali"
964−file-types = ["smali"]
965−comment-token = "#"
966−roots = []
967−indent = { tab-width = 4, unit = " " }
968−
969−[[grammar]]
970−name = "smali"
971−source = { git = "https://github.com/amaanq/tree-sitter-smali", rev = "5ae51e15c4d1ac93cba6127caf3d1f0a072c140c" }
972−
973[[language]]
839[[language]]
974name = "ledger"
840name = "ledger"
975scope = "source.ledger"
841scope = "source.ledger"
976injection-regex = "ledger"
842injection-regex = "ledger"
977file-types = ["ldg", "ledger", "journal"]
843file-types = ["ldg", "ledger", "journal"]
844+roots = []
978comment-token = ";"
845comment-token = ";"
979indent = { tab-width = 4, unit = " " }
846indent = { tab-width = 4, unit = " " }
980
847
@@ -987,19 +854,21 @@ name = "beancount"
987scope = "source.beancount"
854scope = "source.beancount"
988injection-regex = "beancount"
855injection-regex = "beancount"
989file-types = ["beancount", "bean"]
856file-types = ["beancount", "bean"]
857+roots = []
990comment-token = ";"
858comment-token = ";"
991indent = { tab-width = 2, unit = " " }
859indent = { tab-width = 2, unit = " " }
992
860
993[[grammar]]
861[[grammar]]
994name = "beancount"
862name = "beancount"
995−source = { git = "https://github.com/polarmutex/tree-sitter-beancount", rev = "f3741a3a68ade59ec894ed84a64673494d2ba8f3" }
863+source = { git = "https://github.com/polarmutex/tree-sitter-beancount", rev = "4cbd1f09cd07c1f1fabf867c2cf354f9da53cc4c" }
996
864
997[[language]]
865[[language]]
998name = "ocaml"
866name = "ocaml"
999scope = "source.ocaml"
867scope = "source.ocaml"
1000injection-regex = "ocaml"
868injection-regex = "ocaml"
1001file-types = ["ml"]
869file-types = ["ml"]
1002−shebangs = ["ocaml", "ocamlrun", "ocamlscript"]
870+shebangs = []
871+roots = []
1003comment-token = "(**)"
872comment-token = "(**)"
1004language-servers = [ "ocamllsp" ]
873language-servers = [ "ocamllsp" ]
1005indent = { tab-width = 2, unit = " " }
874indent = { tab-width = 2, unit = " " }
@@ -1009,6 +878,7 @@ indent = { tab-width = 2, unit = " " }
1009'{' = '}'
878'{' = '}'
1010'[' = ']'
879'[' = ']'
1011'"' = '"'
880'"' = '"'
881+'`' = '`'
1012
882
1013[[grammar]]
883[[grammar]]
1014name = "ocaml"
884name = "ocaml"
@@ -1019,6 +889,7 @@ name = "ocaml-interface"
1019scope = "source.ocaml.interface"
889scope = "source.ocaml.interface"
1020file-types = ["mli"]
890file-types = ["mli"]
1021shebangs = []
891shebangs = []
892+roots = []
1022comment-token = "(**)"
893comment-token = "(**)"
1023language-servers = [ "ocamllsp" ]
894language-servers = [ "ocamllsp" ]
1024indent = { tab-width = 2, unit = " " }
895indent = { tab-width = 2, unit = " " }
@@ -1028,6 +899,7 @@ indent = { tab-width = 2, unit = " " }
1028'{' = '}'
899'{' = '}'
1029'[' = ']'
900'[' = ']'
1030'"' = '"'
901'"' = '"'
902+'`' = '`'
1031
903
1032[[grammar]]
904[[grammar]]
1033name = "ocaml-interface"
905name = "ocaml-interface"
@@ -1053,6 +925,7 @@ name = "svelte"
1053scope = "source.svelte"
925scope = "source.svelte"
1054injection-regex = "svelte"
926injection-regex = "svelte"
1055file-types = ["svelte"]
927file-types = ["svelte"]
928+roots = []
1056indent = { tab-width = 2, unit = " " }
929indent = { tab-width = 2, unit = " " }
1057language-servers = [ "svelteserver" ]
930language-servers = [ "svelteserver" ]
1058
931
@@ -1077,9 +950,10 @@ source = { git = "https://github.com/ikatyang/tree-sitter-vue", rev = "91fe27547
1077name = "yaml"
950name = "yaml"
1078scope = "source.yaml"
951scope = "source.yaml"
1079file-types = ["yml", "yaml"]
952file-types = ["yml", "yaml"]
953+roots = []
1080comment-token = "#"
954comment-token = "#"
1081indent = { tab-width = 2, unit = " " }
955indent = { tab-width = 2, unit = " " }
1082−language-servers = [ "yaml-language-server", "ansible-language-server" ]
956+language-servers = [ "yaml-language-server" ]
1083injection-regex = "yml|yaml"
957injection-regex = "yml|yaml"
1084
958
1085[[grammar]]
959[[grammar]]
@@ -1098,12 +972,13 @@ indent = { tab-width = 2, unit = " " }
1098
972
1099[[grammar]]
973[[grammar]]
1100name = "haskell"
974name = "haskell"
1101−source = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "d7ac98f49e3ed7e17541256fe3881a967d7ffdd3" }
975+source = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "98fc7f59049aeb713ab9b72a8ff25dcaaef81087" }
1102
976
1103[[language]]
977[[language]]
1104name = "haskell-persistent"
978name = "haskell-persistent"
1105scope = "source.persistentmodels"
979scope = "source.persistentmodels"
1106file-types = ["persistentmodels"]
980file-types = ["persistentmodels"]
981+roots = []
1107comment-token = "--"
982comment-token = "--"
1108indent = { tab-width = 2, unit = " " }
983indent = { tab-width = 2, unit = " " }
1109
984
@@ -1122,10 +997,7 @@ language-servers = [ "purescript-language-server" ]
1122indent = { tab-width = 2, unit = " " }
997indent = { tab-width = 2, unit = " " }
1123auto-format = true
998auto-format = true
1124formatter = { command = "purs-tidy", args = ["format"] }
999formatter = { command = "purs-tidy", args = ["format"] }
1125−
1126−[[grammar]]
1000+grammar = "haskell"
1127−name = "purescript"
1128−source = { git = "https://github.com/postsolar/tree-sitter-purescript", rev = "5ef5592674ea42de75fc2792972e4ea0b6e3da6c" }
1129
1001
1130[[language]]
1002[[language]]
1131name = "zig"
1003name = "zig"
@@ -1164,11 +1036,12 @@ args = { console = "internalConsole", attachCommands = [ "platform select remote
1164
1036
1165[[grammar]]
1037[[grammar]]
1166name = "zig"
1038name = "zig"
1167−source = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "0d08703e4c3f426ec61695d7617415fff97029bd" }
1039+source = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "8d3224c3bd0890fe08358886ebf54fca2ed448a6" }
1168
1040
1169[[language]]
1041[[language]]
1170name = "prolog"
1042name = "prolog"
1171scope = "source.prolog"
1043scope = "source.prolog"
1044+roots = []
1172file-types = ["pl", "prolog"]
1045file-types = ["pl", "prolog"]
1173shebangs = ["swipl"]
1046shebangs = ["swipl"]
1174comment-token = "%"
1047comment-token = "%"
@@ -1177,7 +1050,8 @@ language-servers = [ "swipl" ]
1177[[language]]
1050[[language]]
1178name = "tsq"
1051name = "tsq"
1179scope = "source.tsq"
1052scope = "source.tsq"
1180−file-types = ["tsq"]
1053+file-types = ["scm"]
1054+roots = []
1181comment-token = ";"
1055comment-token = ";"
1182injection-regex = "tsq"
1056injection-regex = "tsq"
1183indent = { tab-width = 2, unit = " " }
1057indent = { tab-width = 2, unit = " " }
@@ -1190,6 +1064,7 @@ source = { git = "https://github.com/the-mikedavis/tree-sitter-tsq", rev = "48b5
1190name = "cmake"
1064name = "cmake"
1191scope = "source.cmake"
1065scope = "source.cmake"
1192file-types = ["cmake", "CMakeLists.txt"]
1066file-types = ["cmake", "CMakeLists.txt"]
1067+roots = []
1193comment-token = "#"
1068comment-token = "#"
1194indent = { tab-width = 2, unit = " " }
1069indent = { tab-width = 2, unit = " " }
1195language-servers = [ "cmake-language-server" ]
1070language-servers = [ "cmake-language-server" ]
@@ -1202,9 +1077,9 @@ source = { git = "https://github.com/uyha/tree-sitter-cmake", rev = "6e51463ef30
1202[[language]]
1077[[language]]
1203name = "make"
1078name = "make"
1204scope = "source.make"
1079scope = "source.make"
1205−file-types = ["Makefile", "makefile", "make", "mk", "mak", "GNUmakefile", "OCamlMakefile"]
1080+file-types = ["Makefile", "makefile", "make", "mk"]
1206−shebangs = ["make", "gmake"]
1207injection-regex = "(make|makefile|Makefile|mk)"
1081injection-regex = "(make|makefile|Makefile|mk)"
1082+roots = []
1208comment-token = "#"
1083comment-token = "#"
1209indent = { tab-width = 4, unit = "\t" }
1084indent = { tab-width = 4, unit = "\t" }
1210
1085
@@ -1216,6 +1091,7 @@ source = { git = "https://github.com/alemuller/tree-sitter-make", rev = "a4b9187
1216name = "glsl"
1091name = "glsl"
1217scope = "source.glsl"
1092scope = "source.glsl"
1218file-types = ["glsl", "vert", "tesc", "tese", "geom", "frag", "comp" ]
1093file-types = ["glsl", "vert", "tesc", "tese", "geom", "frag", "comp" ]
1094+roots = []
1219comment-token = "//"
1095comment-token = "//"
1220indent = { tab-width = 4, unit = " " }
1096indent = { tab-width = 4, unit = " " }
1221injection-regex = "glsl"
1097injection-regex = "glsl"
@@ -1227,39 +1103,31 @@ source = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "88408ff
1227[[language]]
1103[[language]]
1228name = "perl"
1104name = "perl"
1229scope = "source.perl"
1105scope = "source.perl"
1230−file-types = ["pl", "pm", "t", "psgi", "raku", "rakumod", "rakutest", "rakudoc", "nqp", "p6", "pl6", "pm6"]
1106+file-types = ["pl", "pm", "t"]
1231shebangs = ["perl"]
1107shebangs = ["perl"]
1108+roots = []
1232comment-token = "#"
1109comment-token = "#"
1233language-servers = [ "perlnavigator" ]
1110language-servers = [ "perlnavigator" ]
1234indent = { tab-width = 2, unit = " " }
1111indent = { tab-width = 2, unit = " " }
1235
1112
1236[[grammar]]
1113[[grammar]]
1237name = "perl"
1114name = "perl"
1238−source = { git = "https://github.com/tree-sitter-perl/tree-sitter-perl", rev = "e99bb5283805db4cb86c964722d709df21b0ac16" }
1115+source = { git = "https://github.com/tree-sitter-perl/tree-sitter-perl", rev = "ed21ecbcc128a6688770ebafd3ef68a1c9bc1ea9" }
1239−
1240−[[language]]
1241−name = "pod"
1242−scope = "source.pod"
1243−injection-regex = "pod"
1244−file-types = ["pod"]
1245−
1246−[[grammar]]
1247−name = "pod"
1248−source = { git = "https://github.com/tree-sitter-perl/tree-sitter-pod", rev = "d466b84009a63986834498073ec05d58d727d55f" }
1249
1116
1250[[language]]
1117[[language]]
1251name = "racket"
1118name = "racket"
1252scope = "source.racket"
1119scope = "source.racket"
1120+roots = []
1253file-types = ["rkt", "rktd", "rktl", "scrbl"]
1121file-types = ["rkt", "rktd", "rktl", "scrbl"]
1254shebangs = ["racket"]
1122shebangs = ["racket"]
1255comment-token = ";"
1123comment-token = ";"
1256−indent = { tab-width = 2, unit = " " }
1257language-servers = [ "racket" ]
1124language-servers = [ "racket" ]
1258grammar = "scheme"
1125grammar = "scheme"
1259
1126
1260[[language]]
1127[[language]]
1261name = "common-lisp"
1128name = "common-lisp"
1262scope = "source.lisp"
1129scope = "source.lisp"
1130+roots = []
1263file-types = ["lisp", "asd", "cl", "l", "lsp", "ny", "podsl", "sexp"]
1131file-types = ["lisp", "asd", "cl", "l", "lsp", "ny", "podsl", "sexp"]
1264shebangs = ["lisp", "sbcl", "ccl", "clisp", "ecl"]
1132shebangs = ["lisp", "sbcl", "ccl", "clisp", "ecl"]
1265comment-token = ";"
1133comment-token = ";"
@@ -1276,17 +1144,19 @@ grammar = "scheme"
1276[[language]]
1144[[language]]
1277name = "comment"
1145name = "comment"
1278scope = "scope.comment"
1146scope = "scope.comment"
1147+roots = []
1279file-types = []
1148file-types = []
1280injection-regex = "comment"
1149injection-regex = "comment"
1281
1150
1282[[grammar]]
1151[[grammar]]
1283name = "comment"
1152name = "comment"
1284−source = { git = "https://github.com/stsewd/tree-sitter-comment", rev = "a37ca370310ac6f89b6e0ebf2b86b2219780494e" }
1153+source = { git = "https://github.com/stsewd/tree-sitter-comment", rev = "5dd3c62f1bbe378b220fe16b317b85247898639e" }
1285
1154
1286[[language]]
1155[[language]]
1287name = "wgsl"
1156name = "wgsl"
1288scope = "source.wgsl"
1157scope = "source.wgsl"
1289file-types = ["wgsl"]
1158file-types = ["wgsl"]
1159+roots = []
1290comment-token = "//"
1160comment-token = "//"
1291language-servers = [ "wgsl_analyzer" ]
1161language-servers = [ "wgsl_analyzer" ]
1292indent = { tab-width = 4, unit = " " }
1162indent = { tab-width = 4, unit = " " }
@@ -1298,6 +1168,7 @@ source = { git = "https://github.com/szebniok/tree-sitter-wgsl", rev = "272e89ef
1298[[language]]
1168[[language]]
1299name = "llvm"
1169name = "llvm"
1300scope = "source.llvm"
1170scope = "source.llvm"
1171+roots = []
1301file-types = ["ll"]
1172file-types = ["ll"]
1302comment-token = ";"
1173comment-token = ";"
1303indent = { tab-width = 2, unit = " " }
1174indent = { tab-width = 2, unit = " " }
@@ -1305,11 +1176,12 @@ injection-regex = "llvm"
1305
1176
1306[[grammar]]
1177[[grammar]]
1307name = "llvm"
1178name = "llvm"
1308−source = { git = "https://github.com/benwilliamgraham/tree-sitter-llvm", rev = "e9948edc41e9e5869af99dddb2b5ff5cc5581af6" }
1179+source = { git = "https://github.com/benwilliamgraham/tree-sitter-llvm", rev = "3b213925b9c4f42c1acfe2e10bfbb438d9c6834d" }
1309
1180
1310[[language]]
1181[[language]]
1311name = "llvm-mir"
1182name = "llvm-mir"
1312scope = "source.llvm_mir"
1183scope = "source.llvm_mir"
1184+roots = []
1313file-types = []
1185file-types = []
1314comment-token = ";"
1186comment-token = ";"
1315indent = { tab-width = 2, unit = " " }
1187indent = { tab-width = 2, unit = " " }
@@ -1325,6 +1197,7 @@ name = "llvm-mir-yaml"
1325#
1197#
1326# grammar = "yaml"
1198# grammar = "yaml"
1327scope = "source.yaml"
1199scope = "source.yaml"
1200+roots = []
1328file-types = ["mir"]
1201file-types = ["mir"]
1329comment-token = "#"
1202comment-token = "#"
1330indent = { tab-width = 2, unit = " " }
1203indent = { tab-width = 2, unit = " " }
@@ -1332,6 +1205,7 @@ indent = { tab-width = 2, unit = " " }
1332[[language]]
1205[[language]]
1333name = "tablegen"
1206name = "tablegen"
1334scope = "source.tablegen"
1207scope = "source.tablegen"
1208+roots = []
1335file-types = ["td"]
1209file-types = ["td"]
1336comment-token = "//"
1210comment-token = "//"
1337indent = { tab-width = 2, unit = " " }
1211indent = { tab-width = 2, unit = " " }
@@ -1345,25 +1219,26 @@ source = { git = "https://github.com/Flakebi/tree-sitter-tablegen", rev = "568dd
1345name = "markdown"
1219name = "markdown"
1346scope = "source.md"
1220scope = "source.md"
1347injection-regex = "md|markdown"
1221injection-regex = "md|markdown"
1348−file-types = ["md", "markdown", "PULLREQ_EDITMSG", "mkd", "mdwn", "mdown", "markdn", "mdtxt", "mdtext", "workbook"]
1222+file-types = ["md", "markdown", "PULLREQ_EDITMSG"]
1349roots = [".marksman.toml"]
1223roots = [".marksman.toml"]
1350language-servers = [ "marksman" ]
1224language-servers = [ "marksman" ]
1351indent = { tab-width = 2, unit = " " }
1225indent = { tab-width = 2, unit = " " }
1352
1226
1353[[grammar]]
1227[[grammar]]
1354name = "markdown"
1228name = "markdown"
1355−source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "aaf76797aa8ecd9a5e78e0ec3681941de6c945ee", subpath = "tree-sitter-markdown" }
1229+source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "fa6bfd51727e4bef99f7eec5f43947f73d64ea7d", subpath = "tree-sitter-markdown" }
1356
1230
1357[[language]]
1231[[language]]
1358name = "markdown.inline"
1232name = "markdown.inline"
1359scope = "source.markdown.inline"
1233scope = "source.markdown.inline"
1360injection-regex = "markdown\\.inline"
1234injection-regex = "markdown\\.inline"
1361file-types = []
1235file-types = []
1236+roots = []
1362grammar = "markdown_inline"
1237grammar = "markdown_inline"
1363
1238
1364[[grammar]]
1239[[grammar]]
1365name = "markdown_inline"
1240name = "markdown_inline"
1366−source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "aaf76797aa8ecd9a5e78e0ec3681941de6c945ee", subpath = "tree-sitter-markdown-inline" }
1241+source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "fa6bfd51727e4bef99f7eec5f43947f73d64ea7d", subpath = "tree-sitter-markdown-inline" }
1367
1242
1368[[language]]
1243[[language]]
1369name = "dart"
1244name = "dart"
@@ -1409,6 +1284,7 @@ source = { git = "https://github.com/camdencheek/tree-sitter-dockerfile", rev =
1409[[language]]
1284[[language]]
1410name = "git-commit"
1285name = "git-commit"
1411scope = "git.commitmsg"
1286scope = "git.commitmsg"
1287+roots = []
1412file-types = ["COMMIT_EDITMSG"]
1288file-types = ["COMMIT_EDITMSG"]
1413comment-token = "#"
1289comment-token = "#"
1414indent = { tab-width = 2, unit = " " }
1290indent = { tab-width = 2, unit = " " }
@@ -1417,12 +1293,13 @@ text-width = 72
1417
1293
1418[[grammar]]
1294[[grammar]]
1419name = "git-commit"
1295name = "git-commit"
1420−source = { git = "https://github.com/the-mikedavis/tree-sitter-git-commit", rev = "6f193a66e9aa872760823dff020960c6cedc37b3" }
1296+source = { git = "https://github.com/the-mikedavis/tree-sitter-git-commit", rev = "db88cffa3952dd2328b741af5d0fc69bdb76704f" }
1421
1297
1422[[language]]
1298[[language]]
1423name = "diff"
1299name = "diff"
1424scope = "source.diff"
1300scope = "source.diff"
1301+roots = []
1425−file-types = ["diff", "patch", "rej"]
1302+file-types = ["diff", "patch"]
1426injection-regex = "diff"
1303injection-regex = "diff"
1427comment-token = "#"
1304comment-token = "#"
1428indent = { tab-width = 2, unit = " " }
1305indent = { tab-width = 2, unit = " " }
@@ -1434,6 +1311,7 @@ source = { git = "https://github.com/the-mikedavis/tree-sitter-diff", rev = "fd7
1434[[language]]
1311[[language]]
1435name = "git-rebase"
1312name = "git-rebase"
1436scope = "source.gitrebase"
1313scope = "source.gitrebase"
1314+roots = []
1437file-types = ["git-rebase-todo"]
1315file-types = ["git-rebase-todo"]
1438injection-regex = "git-rebase"
1316injection-regex = "git-rebase"
1439comment-token = "#"
1317comment-token = "#"
@@ -1448,6 +1326,7 @@ name = "regex"
1448scope = "source.regex"
1326scope = "source.regex"
1449injection-regex = "regex"
1327injection-regex = "regex"
1450file-types = ["regex", ".Rbuildignore"]
1328file-types = ["regex", ".Rbuildignore"]
1329+roots = []
1451
1330
1452[[grammar]]
1331[[grammar]]
1453name = "regex"
1332name = "regex"
@@ -1456,6 +1335,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-regex", rev = "e1cf
1456[[language]]
1335[[language]]
1457name = "git-config"
1336name = "git-config"
1458scope = "source.gitconfig"
1337scope = "source.gitconfig"
1338+roots = []
1459file-types = [".gitmodules", ".gitconfig", { suffix = ".git/config" }, { suffix = ".config/git/config" }]
1339file-types = [".gitmodules", ".gitconfig", { suffix = ".git/config" }, { suffix = ".config/git/config" }]
1460injection-regex = "git-config"
1340injection-regex = "git-config"
1461comment-token = "#"
1341comment-token = "#"
@@ -1468,6 +1348,7 @@ source = { git = "https://github.com/the-mikedavis/tree-sitter-git-config", rev
1468[[language]]
1348[[language]]
1469name = "git-attributes"
1349name = "git-attributes"
1470scope = "source.gitattributes"
1350scope = "source.gitattributes"
1351+roots = []
1471file-types = [".gitattributes"]
1352file-types = [".gitattributes"]
1472injection-regex = "git-attributes"
1353injection-regex = "git-attributes"
1473comment-token = "#"
1354comment-token = "#"
@@ -1480,7 +1361,8 @@ source = { git = "https://github.com/mtoohey31/tree-sitter-gitattributes", rev =
1480[[language]]
1361[[language]]
1481name = "git-ignore"
1362name = "git-ignore"
1482scope = "source.gitignore"
1363scope = "source.gitignore"
1364+roots = []
1483−file-types = [".gitignore", ".gitignore_global", ".ignore", ".prettierignore", ".eslintignore", ".npmignore", "CODEOWNERS"]
1365+file-types = [".gitignore", ".gitignore_global"]
1484injection-regex = "git-ignore"
1366injection-regex = "git-ignore"
1485comment-token = "#"
1367comment-token = "#"
1486grammar = "gitignore"
1368grammar = "gitignore"
@@ -1494,7 +1376,7 @@ name = "graphql"
1494scope = "source.graphql"
1376scope = "source.graphql"
1495injection-regex = "graphql"
1377injection-regex = "graphql"
1496file-types = ["gql", "graphql", "graphqls"]
1378file-types = ["gql", "graphql", "graphqls"]
1497−language-servers = [ "graphql-language-service" ]
1379+roots = []
1498indent = { tab-width = 2, unit = " " }
1380indent = { tab-width = 2, unit = " " }
1499
1381
1500[[grammar]]
1382[[grammar]]
@@ -1521,6 +1403,7 @@ name = "iex"
1521scope = "source.iex"
1403scope = "source.iex"
1522injection-regex = "iex"
1404injection-regex = "iex"
1523file-types = ["iex"]
1405file-types = ["iex"]
1406+roots = []
1524
1407
1525[[grammar]]
1408[[grammar]]
1526name = "iex"
1409name = "iex"
@@ -1539,7 +1422,7 @@ indent = { tab-width = 2, unit = " " }
1539
1422
1540[[grammar]]
1423[[grammar]]
1541name = "rescript"
1424name = "rescript"
1542−source = { git = "https://github.com/jaredramirez/tree-sitter-rescript", rev = "467dcf99f68c47823d7b378779a6b282d7ef9782" }
1425+source = { git = "https://github.com/jaredramirez/tree-sitter-rescript", rev = "65609807c628477f3b94052e7ef895885ac51c3c" }
1543
1426
1544[[language]]
1427[[language]]
1545name = "erlang"
1428name = "erlang"
@@ -1583,6 +1466,7 @@ scope = "source.hcl"
1583injection-regex = "(hcl|tf|nomad)"
1466injection-regex = "(hcl|tf|nomad)"
1584language-id = "terraform"
1467language-id = "terraform"
1585file-types = ["hcl", "tf", "nomad"]
1468file-types = ["hcl", "tf", "nomad"]
1469+roots = []
1586comment-token = "#"
1470comment-token = "#"
1587indent = { tab-width = 2, unit = " " }
1471indent = { tab-width = 2, unit = " " }
1588language-servers = [ "terraform-ls" ]
1472language-servers = [ "terraform-ls" ]
@@ -1597,6 +1481,7 @@ name = "tfvars"
1597scope = "source.tfvars"
1481scope = "source.tfvars"
1598language-id = "terraform-vars"
1482language-id = "terraform-vars"
1599file-types = ["tfvars"]
1483file-types = ["tfvars"]
1484+roots = []
1600comment-token = "#"
1485comment-token = "#"
1601indent = { tab-width = 2, unit = " " }
1486indent = { tab-width = 2, unit = " " }
1602language-servers = [ "terraform-ls" ]
1487language-servers = [ "terraform-ls" ]
@@ -1608,6 +1493,7 @@ name = "org"
1608scope = "source.org"
1493scope = "source.org"
1609injection-regex = "org"
1494injection-regex = "org"
1610file-types = ["org"]
1495file-types = ["org"]
1496+roots = []
1611indent = { tab-width = 2, unit = " " }
1497indent = { tab-width = 2, unit = " " }
1612
1498
1613[[grammar]]
1499[[grammar]]
@@ -1619,6 +1505,7 @@ name = "solidity"
1619scope = "source.sol"
1505scope = "source.sol"
1620injection-regex = "(sol|solidity)"
1506injection-regex = "(sol|solidity)"
1621file-types = ["sol"]
1507file-types = ["sol"]
1508+roots = []
1622comment-token = "//"
1509comment-token = "//"
1623indent = { tab-width = 4, unit = " " }
1510indent = { tab-width = 4, unit = " " }
1624language-servers = [ "solc" ]
1511language-servers = [ "solc" ]
@@ -1636,23 +1523,20 @@ roots = ["gleam.toml"]
1636comment-token = "//"
1523comment-token = "//"
1637indent = { tab-width = 2, unit = " " }
1524indent = { tab-width = 2, unit = " " }
1638language-servers = [ "gleam" ]
1525language-servers = [ "gleam" ]
1639−auto-format = true
1640
1526
1641[[grammar]]
1527[[grammar]]
1642name = "gleam"
1528name = "gleam"
1643−source = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "b2afa4fd6bb41a7bf912b034c653c90af7ae5122" }
1529+source = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "ae79782c00656945db69641378e688cdb78d52c1" }
1644
1530
1645[[language]]
1531[[language]]
1646name = "ron"
1532name = "ron"
1647scope = "source.ron"
1533scope = "source.ron"
1648injection-regex = "ron"
1534injection-regex = "ron"
1649file-types = ["ron"]
1535file-types = ["ron"]
1536+roots = []
1650comment-token = "//"
1537comment-token = "//"
1651indent = { tab-width = 4, unit = " " }
1538indent = { tab-width = 4, unit = " " }
1652−
1653−[[grammar]]
1539+grammar = "rust"
1654−name = "ron"
1655−source = { git = "https://github.com/zee-editor/tree-sitter-ron", rev = "7762d709a0f7c1f9e269d0125a2e8a7a69006146" }
1656
1540
1657[[language]]
1541[[language]]
1658name = "robot"
1542name = "robot"
@@ -1660,19 +1544,21 @@ scope = "source.robot"
1660injection-regex = "robot"
1544injection-regex = "robot"
1661file-types = ["robot", "resource"]
1545file-types = ["robot", "resource"]
1662comment-token = "#"
1546comment-token = "#"
1547+roots = []
1663indent = { tab-width = 4, unit = " " }
1548indent = { tab-width = 4, unit = " " }
1664language-servers = [ "robotframework_ls" ]
1549language-servers = [ "robotframework_ls" ]
1665
1550
1666[[grammar]]
1551[[grammar]]
1667name = "robot"
1552name = "robot"
1668−source = { git = "https://github.com/Hubro/tree-sitter-robot", rev = "322e4cc65754d2b3fdef4f2f8a71e0762e3d13af" }
1553+source = { git = "https://github.com/Hubro/tree-sitter-robot", rev = "f1142bfaa6acfce95e25d2c6d18d218f4f533927" }
1669
1554
1670[[language]]
1555[[language]]
1671name = "r"
1556name = "r"
1672scope = "source.r"
1557scope = "source.r"
1673injection-regex = "(r|R)"
1558injection-regex = "(r|R)"
1674−file-types = ["r", "R", ".Rprofile", "Rprofile.site", ".RHistory"]
1559+file-types = ["r", "R", ".Rprofile", "Rprofile.site"]
1675shebangs = ["r", "R"]
1560shebangs = ["r", "R"]
1561+roots = []
1676comment-token = "#"
1562comment-token = "#"
1677indent = { tab-width = 2, unit = " " }
1563indent = { tab-width = 2, unit = " " }
1678language-servers = [ "r" ]
1564language-servers = [ "r" ]
@@ -1684,9 +1570,9 @@ source = { git = "https://github.com/r-lib/tree-sitter-r", rev = "cc04302e1bff76
1684[[language]]
1570[[language]]
1685name = "rmarkdown"
1571name = "rmarkdown"
1686scope = "source.rmd"
1572scope = "source.rmd"
1687−language-id = "rmd"
1688injection-regex = "(r|R)md"
1573injection-regex = "(r|R)md"
1689file-types = ["rmd", "Rmd"]
1574file-types = ["rmd", "Rmd"]
1575+roots = []
1690indent = { tab-width = 2, unit = " " }
1576indent = { tab-width = 2, unit = " " }
1691grammar = "markdown"
1577grammar = "markdown"
1692language-servers = [ "r" ]
1578language-servers = [ "r" ]
@@ -1710,6 +1596,7 @@ name = "erb"
1710scope = "text.html.erb"
1596scope = "text.html.erb"
1711injection-regex = "erb"
1597injection-regex = "erb"
1712file-types = ["erb"]
1598file-types = ["erb"]
1599+roots = []
1713indent = { tab-width = 2, unit = " " }
1600indent = { tab-width = 2, unit = " " }
1714grammar = "embedded-template"
1601grammar = "embedded-template"
1715
1602
@@ -1718,6 +1605,7 @@ name = "ejs"
1718scope = "text.html.ejs"
1605scope = "text.html.ejs"
1719injection-regex = "ejs"
1606injection-regex = "ejs"
1720file-types = ["ejs"]
1607file-types = ["ejs"]
1608+roots = []
1721indent = { tab-width = 2, unit = " " }
1609indent = { tab-width = 2, unit = " " }
1722grammar = "embedded-template"
1610grammar = "embedded-template"
1723
1611
@@ -1753,14 +1641,15 @@ source = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "
1753[[language]]
1641[[language]]
1754name = "sql"
1642name = "sql"
1755scope = "source.sql"
1643scope = "source.sql"
1756−file-types = ["sql", "dsql"]
1644+file-types = ["sql"]
1645+roots = []
1757comment-token = "--"
1646comment-token = "--"
1758indent = { tab-width = 4, unit = " " }
1647indent = { tab-width = 4, unit = " " }
1759injection-regex = "sql"
1648injection-regex = "sql"
1760
1649
1761[[grammar]]
1650[[grammar]]
1762name = "sql"
1651name = "sql"
1763−source = { git = "https://github.com/DerekStride/tree-sitter-sql", rev = "25be0b8f17e9189ad9e1b875869d025c5aec1286" }
1652+source = { git = "https://github.com/DerekStride/tree-sitter-sql", rev = "7cbac0472e5b8f8486ce64ffbcf1982d5cd5fc8d" }
1764
1653
1765[[language]]
1654[[language]]
1766name = "gdscript"
1655name = "gdscript"
@@ -1799,19 +1688,20 @@ scope = "source.nu"
1799injection-regex = "nu"
1688injection-regex = "nu"
1800file-types = ["nu"]
1689file-types = ["nu"]
1801shebangs = ["nu"]
1690shebangs = ["nu"]
1691+roots = []
1802comment-token = "#"
1692comment-token = "#"
1803indent = { tab-width = 2, unit = " " }
1693indent = { tab-width = 2, unit = " " }
1804−language-servers = [ "nu-lsp" ]
1805
1694
1806[[grammar]]
1695[[grammar]]
1807name = "nu"
1696name = "nu"
1808−source = { git = "https://github.com/nushell/tree-sitter-nu", rev = "98c11c491e3405c75affa1cf004097692da3dda2" }
1697+source = { git = "https://github.com/LhKipp/tree-sitter-nu", rev = "eb95bdac3abd73ef47e53f19c63e74a31405ebd2" }
1809
1698
1810[[language]]
1699[[language]]
1811name = "vala"
1700name = "vala"
1812scope = "source.vala"
1701scope = "source.vala"
1813injection-regex = "vala"
1702injection-regex = "vala"
1814file-types = ["vala", "vapi"]
1703file-types = ["vala", "vapi"]
1704+roots = []
1815comment-token = "//"
1705comment-token = "//"
1816indent = { tab-width = 2, unit = " " }
1706indent = { tab-width = 2, unit = " " }
1817language-servers = [ "vala-language-server" ]
1707language-servers = [ "vala-language-server" ]
@@ -1825,18 +1715,20 @@ name = "hare"
1825scope = "source.hare"
1715scope = "source.hare"
1826injection-regex = "hare"
1716injection-regex = "hare"
1827file-types = ["ha"]
1717file-types = ["ha"]
1718+roots = []
1828comment-token = "//"
1719comment-token = "//"
1829indent = { tab-width = 8, unit = "\t" }
1720indent = { tab-width = 8, unit = "\t" }
1830
1721
1831[[grammar]]
1722[[grammar]]
1832name = "hare"
1723name = "hare"
1833−source = { git = "https://git.sr.ht/~ecmma/tree-sitter-hare", rev = "2495958aaf3f93581c87ec020164255e80655331" }
1724+source = { git = "https://git.sr.ht/~ecmma/tree-sitter-hare", rev = "bc26a6a949f2e0d98b7bfc437d459b250900a165" }
1834
1725
1835[[language]]
1726[[language]]
1836name = "devicetree"
1727name = "devicetree"
1837scope = "source.devicetree"
1728scope = "source.devicetree"
1838injection-regex = "(dtsi?|devicetree|fdt)"
1729injection-regex = "(dtsi?|devicetree|fdt)"
1839file-types = ["dts", "dtsi"]
1730file-types = ["dts", "dtsi"]
1731+roots = []
1840comment-token = "//"
1732comment-token = "//"
1841indent = { tab-width = 4, unit = "\t" }
1733indent = { tab-width = 4, unit = "\t" }
1842
1734
@@ -1849,6 +1741,7 @@ name = "cairo"
1849scope = "source.cairo"
1741scope = "source.cairo"
1850injection-regex = "cairo"
1742injection-regex = "cairo"
1851file-types = ["cairo"]
1743file-types = ["cairo"]
1744+roots = []
1852comment-token = "//"
1745comment-token = "//"
1853indent = { tab-width = 4, unit = " " }
1746indent = { tab-width = 4, unit = " " }
1854# auto-format = true
1747# auto-format = true
@@ -1860,6 +1753,7 @@ name = "cpon"
1860scope = "scope.cpon"
1753scope = "scope.cpon"
1861injection-regex = "cpon"
1754injection-regex = "cpon"
1862file-types = ["cpon", "cp"]
1755file-types = ["cpon", "cp"]
1756+roots = []
1863auto-format = true
1757auto-format = true
1864comment-token = "//"
1758comment-token = "//"
1865indent = { tab-width = 2, unit = " " }
1759indent = { tab-width = 2, unit = " " }
@@ -1886,7 +1780,8 @@ source = { git = "https://github.com/ap29600/tree-sitter-odin", rev = "b219207e4
1886name = "meson"
1780name = "meson"
1887scope = "source.meson"
1781scope = "source.meson"
1888injection-regex = "meson"
1782injection-regex = "meson"
1889−file-types = ["meson.build", "meson_options.txt"]
1783+file-types = ["meson.build"]
1784+roots = []
1890comment-token = "#"
1785comment-token = "#"
1891indent = { tab-width = 2, unit = " " }
1786indent = { tab-width = 2, unit = " " }
1892
1787
@@ -1898,6 +1793,7 @@ source = { git = "https://github.com/staysail/tree-sitter-meson", rev = "32a83e8
1898name = "sshclientconfig"
1793name = "sshclientconfig"
1899scope = "source.sshclientconfig"
1794scope = "source.sshclientconfig"
1900file-types = [{ suffix = ".ssh/config" }, { suffix = "/etc/ssh/ssh_config" }]
1795file-types = [{ suffix = ".ssh/config" }, { suffix = "/etc/ssh/ssh_config" }]
1796+roots = []
1901comment-token = "#"
1797comment-token = "#"
1902
1798
1903[[grammar]]
1799[[grammar]]
@@ -1908,14 +1804,14 @@ source = { git = "https://github.com/metio/tree-sitter-ssh-client-config", rev =
1908name = "scheme"
1804name = "scheme"
1909scope = "source.scheme"
1805scope = "source.scheme"
1910injection-regex = "scheme"
1806injection-regex = "scheme"
1911−file-types = ["ss", "scm"]
1807+file-types = ["ss"] # "scm",
1912−shebangs = ["scheme", "guile", "chicken"]
1808+roots = []
1913comment-token = ";"
1809comment-token = ";"
1914indent = { tab-width = 2, unit = " " }
1810indent = { tab-width = 2, unit = " " }
1915
1811
1916[[grammar]]
1812[[grammar]]
1917name = "scheme"
1813name = "scheme"
1918−source = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af3af6c9356b936f8a515a1e449c32e804c2b1a8" }
1814+source = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "c0741320bfca6b7b5b7a13b5171275951e96a842" }
1919
1815
1920[[language]]
1816[[language]]
1921name = "v"
1817name = "v"
@@ -1936,6 +1832,7 @@ source = {git = "https://github.com/v-analyzer/v-analyzer", subpath = "tree_sitt
1936name = "verilog"
1832name = "verilog"
1937scope = "source.verilog"
1833scope = "source.verilog"
1938file-types = ["v", "vh", "sv", "svh"]
1834file-types = ["v", "vh", "sv", "svh"]
1835+roots = []
1939comment-token = "//"
1836comment-token = "//"
1940language-servers = [ "svlangserver" ]
1837language-servers = [ "svlangserver" ]
1941indent = { tab-width = 2, unit = " " }
1838indent = { tab-width = 2, unit = " " }
@@ -1950,6 +1847,7 @@ name = "edoc"
1950scope = "source.edoc"
1847scope = "source.edoc"
1951file-types = ["edoc", "edoc.in"]
1848file-types = ["edoc", "edoc.in"]
1952injection-regex = "edoc"
1849injection-regex = "edoc"
1850+roots = []
1953indent = { tab-width = 4, unit = " " }
1851indent = { tab-width = 4, unit = " " }
1954
1852
1955[[grammar]]
1853[[grammar]]
@@ -1961,6 +1859,7 @@ name = "jsdoc"
1961scope = "source.jsdoc"
1859scope = "source.jsdoc"
1962injection-regex = "jsdoc"
1860injection-regex = "jsdoc"
1963file-types = ["jsdoc"]
1861file-types = ["jsdoc"]
1862+roots = []
1964indent = { tab-width = 2, unit = " " }
1863indent = { tab-width = 2, unit = " " }
1965
1864
1966[[grammar]]
1865[[grammar]]
@@ -1972,6 +1871,7 @@ name = "openscad"
1972scope = "source.openscad"
1871scope = "source.openscad"
1973injection-regex = "openscad"
1872injection-regex = "openscad"
1974file-types = ["scad"]
1873file-types = ["scad"]
1874+roots = []
1975comment-token = "//"
1875comment-token = "//"
1976language-servers = [ "openscad-lsp" ]
1876language-servers = [ "openscad-lsp" ]
1977indent = { tab-width = 2, unit = "\t" }
1877indent = { tab-width = 2, unit = "\t" }
@@ -2012,7 +1912,8 @@ source = { git = "https://github.com/sogaiu/tree-sitter-clojure", rev = "e57c569
2012name = "starlark"
1912name = "starlark"
2013scope = "source.starlark"
1913scope = "source.starlark"
2014injection-regex = "(starlark|bzl|bazel)"
1914injection-regex = "(starlark|bzl|bazel)"
2015−file-types = ["bzl", "bazel", "BUILD", "star"]
1915+file-types = ["bzl", "bazel", "BUILD"]
1916+roots = []
2016comment-token = "#"
1917comment-token = "#"
2017indent = { tab-width = 4, unit = " " }
1918indent = { tab-width = 4, unit = " " }
2018grammar = "python"
1919grammar = "python"
@@ -2021,6 +1922,7 @@ grammar = "python"
2021name = "elvish"
1922name = "elvish"
2022scope = "source.elvish"
1923scope = "source.elvish"
2023file-types = ["elv"]
1924file-types = ["elv"]
1925+roots = []
2024comment-token = "#"
1926comment-token = "#"
2025indent = { tab-width = 2, unit = " " }
1927indent = { tab-width = 2, unit = " " }
2026language-servers = [ "elvish" ]
1928language-servers = [ "elvish" ]
@@ -2036,6 +1938,7 @@ scope = "source.idr"
2036injection-regex = "idr"
1938injection-regex = "idr"
2037file-types = ["idr"]
1939file-types = ["idr"]
2038shebangs = []
1940shebangs = []
1941+roots = []
2039comment-token = "--"
1942comment-token = "--"
2040indent = { tab-width = 2, unit = " " }
1943indent = { tab-width = 2, unit = " " }
2041language-servers = [ "idris2-lsp" ]
1944language-servers = [ "idris2-lsp" ]
@@ -2059,18 +1962,20 @@ name = "ungrammar"
2059scope = "source.ungrammar"
1962scope = "source.ungrammar"
2060injection-regex = "ungrammar"
1963injection-regex = "ungrammar"
2061file-types = ["ungram", "ungrammar"]
1964file-types = ["ungram", "ungrammar"]
1965+roots = []
2062comment-token = "//"
1966comment-token = "//"
2063indent = { tab-width = 2, unit = " " }
1967indent = { tab-width = 2, unit = " " }
2064
1968
2065[[grammar]]
1969[[grammar]]
2066name = "ungrammar"
1970name = "ungrammar"
2067−source = { git = "https://github.com/Philipp-M/tree-sitter-ungrammar", rev = "a7e104629cff5a8b7367187610631e8f5eb7c6ea" }
1971+source = { git = "https://github.com/Philipp-M/tree-sitter-ungrammar", rev = "0113de880a58ea14f2a75802e9b99fcc25003d9c" }
2068
1972
2069[[language]]
1973[[language]]
2070name = "dot"
1974name = "dot"
2071scope = "source.dot"
1975scope = "source.dot"
2072injection-regex = "dot"
1976injection-regex = "dot"
2073file-types = ["dot"]
1977file-types = ["dot"]
1978+roots = []
2074comment-token = "//"
1979comment-token = "//"
2075indent = { tab-width = 4, unit = " " }
1980indent = { tab-width = 4, unit = " " }
2076language-servers = [ "dot-language-server" ]
1981language-servers = [ "dot-language-server" ]
@@ -2100,19 +2005,21 @@ name = "slint"
2100scope = "source.slint"
2005scope = "source.slint"
2101injection-regex = "slint"
2006injection-regex = "slint"
2102file-types = ["slint"]
2007file-types = ["slint"]
2008+roots = []
2103comment-token = "//"
2009comment-token = "//"
2104indent = { tab-width = 4, unit = " " }
2010indent = { tab-width = 4, unit = " " }
2105language-servers = [ "slint-lsp" ]
2011language-servers = [ "slint-lsp" ]
2106
2012
2107[[grammar]]
2013[[grammar]]
2108name = "slint"
2014name = "slint"
2109−source = { git = "https://github.com/jrmoulton/tree-sitter-slint", rev = "00c8a2d3645766f68c0d0460086c0a994e5b0d85" }
2015+source = { git = "https://github.com/jrmoulton/tree-sitter-slint", rev = "0d4dda94f96623302dfc234e06be62a5717f47da" }
2110
2016
2111[[language]]
2017[[language]]
2112name = "task"
2018name = "task"
2113scope = "source.task"
2019scope = "source.task"
2114injection-regex = "task"
2020injection-regex = "task"
2115file-types = ["task"]
2021file-types = ["task"]
2022+roots = []
2116comment-token = "#"
2023comment-token = "#"
2117indent = { tab-width = 2, unit = " " }
2024indent = { tab-width = 2, unit = " " }
2118
2025
@@ -2125,6 +2032,7 @@ name = "xit"
2125scope = "source.xit"
2032scope = "source.xit"
2126injection-regex = "xit"
2033injection-regex = "xit"
2127file-types = ["xit"]
2034file-types = ["xit"]
2035+roots = []
2128indent = { tab-width = 4, unit = " " }
2036indent = { tab-width = 4, unit = " " }
2129
2037
2130[[grammar]]
2038[[grammar]]
@@ -2142,13 +2050,14 @@ roots = ["edgedb.toml"]
2142
2050
2143[[grammar]]
2051[[grammar]]
2144name ="esdl"
2052name ="esdl"
2145−source = { git = "https://github.com/greym0uth/tree-sitter-esdl", rev = "df83acc8cacd0cfb139eecee0e718dc32c4f92e2" }
2053+source = { git = "https://github.com/greym0uth/tree-sitter-esdl", rev = "b840c8a8028127e0a7c6e6c45141adade2bd75cf" }
2146
2054
2147[[language]]
2055[[language]]
2148name = "pascal"
2056name = "pascal"
2149scope = "source.pascal"
2057scope = "source.pascal"
2150injection-regex = "pascal"
2058injection-regex = "pascal"
2151file-types = ["pas", "pp", "inc", "lpr", "lfm"]
2059file-types = ["pas", "pp", "inc", "lpr", "lfm"]
2060+roots = []
2152comment-token = "//"
2061comment-token = "//"
2153indent = { tab-width = 2, unit = " " }
2062indent = { tab-width = 2, unit = " " }
2154language-servers = [ "pasls" ]
2063language-servers = [ "pasls" ]
@@ -2163,6 +2072,7 @@ scope = "source.sml"
2163injection-regex = "sml"
2072injection-regex = "sml"
2164file-types = ["sml"]
2073file-types = ["sml"]
2165comment-token = "(*"
2074comment-token = "(*"
2075+roots = []
2166
2076
2167[[grammar]]
2077[[grammar]]
2168name = "sml"
2078name = "sml"
@@ -2186,17 +2096,19 @@ name = "astro"
2186scope = "source.astro"
2096scope = "source.astro"
2187injection-regex = "astro"
2097injection-regex = "astro"
2188file-types = ["astro"]
2098file-types = ["astro"]
2099+roots = []
2189indent = { tab-width = 2, unit = " " }
2100indent = { tab-width = 2, unit = " " }
2190
2101
2191[[grammar]]
2102[[grammar]]
2192name = "astro"
2103name = "astro"
2193−source = { git = "https://github.com/virchau13/tree-sitter-astro", rev = "947e93089e60c66e681eba22283f4037841451e7" }
2104+source = { git = "https://github.com/virchau13/tree-sitter-astro", rev = "5f5c3e73c45967df9aa42f861fad2d77cd4e0900" }
2194
2105
2195[[language]]
2106[[language]]
2196name = "bass"
2107name = "bass"
2197scope = "source.bass"
2108scope = "source.bass"
2198injection-regex = "bass"
2109injection-regex = "bass"
2199file-types = ["bass"]
2110file-types = ["bass"]
2111+roots = []
2200comment-token = ";"
2112comment-token = ";"
2201indent = { tab-width = 2, unit = " " }
2113indent = { tab-width = 2, unit = " " }
2202language-servers = [ "bass" ]
2114language-servers = [ "bass" ]
@@ -2210,6 +2122,7 @@ name = "wat"
2210scope = "source.wat"
2122scope = "source.wat"
2211comment-token = ";;"
2123comment-token = ";;"
2212file-types = ["wat"]
2124file-types = ["wat"]
2125+roots = []
2213
2126
2214[[grammar]]
2127[[grammar]]
2215name = "wat"
2128name = "wat"
@@ -2220,6 +2133,7 @@ name = "wast"
2220scope = "source.wast"
2133scope = "source.wast"
2221comment-token = ";;"
2134comment-token = ";;"
2222file-types = ["wast"]
2135file-types = ["wast"]
2136+roots = []
2223
2137
2224[[grammar]]
2138[[grammar]]
2225name = "wast"
2139name = "wast"
@@ -2229,6 +2143,7 @@ source = { git = "https://github.com/wasm-lsp/tree-sitter-wasm", rev = "2ca28a9f
2229name = "d"
2143name = "d"
2230scope = "source.d"
2144scope = "source.d"
2231file-types = [ "d", "dd" ]
2145file-types = [ "d", "dd" ]
2146+roots = []
2232comment-token = "//"
2147comment-token = "//"
2233injection-regex = "d"
2148injection-regex = "d"
2234indent = { tab-width = 4, unit = " "}
2149indent = { tab-width = 4, unit = " "}
@@ -2237,12 +2152,13 @@ formatter = { command = "dfmt" }
2237
2152
2238[[grammar]]
2153[[grammar]]
2239name = "d"
2154name = "d"
2240−source = { git = "https://github.com/gdamore/tree-sitter-d", rev = "5566f8ce8fc24186fad06170bbb3c8d97c935d74" }
2155+source = { git = "https://github.com/gdamore/tree-sitter-d", rev="601c4a1e8310fb2f3c43fa8a923d0d27497f3c04" }
2241
2156
2242[[language]]
2157[[language]]
2243name = "vhs"
2158name = "vhs"
2244scope = "source.vhs"
2159scope = "source.vhs"
2245file-types = ["tape"]
2160file-types = ["tape"]
2161+roots = []
2246comment-token = "#"
2162comment-token = "#"
2247indent = { tab-width = 2, unit = " " }
2163indent = { tab-width = 2, unit = " " }
2248grammar = "vhs"
2164grammar = "vhs"
@@ -2255,76 +2171,21 @@ source = { git = "https://github.com/charmbracelet/tree-sitter-vhs", rev = "c6d8
2255name = "kdl"
2171name = "kdl"
2256scope = "source.kdl"
2172scope = "source.kdl"
2257file-types = ["kdl"]
2173file-types = ["kdl"]
2174+roots = []
2258comment-token = "//"
2175comment-token = "//"
2259injection-regex = "kdl"
2176injection-regex = "kdl"
2260
2177
2261[[grammar]]
2178[[grammar]]
2262name = "kdl"
2179name = "kdl"
2263−source = { git = "https://github.com/amaanq/tree-sitter-kdl", rev = "3ca569b9f9af43593c24f9e7a21f02f43a13bb88" }
2180+source = { git = "https://github.com/Unoqwy/tree-sitter-kdl", rev = "e1cd292c6d15df6610484e1d4b5c987ecad52373" }
2264
2181
2265[[language]]
2182[[language]]
2266name = "xml"
2183name = "xml"
2267scope = "source.xml"
2184scope = "source.xml"
2268injection-regex = "xml"
2185injection-regex = "xml"
2269−file-types = [
2186+file-types = ["xml", "mobileconfig", "plist", "xib", "storyboard", "svg", "xsd", "gml", "xaml", "gir"]
2270− "xml",
2271− "mobileconfig",
2272− "plist",
2273− "xib",
2274− "storyboard",
2275− "svg",
2276− "xsd",
2277− "gml",
2278− "xaml",
2279− "gir",
2280− "rss",
2281− "atom",
2282− "opml",
2283− "policy",
2284− "ascx",
2285− "axml",
2286− "axaml",
2287− "bpmn",
2288− "cpt",
2289− "csl",
2290− "csproj.user",
2291− "dita",
2292− "ditamap",
2293− "dtml",
2294− "fxml",
2295− "iml",
2296− "isml",
2297− "jmx",
2298− "launch",
2299− "menu",
2300− "mxml",
2301− "nuspec",
2302− "pt",
2303− "publishsettings",
2304− "pubxml",
2305− "pubxml.user",
2306− "rbxlx",
2307− "rbxmx",
2308− "rng",
2309− "shproj",
2310− "tld",
2311− "tmx",
2312− "vbproj.user",
2313− "vcxproj",
2314− "vcxproj.filters",
2315− "wsdl",
2316− "wxi",
2317− "wxs",
2318− "xbl",
2319− "xlf",
2320− "xliff",
2321− "xpdl",
2322− "xul",
2323− "xoml",
2324− "musicxml",
2325− "glif"
2326−]
2327indent = { tab-width = 2, unit = " " }
2187indent = { tab-width = 2, unit = " " }
2188+roots = []
2328
2189
2329[language.auto-pairs]
2190[language.auto-pairs]
2330'(' = ')'
2191'(' = ')'
@@ -2345,6 +2206,7 @@ scope = "source.dtd"
2345injection-regex = "dtd"
2206injection-regex = "dtd"
2346file-types = ["dtd", "ent"]
2207file-types = ["dtd", "ent"]
2347indent = {tab-width = 2, unit = " "}
2208indent = {tab-width = 2, unit = " "}
2209+roots = []
2348
2210
2349[language.auto-pairs]
2211[language.auto-pairs]
2350'(' = ')'
2212'(' = ')'
@@ -2362,6 +2224,7 @@ name = "wit"
2362scope = "source.wit"
2224scope = "source.wit"
2363injection-regex = "wit"
2225injection-regex = "wit"
2364file-types = ["wit"]
2226file-types = ["wit"]
2227+roots = []
2365comment-token = "//"
2228comment-token = "//"
2366indent = { tab-width = 2, unit = " " }
2229indent = { tab-width = 2, unit = " " }
2367
2230
@@ -2380,10 +2243,11 @@ source = { git = "https://github.com/hh9527/tree-sitter-wit", rev = "c917790ab9a
2380[[language]]
2243[[language]]
2381name = "env"
2244name = "env"
2382scope = "source.env"
2245scope = "source.env"
2383−file-types = [".env", ".env.local", ".env.development", ".env.production", ".env.dist", ".envrc", ".envrc.local", ".envrc.private"]
2246+file-types = [".env", ".env.local", ".env.development", ".env.production", ".env.dist", ".envrc"]
2384injection-regex = "env"
2247injection-regex = "env"
2385comment-token = "#"
2248comment-token = "#"
2386indent = { tab-width = 4, unit = "\t" }
2249indent = { tab-width = 4, unit = "\t" }
2250+roots = []
2387grammar = "bash"
2251grammar = "bash"
2388
2252
2389[[language]]
2253[[language]]
@@ -2407,15 +2271,12 @@ file-types = [
2407 "container",
2271 "container",
2408 "volume",
2272 "volume",
2409 "kube",
2273 "kube",
2410− "network",
2274+ "network"
2411− ".editorconfig",
2412− "properties",
2413− "cfg",
2414− "directory"
2415]
2275]
2416injection-regex = "ini"
2276injection-regex = "ini"
2417comment-token = "#"
2277comment-token = "#"
2418indent = { tab-width = 4, unit = "\t" }
2278indent = { tab-width = 4, unit = "\t" }
2279+roots = []
2419
2280
2420[[grammar]]
2281[[grammar]]
2421name = "ini"
2282name = "ini"
@@ -2425,6 +2286,7 @@ source = { git = "https://github.com/justinmk/tree-sitter-ini", rev = "4d247fb87
2425name = "bicep"
2286name = "bicep"
2426scope = "source.bicep"
2287scope = "source.bicep"
2427file-types = ["bicep"]
2288file-types = ["bicep"]
2289+roots = []
2428auto-format = true
2290auto-format = true
2429comment-token = "//"
2291comment-token = "//"
2430indent = { tab-width = 2, unit = " "}
2292indent = { tab-width = 2, unit = " "}
@@ -2438,6 +2300,7 @@ source = { git = "https://github.com/the-mikedavis/tree-sitter-bicep", rev = "d8
2438name = "qml"
2300name = "qml"
2439scope = "source.qml"
2301scope = "source.qml"
2440file-types = ["qml"]
2302file-types = ["qml"]
2303+roots = []
2441language-servers = [ "qmlls" ]
2304language-servers = [ "qmlls" ]
2442indent = { tab-width = 4, unit = " " }
2305indent = { tab-width = 4, unit = " " }
2443grammar = "qmljs"
2306grammar = "qmljs"
@@ -2451,6 +2314,7 @@ name = "mermaid"
2451scope = "source.mermaid"
2314scope = "source.mermaid"
2452injection-regex = "mermaid"
2315injection-regex = "mermaid"
2453file-types = ["mermaid"]
2316file-types = ["mermaid"]
2317+roots = []
2454comment-token = "%%"
2318comment-token = "%%"
2455indent = { tab-width = 4, unit = " " }
2319indent = { tab-width = 4, unit = " " }
2456
2320
@@ -2464,11 +2328,12 @@ scope = "source.m"
2464file-types = ["m"]
2328file-types = ["m"]
2465comment-token = "%"
2329comment-token = "%"
2466shebangs = ["octave-cli", "matlab"]
2330shebangs = ["octave-cli", "matlab"]
2331+roots = []
2467indent = { tab-width = 2, unit = " " }
2332indent = { tab-width = 2, unit = " " }
2468
2333
2469[[grammar]]
2334[[grammar]]
2470name = "matlab"
2335name = "matlab"
2471−source = { git = "https://github.com/acristoffers/tree-sitter-matlab", rev = "6071891a8c39600203eba20513666cf93b4d650a" }
2336+source = { git = "https://github.com/acristoffers/tree-sitter-matlab", rev = "676117eafa64afedc8380a921a77cd9f2244bc6b" }
2472
2337
2473[[language]]
2338[[language]]
2474name = "ponylang"
2339name = "ponylang"
@@ -2488,6 +2353,7 @@ name = "dhall"
2488scope = "source.dhall"
2353scope = "source.dhall"
2489injection-regex = "dhall"
2354injection-regex = "dhall"
2490file-types = ["dhall"]
2355file-types = ["dhall"]
2356+roots = []
2491comment-token = "--"
2357comment-token = "--"
2492indent = { tab-width = 2, unit = " " }
2358indent = { tab-width = 2, unit = " " }
2493language-servers = [ "dhall-lsp-server" ]
2359language-servers = [ "dhall-lsp-server" ]
@@ -2502,6 +2368,7 @@ name = "sage"
2502scope = "source.sage"
2368scope = "source.sage"
2503file-types = ["sage"]
2369file-types = ["sage"]
2504injection-regex = "sage"
2370injection-regex = "sage"
2371+roots = []
2505comment-token = "#"
2372comment-token = "#"
2506indent = { tab-width = 4, unit = " " }
2373indent = { tab-width = 4, unit = " " }
2507grammar = "python"
2374grammar = "python"
@@ -2512,6 +2379,7 @@ scope = "source.msbuild"
2512injection-regex = "msbuild"
2379injection-regex = "msbuild"
2513file-types = ["proj", "vbproj", "csproj", "fsproj", "targets", "props"]
2380file-types = ["proj", "vbproj", "csproj", "fsproj", "targets", "props"]
2514indent = { tab-width = 2, unit = " " }
2381indent = { tab-width = 2, unit = " " }
2382+roots = []
2515grammar = "xml"
2383grammar = "xml"
2516
2384
2517[language.auto-pairs]
2385[language.auto-pairs]
@@ -2527,6 +2395,7 @@ name = "pem"
2527scope = "source.pem"
2395scope = "source.pem"
2528file-types = ["pem", "cert", "crt"]
2396file-types = ["pem", "cert", "crt"]
2529injection-regex = "pem"
2397injection-regex = "pem"
2398+roots = []
2530grammar = "pem"
2399grammar = "pem"
2531
2400
2532[[grammar]]
2401[[grammar]]
@@ -2537,6 +2406,7 @@ source = { git = "https://github.com/mtoohey31/tree-sitter-pem", rev = "be67a433
2537name = "passwd"
2406name = "passwd"
2538scope = "source.passwd"
2407scope = "source.passwd"
2539file-types = ["passwd"]
2408file-types = ["passwd"]
2409+roots = []
2540
2410
2541[[grammar]]
2411[[grammar]]
2542name = "passwd"
2412name = "passwd"
@@ -2546,6 +2416,7 @@ source = { git = "https://github.com/ath3/tree-sitter-passwd", rev = "20239395ea
2546name = "hosts"
2416name = "hosts"
2547scope = "source.hosts"
2417scope = "source.hosts"
2548file-types = ["hosts"]
2418file-types = ["hosts"]
2419+roots = []
2549comment-token = "#"
2420comment-token = "#"
2550
2421
2551[[grammar]]
2422[[grammar]]
@@ -2557,18 +2428,20 @@ name = "uxntal"
2557scope = "source.tal"
2428scope = "source.tal"
2558injection-regex = "tal"
2429injection-regex = "tal"
2559file-types = ["tal"]
2430file-types = ["tal"]
2431+roots = []
2560auto-format = false
2432auto-format = false
2561comment-token = "("
2433comment-token = "("
2562
2434
2563[[grammar]]
2435[[grammar]]
2564name = "uxntal"
2436name = "uxntal"
2565−source = { git = "https://github.com/Jummit/tree-sitter-uxntal", rev = "d68406066648cd6db4c6a2f11ec305af02079884" }
2437+source = { git = "https://github.com/Jummit/tree-sitter-uxntal", rev = "9297e95ef74380b0ad84c4fd98f91e9f6e4319e6" }
2566
2438
2567[[language]]
2439[[language]]
2568name = "yuck"
2440name = "yuck"
2569scope = "source.yuck"
2441scope = "source.yuck"
2570injection-regex = "yuck"
2442injection-regex = "yuck"
2571file-types = ["yuck"]
2443file-types = ["yuck"]
2444+roots = []
2572comment-token = ";"
2445comment-token = ";"
2573indent = { tab-width = 2, unit = " " }
2446indent = { tab-width = 2, unit = " " }
2574
2447
@@ -2581,6 +2454,7 @@ name = "prql"
2581scope = "source.prql"
2454scope = "source.prql"
2582injection-regex = "prql"
2455injection-regex = "prql"
2583file-types = ["prql"]
2456file-types = ["prql"]
2457+roots = []
2584comment-token = "#"
2458comment-token = "#"
2585indent = { tab-width = 4, unit = " " }
2459indent = { tab-width = 4, unit = " " }
2586
2460
@@ -2592,6 +2466,7 @@ source = { git = "https://github.com/PRQL/tree-sitter-prql", rev = "09e158cd3650
2592name = "po"
2466name = "po"
2593scope = "source.po"
2467scope = "source.po"
2594file-types = ["po", "pot"]
2468file-types = ["po", "pot"]
2469+roots = []
2595comment-token = "#"
2470comment-token = "#"
2596
2471
2597[[grammar]]
2472[[grammar]]
@@ -2601,8 +2476,9 @@ source = { git = "https://github.com/erasin/tree-sitter-po", rev = "417cee9abb20
2601[[language]]
2476[[language]]
2602name = "nasm"
2477name = "nasm"
2603scope = "source.nasm"
2478scope = "source.nasm"
2604−file-types = ["asm", "S", "nasm"]
2479+file-types = ["asm", "s", "S", "nasm"]
2605injection-regex = "n?asm"
2480injection-regex = "n?asm"
2481+roots = []
2606comment-token = ";"
2482comment-token = ";"
2607indent = { tab-width = 8, unit = " " }
2483indent = { tab-width = 8, unit = " " }
2608
2484
@@ -2610,23 +2486,12 @@ indent = { tab-width = 8, unit = " " }
2610name = "nasm"
2486name = "nasm"
2611source = { git = "https://github.com/naclsn/tree-sitter-nasm", rev = "a0db15db6fcfb1bf2cc8702500e55e558825c48b" }
2487source = { git = "https://github.com/naclsn/tree-sitter-nasm", rev = "a0db15db6fcfb1bf2cc8702500e55e558825c48b" }
2612
2488
2613−[[language]]
2614−name = "gas"
2615−scope = "source.gas"
2616−file-types = ["s"]
2617−injection-regex = "gas"
2618−comment-token = "#"
2619−indent = { tab-width = 8, unit = " " }
2620−
2621−[[grammar]]
2622−name = "gas"
2623−source = { git = "https://github.com/sirius94/tree-sitter-gas", rev = "60f443646b20edee3b7bf18f3a4fb91dc214259a" }
2624−
2625[[language]]
2489[[language]]
2626name = "rst"
2490name = "rst"
2627scope = "source.rst"
2491scope = "source.rst"
2628comment-token = ".."
2492comment-token = ".."
2629file-types = ["rst"]
2493file-types = ["rst"]
2494+roots = []
2630
2495
2631[[grammar]]
2496[[grammar]]
2632name = "rst"
2497name = "rst"
@@ -2637,6 +2502,7 @@ name = "capnp"
2637scope = "source.capnp"
2502scope = "source.capnp"
2638injection-regex = "capnp"
2503injection-regex = "capnp"
2639file-types = ["capnp"]
2504file-types = ["capnp"]
2505+roots = []
2640comment-token = "#"
2506comment-token = "#"
2641indent = { tab-width = 2, unit = " " }
2507indent = { tab-width = 2, unit = " " }
2642
2508
@@ -2662,6 +2528,7 @@ source = { git = "https://github.com/indoorvivants/tree-sitter-smithy", rev = "c
2662name = "vhdl"
2528name = "vhdl"
2663scope = "source.vhdl"
2529scope = "source.vhdl"
2664file-types = ["vhd", "vhdl"]
2530file-types = ["vhd", "vhdl"]
2531+roots = []
2665comment-token = "--"
2532comment-token = "--"
2666language-servers = [ "vhdl_ls" ]
2533language-servers = [ "vhdl_ls" ]
2667indent = { tab-width = 2, unit = " " }
2534indent = { tab-width = 2, unit = " " }
@@ -2673,6 +2540,7 @@ source = { git = "https://github.com/teburd/tree-sitter-vhdl", rev = "c57313adee
2673
2540
2674[[language]]
2541[[language]]
2675name = "rego"
2542name = "rego"
2543+roots = []
2676scope = "source.rego"
2544scope = "source.rego"
2677injection-regex = "rego"
2545injection-regex = "rego"
2678file-types = ["rego"]
2546file-types = ["rego"]
@@ -2683,7 +2551,7 @@ grammar = "rego"
2683
2551
2684[[grammar]]
2552[[grammar]]
2685name = "rego"
2553name = "rego"
2686−source = { git = "https://github.com/FallenAngel97/tree-sitter-rego", rev = "9ac75e71b2d791e0aadeef68098319d86a2a14cf" }
2554+source = { git = "https://github.com/FallenAngel97/tree-sitter-rego", rev = "b2667c975f07b33be3ceb83bea5cfbad88095866" }
2687
2555
2688[[language]]
2556[[language]]
2689name = "nim"
2557name = "nim"
@@ -2691,6 +2559,7 @@ scope = "source.nim"
2691injection-regex = "nim"
2559injection-regex = "nim"
2692file-types = ["nim", "nims", "nimble"]
2560file-types = ["nim", "nims", "nimble"]
2693shebangs = []
2561shebangs = []
2562+roots = []
2694comment-token = "#"
2563comment-token = "#"
2695indent = { tab-width = 2, unit = " " }
2564indent = { tab-width = 2, unit = " " }
2696language-servers = [ "nimlangserver" ]
2565language-servers = [ "nimlangserver" ]
@@ -2714,13 +2583,13 @@ file-types = [ "cabal" ]
2714roots = ["cabal.project", "Setup.hs"]
2583roots = ["cabal.project", "Setup.hs"]
2715indent = { tab-width = 2, unit = " " }
2584indent = { tab-width = 2, unit = " " }
2716comment-token = "--"
2585comment-token = "--"
2717−language-servers = [ "haskell-language-server" ]
2718
2586
2719[[language]]
2587[[language]]
2720name = "hurl"
2588name = "hurl"
2721scope = "source.hurl"
2589scope = "source.hurl"
2722injection-regex = "hurl"
2590injection-regex = "hurl"
2723file-types = ["hurl"]
2591file-types = ["hurl"]
2592+roots = []
2724comment-token = "#"
2593comment-token = "#"
2725indent = { tab-width = 2, unit = " " }
2594indent = { tab-width = 2, unit = " " }
2726
2595
@@ -2731,6 +2600,7 @@ source = { git = "https://github.com/pfeiferj/tree-sitter-hurl", rev = "264c4206
2731[[language]]
2600[[language]]
2732name = "markdoc"
2601name = "markdoc"
2733scope = "text.markdoc"
2602scope = "text.markdoc"
2603+roots = []
2734file-types = ["mdoc"]
2604file-types = ["mdoc"]
2735language-servers = [ "markdoc-ls" ]
2605language-servers = [ "markdoc-ls" ]
2736
2606
@@ -2743,6 +2613,7 @@ name = "opencl"
2743scope = "source.cl"
2613scope = "source.cl"
2744injection-regex = "(cl|opencl)"
2614injection-regex = "(cl|opencl)"
2745file-types = ["cl"]
2615file-types = ["cl"]
2616+roots = []
2746comment-token = "//"
2617comment-token = "//"
2747language-servers = [ "clangd" ]
2618language-servers = [ "clangd" ]
2748
2619
@@ -2755,6 +2626,7 @@ name = "just"
2755scope = "source.just"
2626scope = "source.just"
2756file-types = ["justfile", "Justfile", ".justfile", ".Justfile"]
2627file-types = ["justfile", "Justfile", ".justfile", ".Justfile"]
2757injection-regex = "just"
2628injection-regex = "just"
2629+roots = []
2758comment-token = "#"
2630comment-token = "#"
2759indent = { tab-width = 4, unit = "\t" }
2631indent = { tab-width = 4, unit = "\t" }
2760
2632
@@ -2762,52 +2634,40 @@ indent = { tab-width = 4, unit = "\t" }
2762name = "just"
2634name = "just"
2763source = { git = "https://github.com/IndianBoy42/tree-sitter-just", rev = "8af0aab79854aaf25b620a52c39485849922f766" }
2635source = { git = "https://github.com/IndianBoy42/tree-sitter-just", rev = "8af0aab79854aaf25b620a52c39485849922f766" }
2764
2636
2765−[[language]]
2766−name = "gn"
2767−scope = "source.gn"
2768−injection-regex = "gn"
2769−file-types = ["gn", "gni"]
2770−roots = []
2771−comment-token = "#"
2772−indent = { tab-width = 2, unit = " " }
2773−formatter = { command = "gn", args = ["format", "--stdin"] }
2774−
2775−[[grammar]]
2776−name = "gn"
2777−source = { git = "https://github.com/willcassella/tree-sitter-gn", rev = "e18d6e36a79b20dafb58f19d407bd38b0e60260e" }
2778−
2779[[language]]
2637[[language]]
2780name = "blueprint"
2638name = "blueprint"
2781scope = "source.blueprint"
2639scope = "source.blueprint"
2782injection-regex = "blueprint"
2640injection-regex = "blueprint"
2783file-types = ["blp"]
2641file-types = ["blp"]
2642+roots = []
2784comment-token = "//"
2643comment-token = "//"
2785language-servers = [ "blueprint-compiler" ]
2644language-servers = [ "blueprint-compiler" ]
2786indent = { tab-width = 4, unit = " " }
2645indent = { tab-width = 4, unit = " " }
2787
2646
2788[[grammar]]
2647[[grammar]]
2789name = "blueprint"
2648name = "blueprint"
2790−source = { git = "https://gitlab.com/gabmus/tree-sitter-blueprint", rev = "863cea9f83ad5637300478e0559262f1e791684b" }
2649+source = { git = "https://gitlab.com/gabmus/tree-sitter-blueprint", rev = "7f1a5df44861291d6951b6b2146a9fef4c226e14" }
2791
2650
2792[[language]]
2651[[language]]
2793name = "forth"
2652name = "forth"
2794scope = "source.forth"
2653scope = "source.forth"
2795injection-regex = "forth"
2654injection-regex = "forth"
2796file-types = ["fs", "forth", "fth", "4th"]
2655file-types = ["fs", "forth", "fth", "4th"]
2656+roots = []
2797comment-token = "\\"
2657comment-token = "\\"
2798language-servers = [ "forth-lsp" ]
2658language-servers = [ "forth-lsp" ]
2799indent = { tab-width = 3, unit = " " }
2659indent = { tab-width = 3, unit = " " }
2800
2660
2801[[grammar]]
2661[[grammar]]
2802name = "forth"
2662name = "forth"
2803−source = { git = "https://github.com/alexanderbrevig/tree-sitter-forth", rev = "90189238385cf636b9ee99ce548b9e5b5e569d48" }
2663+source = { git = "https://github.com/alexanderbrevig/tree-sitter-forth", rev = "304ed77beb113e37af38b20ff14e3c37bf350d10" }
2804
2664
2805[[language]]
2665[[language]]
2806name = "fsharp"
2666name = "fsharp"
2807scope = "source.fs"
2667scope = "source.fs"
2808roots = ["sln", "fsproj"]
2668roots = ["sln", "fsproj"]
2809injection-regex = "fsharp"
2669injection-regex = "fsharp"
2810−file-types = ["fs", "fsx", "fsi", "fsscript"]
2670+file-types = ["fs", "fsx"]
2811comment-token = "//"
2671comment-token = "//"
2812indent = { tab-width = 4, unit = " " }
2672indent = { tab-width = 4, unit = " " }
2813auto-format = true
2673auto-format = true
@@ -2815,194 +2675,38 @@ language-servers = ["fsharp-ls"]
2815
2675
2816[[grammar]]
2676[[grammar]]
2817name = "fsharp"
2677name = "fsharp"
2818−source = { git = "https://github.com/kaashyapan/tree-sitter-fsharp", rev = "18da392fd9bd5e79f357abcce13f61f3a15e3951" }
2678+source = { git = "https://github.com/kaashyapan/tree-sitter-fsharp", rev = "1b5a315e003024db6181744f1d25b8f525732665" }
2819
2679
2820[[language]]
2680[[language]]
2821name = "t32"
2681name = "t32"
2822scope = "source.t32"
2682scope = "source.t32"
2823injection-regex = "t32"
2683injection-regex = "t32"
2824file-types = ["cmm", "t32"]
2684file-types = ["cmm", "t32"]
2685+roots = []
2825comment-token = ";"
2686comment-token = ";"
2826indent = { tab-width = 2, unit = " " }
2687indent = { tab-width = 2, unit = " " }
2827
2688
2828[[grammar]]
2689[[grammar]]
2829name = "t32"
2690name = "t32"
2830−source = { git = "https://gitlab.com/xasc/tree-sitter-t32", rev = "6da5e3cbabd376b566d04282005e52ffe67ef74a" }
2691+source = { git = "https://codeberg.org/xasc/tree-sitter-t32", rev = "1dd98248b01e4a3933c1b85b58bab0875e2ba437" }
2831
2692
2832[[language]]
2693[[language]]
2833name = "webc"
2694name = "webc"
2834scope = "text.html.webc"
2695scope = "text.html.webc"
2835injection-regex = "webc"
2696injection-regex = "webc"
2836file-types = ["webc"]
2697file-types = ["webc"]
2698+roots = []
2837indent = { tab-width = 2, unit = " " }
2699indent = { tab-width = 2, unit = " " }
2838grammar = "html"
2700grammar = "html"
2839
2701
2840−[[language]]
2841−name = "typst"
2842−scope = "source.typst"
2843−injection-regex = "typst"
2844−file-types = ["typst", "typ"]
2845−comment-token = "//"
2846−language-servers = ["typst-lsp"]
2847−indent = { tab-width = 2, unit = " " }
2848−
2849−[language.auto-pairs]
2850−'(' = ')'
2851−'{' = '}'
2852−'[' = ']'
2853−'$' = '$'
2854−'"' = '"'
2855−
2856−[[grammar]]
2857−name = "typst"
2858−source = { git = "https://github.com/uben0/tree-sitter-typst", rev = "e35aa22395fdde82bbc4b5700c324ce346dfc9e5" }
2859−
2860−[[language]]
2861−name = "nunjucks"
2862−scope = "text.html.nunjucks"
2863−injection-regex = "nunjucks"
2864−file-types = ["njk"]
2865−indent = { tab-width = 2, unit = " " }
2866−grammar = "jinja2"
2867−
2868−[[language]]
2869−name = "jinja"
2870−scope = "text.html.jinja"
2871−injection-regex = "jinja"
2872−file-types = ["jinja", "jinja2", "j2"]
2873−indent = { tab-width = 2, unit = " " }
2874−grammar = "jinja2"
2875−
2876−[[grammar]]
2877−name = "jinja2"
2878−source = { git = "https://github.com/varpeti/tree-sitter-jinja2", rev = "a533cd3c33aea6acb0f9bf9a56f35dcfe6a8eb53" }
2879−
2880[[grammar]]
2702[[grammar]]
2881name = "wren"
2703name = "wren"
2882−source = { git = "https://git.sr.ht/~jummit/tree-sitter-wren", rev = "793d58266924e6efcc40e411663393e9d72bec87"}
2704+source = { git = "https://git.sr.ht/~jummit/tree-sitter-wren", rev = "7f576e8ccadac226f6a37cbefe95be3fee9f0a66"}
2883
2705
2884[[language]]
2706[[language]]
2885name = "wren"
2707name = "wren"
2886scope = "source.wren"
2708scope = "source.wren"
2887injection-regex = "wren"
2709injection-regex = "wren"
2888file-types = ["wren"]
2710file-types = ["wren"]
2889−indent = { tab-width = 2, unit = " "}
2890−
2891−[[language]]
2892−name = "unison"
2893−scope = "source.unison"
2894−injection-regex = "unison"
2895−file-types = ["u"]
2896−shebangs = []
2897−auto-format = false
2898−comment-token = "--"
2899−indent = { tab-width = 4, unit = " " }
2900−
2901−[language.auto-pairs]
2902−'(' = ')'
2903−'{' = '}'
2904−'[' = ']'
2905−'"' = '"'
2906−'`' = '`'
2907−
2908−[[grammar]]
2909−name = "unison"
2910−source = { git = "https://github.com/kylegoetz/tree-sitter-unison", rev = "98c4e8bc5c9f5989814a720457cf36963cf4043d" }
2911−
2912−[[language]]
2913−name = "todotxt"
2914−scope = "text.todotxt"
2915−file-types = [{ suffix = ".todo.txt" }, "todotxt"]
2916−formatter = { command = "sort" }
2917−auto-format = true
2918−
2919−[[grammar]]
2920−name = "todotxt"
2921−source = { git = "https://github.com/arnarg/tree-sitter-todotxt", rev = "0207f6a4ab6aeafc4b091914d31d8235049a2578" }
2922−
2923−[[language]]
2924−name = "strace"
2925−scope = "source.strace"
2926−file-types = ["strace"]
2927−
2928−[[grammar]]
2929−name = "strace"
2930−source = { git = "https://github.com/sigmaSd/tree-sitter-strace", rev = "2b18fdf9a01e7ec292cc6006724942c81beb7fd5" }
2931−
2932−[[language]]
2933−name = "gemini"
2934−scope = "source.gmi"
2935−file-types = ["gmi"]
2936−
2937−[[grammar]]
2938−name = "gemini"
2939−source = { git = "https://git.sr.ht/~nbsp/tree-sitter-gemini", rev = "3cc5e4bdf572d5df4277fc2e54d6299bd59a54b3" }
2940−
2941−[[language]]
2942−name = "agda"
2943−scope = "source.agda"
2944−injection-regex = "agda"
2945−file-types = ["agda"]
2946roots = []
2711roots = []
2947−comment-token = "--"
2948−# language-servers = [ "als" ]
2949−# the agda language server is of questionable functionality.
2950−auto-format = false
2951−indent = { tab-width = 2, unit = " " }
2712+indent = { tab-width = 2, unit = " "}
2952−
2953−[language.auto-pairs]
2954−'"' = '"'
2955−"'" = "'"
2956−'{' = '}'
2957−'(' = ')'
2958−'[' = ']'
2959−
2960−# [language.debugger]
2961−# ?? can this be used for proof assistant support? explore
2962−
2963−[[grammar]]
2964−name = "agda"
2965−source = { git = "https://github.com/tree-sitter/tree-sitter-agda", rev = "c21c3a0f996363ed17b8ac99d827fe5a4821f217" }
2966−
2967−[[language]]
2968−name = "templ"
2969−scope = "source.templ"
2970−file-types = ["templ"]
2971−roots = ["go.work", "go.mod"]
2972−comment-token = "//"
2973−indent = { tab-width = 2, unit = " " }
2974−language-servers = [ "templ" ]
2975−
2976−[[grammar]]
2977−name = "templ"
2978−source = { git = "https://github.com/vrischmann/tree-sitter-templ", rev = "ea56ac0655243490a4929a988f4eaa91dfccc995" }
2979−
2980−[[language]]
2981−name = "dbml"
2982−scope = "source.dbml"
2983−injection-regex = "dbml"
2984−file-types = ["dbml"]
2985−comment-token = ";"
2986−indent = { tab-width = 2, unit = " " }
2987−
2988−[[grammar]]
2989−name = "dbml"
2990−source = { git = "https://github.com/dynamotn/tree-sitter-dbml", rev = "2e2fa5640268c33c3d3f27f7e676f631a9c68fd9" }
2991−
2992−[[language]]
2993−name = "log"
2994−scope = "source.log"
2995−file-types = ["log"]
2996−
2997−[[grammar]]
2998−name = "log"
2999−source = { git = "https://github.com/Tudyx/tree-sitter-log", rev = "62cfe307e942af3417171243b599cc7deac5eab9" }
3000−
3001−[[language]]
3002−name = "janet"
3003−scope = "source.janet"
3004−injection-regex = "janet"
3005−file-types = ["janet"]
3006−comment-token = "#"
3007−indent = { tab-width = 2, unit = " " }
3008−grammar = "clojure"
logo.svg+1−1
@@ -1 +1 @@
1−<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" viewBox="663.38 37.57 575.35 903.75"><g transform="matrix(1,0,0,1,-31352.7,-1817.25)"><g transform="matrix(1,0,0,1,31062.7,-20.8972)"><g transform="matrix(1,0,0,1,-130.173,0.00185558)"><path style="fill:#706bc8;" d="M1083.58,1875.72L1635.06,2194.12C1649.8,2202.63 1658.88,2218.37 1658.88,2235.39C1658.88,2264.98 1658.88,2311.74 1658.88,2341.33C1658.88,2349.84 1656.61,2358.03 1652.5,2365.16C1652.5,2365.16 1214.7,2112.4 1107.2,2050.33C1092.58,2041.89 1083.58,2026.29 1083.58,2009.41C1083.58,1963.5 1083.58,1875.72 1083.58,1875.72Z"></path></g><g transform="matrix(1,0,0,1,-130.173,0.00185558)"><path style="fill:#55c5e4;" d="M1635.26,2604.84C1649.88,2613.28 1658.88,2628.87 1658.88,2645.75C1658.88,2691.67 1658.88,2779.44 1658.88,2779.44L1107.41,2461.05C1092.66,2452.53 1083.58,2436.8 1083.58,2419.78C1083.58,2390.19 1083.58,2343.42 1083.58,2313.84C1083.58,2305.32 1085.85,2297.13 1089.96,2290.01C1089.96,2290.01 1527.76,2542.77 1635.26,2604.84Z"></path></g><g transform="matrix(1,0,0,1,216.062,984.098)"><path style="fill:#84ddea;" d="M790.407,1432.56C785.214,1435.55 780.717,1439.9 777.509,1445.46C767.862,1462.16 773.473,1483.76 790.004,1493.59L789.998,1493.59L761.173,1476.95C746.427,1468.44 737.344,1452.71 737.344,1435.68C737.344,1406.09 737.344,1359.33 737.344,1329.74C737.344,1312.71 746.427,1296.98 761.173,1288.47L1259.59,1000.74L1259.83,1000.6C1264.92,997.617 1269.33,993.314 1272.48,987.844C1282.13,971.136 1276.52,949.544 1259.99,939.707L1260,939.707L1288.82,956.349C1303.57,964.862 1312.65,980.595 1312.65,997.622C1312.65,1027.21 1312.65,1073.97 1312.65,1103.56C1312.65,1120.59 1303.57,1136.32 1288.82,1144.83L1259.19,1161.94L1259.59,1161.68L790.407,1432.56Z"></path></g><g transform="matrix(1,0,0,1,216.062,984.098)"><path style="fill:#997bc8;" d="M790.407,1686.24C785.214,1689.23 780.717,1693.58 777.509,1699.13C767.862,1715.84 773.473,1737.43 790.004,1747.27L789.998,1747.27L761.173,1730.63C746.427,1722.12 737.344,1706.38 737.344,1689.36C737.344,1659.77 737.344,1613.01 737.344,1583.42C737.344,1566.39 746.427,1550.66 761.173,1542.15L1259.59,1254.42L1259.83,1254.28C1264.92,1251.29 1269.33,1246.99 1272.48,1241.52C1282.13,1224.81 1276.52,1203.22 1259.99,1193.38L1260,1193.38L1288.82,1210.03C1303.57,1218.54 1312.65,1234.27 1312.65,1251.3C1312.65,1280.89 1312.65,1327.65 1312.65,1357.24C1312.65,1374.26 1303.57,1390 1288.82,1398.51L1259.19,1415.61L1259.59,1415.36L790.407,1686.24Z"></path></g></g></g></svg>
1+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" viewBox="663.38 37.57 575.35 903.75"> <g transform="matrix(1,0,0,1,-31352.7,-1817.25)"> <g transform="matrix(1,0,0,1,31062.7,-20.8972)"> <g transform="matrix(1,0,0,1,-130.173,0.00185558)"> <path d="M1083.58,1875.72L1635.06,2194.12C1649.8,2202.63 1658.88,2218.37 1658.88,2235.39C1658.88,2264.98 1658.88,2311.74 1658.88,2341.33C1658.88,2349.84 1656.61,2358.03 1652.5,2365.16C1652.5,2365.16 1214.7,2112.4 1107.2,2050.33C1092.58,2041.89 1083.58,2026.29 1083.58,2009.41C1083.58,1963.5 1083.58,1875.72 1083.58,1875.72Z" style="fill:#706bc8;"></path> </g> <g transform="matrix(1,0,0,1,-130.173,0.00185558)"> <path d="M1635.26,2604.84C1649.88,2613.28 1658.88,2628.87 1658.88,2645.75C1658.88,2691.67 1658.88,2779.44 1658.88,2779.44L1107.41,2461.05C1092.66,2452.53 1083.58,2436.8 1083.58,2419.78C1083.58,2390.19 1083.58,2343.42 1083.58,2313.84C1083.58,2305.32 1085.85,2297.13 1089.96,2290.01C1089.96,2290.01 1527.76,2542.77 1635.26,2604.84Z" style="fill:#55c5e4;"></path> </g> <g transform="matrix(1,0,0,1,216.062,984.098)"> <path d="M790.407,1432.56C785.214,1435.55 780.717,1439.9 777.509,1445.46C767.862,1462.16 773.473,1483.76 790.004,1493.59L789.998,1493.59L761.173,1476.95C746.427,1468.44 737.344,1452.71 737.344,1435.68C737.344,1406.09 737.344,1359.33 737.344,1329.74C737.344,1312.71 746.427,1296.98 761.173,1288.47L1259.59,1000.74L1259.83,1000.6C1264.92,997.617 1269.33,993.314 1272.48,987.844C1282.13,971.136 1276.52,949.544 1259.99,939.707L1260,939.707L1288.82,956.349C1303.57,964.862 1312.65,980.595 1312.65,997.622C1312.65,1027.21 1312.65,1073.97 1312.65,1103.56C1312.65,1120.59 1303.57,1136.32 1288.82,1144.83L1259.19,1161.94L1259.59,1161.68L790.407,1432.56Z" style="fill:#84ddea;"></path> </g> <g transform="matrix(1,0,0,1,216.062,984.098)"> <path d="M790.407,1686.24C785.214,1689.23 780.717,1693.58 777.509,1699.13C767.862,1715.84 773.473,1737.43 790.004,1747.27L789.998,1747.27L761.173,1730.63C746.427,1722.12 737.344,1706.38 737.344,1689.36C737.344,1659.77 737.344,1613.01 737.344,1583.42C737.344,1566.39 746.427,1550.66 761.173,1542.15L1259.59,1254.42L1259.83,1254.28C1264.92,1251.29 1269.33,1246.99 1272.48,1241.52C1282.13,1224.81 1276.52,1203.22 1259.99,1193.38L1260,1193.38L1288.82,1210.03C1303.57,1218.54 1312.65,1234.27 1312.65,1251.3C1312.65,1280.89 1312.65,1327.65 1312.65,1357.24C1312.65,1374.26 1303.57,1390 1288.82,1398.51L1259.19,1415.61L1259.59,1415.36L790.407,1686.24Z" style="fill:#997bc8;"></path></g></g></g> </svg>
runtime/queries/_jsx/highlights.scm+15−3
@@ -4,6 +4,9 @@
4(jsx_opening_element ((identifier) @constructor
4(jsx_opening_element ((identifier) @constructor
5 (#match? @constructor "^[A-Z]")))
5 (#match? @constructor "^[A-Z]")))
6
6
7+; Handle the dot operator effectively - <My.Component>
8+(jsx_opening_element ((nested_identifier (identifier) @tag (identifier) @constructor)))
9+
7(jsx_opening_element (identifier) @tag)
10(jsx_opening_element (identifier) @tag)
8
11
9; Closing elements
12; Closing elements
@@ -12,6 +15,9 @@
12(jsx_closing_element ((identifier) @constructor
15(jsx_closing_element ((identifier) @constructor
13 (#match? @constructor "^[A-Z]")))
16 (#match? @constructor "^[A-Z]")))
14
17
18+; Handle the dot operator effectively - </My.Component>
19+(jsx_closing_element ((nested_identifier (identifier) @tag (identifier) @constructor)))
20+
15(jsx_closing_element (identifier) @tag)
21(jsx_closing_element (identifier) @tag)
16
22
17; Self-closing elements
23; Self-closing elements
@@ -20,6 +26,9 @@
20(jsx_self_closing_element ((identifier) @constructor
26(jsx_self_closing_element ((identifier) @constructor
21 (#match? @constructor "^[A-Z]")))
27 (#match? @constructor "^[A-Z]")))
22
28
29+; Handle the dot operator effectively - <My.Component />
30+(jsx_self_closing_element ((nested_identifier (identifier) @tag (identifier) @constructor)))
31+
23(jsx_self_closing_element (identifier) @tag)
32(jsx_self_closing_element (identifier) @tag)
24
33
25; Attributes
34; Attributes
@@ -30,14 +39,17 @@
30; Punctuation
39; Punctuation
31; -----------
40; -----------
32
41
33−; Handle attribute delimiter (<Component color="red"/>)
42+; Handle attribute delimiter
34(jsx_attribute "=" @punctuation.delimiter)
43(jsx_attribute "=" @punctuation.delimiter)
35
44
36; <Component>
45; <Component>
37(jsx_opening_element ["<" ">"] @punctuation.bracket)
46(jsx_opening_element ["<" ">"] @punctuation.bracket)
38
47
39; </Component>
48; </Component>
40−(jsx_closing_element ["</" ">"] @punctuation.bracket)
49+(jsx_closing_element ["<" "/" ">"] @punctuation.bracket)
41
50
42; <Component />
51; <Component />
43−(jsx_self_closing_element ["<" "/>"] @punctuation.braket)
52+(jsx_self_closing_element ["<" "/" ">"] @punctuation.bracket)
53+
54+; <> ... </>
55+(jsx_fragment ["<" "/" ">"] @punctuation.bracket)
runtime/queries/_jsx/indents.scm+1
@@ -1,4 +1,5 @@
1[
1[
2+ (jsx_fragment)
2 (jsx_element)
3 (jsx_element)
3 (jsx_self_closing_element)
4 (jsx_self_closing_element)
4] @indent
5] @indent
runtime/queries/_typescript/highlights.scm+3−5
@@ -39,11 +39,11 @@
39 (identifier) @variable.parameter))
39 (identifier) @variable.parameter))
40
40
41; (p?: t)
41; (p?: t)
42−; (p?: t = 1) // Invalid but still possible to highlight.
42+; (p?: t = 1) // Invalid but still posible to hihglight.
43(optional_parameter
43(optional_parameter
44 (identifier) @variable.parameter)
44 (identifier) @variable.parameter)
45
45
46−; (...p?: t) // Invalid but still possible to highlight.
46+; (...p?: t) // Invalid but still posible to hihglight.
47(optional_parameter
47(optional_parameter
48 (rest_pattern
48 (rest_pattern
49 (identifier) @variable.parameter))
49 (identifier) @variable.parameter))
@@ -59,7 +59,7 @@
59 (pair_pattern
59 (pair_pattern
60 value: (identifier) @variable.parameter)))
60 value: (identifier) @variable.parameter)))
61
61
62−; ([ p ]?: t[]) // Invalid but still possible to highlight.
62+; ([ p ]?: t[]) // Invalid but still posible to hihglight.
63(optional_parameter
63(optional_parameter
64 (array_pattern
64 (array_pattern
65 (identifier) @variable.parameter))
65 (identifier) @variable.parameter))
@@ -107,8 +107,6 @@
107; Types
107; Types
108; -----
108; -----
109
109
110−(type_parameter
111− name: (type_identifier) @type.parameter)
112(type_identifier) @type
110(type_identifier) @type
113(predefined_type) @type.builtin
111(predefined_type) @type.builtin
114
112
runtime/queries/_typescript/locals.scm−17
@@ -1,18 +1,6 @@
1−; Scopes
2−;-------
3−
4−[
5− (type_alias_declaration)
6− (class_declaration)
7− (interface_declaration)
8−] @local.scope
9−
10; Definitions
1; Definitions
11;------------
2;------------
12
3
13−(type_parameter
14− name: (type_identifier) @local.definition)
15−
16; Javascript and Typescript Treesitter grammars deviate when defining the
4; Javascript and Typescript Treesitter grammars deviate when defining the
17; tree structure for parameters, so we need to address them in each specific
5; tree structure for parameters, so we need to address them in each specific
18; language instead of ecma.
6; language instead of ecma.
@@ -26,8 +14,3 @@
26; (i?: t = 1) // Invalid but still posible to hihglight.
14; (i?: t = 1) // Invalid but still posible to hihglight.
27(optional_parameter
15(optional_parameter
28 (identifier) @local.definition)
16 (identifier) @local.definition)
29−
30−; References
31−;-----------
32−
33−(type_identifier) @local.reference
runtime/queries/agda/highlights.scmdeleted−124
@@ -1,124 +0,0 @@
1−;; Punctuation
2−[ "." ";" ":"] @punctuation.delimiter
3−[ "(" ")" "{" "}" ] @punctuation.bracket
4−
5−;; Constants
6−(integer) @constant.numeric.integer
7−; (float) @constant.numeric.float
8−(literal) @string
9−
10−;; Pragmas and comments
11−(comment) @comment
12−(pragma) @attribute
13−(macro) @function.macro
14−
15−;; Imports
16−(module_name) @namespace
17−(import_directive (id) @namespace)
18−[(module) (import) (open)] @keyword.control.import
19−
20−;; Types
21−(typed_binding (expr) @type)
22−(record (expr) @type)
23−(data (expr) @type)
24−(signature (expr) @type)
25−(function (rhs (expr) @type))
26−; todo: these are too general. ideally, any nested (atom)
27−; https://github.com/tree-sitter/tree-sitter/issues/880
28−
29−;; Variables
30−(untyped_binding (atom) @variable)
31−(typed_binding (atom) @variable)
32−(field_name) @variable.other.member
33−
34−;; Functions
35−(function_name) @function
36−;(function (lhs
37−; . (atom) @function
38−; (atom) @variable.parameter))
39−; todo: currently fails to parse, upstream tree-sitter bug
40−
41−;; Data
42−[(data_name) (record_name)] @constructor
43−((atom) @constant.builtin.boolean
44− (#any-of? @constant.builtin.boolean "true" "false" "True" "False"))
45−
46−"Set" @type.builtin
47−
48−; postulate
49−; type_signature
50−; pattern
51−; id
52−; bid
53−; typed_binding
54−; primitive
55−; private
56−; record_signature
57−; record_assignments
58−; field_assignment
59−; module_assignment
60−; renaming
61−; import_directive
62−; lambda
63−; let
64−; instance
65−; generalize
66−; record
67−; fields
68−; syntax
69−; hole_name
70−; data_signature
71−
72−;; Keywords
73−[
74− "where"
75− "data"
76− "rewrite"
77− "postulate"
78− "public"
79− "private"
80− "tactic"
81− "Prop"
82− "quote"
83− "renaming"
84− "in"
85− "hiding"
86− "constructor"
87− "abstract"
88− "let"
89− "field"
90− "mutual"
91− "infix"
92− "infixl"
93− "infixr"
94− "record"
95− "overlap"
96− "instance"
97− "do"
98−] @keyword
99−
100−[
101− "="
102−] @operator
103−
104−; = | -> : ? \ .. ... λ ∀ →
105−; (_LAMBDA) (_FORALL) (_ARROW)
106−; "coinductive"
107−; "eta-equality"
108−; "field"
109−; "inductive"
110−; "interleaved"
111−; "macro"
112−; "no-eta-equality"
113−; "pattern"
114−; "primitive"
115−; "quoteTerm"
116−; "rewrite"
117−; "syntax"
118−; "unquote"
119−; "unquoteDecl"
120−; "unquoteDef"
121−; "using"
122−; "variable"
123−; "with"
124−
runtime/queries/bash/indents.scm−1
@@ -2,7 +2,6 @@
2 (function_definition)
2 (function_definition)
3 (if_statement)
3 (if_statement)
4 (for_statement)
4 (for_statement)
5− (while_statement)
6 (case_statement)
5 (case_statement)
7 (pipeline)
6 (pipeline)
8] @indent
7] @indent
runtime/queries/bash/textobjects.scmdeleted−9
@@ -1,9 +0,0 @@
1−(function_definition
2− body: (_) @function.inside) @function.around
3−
4−(command
5− argument: (_) @parameter.inside)
6−
7−(comment) @comment.inside
8−
9−(comment)+ @comment.around
runtime/queries/blueprint/highlights.scm−5
@@ -15,11 +15,6 @@
15(decorator) @attribute
15(decorator) @attribute
16
16
17(property_definition (property_name) @variable.other.member)
17(property_definition (property_name) @variable.other.member)
18−(property_definition
19− (property_binding
20− "bind" @keyword
21− (property_name) @variable.other.member
22− ["no-sync-create" "bidirectional" "inverted"]* @keyword))
23
18
24(object) @type
19(object) @type
25
20
runtime/queries/c/indents.scm+1−18
@@ -5,6 +5,7 @@
5 (enumerator_list)
5 (enumerator_list)
6 (parameter_list)
6 (parameter_list)
7 (init_declarator)
7 (init_declarator)
8+ (case_statement)
8 (expression_statement)
9 (expression_statement)
9] @indent
10] @indent
10
11
@@ -12,7 +13,6 @@
12 "case"
13 "case"
13 "}"
14 "}"
14 "]"
15 "]"
15− ")"
16] @outdent
16] @outdent
17
17
18(if_statement
18(if_statement
@@ -32,20 +32,3 @@
32 (_) @indent
32 (_) @indent
33 (#not-kind-eq? @indent "compound_statement")
33 (#not-kind-eq? @indent "compound_statement")
34 (#set! "scope" "all"))
34 (#set! "scope" "all"))
35−
36−(parameter_list
37− . (parameter_declaration) @anchor
38− (#set! "scope" "tail")) @align
39−(argument_list
40− . (_) @anchor
41− (#set! "scope" "tail")) @align
42−; These are a bit opinionated since some people just indent binary/ternary expressions spanning multiple lines.
43−; Since they are only triggered when a newline is inserted into an already complete binary/ternary expression,
44−; this should happen rarely, so it is not a big deal either way.
45−; Additionally, adding these queries has the advantage of preventing such continuation lines from being used
46−; as the baseline when the `hybrid` indent heuristic is used (which is desirable since their indentation is so inconsistent).
47−(binary_expression
48− (#set! "scope" "tail")) @anchor @align
49−(conditional_expression
50− "?" @anchor
51− (#set! "scope" "tail")) @align
runtime/queries/comment/highlights.scm+8−8
@@ -7,31 +7,31 @@
7
7
8; Hint level tags
8; Hint level tags
9((tag (name) @hint)
9((tag (name) @hint)
10− (#match? @hint "^(HINT|MARK|PASSED|STUB|MOCK)$"))
10+ (#match? @hint "^(HINT|MARK)$"))
11
11
12("text" @hint
12("text" @hint
13− (#match? @hint "^(HINT|MARK|PASSED|STUB|MOCK)$"))
13+ (#match? @hint "^(HINT|MARK)$"))
14
14
15; Info level tags
15; Info level tags
16((tag (name) @info)
16((tag (name) @info)
17− (#match? @info "^(INFO|NOTE|TODO|PERF|OPTIMIZE|PERFORMANCE|QUESTION|ASK)$"))
17+ (#match? @info "^(INFO|NOTE|TODO)$"))
18
18
19("text" @info
19("text" @info
20− (#match? @info "^(INFO|NOTE|TODO|PERF|OPTIMIZE|PERFORMANCE|QUESTION|ASK)$"))
20+ (#match? @info "^(INFO|NOTE|TODO)$"))
21
21
22; Warning level tags
22; Warning level tags
23((tag (name) @warning)
23((tag (name) @warning)
24− (#match? @warning "^(HACK|WARN|WARNING|TEST|TEMP)$"))
24+ (#match? @warning "^(HACK|WARN|WARNING)$"))
25
25
26("text" @warning
26("text" @warning
27− (#match? @warning "^(HACK|WARN|WARNING|TEST|TEMP)$"))
27+ (#match? @warning "^(HACK|WARN|WARNING)$"))
28
28
29; Error level tags
29; Error level tags
30((tag (name) @error)
30((tag (name) @error)
31− (#match? @error "^(BUG|FIXME|ISSUE|XXX|FIX|SAFETY|FIXIT|FAILED|DEBUG)$"))
31+ (#match? @error "^(BUG|FIXME|ISSUE|XXX)$"))
32
32
33("text" @error
33("text" @error
34− (#match? @error "^(BUG|FIXME|ISSUE|XXX|FIX|SAFETY|FIXIT|FAILED|DEBUG)$"))
34+ (#match? @error "^(BUG|FIXME|ISSUE|XXX)$"))
35
35
36(tag
36(tag
37 (name) @ui.text
37 (name) @ui.text
runtime/queries/common-lisp/indents.scmdeleted−1
@@ -1 +0,0 @@
1−; inherits: scheme
runtime/queries/dart/highlights.scm+1−3
@@ -200,7 +200,6 @@
200 "async"
200 "async"
201 "async*"
201 "async*"
202 "await"
202 "await"
203− "base"
204 "class"
203 "class"
205 "covariant"
204 "covariant"
206 "deferred"
205 "deferred"
@@ -220,7 +219,6 @@
220 "operator"
219 "operator"
221 "part"
220 "part"
222 "required"
221 "required"
223− "sealed"
224 "set"
222 "set"
225 "show"
223 "show"
226 "static"
224 "static"
@@ -232,7 +230,7 @@
232
230
233; when used as an identifier:
231; when used as an identifier:
234((identifier) @variable.builtin
232((identifier) @variable.builtin
235− (#match? @variable.builtin "^(abstract|as|base|covariant|deferred|dynamic|export|external|factory|Function|get|implements|import|interface|library|operator|mixin|part|sealed|set|static|typedef)$"))
233+ (#match? @variable.builtin "^(abstract|as|covariant|deferred|dynamic|export|external|factory|Function|get|implements|import|interface|library|operator|mixin|part|set|static|typedef)$"))
236
234
237; Error
235; Error
238(ERROR) @error
236(ERROR) @error
runtime/queries/dbml/highlights.scmdeleted−44
@@ -1,44 +0,0 @@
1−
2−; comments highlighting
3−(comment) @comment
4−
5−; keyword highlighting
6−(keyword_def) @keyword
7−(keyword_enum) @keyword
8−(keyword_ref) @keyword
9−
10−; identify blocks and definitions
11−(definition) @function
12−
13−; for identifiers
14−(identifier) @variable
15−(type) @keyword
16−
17−; Highlight special types for database/data types
18−("Project" ) @type
19−("Table" ) @type
20−("TableGroup" ) @type
21−("database_type" ) @variable
22−
23−; string and number constants
24−("'''") @constant.character.escape
25−(string) @string
26−(number) @constant.numeric
27−
28−; brackets
29−[
30− "("
31− ")"
32− "{"
33− "}"
34− "["
35− "]"
36−] @punctuation.bracket
37−
38−; brackets
39−[
40− ":"
41− "."
42− ","
43−] @punctuation.delimiter
44−
runtime/queries/ecma/highlights.scm+1−1
@@ -109,7 +109,7 @@
109
109
110[
110[
111 ";"
111 ";"
112− (optional_chain) ; ?.
112+ "?."
113 "."
113 "."
114 ","
114 ","
115] @punctuation.delimiter
115] @punctuation.delimiter
runtime/queries/fsharp/locals.scmdeleted−25
@@ -1,25 +0,0 @@
1−; Scopes
2−;-------
3−
4−[
5− (ce_expression)
6− (module_defn)
7− (for_expression)
8− (do_expression)
9− (fun_expression)
10− (function_expression)
11− (try_expression)
12− (match_expression)
13− (elif_expression)
14− (if_expression)
15−] @local.scope
16−
17−; Definitions
18−;------------
19−
20−(function_or_value_defn) @local.definition
21−
22−; References
23−;-----------
24−
25−(identifier) @local.reference
runtime/queries/gas/highlights.scmdeleted−21
@@ -1,21 +0,0 @@
1−(comment) @comment
2−(number) @constant.numeric
3−(directive_name) @keyword.directive
4−(symbol) @variable
5−(label) @function
6−(label)
7−(instruction_prefix) @keyword
8−(instruction_name) @function.special
9−(register) @constant.builtin
10−(string) @string
11−(char) @constant.character
12−(type) @type
13−(constant "$" @constant)
14−(operand_modifier) @attribute
15−
16−(expression
17− ["-" "+" "*" "/" "="] @operator)
18−
19−["(" ")"] @punctuation.bracket
20−
21−["," ":"] @punctuation.delimiter
runtime/queries/gas/injections.scmdeleted−2
@@ -1,2 +0,0 @@
1−((comment) @injection.content
2− (#set! injection.language "comment"))
runtime/queries/gas/textobjects.scmdeleted−2
@@ -1,2 +0,0 @@
1−(comment) @comment.inside
2−(comment)+ @comment.around
runtime/queries/gemini/highlights.scmdeleted−26
@@ -1,26 +0,0 @@
1−(link) @punctuation.bracket
2−(link
3− label: (text) @markup.link.label)
4−(link
5− uri: (uri) @markup.link.url)
6−
7−[
8− (start_pre)
9− (pre)
10− (end_pre)
11−] @markup.raw.block
12−(start_pre
13− alt: (text) @label)
14−
15−(heading1
16− (text) @markup.heading.1) @markup.heading.marker
17−(heading2
18− (text) @markup.heading.2) @markup.heading.marker
19−(heading3
20− (text) @markup.heading.3) @markup.heading.marker
21−
22−(ulist
23− (indicator) @markup.list.unnumbered)
24−(quote
25− (indicator) @markup.quote
26− (text) @markup.italic)
runtime/queries/gleam/highlights.scm+5−16
@@ -21,8 +21,6 @@
21
21
22; Functions
22; Functions
23(unqualified_import (identifier) @function)
23(unqualified_import (identifier) @function)
24−(unqualified_import "type" (type_identifier) @type)
25−(unqualified_import (type_identifier) @constructor)
26(function
24(function
27 name: (identifier) @function)
25 name: (identifier) @function)
28(external_function
26(external_function
@@ -45,13 +43,6 @@
45(tuple_access
43(tuple_access
46 index: (integer) @variable.other.member)
44 index: (integer) @variable.other.member)
47
45
48−; Attributes
49−(attribute
50− "@" @attribute
51− name: (identifier) @attribute)
52−
53−(attribute_value (identifier) @constant)
54−
55; Type names
46; Type names
56(remote_type_identifier) @type
47(remote_type_identifier) @type
57(type_identifier) @type
48(type_identifier) @type
@@ -69,6 +60,10 @@
69(identifier) @variable
60(identifier) @variable
70(discard) @comment.unused
61(discard) @comment.unused
71
62
63+; Operators
64+(binary_expression
65+ operator: _ @operator)
66+
72; Keywords
67; Keywords
73[
68[
74 (visibility_modifier) ; "pub"
69 (visibility_modifier) ; "pub"
@@ -77,7 +72,6 @@
77 "assert"
72 "assert"
78 "case"
73 "case"
79 "const"
74 "const"
80− ; DEPRECATED: 'external' was removed in v0.30.
81 "external"
75 "external"
82 "fn"
76 "fn"
83 "if"
77 "if"
@@ -85,16 +79,11 @@
85 "let"
79 "let"
86 "panic"
80 "panic"
87 "todo"
81 "todo"
82+ "try"
88 "type"
83 "type"
89 "use"
84 "use"
90] @keyword
85] @keyword
91
86
92−; Operators
93−(binary_expression
94− operator: _ @operator)
95−(boolean_negation "!" @operator)
96−(integer_negation "-" @operator)
97−
98; Punctuation
87; Punctuation
99[
88[
100 "("
89 "("
runtime/queries/gn/highlights.scmdeleted−42
@@ -1,42 +0,0 @@
1−; Copyright (C) 2021 Will Cassella (github@willcassella.com)
2−;
3−; Licensed under the Apache License, Version 2.0 (the "License");
4−; you may not use this file except in compliance with the License.
5−; You may obtain a copy of the License at
6−;
7−; http://www.apache.org/licenses/LICENSE-2.0
8−;
9−; Unless required by applicable law or agreed to in writing, software
10−; distributed under the License is distributed on an "AS IS" BASIS,
11−; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12−; See the License for the specific language governing permissions and
13−; limitations under the License.
14−
15−(identifier) @variable.builtin
16−
17−(scope_access field: (_) @variable.other.member)
18−
19−(call target: (_) @function)
20−
21−[ "if" "else" ] @keyword.control.conditional
22−
23−[
24− (assign_op)
25− (arithmetic_binary_op)
26− (comparison_binary_op)
27− (equivalence_binary_op)
28− (logical_and_binary_op)
29− (logical_or_binary_op)
30− (negation_unary_op)
31−] @operator
32−
33−[ "(" ")" "[" "]" "{" "}" ] @punctuation.bracket
34−[ "." "," ] @punctuation.delimiter
35−
36−(string) @string
37−(string_escape) @constant.character.escape
38−(string_expansion [ "$" "${" "}" ] @constant.character.escape)
39−[ (integer) (hex) ] @constant.numeric
40−(boolean) @constant.builtin.boolean
41−
42−(comment) @comment
runtime/queries/gn/injections.scmdeleted−15
@@ -1,15 +0,0 @@
1−; Copyright (C) 2021 Will Cassella (github@willcassella.com)
2−;
3−; Licensed under the Apache License, Version 2.0 (the "License");
4−; you may not use this file except in compliance with the License.
5−; You may obtain a copy of the License at
6−;
7−; http://www.apache.org/licenses/LICENSE-2.0
8−;
9−; Unless required by applicable law or agreed to in writing, software
10−; distributed under the License is distributed on an "AS IS" BASIS,
11−; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12−; See the License for the specific language governing permissions and
13−; limitations under the License.
14−
15−((comment) @injection.content (#set! injection.language "comment"))
runtime/queries/gn/locals.scmdeleted−16
@@ -1,16 +0,0 @@
1−; Copyright (C) 2021 Will Cassella (github@willcassella.com)
2−;
3−; Licensed under the Apache License, Version 2.0 (the "License");
4−; you may not use this file except in compliance with the License.
5−; You may obtain a copy of the License at
6−;
7−; http://www.apache.org/licenses/LICENSE-2.0
8−;
9−; Unless required by applicable law or agreed to in writing, software
10−; distributed under the License is distributed on an "AS IS" BASIS,
11−; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12−; See the License for the specific language governing permissions and
13−; limitations under the License.
14−
15−(source_file) @local.scope
16−(assignment target: (identifier) @local.definition)
runtime/queries/go/highlights.scm+5−13
@@ -11,18 +11,6 @@
11 function: (selector_expression
11 function: (selector_expression
12 field: (field_identifier) @function.method))
12 field: (field_identifier) @function.method))
13
13
14−
15−; Types
16−
17−(type_parameter_list
18− (parameter_declaration
19− name: (identifier) @type.parameter))
20−
21−((type_identifier) @type.builtin
22− (match? @type.builtin "^(any|bool|byte|comparable|complex128|complex64|error|float32|float64|int|int16|int32|int64|int8|rune|string|uint|uint16|uint32|uint64|uint8|uintptr)$"))
23−
24−(type_identifier) @type
25−
26; Function definitions
14; Function definitions
27
15
28(function_declaration
16(function_declaration
@@ -36,16 +24,20 @@
36
24
37; Identifiers
25; Identifiers
38
26
27+((identifier) @constant (match? @constant "^[A-Z][A-Z\\d_]+$"))
39(const_spec
28(const_spec
40 name: (identifier) @constant)
29 name: (identifier) @constant)
41
30
42(parameter_declaration (identifier) @variable.parameter)
31(parameter_declaration (identifier) @variable.parameter)
43(variadic_parameter_declaration (identifier) @variable.parameter)
32(variadic_parameter_declaration (identifier) @variable.parameter)
44
33
34+((type_identifier) @type.builtin
35+ (match? @type.builtin "^(any|bool|byte|comparable|complex128|complex64|error|float32|float64|int|int16|int32|int64|int8|rune|string|uint|uint16|uint32|uint64|uint8|uintptr)$"))
36+
37+(type_identifier) @type
45(type_spec
38(type_spec
46 name: (type_identifier) @constructor)
39 name: (type_identifier) @constructor)
47(field_identifier) @variable.other.member
40(field_identifier) @variable.other.member
48−(keyed_element (literal_element (identifier) @variable.other.member))
49(identifier) @variable
41(identifier) @variable
50(package_identifier) @namespace
42(package_identifier) @namespace
51
43
runtime/queries/go/locals.scm+2−10
@@ -1,17 +1,9 @@
1; Scopes
1; Scopes
2
2
3−[
4− (function_declaration)
5− (type_declaration)
6− (block)
7−] @local.scope
3+(block) @local.scope
8
4
9; Definitions
5; Definitions
10
6
11−(type_parameter_list
12− (parameter_declaration
13− name: (identifier) @local.definition))
14−
15(parameter_declaration (identifier) @local.definition)
7(parameter_declaration (identifier) @local.definition)
16(variadic_parameter_declaration (identifier) @local.definition)
8(variadic_parameter_declaration (identifier) @local.definition)
17
9
@@ -35,4 +27,4 @@
35
27
36(identifier) @local.reference
28(identifier) @local.reference
37(field_identifier) @local.reference
29(field_identifier) @local.reference
38−(type_identifier) @local.reference
30+
runtime/queries/hare/highlights.scm+1−4
@@ -36,10 +36,7 @@
36 "break"
36 "break"
37] @keyword.control.repeat
37] @keyword.control.repeat
38
38
39−[
40− "return"
41− "yield"
42−] @keyword.control.return
39+"return" @keyword.control.return
43
40
44[
41[
45 "abort"
42 "abort"
runtime/queries/haskell/highlights.scm+11−24
@@ -33,11 +33,6 @@
33;; ----------------------------------------------------------------------------
33;; ----------------------------------------------------------------------------
34;; Keywords, operators, includes
34;; Keywords, operators, includes
35
35
36−[
37− "forall"
38− "∀"
39−] @keyword.control.repeat
40−
41(pragma) @constant.macro
36(pragma) @constant.macro
42
37
43[
38[
@@ -73,7 +68,10 @@
73 "@"
68 "@"
74] @operator
69] @operator
75
70
71+(qualified_module (module) @constructor)
76−(module) @namespace
72+(qualified_type (module) @namespace)
73+(qualified_variable (module) @namespace)
74+(import (module) @namespace)
77
75
78[
76[
79 (where)
77 (where)
@@ -94,6 +92,8 @@
94 "do"
92 "do"
95 "mdo"
93 "mdo"
96 "rec"
94 "rec"
95+ "forall"
96+ "∀"
97 "infix"
97 "infix"
98 "infixl"
98 "infixl"
99 "infixr"
99 "infixr"
@@ -104,35 +104,22 @@
104;; Functions and variables
104;; Functions and variables
105
105
106(signature name: (variable) @type)
106(signature name: (variable) @type)
107−(function
108− name: (variable) @function
107+(function name: (variable) @function)
109− patterns: (patterns))
108+
110−((signature (fun)) . (function (variable) @function))
109+(variable) @variable
111−((signature (context (fun))) . (function (variable) @function))
110+"_" @variable.builtin
112−((signature (forall (context (fun)))) . (function (variable) @function))
113
111
114(exp_infix (variable) @operator) ; consider infix functions as operators
112(exp_infix (variable) @operator) ; consider infix functions as operators
115
113
116−(exp_infix (exp_name) @function)
114+("@" @namespace) ; "as" pattern operator, e.g. x@Constructor
117−(exp_apply . (exp_name (variable) @function))
118−(exp_apply . (exp_name (qualified_variable (variable) @function)))
119
115
120−(variable) @variable
121−(pat_wildcard) @variable
122
116
123;; ----------------------------------------------------------------------------
117;; ----------------------------------------------------------------------------
124;; Types
118;; Types
125
119
126(type) @type
120(type) @type
127−(type_variable) @type.parameter
128
121
129(constructor) @constructor
122(constructor) @constructor
130
123
131; True or False
124; True or False
132((constructor) @_bool (#match? @_bool "(True|False)")) @constant.builtin.boolean
125((constructor) @_bool (#match? @_bool "(True|False)")) @constant.builtin.boolean
133−
134−;; ----------------------------------------------------------------------------
135−;; Quasi-quotes
136−
137−(quoter) @function
138−; Highlighting of quasiquote_body is handled by injections.scm
runtime/queries/janet/highlights.scmdeleted−1
@@ -1 +0,0 @@
1−; inherits: clojure
runtime/queries/java/indents.scmdeleted−35
@@ -1,35 +0,0 @@
1−[
2− (class_body)
3− (enum_body)
4− (interface_body)
5− (constructor_body)
6− (annotation_type_body)
7− (module_body)
8− (block)
9− (switch_block)
10− (array_initializer)
11− (argument_list)
12− (formal_parameters)
13− (annotation_argument_list)
14− (element_value_array_initializer)
15−] @indent
16−
17−[
18− "}"
19− ")"
20− "]"
21−] @outdent
22−
23−; Single statement after if/while/for without brackets
24−(if_statement
25− consequence: (_) @indent
26− (#not-kind-eq? @indent "block")
27− (#set! "scope" "all"))
28−(while_statement
29− body: (_) @indent
30− (#not-kind-eq? @indent "block")
31− (#set! "scope" "all"))
32−(for_statement
33− (_) @indent
34− (#not-kind-eq? @indent "block")
35− (#set! "scope" "all"))
runtime/queries/jinja/highlights.scmdeleted−7
@@ -1,7 +0,0 @@
1−(expression) @string
2−(statement) @variable.builtin
3−(keyword) @keyword
4−(comment) @comment
5−(identifier) @variable.parameter
6−(operator) @operator
7−(string) @string
runtime/queries/jinja/injections.scmdeleted−4
@@ -1,4 +0,0 @@
1−((source_file) @injection.content
2− (#set! injection.combined)
3− (#set! injection.include-children)
4− (#set! injection.language "html"))
runtime/queries/json5/highlights.scmdeleted−11
@@ -1,11 +0,0 @@
1−(string) @string
2−
3−(identifier) @constant
4−
5−(number) @constant.numeric
6−
7−(null) @constant.builtin
8−
9−[(true) (false)] @constant.builtin.boolean
10−
11−(comment) @comment
runtime/queries/kdl/highlights.scm+4−4
@@ -1,12 +1,12 @@
1+(comment) @comment
1(single_line_comment) @comment
2(single_line_comment) @comment
2−(multi_line_comment) @comment
3
3
4(node
4(node
5− (identifier) @variable)
5+ name: (identifier) @function)
6−
7(prop (identifier) @attribute)
6(prop (identifier) @attribute)
7+(type) @type
8
8
9−(type (_) @type) @punctuation.bracket
9+(bare_identifier) @variable.other.member
10
10
11(keyword) @keyword
11(keyword) @keyword
12
12
runtime/queries/kdl/indents.scmdeleted−3
@@ -1,3 +0,0 @@
1−(node_children) @indent
2−
3− "}" @outdent
runtime/queries/kdl/textobjects.scmdeleted−27
@@ -1,27 +0,0 @@
1−(type (_) @test.inside) @test.around
2−
3−(node
4− children: (node_children)? @class.inside) @class.around
5−
6−(node
7− children: (node_children)? @function.inside) @function.around
8−
9−(node (identifier) @function.movement)
10−
11−[
12− (single_line_comment)
13− (multi_line_comment)
14−] @comment.inside
15−
16−[
17− (single_line_comment)+
18− (multi_line_comment)+
19−] @comment.around
20−
21−[
22− (prop)
23− (value)
24−] @parameter.inside
25−
26−(value (type) ? (_) @parameter.inside @parameter.movement . ) @parameter.around
27−
runtime/queries/kotlin/highlights.scm+1−4
@@ -244,10 +244,7 @@
244 . (identifier)) @namespace
244 . (identifier)) @namespace
245
245
246((type_identifier) @type.builtin
246((type_identifier) @type.builtin
247− (#match? @type.builtin "^(Byte|Short|Int|Long|UByte|UShort|UInt|ULong|Float|Double|Boolean|Char|String|Array|ByteArray|ShortArray|IntArray|LongArray|UByteArray|UShortArray|UIntArray|ULongArray|FloatArray|DoubleArray|BooleanArray|CharArray|Map|Set|List|EmptyMap|EmptySet|EmptyList|MutableMap|MutableSet|MutableList)$"))
247+ (#match? @function.builtin "^(Byte|Short|Int|Long|UByte|UShort|UInt|ULong|Float|Double|Boolean|Char|String|Array|ByteArray|ShortArray|IntArray|LongArray|UByteArray|UShortArray|UIntArray|ULongArray|FloatArray|DoubleArray|BooleanArray|CharArray|Map|Set|List|EmptyMap|EmptySet|EmptyList|MutableMap|MutableSet|MutableList)$"))
248−
249−(type_parameter
250− (type_identifier) @type.parameter)
251
248
252(type_identifier) @type
249(type_identifier) @type
253
250
runtime/queries/kotlin/locals.scmdeleted−15
@@ -1,15 +0,0 @@
1−; Scopes
2−
3−[
4− (class_declaration)
5− (function_declaration)
6−] @local.scope
7−
8−; Definitions
9−
10−(type_parameter
11− (type_identifier) @local.definition)
12−
13−; References
14−
15−(type_identifier) @local.reference
runtime/queries/log/highlights.scmdeleted−10
@@ -1,10 +0,0 @@
1−(trace) @comment
2−(debug) @hint
3−(info) @info
4−(warn) @warning
5−(error) @error
6−(year_month_day) @keyword
7−(time) @constant
8−(string_literal) @string
9−(number) @constant.numeric
10−(constant) @constant.builtin
runtime/queries/lpf/highlights.scmdeleted−19
@@ -1,19 +0,0 @@
1−[
2− "SYSCONFIG"
3− "BLOCK"
4− "LOCATE"
5− "COMP"
6− "FREQUENCY"
7− "PORT"
8− "IOBUF"
9−] @keyword
10−
11−["SITE"] @keyword.storage
12−
13−["="] @operator
14−
15−((number) @constant.numeric)
16−
17−((string) @string)
18−((line_comment) @comment)
19−
runtime/queries/markdown.inline/injections.scm+1−4
@@ -1,7 +1,4 @@
1
1
2−((html_tag) @injection.content
3− (#set! injection.language "html")
4− (#set! injection.include-unnamed-children)
2+((html_tag) @injection.content (#set! injection.language "html") (#set! injection.include-unnamed-children))
5− (#set! injection.combined))
6
3
7((latex_block) @injection.content (#set! injection.language "latex") (#set! injection.include-unnamed-children))
4((latex_block) @injection.content (#set! injection.language "latex") (#set! injection.include-unnamed-children))
runtime/queries/nix/injections.scm−14
@@ -5,20 +5,6 @@
5((((comment) @injection.language) .
5((((comment) @injection.language) .
6 (indented_string_expression (string_fragment) @injection.content))
6 (indented_string_expression (string_fragment) @injection.content))
7 (#set! injection.combined))
7 (#set! injection.combined))
8−((binding
9− (comment) @injection.language
10− expression: (indented_string_expression (string_fragment) @injection.content))
11− (#set! injection.combined))
12−
13−; Common attribute keys corresponding to Python scripts,
14−; such as those for NixOS VM tests in nixpkgs/nixos/tests.
15−((binding
16− attrpath: (attrpath (identifier) @_path)
17− expression: (indented_string_expression
18− (string_fragment) @injection.content))
19− (#match? @_path "(^|\\.)testScript$")
20− (#set! injection.language "python")
21− (#set! injection.combined))
22
8
23; Common attribute keys corresponding to scripts,
9; Common attribute keys corresponding to scripts,
24; such as those of stdenv.mkDerivation.
10; such as those of stdenv.mkDerivation.
runtime/queries/nu/folds.scmnew file+4
@@ -0,0 +1,4 @@
1+[
2+ (function_definition)
3+ (block)
4+] @fold
runtime/queries/nu/highlights.scm+44−276
@@ -1,287 +1,55 @@
1−;;; ---
2−;;; keywords
3−[
4− "def"
5− "def-env"
6− "alias"
7− "export-env"
8− "export"
9− "extern"
10− "module"
11−
12− "let"
13− "let-env"
14− "mut"
15− "const"
16−
17− "hide-env"
18−
19− "source"
20− "source-env"
21−
22− "overlay"
23− "register"
24−
25− "loop"
26− "while"
27− "error"
28−
29− "do"
30− "if"
31− "else"
32− "try"
33− "catch"
34− "match"
35−
36− "break"
37− "continue"
38− "return"
39−
40−] @keyword
41−
42−(hide_mod "hide" @keyword)
43−(decl_use "use" @keyword)
44−
45−(ctrl_for
46− "for" @keyword
47− "in" @keyword
48−)
49−(overlay_list "list" @keyword.storage.modifier)
50−(overlay_hide "hide" @keyword.storage.modifier)
51−(overlay_new "new" @keyword.storage.modifier)
52−(overlay_use
53− "use" @keyword.storage.modifier
54− "as" @keyword
55−)
56−(ctrl_error "make" @keyword.storage.modifier)
57−
58−;;; ---
59−;;; literals
60−(val_number) @constant.numeric
61−(val_duration
62− unit: [
63− "ns" "µs" "us" "ms" "sec" "min" "hr" "day" "wk"
64− ] @variable.parameter
65−)
66−(val_filesize
67− unit: [
68− "b" "B"
69−
70− "kb" "kB" "Kb" "KB"
71− "mb" "mB" "Mb" "MB"
72− "gb" "gB" "Gb" "GB"
73− "tb" "tB" "Tb" "TB"
74− "pb" "pB" "Pb" "PB"
75− "eb" "eB" "Eb" "EB"
76− "zb" "zB" "Zb" "ZB"
77−
78− "kib" "kiB" "kIB" "kIb" "Kib" "KIb" "KIB"
79− "mib" "miB" "mIB" "mIb" "Mib" "MIb" "MIB"
80− "gib" "giB" "gIB" "gIb" "Gib" "GIb" "GIB"
81− "tib" "tiB" "tIB" "tIb" "Tib" "TIb" "TIB"
82− "pib" "piB" "pIB" "pIb" "Pib" "PIb" "PIB"
83− "eib" "eiB" "eIB" "eIb" "Eib" "EIb" "EIB"
84− "zib" "ziB" "zIB" "zIb" "Zib" "ZIb" "ZIB"
85− ] @variable.parameter
86−)
87−(val_binary
88− [
89− "0b"
90− "0o"
91− "0x"
92− ] @constant.numeric
93− "[" @punctuation.bracket
94− digit: [
95− "," @punctuation.delimiter
96− (hex_digit) @constant.number
97− ]
98− "]" @punctuation.bracket
99−) @constant.numeric
100−(val_bool) @constant.builtin
101−(val_nothing) @constant.builtin
102−(val_string) @string
103−(val_date) @constant.number
104−(inter_escape_sequence) @constant.character.escape
105−(escape_sequence) @constant.character.escape
106−(val_interpolated [
107− "$\""
108− "$\'"
109− "\""
110− "\'"
111−] @string)
1+(string) @string
112−(unescaped_interpolated_content) @string
2+(type) @type
113−(escaped_interpolated_content) @string
3+(value_path) @variable
114−(expr_interpolated ["(" ")"] @variable.parameter)
4+(comment) @comment
115
5
116−;;; ---
6+(number_literal) @constant.numeric
117−;;; operators
7+(range from: (number_literal) @constant.numeric)
118−(expr_binary [
8+(range to: (number_literal) @constant.numeric)
119− "+"
120− "-"
121− "*"
122− "/"
123− "mod"
124− "//"
125− "++"
126− "**"
127− "=="
128− "!="
129− "<"
130− "<="
131− ">"
132− ">="
133− "=~"
134− "!~"
135− "and"
136− "or"
137− "xor"
138− "bit-or"
139− "bit-xor"
140− "bit-and"
141− "bit-shl"
142− "bit-shr"
143− "in"
144− "not-in"
145− "starts-with"
146− "ends-with"
147−] @operator )
148
9
149−(where_command [
10+(command cmd_name: (identifier) @function)
150− "+"
11+(function_definition func_name: (identifier) @function)
151− "-"
152− "*"
153− "/"
154− "mod"
155− "//"
156− "++"
157− "**"
158− "=="
159− "!="
160− "<"
161− "<="
162− ">"
163− ">="
164− "=~"
165− "!~"
166− "and"
167− "or"
168− "xor"
169− "bit-or"
170− "bit-xor"
171− "bit-and"
172− "bit-shl"
173− "bit-shr"
174− "in"
175− "not-in"
176− "starts-with"
177− "ends-with"
178−] @operator)
179
12
180−(assignment [
13+[
181− "="
14+ (variable_declaration name: (identifier))
182− "+="
15+ (parameter (identifier))
183− "-="
16+ (flag (flag_name))
184− "*="
17+ (flag (flag_shorthand_name))
185− "/="
18+ (record_entry entry_name: (identifier))
186− "++="
19+ (block_args block_param: (identifier))
187−] @operator)
20+] @variable.other.member
21+; (parameter (identifier) @variable.parameter) ; -- alternative highlighting group?
188
22
189−(expr_unary ["not" "-"] @operator)
23+(cmd_invocation) @embedded
190
24
191−(val_range [
192− ".."
193− "..="
194− "..<"
195−] @operator)
196
25
197−["=>" "=" "|"] @operator
26+((identifier) @constant
27+ (#match? @constant "^[A-Z][A-Z\\d_]*$"))
198
28
199[
29[
200− "o>" "out>"
30+ "if"
201− "e>" "err>"
31+ "else"
202− "e+o>" "err+out>"
32+ "let"
203− "o+e>" "out+err>"
33+ "def"
204−] @special
34+ "export"
35+] @keyword
205
36
206−;;; ---
207−;;; punctuation
208[
37[
38+ ; "/" Not making / an operator may lead to better highlighting?
39+ "$"
40+ "|"
41+ "+"
42+ "-"
43+ "*"
44+ "="
45+ "!="
46+ "&&"
47+ "||"
48+ "=="
49+ ">"
50+] @operator
51+
52+["."
209− ","
53+ ","
210− ";"
54+ ";"
211] @punctuation.delimiter
55] @punctuation.delimiter
212−
213−(param_short_flag "-" @punctuation.delimiter)
214−(param_long_flag ["--"] @punctuation.delimiter)
215−(long_flag ["--"] @punctuation.delimiter)
216−(param_rest "..." @punctuation.delimiter)
217−(param_type [":"] @punctuation.special)
218−(param_value ["="] @punctuation.special)
219−(param_cmd ["@"] @punctuation.special)
220−(param_opt ["?"] @punctuation.special)
221−
222−[
223− "(" ")"
224− "{" "}"
225− "[" "]"
226−] @punctuation.bracket
227−
228−(val_record
229− (record_entry ":" @punctuation.delimiter))
230−;;; ---
231−;;; identifiers
232−(param_rest
233− name: (_) @variable.parameter)
234−(param_opt
235− name: (_) @variable.parameter)
236−(parameter
237− param_name: (_) @variable.parameter)
238−(param_cmd
239− (cmd_identifier) @string)
240−(param_long_flag) @variable.parameter
241−(param_short_flag) @variable.parameter
242−
243−(short_flag) @variable.parameter
244−(long_flag) @variable.parameter
245−
246−(scope_pattern [(wild_card) @function])
247−
248−(cmd_identifier) @function
249−
250−(command
251− "^" @punctuation.delimiter
252− head: (_) @function
253−)
254−
255−"where" @function
256−
257−(path
258− ["." "?"] @punctuation.delimiter
259−) @variable.parameter
260−
261−(val_variable
262− "$" @variable.parameter
263− [
264− (identifier) @namespace
265− "in"
266− "nu"
267− "env"
268− "nothing"
269− ] @special
270−)
271−;;; ---
272−;;; types
273−(flat_type) @type.builtin
274−(list_type
275− "list" @type.enum
276− ["<" ">"] @punctuation.bracket
277−)
278−(collection_type
279− ["record" "table"] @type.enum
280− "<" @punctuation.bracket
281− key: (_) @variable.parameter
282− ["," ":"] @punctuation.delimiter
283− ">" @punctuation.bracket
284−)
285−
286−(shebang) @comment
287−(comment) @comment
runtime/queries/nu/injections.scm+1−1
@@ -1,2 +1,2 @@
1((comment) @injection.content
1((comment) @injection.content
2− (#set! injection.language "comment"))
2+ (#set! injection.language "comment"))
runtime/queries/nu/locals.scmnew file+13
@@ -0,0 +1,13 @@
1+; Scopes
2+(function_definition) @scope
3+
4+; Definitions
5+(variable_declaration
6+ name: (identifier) @definition.var)
7+
8+(function_definition
9+ func_name: (identifier) @definition.function)
10+
11+; References
12+(value_path) @reference
13+(word) @reference
runtime/queries/nunjucks/highlights.scmdeleted−1
@@ -1 +0,0 @@
1−; inherits: jinja
runtime/queries/nunjucks/injections.scmdeleted−1
@@ -1 +0,0 @@
1−; inherits: jinja
runtime/queries/ocaml/highlights.scm−2
@@ -8,8 +8,6 @@
8
8
9[(class_name) (class_type_name) (type_constructor)] @type
9[(class_name) (class_type_name) (type_constructor)] @type
10
10
11−(type_variable) @type.parameter
12−
13[(constructor_name) (tag)] @constructor
11[(constructor_name) (tag)] @constructor
14
12
15; Functions
13; Functions
runtime/queries/perl/highlights.scm+2
@@ -41,6 +41,8 @@
41(eof_marker) @keyword.directive
41(eof_marker) @keyword.directive
42(data_section) @comment
42(data_section) @comment
43
43
44+(pod) @comment.block.documentation
45+
44(number) @constant.numeric
46(number) @constant.numeric
45(version) @constant
47(version) @constant
46
48
runtime/queries/perl/indents.scmdeleted−29
@@ -1,29 +0,0 @@
1−[
2− (block)
3− (conditional_statement)
4− (loop_statement)
5− (cstyle_for_statement)
6− (for_statement)
7− (elsif)
8− (array_element_expression)
9− (hash_element_expression)
10− (coderef_call_expression)
11− (anonymous_slice_expression)
12− (slice_expression)
13− (keyval_expression)
14− (anonymous_array_expression)
15− (anonymous_hash_expression)
16− (stub_expression)
17− (func0op_call_expression)
18− (func1op_call_expression)
19− (map_grep_expression)
20− (function_call_expression)
21− (method_call_expression)
22− (attribute)
23−] @indent
24−
25−[
26− "}"
27− "]"
28− ")"
29−] @outdent
runtime/queries/perl/injections.scm−2
@@ -1,4 +1,2 @@
1((comment) @injection.content
1((comment) @injection.content
2 (#set! injection.language "comment"))
2 (#set! injection.language "comment"))
3−((pod) @injection.content
4− (#set! injection.language "pod"))
runtime/queries/perl/textobjects.scmdeleted−14
@@ -1,14 +0,0 @@
1−(subroutine_declaration_statement
2− body: (_) @function.inside) @function.around
3−(anonymous_subroutine_expression
4− body: (_) @function.inside) @function.around
5−
6−(package_statement) @class.around
7−(package_statement
8− (block) @class.inside)
9−
10−(list_expression
11− (_) @parameter.inside)
12−
13−(comment) @comment.around
14−(pod) @comment.around
runtime/queries/pod/highlights.scmdeleted−61
@@ -1,61 +0,0 @@
1−[(pod_directive)
2− (head_directive)
3− (over_directive)
4− (item_directive)
5− (back_directive)
6− (encoding_directive)
7− (cut_directive)] @tag
8−
9−(head_paragraph
10− (head_directive) @directive
11− (#eq? @directive "=head1")
12− (content) @markup.heading.1)
13−(head_paragraph
14− (head_directive) @directive
15− (#eq? @directive "=head2")
16− (content) @markup.heading.2)
17−(head_paragraph
18− (head_directive) @directive
19− (#eq? @directive "=head3")
20− (content) @markup.heading.3)
21−(head_paragraph
22− (head_directive) @directive
23− (#eq? @directive "=head4")
24− (content) @markup.heading.4)
25−(head_paragraph
26− (head_directive) @directive
27− (#eq? @directive "=head5")
28− (content) @markup.heading.5)
29−(head_paragraph
30− (head_directive) @directive
31− (#eq? @directive "=head6")
32− (content) @markup.heading.6)
33−
34−(over_paragraph (content) @constant.numeric.integer)
35−(item_paragraph (content) @markup.list)
36−(encoding_paragraph (content) @string)
37−
38−(verbatim_paragraph (content) @markup.raw)
39−
40−(interior_sequence) @tag
41−
42−(interior_sequence
43− (sequence_letter) @letter
44− (#eq? @letter "B")
45− (content) @markup.bold)
46−(interior_sequence
47− (sequence_letter) @letter
48− (#eq? @letter "C")
49− (content) @markup.raw)
50−(interior_sequence
51− (sequence_letter) @letter
52− (#eq? @letter "F")
53− (content) @markup.italic)
54−(interior_sequence
55− (sequence_letter) @letter
56− (#eq? @letter "I")
57− (content) @markup.italic)
58−(interior_sequence
59− (sequence_letter) @letter
60− (#eq? @letter "L")
61− (content) @markup.link.url)
runtime/queries/purescript/highlights.scm+1−164
@@ -1,164 +1 @@
1−; ----------------------------------------------------------------------------
1+; inherits: haskell
2−; Record fields would need to come before literal strings in order to be captured correctly
3−
4− (record_accessor
5− field: [ (variable)
6− (string)
7− (triple_quote_string)
8− ] @variable.other.member)
9−
10− (exp_record_access
11− field: [ (variable)
12− (string)
13− (triple_quote_string)
14− ] @variable.other.member)
15−
16−
17−; ----------------------------------------------------------------------------
18−; Literals and comments
19−
20− (integer) @constant.numeric.integer
21− (exp_negation) @constant.numeric.integer
22− (exp_literal (number)) @constant.numeric.float
23− (char) @constant.character
24− [
25− (string)
26− (triple_quote_string)
27− ] @string
28−
29− (comment) @comment
30−
31−
32−; ----------------------------------------------------------------------------
33−; Punctuation
34−
35− [
36− "("
37− ")"
38− "{"
39− "}"
40− "["
41− "]"
42− ] @punctuation.bracket
43−
44− [
45− (comma)
46− ";"
47− ] @punctuation.delimiter
48−
49−
50−; ----------------------------------------------------------------------------
51−; Keywords, operators, includes
52−
53− ; This needs to come before the other "else" in
54− ; order to be highlighted correctly
55− (class_instance "else" @keyword)
56−
57− [
58− "if"
59− "then"
60− "else"
61− "case"
62− "of"
63− ] @keyword.control.conditional
64−
65− [
66− "import"
67− "module"
68− ] @keyword.control.import
69−
70− [
71− (operator)
72− (constructor_operator)
73− (type_operator)
74− (qualified_module) ; grabs the `.` (dot), ex: import System.IO
75− (all_names)
76−
77− ; `_` wildcards in if-then-else and case-of expressions,
78− ; as well as record updates and operator sections
79− (wildcard)
80− "="
81− "|"
82− "::"
83− "∷"
84− "=>"
85− "⇒"
86− "<="
87− "⇐"
88− "->"
89− "→"
90− "<-"
91− "←"
92− "\\"
93− "`"
94− "@"
95− ] @operator
96−
97− (qualified_module (module) @constructor)
98− (module) @namespace
99− (qualified_type (module) @namespace)
100− (qualified_variable (module) @namespace)
101− (import (module) @namespace)
102−
103− [
104− (where)
105− "let"
106− "in"
107− "class"
108− "instance"
109− "derive"
110− "foreign"
111− "data"
112− "newtype"
113− "type"
114− "as"
115− "hiding"
116− "do"
117− "ado"
118− "forall"
119− "∀"
120− "infix"
121− "infixl"
122− "infixr"
123− ] @keyword
124−
125− (type_role_declaration
126− "role" @keyword
127− role: (type_role) @keyword)
128−
129− (hole) @label
130−
131−; ----------------------------------------------------------------------------
132−; Functions and variables
133−
134− (row_field (field_name) @variable.other.member)
135− (record_field (field_name) @variable.other.member)
136− (record_field (field_pun) @variable.other.member)
137−
138− (signature name: (variable) @type)
139− (function name: (variable) @function)
140− (class_instance (instance_name) @function)
141− (derive_declaration (instance_name) @function)
142−
143− ; true or false
144− ((variable) @constant.builtin.boolean
145− (#match? @constant.builtin.boolean "^(true|false)$"))
146−
147− ; The former one works for `tree-sitter highlight` but not in Helix/Kakoune.
148− ; The latter two work in Helix (but not Kakoune) and are a good compromise between not highlighting anything at all
149− ; as an operator and leaving it to the child nodes, and highlighting everything as an operator.
150− (exp_ticked (_) @operator)
151− (exp_ticked (exp_name (variable) @operator))
152− (exp_ticked (exp_name (qualified_variable (variable) @operator)))
153−
154− (variable) @variable
155−
156− ("@" @namespace) ; "as" pattern operator, e.g. x@Constructor
157−
158−; ----------------------------------------------------------------------------
159−; Types
160−
161− (type) @type
162−
163− (constructor) @constructor
164−
runtime/queries/purescript/injections.scm+1−2
@@ -1,2 +1 @@
1−((comment) @injection.content
1+; inherits: haskell
2− (#set! injection.language "comment"))
runtime/queries/purescript/locals.scm+1−4
@@ -1,4 +1 @@
1−(signature name: (variable)) @local.definition
1+; inherits: haskell
2−(function name: (variable)) @local.definition
3−(pat_name (variable)) @local.definition
4−(exp_name (variable)) @local.reference
runtime/queries/purescript/textobjects.scmdeleted−13
@@ -1,13 +0,0 @@
1−(comment) @comment.inside
2−
3−[
4− (data)
5− (type)
6− (newtype)
7−] @class.around
8−
9−((signature)? (function rhs:(_) @function.inside)) @function.around
10−(exp_lambda) @function.around
11−
12−(data (type_variable) @parameter.inside)
13−(patterns (_) @parameter.inside)
runtime/queries/python/highlights.scm−6
@@ -134,17 +134,14 @@
134 "//="
134 "//="
135 "/="
135 "/="
136 "&"
136 "&"
137− "&="
138 "%"
137 "%"
139 "%="
138 "%="
140 "^"
139 "^"
141− "^="
142 "+"
140 "+"
143 "->"
141 "->"
144 "+="
142 "+="
145 "<"
143 "<"
146 "<<"
144 "<<"
147− "<<="
148 "<="
145 "<="
149 "<>"
146 "<>"
150 "="
147 "="
@@ -153,11 +150,8 @@
153 ">"
150 ">"
154 ">="
151 ">="
155 ">>"
152 ">>"
156− ">>="
157 "|"
153 "|"
158− "|="
159 "~"
154 "~"
160− "@="
161] @operator
155] @operator
162
156
163[
157[
runtime/queries/python/indents.scm−9

This diff is too big to show here.

runtime/queries/racket/indents.scmdeleted−1

This diff is too big to show here.

runtime/queries/robot/highlights.scm+13−51

This diff is too big to show here.

runtime/queries/ron/highlights.scm+1−43

This diff is too big to show here.

runtime/queries/ron/indents.scm+1−12

This diff is too big to show here.

runtime/queries/ron/injections.scm+1−2

This diff is too big to show here.

runtime/queries/rust/highlights.scm−7

This diff is too big to show here.

runtime/queries/rust/indents.scm+2−69

This diff is too big to show here.

runtime/queries/rust/injections.scm−6

This diff is too big to show here.

runtime/queries/rust/locals.scm+1−14

This diff is too big to show here.

runtime/queries/rust/textobjects.scm+1−4

This diff is too big to show here.

runtime/queries/scheme/highlights.scm−5

This diff is too big to show here.

runtime/queries/scheme/indents.scmdeleted−43

This diff is too big to show here.

runtime/queries/slint/highlights.scm+72−118

This diff is too big to show here.

runtime/queries/slint/indents.scm+5−6

This diff is too big to show here.

runtime/queries/slint/injections.scmdeleted−2

This diff is too big to show here.

runtime/queries/slint/locals.scm+1−1

This diff is too big to show here.

runtime/queries/smali/folds.scmdeleted−12

This diff is too big to show here.

runtime/queries/smali/highlights.scmdeleted−216

This diff is too big to show here.

runtime/queries/smali/indents.scmdeleted−31

This diff is too big to show here.

runtime/queries/smali/injections.scmdeleted−2

This diff is too big to show here.

runtime/queries/smali/locals.scmdeleted−42

This diff is too big to show here.

runtime/queries/sql/highlights.scm+231−317

This diff is too big to show here.

runtime/queries/strace/highlights.scmdeleted−8

This diff is too big to show here.

runtime/queries/t32/highlights.scm+26−108

This diff is too big to show here.

runtime/queries/templ/highlights.scmdeleted−99

This diff is too big to show here.

runtime/queries/templ/injections.scmdeleted−4

This diff is too big to show here.

runtime/queries/todotxt/highlights.scmdeleted−6

This diff is too big to show here.

runtime/queries/typst/highlights.scmdeleted−77

This diff is too big to show here.

runtime/queries/typst/injections.scmdeleted−6

This diff is too big to show here.

runtime/queries/unison/highlights.scmdeleted−87

This diff is too big to show here.

runtime/queries/unison/injections.scmdeleted−1

This diff is too big to show here.

runtime/queries/wren/highlights.scm+4−6

This diff is too big to show here.

runtime/queries/wren/indents.scmdeleted−29

This diff is too big to show here.

runtime/queries/wren/injections.scmdeleted−3

This diff is too big to show here.

runtime/queries/wren/locals.scmdeleted−21

This diff is too big to show here.

runtime/queries/wren/textobjects.scm−4

This diff is too big to show here.

runtime/queries/yaml/indents.scm+1−35

This diff is too big to show here.

runtime/queries/yaml/injections.scm−52

This diff is too big to show here.

runtime/queries/yuck/highlights.scm+1−1

This diff is too big to show here.

runtime/queries/zig/highlights.scm+2−5

This diff is too big to show here.

runtime/queries/zig/textobjects.scm+3−3

This diff is too big to show here.

runtime/themes/README.md−8

This diff is too big to show here.

runtime/themes/autumn.toml+65−62

This diff is too big to show here.

runtime/themes/autumn_night.toml+6−6

This diff is too big to show here.

runtime/themes/catppuccin_frappe.toml−2

This diff is too big to show here.

runtime/themes/catppuccin_latte.toml−2

This diff is too big to show here.

runtime/themes/catppuccin_macchiato.toml−2

This diff is too big to show here.

runtime/themes/catppuccin_mocha.toml−11

This diff is too big to show here.

runtime/themes/cyan_light.tomldeleted−154

This diff is too big to show here.

runtime/themes/darcula-solid.toml+4−1

This diff is too big to show here.

runtime/themes/darcula.toml+4−10

This diff is too big to show here.

runtime/themes/dracula.toml−2

This diff is too big to show here.

runtime/themes/everblush.toml−1

This diff is too big to show here.

runtime/themes/gruber-darker.tomldeleted−114

This diff is too big to show here.

runtime/themes/horizon-dark.tomldeleted−80

This diff is too big to show here.

runtime/themes/jellybeans.toml+1−1

This diff is too big to show here.

runtime/themes/licenses/everforest.LICENSEdeleted−21

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi.LICENSEdeleted−5

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi_deuteranopia.LICENSEdeleted−5

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi_tinted.LICENSEdeleted−5

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi_tritanopia.LICENSEdeleted−5

This diff is too big to show here.

runtime/themes/licenses/poimandres.LICENSEdeleted−21

This diff is too big to show here.

runtime/themes/licenses/serika-syntax.LICENSEdeleted−21

This diff is too big to show here.

runtime/themes/licenses/sonokai.LICENSEdeleted−21

This diff is too big to show here.

runtime/themes/licenses/starlight.LICENSEdeleted−24

This diff is too big to show here.

runtime/themes/material_darker.tomldeleted−20

This diff is too big to show here.

runtime/themes/material_deep_ocean.tomldeleted−123

This diff is too big to show here.

runtime/themes/material_oceanic.tomldeleted−21

This diff is too big to show here.

runtime/themes/material_palenight.tomldeleted−19

This diff is too big to show here.

runtime/themes/modus_operandi.tomldeleted−194

This diff is too big to show here.

runtime/themes/modus_operandi_deuteranopia.tomldeleted−46

This diff is too big to show here.

runtime/themes/modus_operandi_tinted.tomldeleted−45

This diff is too big to show here.

runtime/themes/modus_operandi_tritanopia.tomldeleted−73

This diff is too big to show here.

runtime/themes/modus_vivendi.tomldeleted−242

This diff is too big to show here.

runtime/themes/modus_vivendi_deuteranopia.tomldeleted−158

This diff is too big to show here.

runtime/themes/modus_vivendi_tinted.tomldeleted−144

This diff is too big to show here.

runtime/themes/modus_vivendi_tritanopia.tomldeleted−186

This diff is too big to show here.

runtime/themes/molokai.toml+1−1

This diff is too big to show here.

runtime/themes/monokai.toml−5

This diff is too big to show here.

runtime/themes/naysayer.toml+2−2

This diff is too big to show here.

runtime/themes/new_moon.tomldeleted−124

This diff is too big to show here.

runtime/themes/nightfox.toml+2−2

This diff is too big to show here.

runtime/themes/noctis.toml+26−26

This diff is too big to show here.

runtime/themes/nord-night.tomldeleted−23

This diff is too big to show here.

runtime/themes/nord.toml+13−23

This diff is too big to show here.

runtime/themes/onedark.toml−1

This diff is too big to show here.

runtime/themes/papercolor-dark.toml+116−67

This diff is too big to show here.

runtime/themes/papercolor-light.toml+103−194

This diff is too big to show here.

runtime/themes/poimandres.tomldeleted−113

This diff is too big to show here.

runtime/themes/poimandres_storm.tomldeleted−19

This diff is too big to show here.

runtime/themes/rose_pine.toml+29−31

This diff is too big to show here.

runtime/themes/starlight.tomldeleted−113

This diff is too big to show here.

runtime/themes/yellowed.tomldeleted−135

This diff is too big to show here.

runtime/themes/zenburn.toml+1−1

This diff is too big to show here.

runtime/tutor+3−157

This diff is too big to show here.

rust-toolchain.toml+2−2

This diff is too big to show here.

xtask/Cargo.toml+6−12

This diff is too big to show here.