on 3fb0562 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@v20
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
.ignore+3
@@ -1,2 +1,5 @@
1# Things that we don't want ripgrep to search that we do want in git
1# Things that we don't want ripgrep to search that we do want in git
2# https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#automatic-filtering
2# https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#automatic-filtering
3+
4+# Minified JS vendored from mdbook
5+book/theme/highlight.js
CHANGELOG.md+1−378
@@ -1,380 +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)
262−
263−23.05 is a smaller release focusing on fixes. There were 88 contributors in this release. Thank you all!
264−
265−Features:
266−
267−- Add a config option to exclude declaration from LSP references request ([#6886](https://github.com/helix-editor/helix/pull/6886))
268−- Enable injecting languages based on their file extension and shebang ([#3970](https://github.com/helix-editor/helix/pull/3970))
269−- Sort the buffer picker by most recent access ([#2980](https://github.com/helix-editor/helix/pull/2980))
270−- Perform syntax highlighting in the picker asynchronously ([#7028](https://github.com/helix-editor/helix/pull/7028))
271−
272−Commands:
273−
274−- `:update` is now aliased as `:u` ([#6835](https://github.com/helix-editor/helix/pull/6835))
275−- Add `extend_to_first_nonwhitespace` which acts the same as `goto_first_nonwhitespace` but always extends ([#6837](https://github.com/helix-editor/helix/pull/6837))
276−- Add `:clear-register` for clearing the given register or all registers ([#5695](https://github.com/helix-editor/helix/pull/5695))
277−- Add `:write-buffer-close` and `:write-buffer-close!` ([#6947](https://github.com/helix-editor/helix/pull/6947))
278−
279−Fixes:
280−
281−- Normalize LSP workspace paths ([#6517](https://github.com/helix-editor/helix/pull/6517))
282−- Robustly handle invalid LSP ranges ([#6512](https://github.com/helix-editor/helix/pull/6512))
283−- Fix line number display for LSP goto pickers ([#6559](https://github.com/helix-editor/helix/pull/6559))
284−- Fix toggling of `soft-wrap.enable` option ([#6656](https://github.com/helix-editor/helix/pull/6656), [58e457a](https://github.com/helix-editor/helix/commit/58e457a), [#6742](https://github.com/helix-editor/helix/pull/6742))
285−- Handle `workspace/configuration` requests from stopped language servers ([#6693](https://github.com/helix-editor/helix/pull/6693))
286−- Fix possible crash from opening the jumplist picker ([#6672](https://github.com/helix-editor/helix/pull/6672))
287−- Fix theme preview returning to current theme on line and word deletions ([#6694](https://github.com/helix-editor/helix/pull/6694))
288−- Re-run crate build scripts on changes to revision and grammar repositories ([#6743](https://github.com/helix-editor/helix/pull/6743))
289−- Fix crash on opening from suspended state ([#6764](https://github.com/helix-editor/helix/pull/6764))
290−- Fix unwrap bug in DAP ([#6786](https://github.com/helix-editor/helix/pull/6786))
291−- Always build tree-sitter parsers with C++14 and C11 ([#6792](https://github.com/helix-editor/helix/pull/6792), [#6834](https://github.com/helix-editor/helix/pull/6834), [#6845](https://github.com/helix-editor/helix/pull/6845))
292−- Exit with a non-zero statuscode when tree-sitter parser builds fail ([#6795](https://github.com/helix-editor/helix/pull/6795))
293−- Flip symbol range in LSP goto commands ([#6794](https://github.com/helix-editor/helix/pull/6794))
294−- Fix runtime toggling of the `mouse` option ([#6675](https://github.com/helix-editor/helix/pull/6675))
295−- Fix panic in inlay hint computation when view anchor is out of bounds ([#6883](https://github.com/helix-editor/helix/pull/6883))
296−- Significantly improve performance of git discovery on slow file systems ([#6890](https://github.com/helix-editor/helix/pull/6890))
297−- Downgrade gix log level to info ([#6915](https://github.com/helix-editor/helix/pull/6915))
298−- Conserve BOM and properly support saving UTF16 files ([#6497](https://github.com/helix-editor/helix/pull/6497))
299−- Correctly handle completion re-request ([#6594](https://github.com/helix-editor/helix/pull/6594))
300−- Fix offset encoding in LSP `didChange` notifications ([#6921](https://github.com/helix-editor/helix/pull/6921))
301−- Change `gix` logging level to info ([#6915](https://github.com/helix-editor/helix/pull/6915))
302−- Improve error message when writes fail because parent directories do not exist ([#7014](https://github.com/helix-editor/helix/pull/7014))
303−- Replace DAP variables popup instead of pushing more popups ([#7034](https://github.com/helix-editor/helix/pull/7034))
304−- Disable tree-sitter for files after parsing for 500ms ([#7028](https://github.com/helix-editor/helix/pull/7028))
305−- Fix crash when deleting with multiple cursors ([#6024](https://github.com/helix-editor/helix/pull/6024))
306−- Fix selection sliding when deleting forwards in append mode ([#6024](https://github.com/helix-editor/helix/pull/6024))
307−- Fix completion on paths containing spaces ([#6779](https://github.com/helix-editor/helix/pull/6779))
308−
309−Themes:
310−
311−- Style inlay hints in `dracula` theme ([#6515](https://github.com/helix-editor/helix/pull/6515))
312−- Style inlay hints in `onedark` theme ([#6503](https://github.com/helix-editor/helix/pull/6503))
313−- Style inlay hints and the soft-wrap indicator in `varua` ([#6568](https://github.com/helix-editor/helix/pull/6568), [#6589](https://github.com/helix-editor/helix/pull/6589))
314−- Style inlay hints in `emacs` theme ([#6569](https://github.com/helix-editor/helix/pull/6569))
315−- Update `base16_transparent` and `dark_high_contrast` themes ([#6577](https://github.com/helix-editor/helix/pull/6577))
316−- Style inlay hints for `mellow` and `rasmus` themes ([#6583](https://github.com/helix-editor/helix/pull/6583))
317−- Dim pane divider for `base16_transparent` theme ([#6534](https://github.com/helix-editor/helix/pull/6534))
318−- Style inlay hints in `zenburn` theme ([#6593](https://github.com/helix-editor/helix/pull/6593))
319−- Style inlay hints in `boo_berry` theme ([#6625](https://github.com/helix-editor/helix/pull/6625))
320−- Add `ferra` theme ([#6619](https://github.com/helix-editor/helix/pull/6619), [#6776](https://github.com/helix-editor/helix/pull/6776))
321−- Style inlay hints in `nightfox` theme ([#6655](https://github.com/helix-editor/helix/pull/6655))
322−- Fix `ayu` theme family markup code block background ([#6538](https://github.com/helix-editor/helix/pull/6538))
323−- Improve whitespace and search match colors in `rose_pine` theme ([#6679](https://github.com/helix-editor/helix/pull/6679))
324−- Highlight selected items in `base16_transparent` theme ([#6716](https://github.com/helix-editor/helix/pull/6716))
325−- Adjust everforest to resemble original more closely ([#5866](https://github.com/helix-editor/helix/pull/5866))
326−- Refactor `dracula` theme ([#6552](https://github.com/helix-editor/helix/pull/6552), [#6767](https://github.com/helix-editor/helix/pull/6767), [#6855](https://github.com/helix-editor/helix/pull/6855), [#6987](https://github.com/helix-editor/helix/pull/6987))
327−- Style inlay hints in `darcula` theme ([#6732](https://github.com/helix-editor/helix/pull/6732))
328−- Style inlay hints in `kanagawa` theme ([#6773](https://github.com/helix-editor/helix/pull/6773))
329−- Improve `ayu_dark` theme ([#6622](https://github.com/helix-editor/helix/pull/6622))
330−- Refactor `noctis` theme multiple cursor highlighting ([96720e7](https://github.com/helix-editor/helix/commit/96720e7))
331−- Refactor `noctis` theme whitespace rendering and indent guides ([f2ccc03](https://github.com/helix-editor/helix/commit/f2ccc03))
332−- Add `amberwood` theme ([#6924](https://github.com/helix-editor/helix/pull/6924))
333−- Update `nightfox` theme ([#7061](https://github.com/helix-editor/helix/pull/7061))
334−
335−Language support:
336−
337−- R language server: use the `--no-echo` flag to silence output ([#6570](https://github.com/helix-editor/helix/pull/6570))
338−- Recognize CUDA files as C++ ([#6521](https://github.com/helix-editor/helix/pull/6521))
339−- Add support for Hurl ([#6450](https://github.com/helix-editor/helix/pull/6450))
340−- Add textobject queries for Julia ([#6588](https://github.com/helix-editor/helix/pull/6588))
341−- Update Ruby highlight queries ([#6587](https://github.com/helix-editor/helix/pull/6587))
342−- Add xsd to XML file-types ([#6631](https://github.com/helix-editor/helix/pull/6631))
343−- Support Robot Framework ([#6611](https://github.com/helix-editor/helix/pull/6611))
344−- Update Gleam tree-sitter parser ([#6641](https://github.com/helix-editor/helix/pull/6641))
345−- Update git-commit tree-sitter parser ([#6692](https://github.com/helix-editor/helix/pull/6692))
346−- Update Haskell tree-sitter parser ([#6317](https://github.com/helix-editor/helix/pull/6317))
347−- Add injection queries for Haskell quasiquotes ([#6474](https://github.com/helix-editor/helix/pull/6474))
348−- Highlight C/C++ escape sequences ([#6724](https://github.com/helix-editor/helix/pull/6724))
349−- Support Markdoc ([#6432](https://github.com/helix-editor/helix/pull/6432))
350−- Support OpenCL ([#6473](https://github.com/helix-editor/helix/pull/6473))
351−- Support DTD ([#6644](https://github.com/helix-editor/helix/pull/6644))
352−- Fix constant highlighting in Python queries ([#6751](https://github.com/helix-editor/helix/pull/6751))
353−- Support Just ([#6453](https://github.com/helix-editor/helix/pull/6453))
354−- Fix Go locals query for `var_spec` identifiers ([#6763](https://github.com/helix-editor/helix/pull/6763))
355−- Update Markdown tree-sitter parser ([#6785](https://github.com/helix-editor/helix/pull/6785))
356−- Fix Haskell workspace root for cabal projects ([#6828](https://github.com/helix-editor/helix/pull/6828))
357−- Avoid extra indentation in Go switches ([#6817](https://github.com/helix-editor/helix/pull/6817))
358−- Fix Go workspace roots ([#6884](https://github.com/helix-editor/helix/pull/6884))
359−- Set PerlNavigator as the default Perl language server ([#6860](https://github.com/helix-editor/helix/pull/6860))
360−- Highlight more sqlx macros in Rust ([#6793](https://github.com/helix-editor/helix/pull/6793))
361−- Switch Odin tree-sitter grammar ([#6766](https://github.com/helix-editor/helix/pull/6766))
362−- Recognize `poetry.lock` as TOML ([#6928](https://github.com/helix-editor/helix/pull/6928))
363−- Recognize Jupyter notebooks as JSON ([#6927](https://github.com/helix-editor/helix/pull/6927))
364−- Add language server configuration for Crystal ([#6948](https://github.com/helix-editor/helix/pull/6948))
365−- Add `build.gradle.kts` to Java and Scala roots ([#6970](https://github.com/helix-editor/helix/pull/6970))
366−- Recognize `sty` and `cls` files as latex ([#6986](https://github.com/helix-editor/helix/pull/6986))
367−- Update Dockerfile tree-sitter grammar ([#6895](https://github.com/helix-editor/helix/pull/6895))
368−- Add comment injections for Odin ([#7027](https://github.com/helix-editor/helix/pull/7027))
369−- Recognize `gml` as XML ([#7055](https://github.com/helix-editor/helix/pull/7055))
370−- Recognize `geojson` as JSON ([#7054](https://github.com/helix-editor/helix/pull/7054))
371−
372−Packaging:
373−
374−- Update the Nix flake dependencies, remove a deprecated option ([#6546](https://github.com/helix-editor/helix/pull/6546))
375−- Fix and re-enable aarch64-macos release binary builds ([#6504](https://github.com/helix-editor/helix/pull/6504))
376−- The git dependency on `tree-sitter` has been replaced with a regular crates.io dependency ([#6608](https://github.com/helix-editor/helix/pull/6608))
377−
378# 23.03 (2023-03-31)
1# 23.03 (2023-03-31)
379
2
38023.03 brings some long-awaited and exciting features. Thank you to everyone involved! This release saw changes from 102 contributors.
323.03 brings some long-awaited and exciting features. Thank you to everyone involved! This release saw changes from 102 contributors.
@@ -1856,7 +1479,7 @@ to distinguish it in bug reports..
1856
1479
1857- The `runtime/` directory is now properly detected on binary releases and
1480- The `runtime/` directory is now properly detected on binary releases and
1858 on cargo run. `~/.config/helix/runtime` can also be used.
1481 on cargo run. `~/.config/helix/runtime` can also be used.
1859−- Registers can now be selected via " (for example, `"ay`)
1482+- Registers can now be selected via " (for example `"ay`)
1860- Support for Nix files was added
1483- Support for Nix files was added
1861- Movement is now fully tested and matches Kakoune implementation
1484- Movement is now fully tested and matches Kakoune implementation
1862- A per-file LSP symbol picker was added to space+s
1485- A per-file LSP symbol picker was added to space+s
Cargo.lock+607−667
@@ -2,15 +2,6 @@
2# It is not intended for manual editing.
2# It is not intended for manual editing.
3version = 3
3version = 3
4
4
5−[[package]]
6−name = "addr2line"
7−version = "0.20.0"
8−source = "registry+https://github.com/rust-lang/crates.io-index"
9−checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
10−dependencies = [
11− "gimli",
12−]
13−
14[[package]]
5[[package]]
15name = "adler"
6name = "adler"
16version = "1.0.2"
7version = "1.0.2"
@@ -19,37 +10,44 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19
10
20[[package]]
11[[package]]
21name = "ahash"
12name = "ahash"
22−version = "0.8.6"
13+version = "0.7.6"
23source = "registry+https://github.com/rust-lang/crates.io-index"
14source = "registry+https://github.com/rust-lang/crates.io-index"
24−checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
15+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
25dependencies = [
16dependencies = [
26− "cfg-if",
27 "getrandom",
17 "getrandom",
28 "once_cell",
18 "once_cell",
29 "version_check",
19 "version_check",
30− "zerocopy",
31]
20]
32
21
33[[package]]
22[[package]]
34−name = "aho-corasick"
23+name = "ahash"
35−version = "1.1.2"
24+version = "0.8.3"
36source = "registry+https://github.com/rust-lang/crates.io-index"
25source = "registry+https://github.com/rust-lang/crates.io-index"
37−checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
26+checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
38dependencies = [
27dependencies = [
39− "memchr",
28+ "cfg-if",
29+ "getrandom",
30+ "once_cell",
31+ "version_check",
40]
32]
41
33
42[[package]]
34[[package]]
43−name = "allocator-api2"
35+name = "aho-corasick"
44−version = "0.2.14"
36+version = "0.7.20"
45source = "registry+https://github.com/rust-lang/crates.io-index"
37source = "registry+https://github.com/rust-lang/crates.io-index"
46−checksum = "c4f263788a35611fba42eb41ff811c5d0360c58b97402570312a350736e2542e"
38+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
39+dependencies = [
40+ "memchr",
41+]
47
42
48[[package]]
43[[package]]
49−name = "android-tzdata"
44+name = "aho-corasick"
50−version = "0.1.1"
45+version = "1.0.1"
51source = "registry+https://github.com/rust-lang/crates.io-index"
46source = "registry+https://github.com/rust-lang/crates.io-index"
52−checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
47+checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
48+dependencies = [
49+ "memchr",
50+]
53
51
54[[package]]
52[[package]]
55name = "android_system_properties"
53name = "android_system_properties"
@@ -62,9 +60,9 @@ dependencies = [
62
60
63[[package]]
61[[package]]
64name = "anyhow"
62name = "anyhow"
65−version = "1.0.76"
63+version = "1.0.71"
66source = "registry+https://github.com/rust-lang/crates.io-index"
64source = "registry+https://github.com/rust-lang/crates.io-index"
67−checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
65+checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
68
66
69[[package]]
67[[package]]
70name = "arc-swap"
68name = "arc-swap"
@@ -78,21 +76,6 @@ version = "1.1.0"
78source = "registry+https://github.com/rust-lang/crates.io-index"
76source = "registry+https://github.com/rust-lang/crates.io-index"
79checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
77checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
80
78
81−[[package]]
82−name = "backtrace"
83−version = "0.3.68"
84−source = "registry+https://github.com/rust-lang/crates.io-index"
85−checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
86−dependencies = [
87− "addr2line",
88− "cc",
89− "cfg-if",
90− "libc",
91− "miniz_oxide",
92− "object",
93− "rustc-demangle",
94−]
95−
96[[package]]
79[[package]]
97name = "bitflags"
80name = "bitflags"
98version = "1.3.2"
81version = "1.3.2"
@@ -101,17 +84,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
101
84
102[[package]]
85[[package]]
103name = "bitflags"
86name = "bitflags"
104−version = "2.4.1"
87+version = "2.2.1"
105source = "registry+https://github.com/rust-lang/crates.io-index"
88source = "registry+https://github.com/rust-lang/crates.io-index"
106−checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
89+checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813"
107
90
108[[package]]
91[[package]]
109name = "bstr"
92name = "bstr"
110−version = "1.8.0"
93+version = "1.4.0"
111source = "registry+https://github.com/rust-lang/crates.io-index"
94source = "registry+https://github.com/rust-lang/crates.io-index"
112−checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
95+checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
113dependencies = [
96dependencies = [
114 "memchr",
97 "memchr",
98+ "once_cell",
115 "regex-automata",
99 "regex-automata",
116 "serde",
100 "serde",
117]
101]
@@ -131,6 +115,12 @@ version = "3.12.0"
131source = "registry+https://github.com/rust-lang/crates.io-index"
115source = "registry+https://github.com/rust-lang/crates.io-index"
132checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
116checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
133
117
118+[[package]]
119+name = "bytecount"
120+version = "0.6.3"
121+source = "registry+https://github.com/rust-lang/crates.io-index"
122+checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
123+
134[[package]]
124[[package]]
135name = "bytes"
125name = "bytes"
136version = "1.4.0"
126version = "1.4.0"
@@ -145,12 +135,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
145
135
146[[package]]
136[[package]]
147name = "cc"
137name = "cc"
148−version = "1.0.84"
138+version = "1.0.79"
149source = "registry+https://github.com/rust-lang/crates.io-index"
139source = "registry+https://github.com/rust-lang/crates.io-index"
150−checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
140+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
151−dependencies = [
152− "libc",
153−]
154
141
155[[package]]
142[[package]]
156name = "cfg-if"
143name = "cfg-if"
@@ -171,23 +158,25 @@ dependencies = [
171
158
172[[package]]
159[[package]]
173name = "chrono"
160name = "chrono"
174−version = "0.4.31"
161+version = "0.4.24"
175source = "registry+https://github.com/rust-lang/crates.io-index"
162source = "registry+https://github.com/rust-lang/crates.io-index"
176−checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
163+checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
177dependencies = [
164dependencies = [
178− "android-tzdata",
179 "iana-time-zone",
165 "iana-time-zone",
166+ "num-integer",
180 "num-traits",
167 "num-traits",
181− "windows-targets 0.48.0",
168+ "winapi",
182]
169]
183
170
184[[package]]
171[[package]]
185name = "clipboard-win"
172name = "clipboard-win"
186−version = "5.0.0"
173+version = "4.5.0"
187source = "registry+https://github.com/rust-lang/crates.io-index"
174source = "registry+https://github.com/rust-lang/crates.io-index"
188−checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
175+checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
189dependencies = [
176dependencies = [
190 "error-code",
177 "error-code",
178+ "str-buf",
179+ "winapi",
191]
180]
192
181
193[[package]]
182[[package]]
@@ -221,12 +210,6 @@ version = "0.8.4"
221source = "registry+https://github.com/rust-lang/crates.io-index"
210source = "registry+https://github.com/rust-lang/crates.io-index"
222checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
211checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
223
212
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]]
213[[package]]
231name = "crc32fast"
214name = "crc32fast"
232version = "1.3.2"
215version = "1.3.2"
@@ -236,58 +219,14 @@ dependencies = [
236 "cfg-if",
219 "cfg-if",
237]
220]
238
221
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]]
222[[package]]
283name = "crossterm"
223name = "crossterm"
284−version = "0.27.0"
224+version = "0.26.1"
285source = "registry+https://github.com/rust-lang/crates.io-index"
225source = "registry+https://github.com/rust-lang/crates.io-index"
286−checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
226+checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
287dependencies = [
227dependencies = [
288− "bitflags 2.4.1",
228+ "bitflags 1.3.2",
289 "crossterm_winapi",
229 "crossterm_winapi",
290− "filedescriptor",
291 "futures-core",
230 "futures-core",
292 "libc",
231 "libc",
293 "mio",
232 "mio",
@@ -299,9 +238,9 @@ dependencies = [
299
238
300[[package]]
239[[package]]
301name = "crossterm_winapi"
240name = "crossterm_winapi"
302−version = "0.9.1"
241+version = "0.9.0"
303source = "registry+https://github.com/rust-lang/crates.io-index"
242source = "registry+https://github.com/rust-lang/crates.io-index"
304−checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
243+checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
305dependencies = [
244dependencies = [
306 "winapi",
245 "winapi",
307]
246]
@@ -330,7 +269,7 @@ dependencies = [
330 "proc-macro2",
269 "proc-macro2",
331 "quote",
270 "quote",
332 "scratch",
271 "scratch",
333− "syn 2.0.38",
272+ "syn 2.0.15",
334]
273]
335
274
336[[package]]
275[[package]]
@@ -347,7 +286,28 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
347dependencies = [
286dependencies = [
348 "proc-macro2",
287 "proc-macro2",
349 "quote",
288 "quote",
350− "syn 2.0.38",
289+ "syn 2.0.15",
290+]
291+
292+[[package]]
293+name = "dirs-next"
294+version = "2.0.0"
295+source = "registry+https://github.com/rust-lang/crates.io-index"
296+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
297+dependencies = [
298+ "cfg-if",
299+ "dirs-sys-next",
300+]
301+
302+[[package]]
303+name = "dirs-sys-next"
304+version = "0.1.2"
305+source = "registry+https://github.com/rust-lang/crates.io-index"
306+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
307+dependencies = [
308+ "libc",
309+ "redox_users",
310+ "winapi",
351]
311]
352
312
353[[package]]
313[[package]]
@@ -364,9 +324,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
364
324
365[[package]]
325[[package]]
366name = "encoding_rs"
326name = "encoding_rs"
367−version = "0.8.33"
327+version = "0.8.32"
368source = "registry+https://github.com/rust-lang/crates.io-index"
328source = "registry+https://github.com/rust-lang/crates.io-index"
369−checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
329+checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
370dependencies = [
330dependencies = [
371 "cfg-if",
331 "cfg-if",
372]
332]
@@ -381,26 +341,35 @@ dependencies = [
381]
341]
382
342
383[[package]]
343[[package]]
384−name = "equivalent"
344+name = "errno"
385−version = "1.0.0"
345+version = "0.3.1"
386source = "registry+https://github.com/rust-lang/crates.io-index"
346source = "registry+https://github.com/rust-lang/crates.io-index"
387−checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
347+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
348+dependencies = [
349+ "errno-dragonfly",
350+ "libc",
351+ "windows-sys 0.48.0",
352+]
388
353
389[[package]]
354[[package]]
390−name = "errno"
355+name = "errno-dragonfly"
391−version = "0.3.8"
356+version = "0.1.2"
392source = "registry+https://github.com/rust-lang/crates.io-index"
357source = "registry+https://github.com/rust-lang/crates.io-index"
393−checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
358+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
394dependencies = [
359dependencies = [
360+ "cc",
395 "libc",
361 "libc",
396− "windows-sys 0.52.0",
397]
362]
398
363
399[[package]]
364[[package]]
400name = "error-code"
365name = "error-code"
401−version = "3.0.0"
366+version = "2.3.1"
402source = "registry+https://github.com/rust-lang/crates.io-index"
367source = "registry+https://github.com/rust-lang/crates.io-index"
403−checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
368+checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
369+dependencies = [
370+ "libc",
371+ "str-buf",
372+]
404
373
405[[package]]
374[[package]]
406name = "etcetera"
375name = "etcetera"
@@ -414,20 +383,14 @@ dependencies = [
414]
383]
415
384
416[[package]]
385[[package]]
417−name = "faster-hex"
386+name = "fastrand"
418−version = "0.9.0"
387+version = "1.9.0"
419source = "registry+https://github.com/rust-lang/crates.io-index"
388source = "registry+https://github.com/rust-lang/crates.io-index"
420−checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
389+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
421dependencies = [
390dependencies = [
422− "serde",
391+ "instant",
423]
392]
424
393
425−[[package]]
426−name = "fastrand"
427−version = "2.0.0"
428−source = "registry+https://github.com/rust-lang/crates.io-index"
429−checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
430−
431[[package]]
394[[package]]
432name = "fern"
395name = "fern"
433version = "0.6.2"
396version = "0.6.2"
@@ -438,46 +401,53 @@ dependencies = [
438]
401]
439
402
440[[package]]
403[[package]]
441−name = "filedescriptor"
404+name = "filetime"
442−version = "0.8.2"
405+version = "0.2.21"
443source = "registry+https://github.com/rust-lang/crates.io-index"
406source = "registry+https://github.com/rust-lang/crates.io-index"
444−checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
407+checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
445dependencies = [
408dependencies = [
409+ "cfg-if",
446 "libc",
410 "libc",
447− "thiserror",
411+ "redox_syscall 0.2.16",
448− "winapi",
412+ "windows-sys 0.48.0",
449]
413]
450
414
451[[package]]
415[[package]]
452name = "flate2"
416name = "flate2"
453−version = "1.0.27"
417+version = "1.0.25"
454source = "registry+https://github.com/rust-lang/crates.io-index"
418source = "registry+https://github.com/rust-lang/crates.io-index"
455−checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
419+checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
456dependencies = [
420dependencies = [
457 "crc32fast",
421 "crc32fast",
458 "miniz_oxide",
422 "miniz_oxide",
459]
423]
460
424
425+[[package]]
426+name = "fnv"
427+version = "1.0.7"
428+source = "registry+https://github.com/rust-lang/crates.io-index"
429+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
430+
461[[package]]
431[[package]]
462name = "form_urlencoded"
432name = "form_urlencoded"
463−version = "1.2.1"
433+version = "1.1.0"
464source = "registry+https://github.com/rust-lang/crates.io-index"
434source = "registry+https://github.com/rust-lang/crates.io-index"
465−checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
435+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
466dependencies = [
436dependencies = [
467 "percent-encoding",
437 "percent-encoding",
468]
438]
469
439
470[[package]]
440[[package]]
471name = "futures-core"
441name = "futures-core"
472−version = "0.3.30"
442+version = "0.3.28"
473source = "registry+https://github.com/rust-lang/crates.io-index"
443source = "registry+https://github.com/rust-lang/crates.io-index"
474−checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
444+checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
475
445
476[[package]]
446[[package]]
477name = "futures-executor"
447name = "futures-executor"
478−version = "0.3.29"
448+version = "0.3.28"
479source = "registry+https://github.com/rust-lang/crates.io-index"
449source = "registry+https://github.com/rust-lang/crates.io-index"
480−checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
450+checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
481dependencies = [
451dependencies = [
482 "futures-core",
452 "futures-core",
483 "futures-task",
453 "futures-task",
@@ -486,15 +456,15 @@ dependencies = [
486
456
487[[package]]
457[[package]]
488name = "futures-task"
458name = "futures-task"
489−version = "0.3.30"
459+version = "0.3.28"
490source = "registry+https://github.com/rust-lang/crates.io-index"
460source = "registry+https://github.com/rust-lang/crates.io-index"
491−checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
461+checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
492
462
493[[package]]
463[[package]]
494name = "futures-util"
464name = "futures-util"
495−version = "0.3.30"
465+version = "0.3.28"
496source = "registry+https://github.com/rust-lang/crates.io-index"
466source = "registry+https://github.com/rust-lang/crates.io-index"
497−checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
467+checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
498dependencies = [
468dependencies = [
499 "futures-core",
469 "futures-core",
500 "futures-task",
470 "futures-task",
@@ -503,6 +473,15 @@ dependencies = [
503 "slab",
473 "slab",
504]
474]
505
475
476+[[package]]
477+name = "fuzzy-matcher"
478+version = "0.3.7"
479+source = "registry+https://github.com/rust-lang/crates.io-index"
480+checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
481+dependencies = [
482+ "thread_local",
483+]
484+
506[[package]]
485[[package]]
507name = "getrandom"
486name = "getrandom"
508version = "0.2.9"
487version = "0.2.9"
@@ -514,21 +493,16 @@ dependencies = [
514 "wasi",
493 "wasi",
515]
494]
516
495
517−[[package]]
518−name = "gimli"
519−version = "0.27.3"
520−source = "registry+https://github.com/rust-lang/crates.io-index"
521−checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
522−
523[[package]]
496[[package]]
524name = "gix"
497name = "gix"
525−version = "0.57.0"
498+version = "0.44.1"
526source = "registry+https://github.com/rust-lang/crates.io-index"
499source = "registry+https://github.com/rust-lang/crates.io-index"
527−checksum = "721c7497ab24b665ed5a1eb2b3526936aa60068e61ba260651f7e77c52feec69"
500+checksum = "6bf41b61f7df395284f7a579c0fa1a7e012c5aede655174d4e91299ef1cac643"
528dependencies = [
501dependencies = [
529 "gix-actor",
502 "gix-actor",
530− "gix-commitgraph",
503+ "gix-attributes",
531 "gix-config",
504 "gix-config",
505+ "gix-credentials",
532 "gix-date",
506 "gix-date",
533 "gix-diff",
507 "gix-diff",
534 "gix-discover",
508 "gix-discover",
@@ -537,25 +511,28 @@ dependencies = [
537 "gix-glob",
511 "gix-glob",
538 "gix-hash",
512 "gix-hash",
539 "gix-hashtable",
513 "gix-hashtable",
514+ "gix-ignore",
515+ "gix-index",
540 "gix-lock",
516 "gix-lock",
541− "gix-macros",
517+ "gix-mailmap",
542 "gix-object",
518 "gix-object",
543 "gix-odb",
519 "gix-odb",
544 "gix-pack",
520 "gix-pack",
545 "gix-path",
521 "gix-path",
522+ "gix-prompt",
546 "gix-ref",
523 "gix-ref",
547 "gix-refspec",
524 "gix-refspec",
548 "gix-revision",
525 "gix-revision",
549− "gix-revwalk",
550 "gix-sec",
526 "gix-sec",
551 "gix-tempfile",
527 "gix-tempfile",
552− "gix-trace",
553 "gix-traverse",
528 "gix-traverse",
554 "gix-url",
529 "gix-url",
555 "gix-utils",
530 "gix-utils",
556 "gix-validate",
531 "gix-validate",
532+ "gix-worktree",
533+ "log",
557 "once_cell",
534 "once_cell",
558− "parking_lot",
535+ "signal-hook",
559 "smallvec",
536 "smallvec",
560 "thiserror",
537 "thiserror",
561 "unicode-normalization",
538 "unicode-normalization",
@@ -563,46 +540,67 @@ dependencies = [
563
540
564[[package]]
541[[package]]
565name = "gix-actor"
542name = "gix-actor"
566−version = "0.29.0"
543+version = "0.20.0"
567source = "registry+https://github.com/rust-lang/crates.io-index"
544source = "registry+https://github.com/rust-lang/crates.io-index"
568−checksum = "886014c4865b93ce268f1d3eddd4fd3261242c3f3ee61eb36009f913016a9059"
545+checksum = "848efa0f1210cea8638f95691c82a46f98a74b9e3524f01d4955ebc25a8f84f3"
569dependencies = [
546dependencies = [
570 "bstr",
547 "bstr",
571 "btoi",
548 "btoi",
572 "gix-date",
549 "gix-date",
573 "itoa",
550 "itoa",
551+ "nom",
552+ "thiserror",
553+]
554+
555+[[package]]
556+name = "gix-attributes"
557+version = "0.12.0"
558+source = "registry+https://github.com/rust-lang/crates.io-index"
559+checksum = "3015baa01ad2122fbcaab7863c857a603eb7b7ec12ac8141207c42c6439805e2"
560+dependencies = [
561+ "bstr",
562+ "gix-glob",
563+ "gix-path",
564+ "gix-quote",
565+ "kstring",
566+ "log",
567+ "smallvec",
568+ "thiserror",
569+ "unicode-bom",
570+]
571+
572+[[package]]
573+name = "gix-bitmap"
574+version = "0.2.3"
575+source = "registry+https://github.com/rust-lang/crates.io-index"
576+checksum = "55a95f4942360766c3880bdb2b4b57f1ef73b190fc424755e7fdf480430af618"
577+dependencies = [
574 "thiserror",
578 "thiserror",
575− "winnow 0.5.28",
576]
579]
577
580
578[[package]]
581[[package]]
579name = "gix-chunk"
582name = "gix-chunk"
580−version = "0.4.6"
583+version = "0.4.1"
581source = "registry+https://github.com/rust-lang/crates.io-index"
584source = "registry+https://github.com/rust-lang/crates.io-index"
582−checksum = "2ade37ef69870de0ed966b97c57a3a947a22ff3482a52c3b99b205f77bcb08fb"
585+checksum = "b0d39583cab06464b8bf73b3f1707458270f0e7383cb24c3c9c1a16e6f792978"
583dependencies = [
586dependencies = [
584 "thiserror",
587 "thiserror",
585]
588]
586
589
587[[package]]
590[[package]]
588−name = "gix-commitgraph"
591+name = "gix-command"
589−version = "0.23.0"
592+version = "0.2.4"
590source = "registry+https://github.com/rust-lang/crates.io-index"
593source = "registry+https://github.com/rust-lang/crates.io-index"
591−checksum = "e7559ea9cefee188cd88b05afcc8e3ef7a3cb4a5c647bccf06b981e591b02b77"
594+checksum = "b2c6f75c1e0f924de39e750880a6e21307194bb1ab773efe3c7d2d787277f8ab"
592dependencies = [
595dependencies = [
593 "bstr",
596 "bstr",
594− "gix-chunk",
595− "gix-features",
596− "gix-hash",
597− "memmap2",
598− "thiserror",
599]
597]
600
598
601[[package]]
599[[package]]
602name = "gix-config"
600name = "gix-config"
603−version = "0.33.0"
601+version = "0.22.0"
604source = "registry+https://github.com/rust-lang/crates.io-index"
602source = "registry+https://github.com/rust-lang/crates.io-index"
605−checksum = "9c35dc7f9c00a42bbc9cfa1ca2ec0a78ad1b76ff0736d3d35dfd612962244467"
603+checksum = "1d252a0eddb6df74600d3d8872dc9fe98835a7da43110411d705b682f49d4ac1"
606dependencies = [
604dependencies = [
607 "bstr",
605 "bstr",
608 "gix-config-value",
606 "gix-config-value",
@@ -611,32 +609,49 @@ dependencies = [
611 "gix-path",
609 "gix-path",
612 "gix-ref",
610 "gix-ref",
613 "gix-sec",
611 "gix-sec",
612+ "log",
614 "memchr",
613 "memchr",
614+ "nom",
615 "once_cell",
615 "once_cell",
616 "smallvec",
616 "smallvec",
617 "thiserror",
617 "thiserror",
618 "unicode-bom",
618 "unicode-bom",
619− "winnow 0.5.28",
620]
619]
621
620
622[[package]]
621[[package]]
623name = "gix-config-value"
622name = "gix-config-value"
624−version = "0.14.2"
623+version = "0.12.0"
625source = "registry+https://github.com/rust-lang/crates.io-index"
624source = "registry+https://github.com/rust-lang/crates.io-index"
626−checksum = "39f388cb2396aee82d6f460a2e7770659bdf8854e9e5478f7d2b1324a9698284"
625+checksum = "786861e84a5793ad5f863d846de5eb064cd23b87e61ad708c8c402608202e7be"
627dependencies = [
626dependencies = [
628− "bitflags 2.4.1",
627+ "bitflags 2.2.1",
629 "bstr",
628 "bstr",
630 "gix-path",
629 "gix-path",
631 "libc",
630 "libc",
632 "thiserror",
631 "thiserror",
633]
632]
634
633
634+[[package]]
635+name = "gix-credentials"
636+version = "0.14.0"
637+source = "registry+https://github.com/rust-lang/crates.io-index"
638+checksum = "4874a4fc11ffa844a3c2b87a66957bda30a73b577ef1acf15ac34df5745de5ff"
639+dependencies = [
640+ "bstr",
641+ "gix-command",
642+ "gix-config-value",
643+ "gix-path",
644+ "gix-prompt",
645+ "gix-sec",
646+ "gix-url",
647+ "thiserror",
648+]
649+
635[[package]]
650[[package]]
636name = "gix-date"
651name = "gix-date"
637−version = "0.8.2"
652+version = "0.5.0"
638source = "registry+https://github.com/rust-lang/crates.io-index"
653source = "registry+https://github.com/rust-lang/crates.io-index"
639−checksum = "9d85f4a01e0d05c3de585e28bae514d4baf01655e3fc3f14ce6f30bf62405345"
654+checksum = "99056f37270715f5c7584fd8b46899a2296af9cae92463bf58b8bd1f5a78e553"
640dependencies = [
655dependencies = [
641 "bstr",
656 "bstr",
642 "itoa",
657 "itoa",
@@ -646,21 +661,21 @@ dependencies = [
646
661
647[[package]]
662[[package]]
648name = "gix-diff"
663name = "gix-diff"
649−version = "0.39.0"
664+version = "0.29.0"
650source = "registry+https://github.com/rust-lang/crates.io-index"
665source = "registry+https://github.com/rust-lang/crates.io-index"
651−checksum = "ac0e75f5afd2f6c47c800b6b0a000a08045739d0450d20482e8faa42543f62d1"
666+checksum = "644a0f2768bc42d7a69289ada80c9e15c589caefc6a315d2307202df83ed1186"
652dependencies = [
667dependencies = [
653− "bstr",
654 "gix-hash",
668 "gix-hash",
655 "gix-object",
669 "gix-object",
670+ "imara-diff",
656 "thiserror",
671 "thiserror",
657]
672]
658
673
659[[package]]
674[[package]]
660name = "gix-discover"
675name = "gix-discover"
661−version = "0.28.0"
676+version = "0.18.1"
662source = "registry+https://github.com/rust-lang/crates.io-index"
677source = "registry+https://github.com/rust-lang/crates.io-index"
663−checksum = "f0b63ce2ad81632ac1a84ac370f85a5e580c3261580bd85ea17ff35d3a0bba18"
678+checksum = "1a6b61363e63e7cdaa3e6f96acb0257ebdb3d8883e21eba5930c99f07f0a5fc0"
664dependencies = [
679dependencies = [
665 "bstr",
680 "bstr",
666 "dunce",
681 "dunce",
@@ -673,14 +688,13 @@ dependencies = [
673
688
674[[package]]
689[[package]]
675name = "gix-features"
690name = "gix-features"
676−version = "0.37.0"
691+version = "0.29.0"
677source = "registry+https://github.com/rust-lang/crates.io-index"
692source = "registry+https://github.com/rust-lang/crates.io-index"
678−checksum = "befe7edea299a824504b5acc96d7a3a538125b38c42f3a8379f6912a29c90c81"
693+checksum = "cf69b0f5c701cc3ae22d3204b671907668f6437ca88862d355eaf9bc47a4f897"
679dependencies = [
694dependencies = [
680 "crc32fast",
695 "crc32fast",
681 "flate2",
696 "flate2",
682 "gix-hash",
697 "gix-hash",
683− "gix-trace",
684 "libc",
698 "libc",
685 "once_cell",
699 "once_cell",
686 "prodash",
700 "prodash",
@@ -691,20 +705,20 @@ dependencies = [
691
705
692[[package]]
706[[package]]
693name = "gix-fs"
707name = "gix-fs"
694−version = "0.9.0"
708+version = "0.1.1"
695source = "registry+https://github.com/rust-lang/crates.io-index"
709source = "registry+https://github.com/rust-lang/crates.io-index"
696−checksum = "007017ce93b819ea52c0ec68306f7d72212a2d307c3d70f1548c7141c015d0a1"
710+checksum = "9b37a1832f691fdc09910bd267f9a2e413737c1f9ec68c6e31f9e802616278a9"
697dependencies = [
711dependencies = [
698 "gix-features",
712 "gix-features",
699]
713]
700
714
701[[package]]
715[[package]]
702name = "gix-glob"
716name = "gix-glob"
703−version = "0.15.0"
717+version = "0.7.0"
704source = "registry+https://github.com/rust-lang/crates.io-index"
718source = "registry+https://github.com/rust-lang/crates.io-index"
705−checksum = "61fb116c2516d3a1170e010118f639944a6389872c875a008960cdab2a44ac72"
719+checksum = "c07c98204529ac3f24b34754540a852593d2a4c7349008df389240266627a72a"
706dependencies = [
720dependencies = [
707− "bitflags 2.4.1",
721+ "bitflags 2.2.1",
708 "bstr",
722 "bstr",
709 "gix-features",
723 "gix-features",
710 "gix-path",
724 "gix-path",
@@ -712,74 +726,107 @@ dependencies = [
712
726
713[[package]]
727[[package]]
714name = "gix-hash"
728name = "gix-hash"
715−version = "0.14.0"
729+version = "0.11.1"
716source = "registry+https://github.com/rust-lang/crates.io-index"
730source = "registry+https://github.com/rust-lang/crates.io-index"
717−checksum = "c52c3170a17b6031833cd2eb4ad7bc23f2755d06e6e70f78dec21d42e8fe1b30"
731+checksum = "078eec3ac2808cc03f0bddd2704cb661da5c5dc33b41a9d7947b141d499c7c42"
718dependencies = [
732dependencies = [
719− "faster-hex",
733+ "hex",
720 "thiserror",
734 "thiserror",
721]
735]
722
736
723[[package]]
737[[package]]
724name = "gix-hashtable"
738name = "gix-hashtable"
725−version = "0.5.0"
739+version = "0.2.0"
726source = "registry+https://github.com/rust-lang/crates.io-index"
740source = "registry+https://github.com/rust-lang/crates.io-index"
727−checksum = "40a838e6366a5e5b84668b6997ce0981b833136468e8ba949f424c0ef2927eba"
741+checksum = "afebb85691c6a085b114e01a27f4a61364519298c5826cb87a45c304802299bc"
728dependencies = [
742dependencies = [
729 "gix-hash",
743 "gix-hash",
730− "hashbrown 0.14.3",
744+ "hashbrown 0.13.2",
731 "parking_lot",
745 "parking_lot",
732]
746]
733
747
748+[[package]]
749+name = "gix-ignore"
750+version = "0.2.0"
751+source = "registry+https://github.com/rust-lang/crates.io-index"
752+checksum = "ba205b6df563e2906768bb22834c82eb46c5fdfcd86ba2c347270bc8309a05b2"
753+dependencies = [
754+ "bstr",
755+ "gix-glob",
756+ "gix-path",
757+ "unicode-bom",
758+]
759+
760+[[package]]
761+name = "gix-index"
762+version = "0.16.0"
763+source = "registry+https://github.com/rust-lang/crates.io-index"
764+checksum = "fa282756760f79c401d4f4f42588fbb4aa27bbb4b0830f3b4d3480c21a4ac5a7"
765+dependencies = [
766+ "bitflags 2.2.1",
767+ "bstr",
768+ "btoi",
769+ "filetime",
770+ "gix-bitmap",
771+ "gix-features",
772+ "gix-hash",
773+ "gix-lock",
774+ "gix-object",
775+ "gix-traverse",
776+ "itoa",
777+ "memmap2",
778+ "smallvec",
779+ "thiserror",
780+]
781+
734[[package]]
782[[package]]
735name = "gix-lock"
783name = "gix-lock"
736−version = "12.0.0"
784+version = "5.0.0"
737source = "registry+https://github.com/rust-lang/crates.io-index"
785source = "registry+https://github.com/rust-lang/crates.io-index"
738−checksum = "6cf112ddee94223c119a8534dad027740dc3aba3365ac5edeef8a7f6660c74db"
786+checksum = "41b80172055c5d8017a48ddac5cc7a95421c00211047db0165c97853c4f05194"
739dependencies = [
787dependencies = [
788+ "fastrand",
740 "gix-tempfile",
789 "gix-tempfile",
741− "gix-utils",
742 "thiserror",
790 "thiserror",
743]
791]
744
792
745[[package]]
793[[package]]
746−name = "gix-macros"
794+name = "gix-mailmap"
747−version = "0.1.2"
795+version = "0.12.0"
748source = "registry+https://github.com/rust-lang/crates.io-index"
796source = "registry+https://github.com/rust-lang/crates.io-index"
749−checksum = "fc207b64189cf71bcb17fc841ab99a5d63d0845546b611e2703ce467f659323a"
797+checksum = "e8856cec3bdc3610c06970d28b6cb20a0c6621621cf9a8ec48cbd23f2630f362"
750dependencies = [
798dependencies = [
751− "proc-macro2",
799+ "bstr",
752− "quote",
800+ "gix-actor",
753− "syn 2.0.38",
801+ "thiserror",
754]
802]
755
803
756[[package]]
804[[package]]
757name = "gix-object"
805name = "gix-object"
758−version = "0.40.0"
806+version = "0.29.1"
759source = "registry+https://github.com/rust-lang/crates.io-index"
807source = "registry+https://github.com/rust-lang/crates.io-index"
760−checksum = "e4c77e47ffba92127faf632a841fce23d19547e269bd8b88e68961a70eab4e93"
808+checksum = "c9bb30ce0818d37096daa29efe361a4bc6dd0b51a5726598898be7e9a40a01e1"
761dependencies = [
809dependencies = [
762 "bstr",
810 "bstr",
763 "btoi",
811 "btoi",
764 "gix-actor",
812 "gix-actor",
765− "gix-date",
766 "gix-features",
813 "gix-features",
767 "gix-hash",
814 "gix-hash",
768 "gix-validate",
815 "gix-validate",
816+ "hex",
769 "itoa",
817 "itoa",
818+ "nom",
770 "smallvec",
819 "smallvec",
771 "thiserror",
820 "thiserror",
772− "winnow 0.5.28",
773]
821]
774
822
775[[package]]
823[[package]]
776name = "gix-odb"
824name = "gix-odb"
777−version = "0.56.0"
825+version = "0.45.0"
778source = "registry+https://github.com/rust-lang/crates.io-index"
826source = "registry+https://github.com/rust-lang/crates.io-index"
779−checksum = "a3254f2005cc7553ea78e85e816a09150c6f7a64e6b7627b8d1fdc56721bea73"
827+checksum = "bca2f324aa67672b6d0f2c0fa93f96eb6a7029d260e4c1df5dce3c015f5e5add"
780dependencies = [
828dependencies = [
781 "arc-swap",
829 "arc-swap",
782− "gix-date",
783 "gix-features",
830 "gix-features",
784 "gix-hash",
831 "gix-hash",
785 "gix-object",
832 "gix-object",
@@ -793,18 +840,20 @@ dependencies = [
793
840
794[[package]]
841[[package]]
795name = "gix-pack"
842name = "gix-pack"
796−version = "0.46.0"
843+version = "0.35.0"
797source = "registry+https://github.com/rust-lang/crates.io-index"
844source = "registry+https://github.com/rust-lang/crates.io-index"
798−checksum = "02ebc8cd657eec207d82d8f876ca402361308ecd3c87a47935b0299506257b4f"
845+checksum = "164a515900a83257ae4aa80e741655bee7a2e39113fb535d7a5ac623b445ff20"
799dependencies = [
846dependencies = [
800 "clru",
847 "clru",
801 "gix-chunk",
848 "gix-chunk",
849+ "gix-diff",
802 "gix-features",
850 "gix-features",
803 "gix-hash",
851 "gix-hash",
804 "gix-hashtable",
852 "gix-hashtable",
805 "gix-object",
853 "gix-object",
806 "gix-path",
854 "gix-path",
807 "gix-tempfile",
855 "gix-tempfile",
856+ "gix-traverse",
808 "memmap2",
857 "memmap2",
809 "parking_lot",
858 "parking_lot",
810 "smallvec",
859 "smallvec",
@@ -813,22 +862,34 @@ dependencies = [
813
862
814[[package]]
863[[package]]
815name = "gix-path"
864name = "gix-path"
816−version = "0.10.2"
865+version = "0.8.0"
817source = "registry+https://github.com/rust-lang/crates.io-index"
866source = "registry+https://github.com/rust-lang/crates.io-index"
818−checksum = "10931652a3da126990ac93bce1b1c600cc99d7c268d712b6360ed52174ce1b68"
867+checksum = "4fc78f47095a0c15aea0e66103838f0748f4494bf7a9555dfe0f00425400396c"
819dependencies = [
868dependencies = [
820 "bstr",
869 "bstr",
821− "gix-trace",
822 "home",
870 "home",
823 "once_cell",
871 "once_cell",
824 "thiserror",
872 "thiserror",
825]
873]
826
874
875+[[package]]
876+name = "gix-prompt"
877+version = "0.5.0"
878+source = "registry+https://github.com/rust-lang/crates.io-index"
879+checksum = "330d11fdf88fff3366c2491efde2f3e454958efe7d5ddf60272e8fb1d944bb01"
880+dependencies = [
881+ "gix-command",
882+ "gix-config-value",
883+ "parking_lot",
884+ "rustix",
885+ "thiserror",
886+]
887+
827[[package]]
888[[package]]
828name = "gix-quote"
889name = "gix-quote"
829−version = "0.4.9"
890+version = "0.4.3"
830source = "registry+https://github.com/rust-lang/crates.io-index"
891source = "registry+https://github.com/rust-lang/crates.io-index"
831−checksum = "6c43530cb94a7759807e6f8d180e17ac9c65673b891645c6c433831dc0cf4342"
892+checksum = "a282f5a8d9ee0b09ec47390ac727350c48f2f5c76d803cd8da6b3e7ad56e0bcb"
832dependencies = [
893dependencies = [
833 "bstr",
894 "bstr",
834 "btoi",
895 "btoi",
@@ -837,12 +898,11 @@ dependencies = [
837
898
838[[package]]
899[[package]]
839name = "gix-ref"
900name = "gix-ref"
840−version = "0.40.0"
901+version = "0.29.1"
841source = "registry+https://github.com/rust-lang/crates.io-index"
902source = "registry+https://github.com/rust-lang/crates.io-index"
842−checksum = "baa951b3b850d6d1be4f900768e49af20b76bf9505beac22af723d57249a2f1d"
903+checksum = "1e03989e9d49954368e1b526578230fc7189d1634acdfbe79e9ba1de717e15d5"
843dependencies = [
904dependencies = [
844 "gix-actor",
905 "gix-actor",
845− "gix-date",
846 "gix-features",
906 "gix-features",
847 "gix-fs",
907 "gix-fs",
848 "gix-hash",
908 "gix-hash",
@@ -852,15 +912,15 @@ dependencies = [
852 "gix-tempfile",
912 "gix-tempfile",
853 "gix-validate",
913 "gix-validate",
854 "memmap2",
914 "memmap2",
915+ "nom",
855 "thiserror",
916 "thiserror",
856− "winnow 0.5.28",
857]
917]
858
918
859[[package]]
919[[package]]
860name = "gix-refspec"
920name = "gix-refspec"
861−version = "0.21.0"
921+version = "0.10.1"
862source = "registry+https://github.com/rust-lang/crates.io-index"
922source = "registry+https://github.com/rust-lang/crates.io-index"
863−checksum = "88c61f849d58c06e3068a0b601cf10127d2a07cdad00a725ed66cf303f76f6b3"
923+checksum = "0a6ea733820df67e4cd7797deb12727905824d8f5b7c59d943c456d314475892"
864dependencies = [
924dependencies = [
865 "bstr",
925 "bstr",
866 "gix-hash",
926 "gix-hash",
@@ -872,87 +932,61 @@ dependencies = [
872
932
873[[package]]
933[[package]]
874name = "gix-revision"
934name = "gix-revision"
875−version = "0.25.0"
935+version = "0.13.0"
876source = "registry+https://github.com/rust-lang/crates.io-index"
936source = "registry+https://github.com/rust-lang/crates.io-index"
877−checksum = "bcbbf91f4200c5c76802ef5f057b96f5a336827508881fe55a780be71a794d22"
937+checksum = "810f35e9afeccca999d5d348b239f9c162353127d2e13ff3240e31b919e35476"
878dependencies = [
938dependencies = [
879 "bstr",
939 "bstr",
880 "gix-date",
940 "gix-date",
881 "gix-hash",
941 "gix-hash",
882 "gix-hashtable",
942 "gix-hashtable",
883 "gix-object",
943 "gix-object",
884− "gix-revwalk",
885− "gix-trace",
886− "thiserror",
887−]
888−
889−[[package]]
890−name = "gix-revwalk"
891−version = "0.11.0"
892−source = "registry+https://github.com/rust-lang/crates.io-index"
893−checksum = "33ab66354d83f70f2730391747d0c75f94ef3c3dd40ebde2e206db9faaf7a0a7"
894−dependencies = [
895− "gix-commitgraph",
896− "gix-date",
897− "gix-hash",
898− "gix-hashtable",
899− "gix-object",
900− "smallvec",
901 "thiserror",
944 "thiserror",
902]
945]
903
946
904[[package]]
947[[package]]
905name = "gix-sec"
948name = "gix-sec"
906−version = "0.10.2"
949+version = "0.8.0"
907source = "registry+https://github.com/rust-lang/crates.io-index"
950source = "registry+https://github.com/rust-lang/crates.io-index"
908−checksum = "a9963f38a42144253ed4d571882d7db5ef644c12e6726e4b75135597cc9d0e1a"
951+checksum = "794520043d5a024dfeac335c6e520cb616f6963e30dab995892382e998c12897"
909dependencies = [
952dependencies = [
910− "bitflags 2.4.1",
953+ "bitflags 2.2.1",
911 "gix-path",
954 "gix-path",
912 "libc",
955 "libc",
913− "windows 0.52.0",
956+ "windows",
914]
957]
915
958
916[[package]]
959[[package]]
917name = "gix-tempfile"
960name = "gix-tempfile"
918−version = "12.0.0"
961+version = "5.0.2"
919source = "registry+https://github.com/rust-lang/crates.io-index"
962source = "registry+https://github.com/rust-lang/crates.io-index"
920−checksum = "e76a494bd530e1a1309188ff971825a24f159c76c2db0bf71fa5dfb469a2c915"
963+checksum = "c2ceb30a610e3f5f2d5f9a5114689fde507ba9417705a8cf3429604275b2153c"
921dependencies = [
964dependencies = [
922− "gix-fs",
923 "libc",
965 "libc",
924 "once_cell",
966 "once_cell",
925 "parking_lot",
967 "parking_lot",
968+ "signal-hook",
969+ "signal-hook-registry",
926 "tempfile",
970 "tempfile",
927]
971]
928
972
929−[[package]]
930−name = "gix-trace"
931−version = "0.1.5"
932−source = "registry+https://github.com/rust-lang/crates.io-index"
933−checksum = "bda62acb44dd86a40c7c3762a5403cfc1ac789ea559df54085cedf79864f809e"
934−
935[[package]]
973[[package]]
936name = "gix-traverse"
974name = "gix-traverse"
937−version = "0.36.0"
975+version = "0.25.0"
938source = "registry+https://github.com/rust-lang/crates.io-index"
976source = "registry+https://github.com/rust-lang/crates.io-index"
939−checksum = "8661fab39985c9214e56d81a63ceb5886ac948cec2fba76c39494d1e0e307ea8"
977+checksum = "a5be1e807f288c33bb005075111886cceb43ed8a167b3182a0f62c186e2a0dd1"
940dependencies = [
978dependencies = [
941− "gix-commitgraph",
942− "gix-date",
943 "gix-hash",
979 "gix-hash",
944 "gix-hashtable",
980 "gix-hashtable",
945 "gix-object",
981 "gix-object",
946− "gix-revwalk",
947− "smallvec",
948 "thiserror",
982 "thiserror",
949]
983]
950
984
951[[package]]
985[[package]]
952name = "gix-url"
986name = "gix-url"
953−version = "0.26.0"
987+version = "0.18.0"
954source = "registry+https://github.com/rust-lang/crates.io-index"
988source = "registry+https://github.com/rust-lang/crates.io-index"
955−checksum = "10a0129c1e8b52736d7c5128300a4485dbc85863001371e2771ac1754bd89fd7"
989+checksum = "dfc77f89054297cc81491e31f1bab4027e554b5ef742a44bd7035db9a0f78b76"
956dependencies = [
990dependencies = [
957 "bstr",
991 "bstr",
958 "gix-features",
992 "gix-features",
@@ -964,70 +998,93 @@ dependencies = [
964
998
965[[package]]
999[[package]]
966name = "gix-utils"
1000name = "gix-utils"
967−version = "0.1.7"
1001+version = "0.1.1"
968source = "registry+https://github.com/rust-lang/crates.io-index"
1002source = "registry+https://github.com/rust-lang/crates.io-index"
969−checksum = "ab9277a5e32e85d53f738096d872a4a9b76067ad471894ad31bd99c8fa2da1dc"
1003+checksum = "c10b69beac219acb8df673187a1f07dde2d74092f974fb3f9eb385aeb667c909"
970dependencies = [
1004dependencies = [
971 "fastrand",
1005 "fastrand",
972]
1006]
973
1007
974[[package]]
1008[[package]]
975name = "gix-validate"
1009name = "gix-validate"
976−version = "0.8.2"
1010+version = "0.7.4"
977source = "registry+https://github.com/rust-lang/crates.io-index"
1011source = "registry+https://github.com/rust-lang/crates.io-index"
978−checksum = "f805ebbdbaa4bfd98e2ee43e6d14099b8a4d9141f5d7b8202fea4d48e44263e7"
1012+checksum = "7bd629d3680773e1785e585d76fd4295b740b559cad9141517300d99a0c8c049"
979dependencies = [
1013dependencies = [
980 "bstr",
1014 "bstr",
981 "thiserror",
1015 "thiserror",
982]
1016]
983
1017
1018+[[package]]
1019+name = "gix-worktree"
1020+version = "0.17.0"
1021+source = "registry+https://github.com/rust-lang/crates.io-index"
1022+checksum = "10bf56a1f5037d84293ea6cece61d9f27c4866b1e13c1c95f37cf56b7da7af25"
1023+dependencies = [
1024+ "bstr",
1025+ "filetime",
1026+ "gix-attributes",
1027+ "gix-features",
1028+ "gix-fs",
1029+ "gix-glob",
1030+ "gix-hash",
1031+ "gix-ignore",
1032+ "gix-index",
1033+ "gix-object",
1034+ "gix-path",
1035+ "io-close",
1036+ "thiserror",
1037+]
1038+
984[[package]]
1039[[package]]
985name = "globset"
1040name = "globset"
986−version = "0.4.14"
1041+version = "0.4.10"
987source = "registry+https://github.com/rust-lang/crates.io-index"
1042source = "registry+https://github.com/rust-lang/crates.io-index"
988−checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
1043+checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
989dependencies = [
1044dependencies = [
990− "aho-corasick",
1045+ "aho-corasick 0.7.20",
991 "bstr",
1046 "bstr",
1047+ "fnv",
992 "log",
1048 "log",
993− "regex-automata",
1049+ "regex",
994− "regex-syntax",
995]
1050]
996
1051
997[[package]]
1052[[package]]
998name = "grep-matcher"
1053name = "grep-matcher"
999−version = "0.1.7"
1054+version = "0.1.6"
1000source = "registry+https://github.com/rust-lang/crates.io-index"
1055source = "registry+https://github.com/rust-lang/crates.io-index"
1001−checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02"
1056+checksum = "3902ca28f26945fe35cad349d776f163981d777fee382ccd6ef451126f51b319"
1002dependencies = [
1057dependencies = [
1003 "memchr",
1058 "memchr",
1004]
1059]
1005
1060
1006[[package]]
1061[[package]]
1007name = "grep-regex"
1062name = "grep-regex"
1008−version = "0.1.12"
1063+version = "0.1.11"
1009source = "registry+https://github.com/rust-lang/crates.io-index"
1064source = "registry+https://github.com/rust-lang/crates.io-index"
1010−checksum = "f748bb135ca835da5cbc67ca0e6955f968db9c5df74ca4f56b18e1ddbc68230d"
1065+checksum = "997598b41d53a37a2e3fc5300d5c11d825368c054420a9c65125b8fe1078463f"
1011dependencies = [
1066dependencies = [
1067+ "aho-corasick 0.7.20",
1012 "bstr",
1068 "bstr",
1013 "grep-matcher",
1069 "grep-matcher",
1014 "log",
1070 "log",
1015− "regex-automata",
1071+ "regex",
1016− "regex-syntax",
1072+ "regex-syntax 0.6.29",
1073+ "thread_local",
1017]
1074]
1018
1075
1019[[package]]
1076[[package]]
1020name = "grep-searcher"
1077name = "grep-searcher"
1021−version = "0.1.13"
1078+version = "0.1.11"
1022source = "registry+https://github.com/rust-lang/crates.io-index"
1079source = "registry+https://github.com/rust-lang/crates.io-index"
1023−checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54"
1080+checksum = "5601c4b9f480f0c9ebb40b1f6cbf447b8a50c5369223937a6c5214368c58779f"
1024dependencies = [
1081dependencies = [
1025 "bstr",
1082 "bstr",
1083+ "bytecount",
1026 "encoding_rs",
1084 "encoding_rs",
1027 "encoding_rs_io",
1085 "encoding_rs_io",
1028 "grep-matcher",
1086 "grep-matcher",
1029 "log",
1087 "log",
1030− "memchr",
1031 "memmap2",
1088 "memmap2",
1032]
1089]
1033
1090
@@ -1036,36 +1093,36 @@ name = "hashbrown"
1036version = "0.12.3"
1093version = "0.12.3"
1037source = "registry+https://github.com/rust-lang/crates.io-index"
1094source = "registry+https://github.com/rust-lang/crates.io-index"
1038checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1095checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1096+dependencies = [
1097+ "ahash 0.7.6",
1098+]
1039
1099
1040[[package]]
1100[[package]]
1041name = "hashbrown"
1101name = "hashbrown"
1042−version = "0.14.3"
1102+version = "0.13.2"
1043source = "registry+https://github.com/rust-lang/crates.io-index"
1103source = "registry+https://github.com/rust-lang/crates.io-index"
1044−checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
1104+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
1045dependencies = [
1105dependencies = [
1046− "ahash",
1106+ "ahash 0.8.3",
1047− "allocator-api2",
1048]
1107]
1049
1108
1050[[package]]
1109[[package]]
1051name = "helix-core"
1110name = "helix-core"
1052−version = "23.10.0"
1111+version = "0.6.0"
1053dependencies = [
1112dependencies = [
1054− "ahash",
1113+ "ahash 0.8.3",
1055 "arc-swap",
1114 "arc-swap",
1056− "bitflags 2.4.1",
1115+ "bitflags 2.2.1",
1057 "chrono",
1116 "chrono",
1058 "dunce",
1117 "dunce",
1059 "encoding_rs",
1118 "encoding_rs",
1060 "etcetera",
1119 "etcetera",
1061− "hashbrown 0.14.3",
1120+ "hashbrown 0.13.2",
1062 "helix-loader",
1121 "helix-loader",
1063 "imara-diff",
1122 "imara-diff",
1064 "indoc",
1123 "indoc",
1065 "log",
1124 "log",
1066− "nucleo",
1067 "once_cell",
1125 "once_cell",
1068− "parking_lot",
1069 "quickcheck",
1126 "quickcheck",
1070 "regex",
1127 "regex",
1071 "ropey",
1128 "ropey",
@@ -1084,34 +1141,25 @@ dependencies = [
1084
1141
1085[[package]]
1142[[package]]
1086name = "helix-dap"
1143name = "helix-dap"
1087−version = "23.10.0"
1144+version = "0.6.0"
1088dependencies = [
1145dependencies = [
1089 "anyhow",
1146 "anyhow",
1090 "fern",
1147 "fern",
1091 "helix-core",
1148 "helix-core",
1092 "log",
1149 "log",
1093 "serde",
1150 "serde",
1094− "serde_json",
1151+ "serde_json",
1095− "thiserror",
1152+ "thiserror",
1096− "tokio",
1097− "which",
1098−]
1099−
1100−[[package]]
1101−name = "helix-event"
1102−version = "23.10.0"
1103−dependencies = [
1104− "parking_lot",
1105 "tokio",
1153 "tokio",
1154+ "which",
1106]
1155]
1107
1156
1108[[package]]
1157[[package]]
1109name = "helix-loader"
1158name = "helix-loader"
1110−version = "23.10.0"
1159+version = "0.6.0"
1111dependencies = [
1160dependencies = [
1112 "anyhow",
1161 "anyhow",
1113 "cc",
1162 "cc",
1114− "dunce",
1115 "etcetera",
1163 "etcetera",
1116 "libloading",
1164 "libloading",
1117 "log",
1165 "log",
@@ -1121,17 +1169,15 @@ dependencies = [
1121 "threadpool",
1169 "threadpool",
1122 "toml",
1170 "toml",
1123 "tree-sitter",
1171 "tree-sitter",
1124− "which",
1125]
1172]
1126
1173
1127[[package]]
1174[[package]]
1128name = "helix-lsp"
1175name = "helix-lsp"
1129−version = "23.10.0"
1176+version = "0.6.0"
1130dependencies = [
1177dependencies = [
1131 "anyhow",
1178 "anyhow",
1132 "futures-executor",
1179 "futures-executor",
1133 "futures-util",
1180 "futures-util",
1134− "globset",
1135 "helix-core",
1181 "helix-core",
1136 "helix-loader",
1182 "helix-loader",
1137 "helix-parsec",
1183 "helix-parsec",
@@ -1148,11 +1194,11 @@ dependencies = [
1148
1194
1149[[package]]
1195[[package]]
1150name = "helix-parsec"
1196name = "helix-parsec"
1151−version = "23.10.0"
1197+version = "0.6.0"
1152
1198
1153[[package]]
1199[[package]]
1154name = "helix-term"
1200name = "helix-term"
1155−version = "23.10.0"
1201+version = "0.6.0"
1156dependencies = [
1202dependencies = [
1157 "anyhow",
1203 "anyhow",
1158 "arc-swap",
1204 "arc-swap",
@@ -1161,11 +1207,11 @@ dependencies = [
1161 "crossterm",
1207 "crossterm",
1162 "fern",
1208 "fern",
1163 "futures-util",
1209 "futures-util",
1210+ "fuzzy-matcher",
1164 "grep-regex",
1211 "grep-regex",
1165 "grep-searcher",
1212 "grep-searcher",
1166 "helix-core",
1213 "helix-core",
1167 "helix-dap",
1214 "helix-dap",
1168− "helix-event",
1169 "helix-loader",
1215 "helix-loader",
1170 "helix-lsp",
1216 "helix-lsp",
1171 "helix-tui",
1217 "helix-tui",
@@ -1175,9 +1221,7 @@ dependencies = [
1175 "indoc",
1221 "indoc",
1176 "libc",
1222 "libc",
1177 "log",
1223 "log",
1178− "nucleo",
1179 "once_cell",
1224 "once_cell",
1180− "open",
1181 "pulldown-cmark",
1225 "pulldown-cmark",
1182 "serde",
1226 "serde",
1183 "serde_json",
1227 "serde_json",
@@ -1188,15 +1232,14 @@ dependencies = [
1188 "tokio",
1232 "tokio",
1189 "tokio-stream",
1233 "tokio-stream",
1190 "toml",
1234 "toml",
1191− "url",
1192 "which",
1235 "which",
1193]
1236]
1194
1237
1195[[package]]
1238[[package]]
1196name = "helix-tui"
1239name = "helix-tui"
1197−version = "23.10.0"
1240+version = "0.6.0"
1198dependencies = [
1241dependencies = [
1199− "bitflags 2.4.1",
1242+ "bitflags 2.2.1",
1200 "cassowary",
1243 "cassowary",
1201 "crossterm",
1244 "crossterm",
1202 "helix-core",
1245 "helix-core",
@@ -1210,13 +1253,12 @@ dependencies = [
1210
1253
1211[[package]]
1254[[package]]
1212name = "helix-vcs"
1255name = "helix-vcs"
1213−version = "23.10.0"
1256+version = "0.6.0"
1214dependencies = [
1257dependencies = [
1215 "anyhow",
1258 "anyhow",
1216 "arc-swap",
1259 "arc-swap",
1217 "gix",
1260 "gix",
1218 "helix-core",
1261 "helix-core",
1219− "helix-event",
1220 "imara-diff",
1262 "imara-diff",
1221 "log",
1263 "log",
1222 "parking_lot",
1264 "parking_lot",
@@ -1226,18 +1268,17 @@ dependencies = [
1226
1268
1227[[package]]
1269[[package]]
1228name = "helix-view"
1270name = "helix-view"
1229−version = "23.10.0"
1271+version = "0.6.0"
1230dependencies = [
1272dependencies = [
1231 "anyhow",
1273 "anyhow",
1232 "arc-swap",
1274 "arc-swap",
1233− "bitflags 2.4.1",
1275+ "bitflags 2.2.1",
1234 "chardetng",
1276 "chardetng",
1235 "clipboard-win",
1277 "clipboard-win",
1236 "crossterm",
1278 "crossterm",
1237 "futures-util",
1279 "futures-util",
1238 "helix-core",
1280 "helix-core",
1239 "helix-dap",
1281 "helix-dap",
1240− "helix-event",
1241 "helix-loader",
1282 "helix-loader",
1242 "helix-lsp",
1283 "helix-lsp",
1243 "helix-tui",
1284 "helix-tui",
@@ -1246,7 +1287,6 @@ dependencies = [
1246 "log",
1287 "log",
1247 "once_cell",
1288 "once_cell",
1248 "parking_lot",
1289 "parking_lot",
1249− "rustix",
1250 "serde",
1290 "serde",
1251 "serde_json",
1291 "serde_json",
1252 "slotmap",
1292 "slotmap",
@@ -1266,13 +1306,25 @@ dependencies = [
1266 "libc",
1306 "libc",
1267]
1307]
1268
1308
1309+[[package]]
1310+name = "hermit-abi"
1311+version = "0.3.1"
1312+source = "registry+https://github.com/rust-lang/crates.io-index"
1313+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
1314+
1315+[[package]]
1316+name = "hex"
1317+version = "0.4.3"
1318+source = "registry+https://github.com/rust-lang/crates.io-index"
1319+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1320+
1269[[package]]
1321[[package]]
1270name = "home"
1322name = "home"
1271−version = "0.5.5"
1323+version = "0.5.4"
1272source = "registry+https://github.com/rust-lang/crates.io-index"
1324source = "registry+https://github.com/rust-lang/crates.io-index"
1273−checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
1325+checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
1274dependencies = [
1326dependencies = [
1275− "windows-sys 0.48.0",
1327+ "winapi",
1276]
1328]
1277
1329
1278[[package]]
1330[[package]]
@@ -1286,7 +1338,7 @@ dependencies = [
1286 "iana-time-zone-haiku",
1338 "iana-time-zone-haiku",
1287 "js-sys",
1339 "js-sys",
1288 "wasm-bindgen",
1340 "wasm-bindgen",
1289− "windows 0.48.0",
1341+ "windows",
1290]
1342]
1291
1343
1292[[package]]
1344[[package]]
@@ -1301,9 +1353,9 @@ dependencies = [
1301
1353
1302[[package]]
1354[[package]]
1303name = "idna"
1355name = "idna"
1304−version = "0.5.0"
1356+version = "0.3.0"
1305source = "registry+https://github.com/rust-lang/crates.io-index"
1357source = "registry+https://github.com/rust-lang/crates.io-index"
1306−checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
1358+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
1307dependencies = [
1359dependencies = [
1308 "unicode-bidi",
1360 "unicode-bidi",
1309 "unicode-normalization",
1361 "unicode-normalization",
@@ -1311,16 +1363,17 @@ dependencies = [
1311
1363
1312[[package]]
1364[[package]]
1313name = "ignore"
1365name = "ignore"
1314−version = "0.4.21"
1366+version = "0.4.20"
1315source = "registry+https://github.com/rust-lang/crates.io-index"
1367source = "registry+https://github.com/rust-lang/crates.io-index"
1316−checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
1368+checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
1317dependencies = [
1369dependencies = [
1318− "crossbeam-deque",
1319 "globset",
1370 "globset",
1371+ "lazy_static",
1320 "log",
1372 "log",
1321 "memchr",
1373 "memchr",
1322− "regex-automata",
1374+ "regex",
1323 "same-file",
1375 "same-file",
1376+ "thread_local",
1324 "walkdir",
1377 "walkdir",
1325 "winapi-util",
1378 "winapi-util",
1326]
1379]
@@ -1331,43 +1384,54 @@ version = "0.1.5"
1331source = "registry+https://github.com/rust-lang/crates.io-index"
1384source = "registry+https://github.com/rust-lang/crates.io-index"
1332checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
1385checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
1333dependencies = [
1386dependencies = [
1334− "ahash",
1387+ "ahash 0.8.3",
1335 "hashbrown 0.12.3",
1388 "hashbrown 0.12.3",
1336]
1389]
1337
1390
1338[[package]]
1391[[package]]
1339name = "indexmap"
1392name = "indexmap"
1340−version = "2.0.0"
1393+version = "1.9.3"
1341source = "registry+https://github.com/rust-lang/crates.io-index"
1394source = "registry+https://github.com/rust-lang/crates.io-index"
1342−checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
1395+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
1343dependencies = [
1396dependencies = [
1344− "equivalent",
1397+ "autocfg",
1345− "hashbrown 0.14.3",
1398+ "hashbrown 0.12.3",
1346]
1399]
1347
1400
1348[[package]]
1401[[package]]
1349name = "indoc"
1402name = "indoc"
1350−version = "2.0.4"
1403+version = "2.0.1"
1351source = "registry+https://github.com/rust-lang/crates.io-index"
1404source = "registry+https://github.com/rust-lang/crates.io-index"
1352−checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
1405+checksum = "9f2cb48b81b1dc9f39676bf99f5499babfec7cd8fe14307f7b3d747208fb5690"
1353
1406
1354[[package]]
1407[[package]]
1355−name = "is-docker"
1408+name = "instant"
1356−version = "0.2.0"
1409+version = "0.1.12"
1357source = "registry+https://github.com/rust-lang/crates.io-index"
1410source = "registry+https://github.com/rust-lang/crates.io-index"
1358−checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
1411+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
1359dependencies = [
1412dependencies = [
1360− "once_cell",
1413+ "cfg-if",
1361]
1414]
1362
1415
1363[[package]]
1416[[package]]
1364−name = "is-wsl"
1417+name = "io-close"
1365−version = "0.4.0"
1418+version = "0.3.7"
1366source = "registry+https://github.com/rust-lang/crates.io-index"
1419source = "registry+https://github.com/rust-lang/crates.io-index"
1367−checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
1420+checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc"
1368dependencies = [
1421dependencies = [
1369− "is-docker",
1422+ "libc",
1370− "once_cell",
1423+ "winapi",
1424+]
1425+
1426+[[package]]
1427+name = "io-lifetimes"
1428+version = "1.0.10"
1429+source = "registry+https://github.com/rust-lang/crates.io-index"
1430+checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
1431+dependencies = [
1432+ "hermit-abi 0.3.1",
1433+ "libc",
1434+ "windows-sys 0.48.0",
1371]
1435]
1372
1436
1373[[package]]
1437[[package]]
@@ -1385,17 +1449,32 @@ dependencies = [
1385 "wasm-bindgen",
1449 "wasm-bindgen",
1386]
1450]
1387
1451
1452+[[package]]
1453+name = "kstring"
1454+version = "2.0.0"
1455+source = "registry+https://github.com/rust-lang/crates.io-index"
1456+checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747"
1457+dependencies = [
1458+ "static_assertions",
1459+]
1460+
1461+[[package]]
1462+name = "lazy_static"
1463+version = "1.4.0"
1464+source = "registry+https://github.com/rust-lang/crates.io-index"
1465+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
1466+
1388[[package]]
1467[[package]]
1389name = "libc"
1468name = "libc"
1390−version = "0.2.151"
1469+version = "0.2.142"
1391source = "registry+https://github.com/rust-lang/crates.io-index"
1470source = "registry+https://github.com/rust-lang/crates.io-index"
1392−checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
1471+checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
1393
1472
1394[[package]]
1473[[package]]
1395name = "libloading"
1474name = "libloading"
1396−version = "0.8.1"
1475+version = "0.8.0"
1397source = "registry+https://github.com/rust-lang/crates.io-index"
1476source = "registry+https://github.com/rust-lang/crates.io-index"
1398−checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
1477+checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb"
1399dependencies = [
1478dependencies = [
1400 "cfg-if",
1479 "cfg-if",
1401 "windows-sys 0.48.0",
1480 "windows-sys 0.48.0",
@@ -1412,9 +1491,9 @@ dependencies = [
1412
1491
1413[[package]]
1492[[package]]
1414name = "linux-raw-sys"
1493name = "linux-raw-sys"
1415−version = "0.4.11"
1494+version = "0.3.4"
1416source = "registry+https://github.com/rust-lang/crates.io-index"
1495source = "registry+https://github.com/rust-lang/crates.io-index"
1417−checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
1496+checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
1418
1497
1419[[package]]
1498[[package]]
1420name = "lock_api"
1499name = "lock_api"
@@ -1428,15 +1507,18 @@ dependencies = [
1428
1507
1429[[package]]
1508[[package]]
1430name = "log"
1509name = "log"
1431−version = "0.4.20"
1510+version = "0.4.17"
1432source = "registry+https://github.com/rust-lang/crates.io-index"
1511source = "registry+https://github.com/rust-lang/crates.io-index"
1433−checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
1512+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
1513+dependencies = [
1514+ "cfg-if",
1515+]
1434
1516
1435[[package]]
1517[[package]]
1436name = "lsp-types"
1518name = "lsp-types"
1437−version = "0.95.0"
1519+version = "0.94.0"
1438source = "registry+https://github.com/rust-lang/crates.io-index"
1520source = "registry+https://github.com/rust-lang/crates.io-index"
1439−checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
1521+checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
1440dependencies = [
1522dependencies = [
1441 "bitflags 1.3.2",
1523 "bitflags 1.3.2",
1442 "serde",
1524 "serde",
@@ -1447,69 +1529,64 @@ dependencies = [
1447
1529
1448[[package]]
1530[[package]]
1449name = "memchr"
1531name = "memchr"
1450−version = "2.6.3"
1532+version = "2.5.0"
1451source = "registry+https://github.com/rust-lang/crates.io-index"
1533source = "registry+https://github.com/rust-lang/crates.io-index"
1452−checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
1534+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
1453
1535
1454[[package]]
1536[[package]]
1455name = "memmap2"
1537name = "memmap2"
1456−version = "0.9.0"
1538+version = "0.5.10"
1457source = "registry+https://github.com/rust-lang/crates.io-index"
1539source = "registry+https://github.com/rust-lang/crates.io-index"
1458−checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
1540+checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
1459dependencies = [
1541dependencies = [
1460 "libc",
1542 "libc",
1461]
1543]
1462
1544
1463[[package]]
1545[[package]]
1464−name = "memoffset"
1546+name = "minimal-lexical"
1465−version = "0.9.0"
1547+version = "0.2.1"
1466source = "registry+https://github.com/rust-lang/crates.io-index"
1548source = "registry+https://github.com/rust-lang/crates.io-index"
1467−checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
1549+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
1468−dependencies = [
1469− "autocfg",
1470−]
1471
1550
1472[[package]]
1551[[package]]
1473name = "miniz_oxide"
1552name = "miniz_oxide"
1474−version = "0.7.1"
1553+version = "0.6.2"
1475source = "registry+https://github.com/rust-lang/crates.io-index"
1554source = "registry+https://github.com/rust-lang/crates.io-index"
1476−checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
1555+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
1477dependencies = [
1556dependencies = [
1478 "adler",
1557 "adler",
1479]
1558]
1480
1559
1481[[package]]
1560[[package]]
1482name = "mio"
1561name = "mio"
1483−version = "0.8.9"
1562+version = "0.8.6"
1484source = "registry+https://github.com/rust-lang/crates.io-index"
1563source = "registry+https://github.com/rust-lang/crates.io-index"
1485−checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
1564+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
1486dependencies = [
1565dependencies = [
1487 "libc",
1566 "libc",
1488 "log",
1567 "log",
1489 "wasi",
1568 "wasi",
1490− "windows-sys 0.48.0",
1569+ "windows-sys 0.45.0",
1491]
1570]
1492
1571
1493[[package]]
1572[[package]]
1494−name = "nucleo"
1573+name = "nom"
1495−version = "0.2.1"
1574+version = "7.1.3"
1496source = "registry+https://github.com/rust-lang/crates.io-index"
1575source = "registry+https://github.com/rust-lang/crates.io-index"
1497−checksum = "ae5331f4bcce475cf28cb29c95366c3091af4b0aa7703f1a6bc858f29718fdf3"
1576+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
1498dependencies = [
1577dependencies = [
1499− "nucleo-matcher",
1578+ "memchr",
1500− "parking_lot",
1579+ "minimal-lexical",
1501− "rayon",
1502]
1580]
1503
1581
1504[[package]]
1582[[package]]
1505−name = "nucleo-matcher"
1583+name = "num-integer"
1506−version = "0.2.0"
1584+version = "0.1.45"
1507source = "registry+https://github.com/rust-lang/crates.io-index"
1585source = "registry+https://github.com/rust-lang/crates.io-index"
1508−checksum = "1b702b402fe286162d1f00b552a046ce74365d2ac473a2607ff36ba650f9bd57"
1586+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
1509dependencies = [
1587dependencies = [
1510− "cov-mark",
1588+ "autocfg",
1511− "memchr",
1589+ "num-traits",
1512− "unicode-segmentation",
1513]
1590]
1514
1591
1515[[package]]
1592[[package]]
@@ -1527,7 +1604,7 @@ version = "1.15.0"
1527source = "registry+https://github.com/rust-lang/crates.io-index"
1604source = "registry+https://github.com/rust-lang/crates.io-index"
1528checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
1605checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
1529dependencies = [
1606dependencies = [
1530− "hermit-abi",
1607+ "hermit-abi 0.2.6",
1531 "libc",
1608 "libc",
1532]
1609]
1533
1610
@@ -1540,31 +1617,11 @@ dependencies = [
1540 "libc",
1617 "libc",
1541]
1618]
1542
1619
1543−[[package]]
1544−name = "object"
1545−version = "0.31.1"
1546−source = "registry+https://github.com/rust-lang/crates.io-index"
1547−checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
1548−dependencies = [
1549− "memchr",
1550−]
1551−
1552[[package]]
1620[[package]]
1553name = "once_cell"
1621name = "once_cell"
1554−version = "1.19.0"
1622+version = "1.17.1"
1555−source = "registry+https://github.com/rust-lang/crates.io-index"
1556−checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
1557−
1558−[[package]]
1559−name = "open"
1560−version = "5.0.1"
1561source = "registry+https://github.com/rust-lang/crates.io-index"
1623source = "registry+https://github.com/rust-lang/crates.io-index"
1562−checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349"
1624+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
1563−dependencies = [
1564− "is-wsl",
1565− "libc",
1566− "pathdiff",
1567−]
1568
1625
1569[[package]]
1626[[package]]
1570name = "parking_lot"
1627name = "parking_lot"
@@ -1589,23 +1646,17 @@ dependencies = [
1589 "windows-sys 0.45.0",
1646 "windows-sys 0.45.0",
1590]
1647]
1591
1648
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]]
1649[[package]]
1599name = "percent-encoding"
1650name = "percent-encoding"
1600−version = "2.3.1"
1651+version = "2.2.0"
1601source = "registry+https://github.com/rust-lang/crates.io-index"
1652source = "registry+https://github.com/rust-lang/crates.io-index"
1602−checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
1653+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
1603
1654
1604[[package]]
1655[[package]]
1605name = "pin-project-lite"
1656name = "pin-project-lite"
1606−version = "0.2.12"
1657+version = "0.2.9"
1607source = "registry+https://github.com/rust-lang/crates.io-index"
1658source = "registry+https://github.com/rust-lang/crates.io-index"
1608−checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
1659+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
1609
1660
1610[[package]]
1661[[package]]
1611name = "pin-utils"
1662name = "pin-utils"
@@ -1615,24 +1666,24 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1615
1666
1616[[package]]
1667[[package]]
1617name = "proc-macro2"
1668name = "proc-macro2"
1618−version = "1.0.69"
1669+version = "1.0.56"
1619source = "registry+https://github.com/rust-lang/crates.io-index"
1670source = "registry+https://github.com/rust-lang/crates.io-index"
1620−checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
1671+checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
1621dependencies = [
1672dependencies = [
1622 "unicode-ident",
1673 "unicode-ident",
1623]
1674]
1624
1675
1625[[package]]
1676[[package]]
1626name = "prodash"
1677name = "prodash"
1627−version = "28.0.0"
1678+version = "23.1.2"
1628source = "registry+https://github.com/rust-lang/crates.io-index"
1679source = "registry+https://github.com/rust-lang/crates.io-index"
1629−checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
1680+checksum = "9516b775656bc3e8985e19cd4b8c0c0de045095074e453d2c0a513b5f978392d"
1630
1681
1631[[package]]
1682[[package]]
1632name = "pulldown-cmark"
1683name = "pulldown-cmark"
1633−version = "0.9.3"
1684+version = "0.9.2"
1634source = "registry+https://github.com/rust-lang/crates.io-index"
1685source = "registry+https://github.com/rust-lang/crates.io-index"
1635−checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
1686+checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
1636dependencies = [
1687dependencies = [
1637 "bitflags 1.3.2",
1688 "bitflags 1.3.2",
1638 "memchr",
1689 "memchr",
@@ -1650,9 +1701,9 @@ dependencies = [
1650
1701
1651[[package]]
1702[[package]]
1652name = "quote"
1703name = "quote"
1653−version = "1.0.29"
1704+version = "1.0.26"
1654source = "registry+https://github.com/rust-lang/crates.io-index"
1705source = "registry+https://github.com/rust-lang/crates.io-index"
1655−checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
1706+checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
1656dependencies = [
1707dependencies = [
1657 "proc-macro2",
1708 "proc-macro2",
1658]
1709]
@@ -1676,101 +1727,85 @@ dependencies = [
1676]
1727]
1677
1728
1678[[package]]
1729[[package]]
1679−name = "rayon"
1730+name = "redox_syscall"
1680−version = "1.7.0"
1731+version = "0.2.16"
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"
1691source = "registry+https://github.com/rust-lang/crates.io-index"
1732source = "registry+https://github.com/rust-lang/crates.io-index"
1692−checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
1733+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
1693dependencies = [
1734dependencies = [
1694− "crossbeam-channel",
1735+ "bitflags 1.3.2",
1695− "crossbeam-deque",
1696− "crossbeam-utils",
1697− "num_cpus",
1698]
1736]
1699
1737
1700[[package]]
1738[[package]]
1701name = "redox_syscall"
1739name = "redox_syscall"
1702−version = "0.2.16"
1740+version = "0.3.5"
1703source = "registry+https://github.com/rust-lang/crates.io-index"
1741source = "registry+https://github.com/rust-lang/crates.io-index"
1704−checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
1742+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
1705dependencies = [
1743dependencies = [
1706 "bitflags 1.3.2",
1744 "bitflags 1.3.2",
1707]
1745]
1708
1746
1709[[package]]
1747[[package]]
1710−name = "redox_syscall"
1748+name = "redox_users"
1711−version = "0.4.1"
1749+version = "0.4.3"
1712source = "registry+https://github.com/rust-lang/crates.io-index"
1750source = "registry+https://github.com/rust-lang/crates.io-index"
1713−checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
1751+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
1714dependencies = [
1752dependencies = [
1715− "bitflags 1.3.2",
1753+ "getrandom",
1754+ "redox_syscall 0.2.16",
1755+ "thiserror",
1716]
1756]
1717
1757
1718[[package]]
1758[[package]]
1719name = "regex"
1759name = "regex"
1720−version = "1.10.2"
1760+version = "1.8.1"
1721source = "registry+https://github.com/rust-lang/crates.io-index"
1761source = "registry+https://github.com/rust-lang/crates.io-index"
1722−checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
1762+checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
1723dependencies = [
1763dependencies = [
1724− "aho-corasick",
1764+ "aho-corasick 1.0.1",
1725 "memchr",
1765 "memchr",
1726− "regex-automata",
1727− "regex-syntax",
1766+ "regex-syntax 0.7.1",
1728]
1767]
1729
1768
1730[[package]]
1769[[package]]
1731name = "regex-automata"
1770name = "regex-automata"
1732−version = "0.4.3"
1771+version = "0.1.10"
1733source = "registry+https://github.com/rust-lang/crates.io-index"
1772source = "registry+https://github.com/rust-lang/crates.io-index"
1734−checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
1773+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
1735−dependencies = [
1774+
1736− "aho-corasick",
1775+[[package]]
1737− "memchr",
1738− "regex-syntax",
1776+name = "regex-syntax"
1739−]
1777+version = "0.6.29"
1778+source = "registry+https://github.com/rust-lang/crates.io-index"
1779+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
1740
1780
1741[[package]]
1781[[package]]
1742name = "regex-syntax"
1782name = "regex-syntax"
1743−version = "0.8.2"
1783+version = "0.7.1"
1744source = "registry+https://github.com/rust-lang/crates.io-index"
1784source = "registry+https://github.com/rust-lang/crates.io-index"
1745−checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
1785+checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
1746
1786
1747[[package]]
1787[[package]]
1748name = "ropey"
1788name = "ropey"
1749−version = "1.6.1"
1789+version = "1.6.0"
1750source = "registry+https://github.com/rust-lang/crates.io-index"
1790source = "registry+https://github.com/rust-lang/crates.io-index"
1751−checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
1791+checksum = "53ce7a2c43a32e50d666e33c5a80251b31147bb4b49024bcab11fb6f20c671ed"
1752dependencies = [
1792dependencies = [
1753 "smallvec",
1793 "smallvec",
1754 "str_indices",
1794 "str_indices",
1755]
1795]
1756
1796
1757−[[package]]
1758−name = "rustc-demangle"
1759−version = "0.1.23"
1760−source = "registry+https://github.com/rust-lang/crates.io-index"
1761−checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
1762−
1763[[package]]
1797[[package]]
1764name = "rustix"
1798name = "rustix"
1765−version = "0.38.28"
1799+version = "0.37.15"
1766source = "registry+https://github.com/rust-lang/crates.io-index"
1800source = "registry+https://github.com/rust-lang/crates.io-index"
1767−checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
1801+checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece"
1768dependencies = [
1802dependencies = [
1769− "bitflags 2.4.1",
1803+ "bitflags 1.3.2",
1770 "errno",
1804 "errno",
1805+ "io-lifetimes",
1771 "libc",
1806 "libc",
1772 "linux-raw-sys",
1807 "linux-raw-sys",
1773− "windows-sys 0.52.0",
1808+ "windows-sys 0.48.0",
1774]
1809]
1775
1810
1776[[package]]
1811[[package]]
@@ -1802,29 +1837,29 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
1802
1837
1803[[package]]
1838[[package]]
1804name = "serde"
1839name = "serde"
1805−version = "1.0.193"
1840+version = "1.0.160"
1806source = "registry+https://github.com/rust-lang/crates.io-index"
1841source = "registry+https://github.com/rust-lang/crates.io-index"
1807−checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
1842+checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
1808dependencies = [
1843dependencies = [
1809 "serde_derive",
1844 "serde_derive",
1810]
1845]
1811
1846
1812[[package]]
1847[[package]]
1813name = "serde_derive"
1848name = "serde_derive"
1814−version = "1.0.193"
1849+version = "1.0.160"
1815source = "registry+https://github.com/rust-lang/crates.io-index"
1850source = "registry+https://github.com/rust-lang/crates.io-index"
1816−checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
1851+checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
1817dependencies = [
1852dependencies = [
1818 "proc-macro2",
1853 "proc-macro2",
1819 "quote",
1854 "quote",
1820− "syn 2.0.38",
1855+ "syn 2.0.15",
1821]
1856]
1822
1857
1823[[package]]
1858[[package]]
1824name = "serde_json"
1859name = "serde_json"
1825−version = "1.0.108"
1860+version = "1.0.96"
1826source = "registry+https://github.com/rust-lang/crates.io-index"
1861source = "registry+https://github.com/rust-lang/crates.io-index"
1827−checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
1862+checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
1828dependencies = [
1863dependencies = [
1829 "itoa",
1864 "itoa",
1830 "ryu",
1865 "ryu",
@@ -1839,14 +1874,14 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
1839dependencies = [
1874dependencies = [
1840 "proc-macro2",
1875 "proc-macro2",
1841 "quote",
1876 "quote",
1842− "syn 2.0.38",
1877+ "syn 2.0.15",
1843]
1878]
1844
1879
1845[[package]]
1880[[package]]
1846name = "serde_spanned"
1881name = "serde_spanned"
1847−version = "0.6.3"
1882+version = "0.6.1"
1848source = "registry+https://github.com/rust-lang/crates.io-index"
1883source = "registry+https://github.com/rust-lang/crates.io-index"
1849−checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
1884+checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
1850dependencies = [
1885dependencies = [
1851 "serde",
1886 "serde",
1852]
1887]
@@ -1859,9 +1894,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
1859
1894
1860[[package]]
1895[[package]]
1861name = "signal-hook"
1896name = "signal-hook"
1862−version = "0.3.17"
1897+version = "0.3.15"
1863source = "registry+https://github.com/rust-lang/crates.io-index"
1898source = "registry+https://github.com/rust-lang/crates.io-index"
1864−checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
1899+checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
1865dependencies = [
1900dependencies = [
1866 "libc",
1901 "libc",
1867 "signal-hook-registry",
1902 "signal-hook-registry",
@@ -1910,18 +1945,18 @@ dependencies = [
1910
1945
1911[[package]]
1946[[package]]
1912name = "slotmap"
1947name = "slotmap"
1913−version = "1.0.7"
1948+version = "1.0.6"
1914source = "registry+https://github.com/rust-lang/crates.io-index"
1949source = "registry+https://github.com/rust-lang/crates.io-index"
1915−checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
1950+checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
1916dependencies = [
1951dependencies = [
1917 "version_check",
1952 "version_check",
1918]
1953]
1919
1954
1920[[package]]
1955[[package]]
1921name = "smallvec"
1956name = "smallvec"
1922−version = "1.11.2"
1957+version = "1.10.0"
1923source = "registry+https://github.com/rust-lang/crates.io-index"
1958source = "registry+https://github.com/rust-lang/crates.io-index"
1924−checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
1959+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
1925
1960
1926[[package]]
1961[[package]]
1927name = "smartstring"
1962name = "smartstring"
@@ -1942,12 +1977,12 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
1942
1977
1943[[package]]
1978[[package]]
1944name = "socket2"
1979name = "socket2"
1945−version = "0.5.5"
1980+version = "0.4.9"
1946source = "registry+https://github.com/rust-lang/crates.io-index"
1981source = "registry+https://github.com/rust-lang/crates.io-index"
1947−checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
1982+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
1948dependencies = [
1983dependencies = [
1949 "libc",
1984 "libc",
1950− "windows-sys 0.48.0",
1985+ "winapi",
1951]
1986]
1952
1987
1953[[package]]
1988[[package]]
@@ -1956,6 +1991,12 @@ version = "1.1.0"
1956source = "registry+https://github.com/rust-lang/crates.io-index"
1991source = "registry+https://github.com/rust-lang/crates.io-index"
1957checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1992checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1958
1993
1994+[[package]]
1995+name = "str-buf"
1996+version = "1.0.6"
1997+source = "registry+https://github.com/rust-lang/crates.io-index"
1998+checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
1999+
1959[[package]]
2000[[package]]
1960name = "str_indices"
2001name = "str_indices"
1961version = "0.4.1"
2002version = "0.4.1"
@@ -1975,9 +2016,9 @@ dependencies = [
1975
2016
1976[[package]]
2017[[package]]
1977name = "syn"
2018name = "syn"
1978−version = "2.0.38"
2019+version = "2.0.15"
1979source = "registry+https://github.com/rust-lang/crates.io-index"
2020source = "registry+https://github.com/rust-lang/crates.io-index"
1980−checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
2021+checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
1981dependencies = [
2022dependencies = [
1982 "proc-macro2",
2023 "proc-macro2",
1983 "quote",
2024 "quote",
@@ -1986,15 +2027,15 @@ dependencies = [
1986
2027
1987[[package]]
2028[[package]]
1988name = "tempfile"
2029name = "tempfile"
1989−version = "3.8.1"
2030+version = "3.5.0"
1990source = "registry+https://github.com/rust-lang/crates.io-index"
2031source = "registry+https://github.com/rust-lang/crates.io-index"
1991−checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
2032+checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
1992dependencies = [
2033dependencies = [
1993 "cfg-if",
2034 "cfg-if",
1994 "fastrand",
2035 "fastrand",
1995− "redox_syscall 0.4.1",
2036+ "redox_syscall 0.3.5",
1996 "rustix",
2037 "rustix",
1997− "windows-sys 0.48.0",
2038+ "windows-sys 0.45.0",
1998]
2039]
1999
2040
2000[[package]]
2041[[package]]
@@ -2008,11 +2049,11 @@ dependencies = [
2008
2049
2009[[package]]
2050[[package]]
2010name = "termini"
2051name = "termini"
2011−version = "1.0.0"
2052+version = "0.1.4"
2012source = "registry+https://github.com/rust-lang/crates.io-index"
2053source = "registry+https://github.com/rust-lang/crates.io-index"
2013−checksum = "2ad441d87dd98bc5eeb31cf2fb7e4839968763006b478efb38668a3bf9da0d59"
2054+checksum = "8c0f7ecb9c2a380d2686a747e4fc574043712326e8d39fbd220ab3bd29768a12"
2014dependencies = [
2055dependencies = [
2015− "home",
2056+ "dirs-next",
2016]
2057]
2017
2058
2018[[package]]
2059[[package]]
@@ -2028,22 +2069,32 @@ dependencies = [
2028
2069
2029[[package]]
2070[[package]]
2030name = "thiserror"
2071name = "thiserror"
2031−version = "1.0.52"
2072+version = "1.0.40"
2032source = "registry+https://github.com/rust-lang/crates.io-index"
2073source = "registry+https://github.com/rust-lang/crates.io-index"
2033−checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
2074+checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
2034dependencies = [
2075dependencies = [
2035 "thiserror-impl",
2076 "thiserror-impl",
2036]
2077]
2037
2078
2038[[package]]
2079[[package]]
2039name = "thiserror-impl"
2080name = "thiserror-impl"
2040−version = "1.0.52"
2081+version = "1.0.40"
2041source = "registry+https://github.com/rust-lang/crates.io-index"
2082source = "registry+https://github.com/rust-lang/crates.io-index"
2042−checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
2083+checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
2043dependencies = [
2084dependencies = [
2044 "proc-macro2",
2085 "proc-macro2",
2045 "quote",
2086 "quote",
2046− "syn 2.0.38",
2087+ "syn 2.0.15",
2088+]
2089+
2090+[[package]]
2091+name = "thread_local"
2092+version = "1.1.7"
2093+source = "registry+https://github.com/rust-lang/crates.io-index"
2094+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
2095+dependencies = [
2096+ "cfg-if",
2097+ "once_cell",
2047]
2098]
2048
2099
2049[[package]]
2100[[package]]
@@ -2057,9 +2108,9 @@ dependencies = [
2057
2108
2058[[package]]
2109[[package]]
2059name = "time"
2110name = "time"
2060−version = "0.3.23"
2111+version = "0.3.20"
2061source = "registry+https://github.com/rust-lang/crates.io-index"
2112source = "registry+https://github.com/rust-lang/crates.io-index"
2062−checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
2113+checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
2063dependencies = [
2114dependencies = [
2064 "itoa",
2115 "itoa",
2065 "libc",
2116 "libc",
@@ -2071,15 +2122,15 @@ dependencies = [
2071
2122
2072[[package]]
2123[[package]]
2073name = "time-core"
2124name = "time-core"
2074−version = "0.1.1"
2125+version = "0.1.0"
2075source = "registry+https://github.com/rust-lang/crates.io-index"
2126source = "registry+https://github.com/rust-lang/crates.io-index"
2076−checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
2127+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
2077
2128
2078[[package]]
2129[[package]]
2079name = "time-macros"
2130name = "time-macros"
2080−version = "0.2.10"
2131+version = "0.2.8"
2081source = "registry+https://github.com/rust-lang/crates.io-index"
2132source = "registry+https://github.com/rust-lang/crates.io-index"
2082−checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
2133+checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
2083dependencies = [
2134dependencies = [
2084 "time-core",
2135 "time-core",
2085]
2136]
@@ -2101,11 +2152,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
2101
2152
2102[[package]]
2153[[package]]
2103name = "tokio"
2154name = "tokio"
2104−version = "1.35.1"
2155+version = "1.28.0"
2105source = "registry+https://github.com/rust-lang/crates.io-index"
2156source = "registry+https://github.com/rust-lang/crates.io-index"
2106−checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
2157+checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f"
2107dependencies = [
2158dependencies = [
2108− "backtrace",
2159+ "autocfg",
2109 "bytes",
2160 "bytes",
2110 "libc",
2161 "libc",
2111 "mio",
2162 "mio",
@@ -2120,13 +2171,13 @@ dependencies = [
2120
2171
2121[[package]]
2172[[package]]
2122name = "tokio-macros"
2173name = "tokio-macros"
2123−version = "2.2.0"
2174+version = "2.1.0"
2124source = "registry+https://github.com/rust-lang/crates.io-index"
2175source = "registry+https://github.com/rust-lang/crates.io-index"
2125−checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
2176+checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
2126dependencies = [
2177dependencies = [
2127 "proc-macro2",
2178 "proc-macro2",
2128 "quote",
2179 "quote",
2129− "syn 2.0.38",
2180+ "syn 2.0.15",
2130]
2181]
2131
2182
2132[[package]]
2183[[package]]
@@ -2142,9 +2193,9 @@ dependencies = [
2142
2193
2143[[package]]
2194[[package]]
2144name = "toml"
2195name = "toml"
2145−version = "0.7.6"
2196+version = "0.7.3"
2146source = "registry+https://github.com/rust-lang/crates.io-index"
2197source = "registry+https://github.com/rust-lang/crates.io-index"
2147−checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
2198+checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
2148dependencies = [
2199dependencies = [
2149 "serde",
2200 "serde",
2150 "serde_spanned",
2201 "serde_spanned",
@@ -2154,30 +2205,31 @@ dependencies = [
2154
2205
2155[[package]]
2206[[package]]
2156name = "toml_datetime"
2207name = "toml_datetime"
2157−version = "0.6.3"
2208+version = "0.6.1"
2158source = "registry+https://github.com/rust-lang/crates.io-index"
2209source = "registry+https://github.com/rust-lang/crates.io-index"
2159−checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
2210+checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
2160dependencies = [
2211dependencies = [
2161 "serde",
2212 "serde",
2162]
2213]
2163
2214
2164[[package]]
2215[[package]]
2165name = "toml_edit"
2216name = "toml_edit"
2166−version = "0.19.12"
2217+version = "0.19.8"
2167source = "registry+https://github.com/rust-lang/crates.io-index"
2218source = "registry+https://github.com/rust-lang/crates.io-index"
2168−checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
2219+checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
2169dependencies = [
2220dependencies = [
2170 "indexmap",
2221 "indexmap",
2171 "serde",
2222 "serde",
2172 "serde_spanned",
2223 "serde_spanned",
2173 "toml_datetime",
2224 "toml_datetime",
2174− "winnow 0.4.6",
2225+ "winnow",
2175]
2226]
2176
2227
2177[[package]]
2228[[package]]
2178name = "tree-sitter"
2229name = "tree-sitter"
2179version = "0.20.10"
2230version = "0.20.10"
2180−source = "git+https://github.com/tree-sitter/tree-sitter?rev=ab09ae20d640711174b8da8a654f6b3dec93da1a#ab09ae20d640711174b8da8a654f6b3dec93da1a"
2231+source = "registry+https://github.com/rust-lang/crates.io-index"
2232+checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d"
2181dependencies = [
2233dependencies = [
2182 "cc",
2234 "cc",
2183 "regex",
2235 "regex",
@@ -2218,9 +2270,13 @@ checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
2218
2270
2219[[package]]
2271[[package]]
2220name = "unicode-linebreak"
2272name = "unicode-linebreak"
2221−version = "0.1.5"
2273+version = "0.1.4"
2222source = "registry+https://github.com/rust-lang/crates.io-index"
2274source = "registry+https://github.com/rust-lang/crates.io-index"
2223−checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
2275+checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
2276+dependencies = [
2277+ "hashbrown 0.12.3",
2278+ "regex",
2279+]
2224
2280
2225[[package]]
2281[[package]]
2226name = "unicode-normalization"
2282name = "unicode-normalization"
@@ -2239,15 +2295,15 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
2239
2295
2240[[package]]
2296[[package]]
2241name = "unicode-width"
2297name = "unicode-width"
2242−version = "0.1.11"
2298+version = "0.1.10"
2243source = "registry+https://github.com/rust-lang/crates.io-index"
2299source = "registry+https://github.com/rust-lang/crates.io-index"
2244−checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
2300+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
2245
2301
2246[[package]]
2302[[package]]
2247name = "url"
2303name = "url"
2248−version = "2.5.0"
2304+version = "2.3.1"
2249source = "registry+https://github.com/rust-lang/crates.io-index"
2305source = "registry+https://github.com/rust-lang/crates.io-index"
2250−checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
2306+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
2251dependencies = [
2307dependencies = [
2252 "form_urlencoded",
2308 "form_urlencoded",
2253 "idna",
2309 "idna",
@@ -2263,9 +2319,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
2263
2319
2264[[package]]
2320[[package]]
2265name = "walkdir"
2321name = "walkdir"
2266−version = "2.4.0"
2322+version = "2.3.3"
2267source = "registry+https://github.com/rust-lang/crates.io-index"
2323source = "registry+https://github.com/rust-lang/crates.io-index"
2268−checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
2324+checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
2269dependencies = [
2325dependencies = [
2270 "same-file",
2326 "same-file",
2271 "winapi-util",
2327 "winapi-util",
@@ -2333,15 +2389,13 @@ checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
2333
2389
2334[[package]]
2390[[package]]
2335name = "which"
2391name = "which"
2336−version = "5.0.0"
2392+version = "4.4.0"
2337source = "registry+https://github.com/rust-lang/crates.io-index"
2393source = "registry+https://github.com/rust-lang/crates.io-index"
2338−checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14"
2394+checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
2339dependencies = [
2395dependencies = [
2340 "either",
2396 "either",
2341− "home",
2397+ "libc",
2342 "once_cell",
2398 "once_cell",
2343− "rustix",
2344− "windows-sys 0.48.0",
2345]
2399]
2346
2400
2347[[package]]
2401[[package]]
@@ -2384,25 +2438,6 @@ dependencies = [
2384 "windows-targets 0.48.0",
2438 "windows-targets 0.48.0",
2385]
2439]
2386
2440
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]]
2441[[package]]
2407name = "windows-sys"
2442name = "windows-sys"
2408version = "0.45.0"
2443version = "0.45.0"
@@ -2421,15 +2456,6 @@ dependencies = [
2421 "windows-targets 0.48.0",
2456 "windows-targets 0.48.0",
2422]
2457]
2423
2458
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]]
2459[[package]]
2434name = "windows-targets"
2460name = "windows-targets"
2435version = "0.42.2"
2461version = "0.42.2"
@@ -2460,21 +2486,6 @@ dependencies = [
2460 "windows_x86_64_msvc 0.48.0",
2486 "windows_x86_64_msvc 0.48.0",
2461]
2487]
2462
2488
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]]
2489[[package]]
2479name = "windows_aarch64_gnullvm"
2490name = "windows_aarch64_gnullvm"
2480version = "0.42.2"
2491version = "0.42.2"
@@ -2487,12 +2498,6 @@ version = "0.48.0"
2487source = "registry+https://github.com/rust-lang/crates.io-index"
2498source = "registry+https://github.com/rust-lang/crates.io-index"
2488checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
2499checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
2489
2500
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]]
2501[[package]]
2497name = "windows_aarch64_msvc"
2502name = "windows_aarch64_msvc"
2498version = "0.42.2"
2503version = "0.42.2"
@@ -2505,12 +2510,6 @@ version = "0.48.0"
2505source = "registry+https://github.com/rust-lang/crates.io-index"
2510source = "registry+https://github.com/rust-lang/crates.io-index"
2506checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
2511checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
2507
2512
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]]
2513[[package]]
2515name = "windows_i686_gnu"
2514name = "windows_i686_gnu"
2516version = "0.42.2"
2515version = "0.42.2"
@@ -2523,12 +2522,6 @@ version = "0.48.0"
2523source = "registry+https://github.com/rust-lang/crates.io-index"
2522source = "registry+https://github.com/rust-lang/crates.io-index"
2524checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
2523checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
2525
2524
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]]
2525[[package]]
2533name = "windows_i686_msvc"
2526name = "windows_i686_msvc"
2534version = "0.42.2"
2527version = "0.42.2"
@@ -2541,12 +2534,6 @@ version = "0.48.0"
2541source = "registry+https://github.com/rust-lang/crates.io-index"
2534source = "registry+https://github.com/rust-lang/crates.io-index"
2542checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
2535checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
2543
2536
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]]
2537[[package]]
2551name = "windows_x86_64_gnu"
2538name = "windows_x86_64_gnu"
2552version = "0.42.2"
2539version = "0.42.2"
@@ -2559,12 +2546,6 @@ version = "0.48.0"
2559source = "registry+https://github.com/rust-lang/crates.io-index"
2546source = "registry+https://github.com/rust-lang/crates.io-index"
2560checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
2547checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
2561
2548
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]]
2549[[package]]
2569name = "windows_x86_64_gnullvm"
2550name = "windows_x86_64_gnullvm"
2570version = "0.42.2"
2551version = "0.42.2"
@@ -2577,12 +2558,6 @@ version = "0.48.0"
2577source = "registry+https://github.com/rust-lang/crates.io-index"
2558source = "registry+https://github.com/rust-lang/crates.io-index"
2578checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
2559checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
2579
2560
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]]
2561[[package]]
2587name = "windows_x86_64_msvc"
2562name = "windows_x86_64_msvc"
2588version = "0.42.2"
2563version = "0.42.2"
@@ -2595,33 +2570,18 @@ version = "0.48.0"
2595source = "registry+https://github.com/rust-lang/crates.io-index"
2570source = "registry+https://github.com/rust-lang/crates.io-index"
2596checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
2571checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
2597
2572
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]]
2605−name = "winnow"
2606−version = "0.4.6"
2607−source = "registry+https://github.com/rust-lang/crates.io-index"
2608−checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
2609−dependencies = [
2610− "memchr",
2611−]
2612−
2613[[package]]
2573[[package]]
2614name = "winnow"
2574name = "winnow"
2615−version = "0.5.28"
2575+version = "0.4.1"
2616source = "registry+https://github.com/rust-lang/crates.io-index"
2576source = "registry+https://github.com/rust-lang/crates.io-index"
2617−checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
2577+checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
2618dependencies = [
2578dependencies = [
2619 "memchr",
2579 "memchr",
2620]
2580]
2621
2581
2622[[package]]
2582[[package]]
2623name = "xtask"
2583name = "xtask"
2624−version = "23.10.0"
2584+version = "0.6.0"
2625dependencies = [
2585dependencies = [
2626 "helix-core",
2586 "helix-core",
2627 "helix-loader",
2587 "helix-loader",
@@ -2629,23 +2589,3 @@ dependencies = [
2629 "helix-view",
2589 "helix-view",
2630 "toml",
2590 "toml",
2631]
2591]
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−16
@@ -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",
@@ -34,17 +32,3 @@ inherits = "test"
34package.helix-core.opt-level = 2
32package.helix-core.opt-level = 2
35package.helix-tui.opt-level = 2
33package.helix-tui.opt-level = 2
36package.helix-term.opt-level = 2
34package.helix-term.opt-level = 2
37−
38−[workspace.dependencies]
39−tree-sitter = { version = "0.20", git = "https://github.com/tree-sitter/tree-sitter", rev = "ab09ae20d640711174b8da8a654f6b3dec93da1a" }
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+2−2
@@ -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.
@@ -61,4 +61,4 @@ Discuss the project on the community [Matrix Space](https://matrix.to/#/#helix-c
61
61
62# Credits
62# Credits
63
63
64−Thanks to [@jakenvac](https://github.com/jakenvac) for designing the logo!
64+Thanks to [@JakeHL](https://github.com/JakeHL) for designing the logo!
VERSIONnew file+1
@@ -0,0 +1 @@
1+23.03
book/book.toml+1−5
@@ -3,14 +3,10 @@ authors = ["Blaž Hrastnik"]
3language = "en"
3language = "en"
4multilingual = false
4multilingual = false
5src = "src"
5src = "src"
6+edit-url-template = "https://github.com/helix-editor/helix/tree/master/book/{path}?mode=edit"
6
7
7[output.html]
8[output.html]
8cname = "docs.helix-editor.com"
9cname = "docs.helix-editor.com"
9default-theme = "colibri"
10default-theme = "colibri"
10preferred-dark-theme = "colibri"
11preferred-dark-theme = "colibri"
11git-repository-url = "https://github.com/helix-editor/helix"
12git-repository-url = "https://github.com/helix-editor/helix"
12−edit-url-template = "https://github.com/helix-editor/helix/edit/master/book/{path}"
13−additional-css = ["custom.css"]
14−
15−[output.html.search]
16−use-boolean-and = true
book/custom.cssdeleted−231
@@ -1,231 +0,0 @@
1−html {
2− font-family: "Inter", sans-serif;
3−}
4−
5−.sidebar .sidebar-scrollbox {
6− padding: 0;
7−}
8−
9−.chapter {
10− margin: 0.25rem 0;
11−}
12−
13−.chapter li.chapter-item {
14− line-height: initial;
15− margin: 0;
16− padding: 1rem 1.5rem;
17−}
18−
19−.chapter .section li.chapter-item {
20− line-height: inherit;
21− padding: .5rem .5rem 0 .5rem;
22−}
23−
24−.content {
25− overflow-y: auto;
26− padding: 0 15px;
27− padding-bottom: 50px;
28−}
29−
30−/* 2 1.75 1.5 1.25 1 .875 */
31−.content h1 { font-size: 2em }
32−.content h2 { font-size: 1.75em }
33−.content h3 { font-size: 1.5em }
34−.content h4 { font-size: 1.25em }
35−.content h5 { font-size: 1em }
36−.content h6 { font-size: .875em }
37−
38−.content h1,
39−.content h2,
40−.content h3,
41−.content h4 {
42− font-weight: 500;
43− margin-top: 1.275em;
44− margin-bottom: .875em;
45−}
46−
47−.content p,
48−.content ol,
49−.content ul,
50−.content table {
51− margin-top: 0;
52− margin-bottom: .875em;
53−}
54−
55−.content ul li {
56− margin-bottom: .25rem;
57−}
58−
59−.content ul {
60− list-style-type: square;
61−}
62−
63−.content ul ul,
64−.content ol ul {
65− margin-bottom: .5rem;
66−}
67−
68−.content li p {
69− margin-bottom: .5em;
70−}
71−
72−blockquote {
73− margin: 1.5rem 0;
74− padding: 1rem 1.5rem;
75− color: var(--fg);
76− opacity: .9;
77− background-color: var(--quote-bg);
78− border-left: 4px solid var(--quote-border);
79− border-top: none;
80− border-bottom: none;
81−}
82−
83−blockquote *:last-child {
84− margin-bottom: 0;
85−}
86−
87−table {
88− width: 100%;
89−}
90−
91−table thead th {
92− padding: .75rem;
93− text-align: left;
94− font-weight: 500;
95− line-height: 1.5;
96− width: auto;
97−}
98−
99−table td {
100− padding: .75rem;
101− border: none;
102−}
103−
104−table thead tr {
105− border: none;
106− border-bottom: 2px var(--table-border-color) solid;
107−}
108−
109−table tbody tr {
110− border-bottom: 1px var(--table-border-line) solid;
111−}
112−
113−table tbody tr:nth-child(2n) {
114− background: unset;
115−}
116−
117−pre code.hljs {
118− display: block;
119− overflow-x: auto;
120− padding: 1em;
121−}
122−
123−code.hljs {
124− padding: 3px 5px;
125−}
126−
127−.colibri {
128− --bg: #3b224c;
129− --fg: #bcbdd0;
130− --heading-fg: #fff;
131−
132− --sidebar-bg: #281733;
133− --sidebar-fg: #c8c9db;
134− --sidebar-non-existent: #505274;
135− --sidebar-active: #a4a0e8;
136− --sidebar-spacer: #2d334f;
137−
138− --scrollbar: var(--sidebar-fg);
139−
140− --icons: #737480;
141− --icons-hover: #b7b9cc;
142−
143− /* --links: #a4a0e8; */
144− --links: #ECCDBA;
145−
146− --inline-code-color: hsl(48.7, 7.8%, 70%);
147−
148− --theme-popup-bg: #161923;
149− --theme-popup-border: #737480;
150− --theme-hover: rgba(0, 0, 0, .2);
151−
152− --quote-bg: #281733;
153− --quote-border: hsl(226, 15%, 22%);
154−
155− --table-border-color: hsl(226, 23%, 76%);
156− --table-header-bg: hsla(226, 23%, 31%, 0);
157− --table-alternate-bg: hsl(226, 23%, 14%);
158− --table-border-line: hsla(201deg, 20%, 92%, 0.2);
159−
160− --searchbar-border-color: #aaa;
161− --searchbar-bg: #aeaec6;
162− --searchbar-fg: #000;
163− --searchbar-shadow-color: #aaa;
164− --searchresults-header-fg: #5f5f71;
165− --searchresults-border-color: #5c5c68;
166− --searchresults-li-bg: #242430;
167− --search-mark-bg: #a2cff5;
168−}
169−
170−.colibri .content .header {
171− color: #fff;
172−}
173−
174−/* highlight.js theme, :where() is used to avoid increasing specificity */
175−
176−:where(.colibri) .hljs {
177− background: #2f1e2e;
178− color: #a39e9b;
179−}
180−
181−:where(.colibri) .hljs-comment,
182−:where(.colibri) .hljs-quote {
183− color: #8d8687;
184−}
185−
186−:where(.colibri) .hljs-link,
187−:where(.colibri) .hljs-meta,
188−:where(.colibri) .hljs-name,
189−:where(.colibri) .hljs-regexp,
190−:where(.colibri) .hljs-selector-class,
191−:where(.colibri) .hljs-selector-id,
192−:where(.colibri) .hljs-tag,
193−:where(.colibri) .hljs-template-variable,
194−:where(.colibri) .hljs-variable {
195− color: #ef6155;
196−}
197−
198−:where(.colibri) .hljs-built_in,
199−:where(.colibri) .hljs-deletion,
200−:where(.colibri) .hljs-literal,
201−:where(.colibri) .hljs-number,
202−:where(.colibri) .hljs-params,
203−:where(.colibri) .hljs-type {
204− color: #f99b15;
205−}
206−
207−:where(.colibri) .hljs-attribute,
208−:where(.colibri) .hljs-section,
209−:where(.colibri) .hljs-title {
210− color: #fec418;
211−}
212−
213−:where(.colibri) .hljs-addition,
214−:where(.colibri) .hljs-bullet,
215−:where(.colibri) .hljs-string,
216−:where(.colibri) .hljs-symbol {
217− color: #48b685;
218−}
219−
220−:where(.colibri) .hljs-keyword,
221−:where(.colibri) .hljs-selector-tag {
222− color: #815ba4;
223−}
224−
225−:where(.colibri) .hljs-emphasis {
226− font-style: italic;
227−}
228−
229−:where(.colibri) .hljs-strong {
230− font-weight: 700;
231−}
book/src/configuration.md+13−43
@@ -51,8 +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` |
56| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
55| `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` |
56| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
58| `auto-info` | Whether to display info boxes | `true` |
57| `auto-info` | Whether to display info boxes | `true` |
@@ -63,10 +62,6 @@ Its settings will be merged with the configuration directory `config.toml` and t
63| `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` |
62| `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` |
64| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
63| `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` | `[]` |
64| `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` |
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
65
71### `[editor.statusline]` Section
66### `[editor.statusline]` Section
72
67
@@ -92,9 +87,9 @@ The `[editor.statusline]` key takes the following sub-keys:
92
87
93| Key | Description | Default |
88| Key | Description | Default |
94| --- | --- | --- |
89| --- | --- | --- |
95−| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "read-only-indicator", "file-modification-indicator"]` |
90+| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name"]` |
96| `center` | A list of elements aligned to the middle of the statusline | `[]` |
91| `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"]` |
92+| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "position", "file-encoding"]` |
98| `separator` | The character used to separate elements in the statusline | `"│"` |
93| `separator` | The character used to separate elements in the statusline | `"│"` |
99| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
94| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
100| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
95| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
@@ -111,7 +106,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) |
106| `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 |
107| `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) |
108| `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 |
109| `total-line-numbers` | The total line numbers of the opened file |
116| `file-type` | The type of the opened file |
110| `file-type` | The type of the opened file |
117| `diagnostics` | The number of warnings and/or errors |
111| `diagnostics` | The number of warnings and/or errors |
@@ -123,7 +117,6 @@ The following statusline elements can be configured:
123| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
117| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
124| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
118| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
125| `version-control` | The current branch name or detached commit hash of the opened workspace |
119| `version-control` | The current branch name or detached commit hash of the opened workspace |
126−| `register` | The current selected register |
127
120
128### `[editor.lsp]` Section
121### `[editor.lsp]` Section
129
122
@@ -138,8 +131,8 @@ The following statusline elements can be configured:
138| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
131| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
139
132
140[^1]: By default, a progress spinner is shown in the statusline beside the file path.
133[^1]: By default, a progress spinner is shown in the statusline beside the file path.
141−
134+[^2]: You may also have to activate them in the LSP config for them to appear, not just in Helix.
142−[^2]: You may also have to activate them in the LSP config for them to appear, not just in Helix. Inlay hints in Helix are still being improved on and may be a little bit laggy/janky under some circumstances. Please report any bugs you see so we can fix them!
135+ Inlay hints in Helix are still being improved on and may be a little bit laggy/janky under some circumstances, please report any bugs you see so we can fix them!
143
136
144### `[editor.cursor-shape]` Section
137### `[editor.cursor-shape]` Section
145
138
@@ -168,31 +161,16 @@ All git related options are only enabled in a git repository.
168
161
169| Key | Description | Default |
162| Key | Description | Default |
170|--|--|---------|
163|--|--|---------|
171−|`hidden` | Enables ignoring hidden files | `true`
164+|`hidden` | Enables ignoring hidden files | true
172−|`follow-symlinks` | Follow symlinks instead of ignoring them | `true`
165+|`follow-symlinks` | Follow symlinks instead of ignoring them | true
173−|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | `true`
166+|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | true
174−|`parents` | Enables reading ignore files from parent directories | `true`
167+|`parents` | Enables reading ignore files from parent directories | true
175−|`ignore` | Enables reading `.ignore` files | `true`
168+|`ignore` | Enables reading `.ignore` files | true
176−|`git-ignore` | Enables reading `.gitignore` files | `true`
169+|`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`
170+|`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`
171+|`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`.
172|`max-depth` | Set with an integer value for maximum depth to recurse | Defaults to `None`.
180
173
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
174### `[editor.auto-pairs]` Section
197
175
198Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a
176Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a
@@ -366,11 +344,3 @@ max-wrap = 25 # increase value to reduce forced mid-word wrapping
366max-indent-retain = 0
344max-indent-retain = 0
367wrap-indicator = "" # set wrap-indicator to "" to hide it
345wrap-indicator = "" # set wrap-indicator to "" to hide it
368```
346```
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+18−43
@@ -1,23 +1,21 @@
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` |
10| bicep | ✓ | | | `bicep-langserver` |
9| bicep | ✓ | | | `bicep-langserver` |
11−| blueprint | ✓ | | | `blueprint-compiler` |
12| c | ✓ | ✓ | ✓ | `clangd` |
10| c | ✓ | ✓ | ✓ | `clangd` |
13| c-sharp | ✓ | ✓ | | `OmniSharp` |
11| c-sharp | ✓ | ✓ | | `OmniSharp` |
14−| cabal | | | | `haskell-language-server-wrapper` |
12+| cabal | ✓ | | | |
15−| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
13+| cairo | ✓ | | | |
16| capnp | ✓ | | ✓ | |
14| capnp | ✓ | | ✓ | |
17| clojure | ✓ | | | `clojure-lsp` |
15| clojure | ✓ | | | `clojure-lsp` |
18| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
16| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
19| comment | ✓ | | | |
17| comment | ✓ | | | |
20−| common-lisp | ✓ | | ✓ | `cl-lsp` |
18+| common-lisp | ✓ | | | `cl-lsp` |
21| cpon | ✓ | | ✓ | |
19| cpon | ✓ | | ✓ | |
22| cpp | ✓ | ✓ | ✓ | `clangd` |
20| cpp | ✓ | ✓ | ✓ | `clangd` |
23| crystal | ✓ | ✓ | | `crystalline` |
21| crystal | ✓ | ✓ | | `crystalline` |
@@ -25,7 +23,6 @@
25| cue | ✓ | | | `cuelsp` |
23| cue | ✓ | | | `cuelsp` |
26| d | ✓ | ✓ | ✓ | `serve-d` |
24| d | ✓ | ✓ | ✓ | `serve-d` |
27| dart | ✓ | | ✓ | `dart` |
25| dart | ✓ | | ✓ | `dart` |
28−| dbml | ✓ | | | |
29| devicetree | ✓ | | | |
26| devicetree | ✓ | | | |
30| dhall | ✓ | ✓ | | `dhall-lsp-server` |
27| dhall | ✓ | ✓ | | `dhall-lsp-server` |
31| diff | ✓ | | | |
28| diff | ✓ | | | |
@@ -43,12 +40,8 @@
43| erlang | ✓ | ✓ | | `erlang_ls` |
40| erlang | ✓ | ✓ | | `erlang_ls` |
44| esdl | ✓ | | | |
41| esdl | ✓ | | | |
45| fish | ✓ | ✓ | ✓ | |
42| fish | ✓ | ✓ | ✓ | |
46−| forth | ✓ | | | `forth-lsp` |
47| fortran | ✓ | | ✓ | `fortls` |
43| fortran | ✓ | | ✓ | `fortls` |
48−| fsharp | ✓ | | | `fsautocomplete` |
49−| gas | ✓ | ✓ | | |
50| gdscript | ✓ | ✓ | ✓ | |
44| gdscript | ✓ | ✓ | ✓ | |
51−| gemini | ✓ | | | |
52| git-attributes | ✓ | | | |
45| git-attributes | ✓ | | | |
53| git-commit | ✓ | ✓ | | |
46| git-commit | ✓ | ✓ | | |
54| git-config | ✓ | | | |
47| git-config | ✓ | | | |
@@ -56,16 +49,14 @@
56| git-rebase | ✓ | | | |
49| git-rebase | ✓ | | | |
57| gleam | ✓ | ✓ | | `gleam` |
50| gleam | ✓ | ✓ | | `gleam` |
58| glsl | ✓ | ✓ | ✓ | |
51| glsl | ✓ | ✓ | ✓ | |
59−| gn | ✓ | | | |
52+| go | ✓ | ✓ | ✓ | `gopls` |
60−| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
61| godot-resource | ✓ | | | |
53| godot-resource | ✓ | | | |
62| gomod | ✓ | | | `gopls` |
54| gomod | ✓ | | | `gopls` |
63| gotmpl | ✓ | | | `gopls` |
55| gotmpl | ✓ | | | `gopls` |
64| gowork | ✓ | | | `gopls` |
56| gowork | ✓ | | | `gopls` |
65−| graphql | ✓ | | | `graphql-lsp` |
57+| graphql | ✓ | | | |
66| hare | ✓ | | | |
58| hare | ✓ | | | |
67| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
59| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
68−| haskell-persistent | ✓ | | | |
69| hcl | ✓ | | ✓ | `terraform-ls` |
60| hcl | ✓ | | ✓ | `terraform-ls` |
70| heex | ✓ | ✓ | | `elixir-ls` |
61| heex | ✓ | ✓ | | `elixir-ls` |
71| hosts | ✓ | | | |
62| hosts | ✓ | | | |
@@ -74,18 +65,15 @@
74| idris | | | | `idris2-lsp` |
65| idris | | | | `idris2-lsp` |
75| iex | ✓ | | | |
66| iex | ✓ | | | |
76| ini | ✓ | | | |
67| ini | ✓ | | | |
77−| janet | ✓ | | | |
78−| java | ✓ | ✓ | ✓ | `jdtls` |
68+| java | ✓ | ✓ | | `jdtls` |
79| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
69| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
80−| jinja | ✓ | | | |
81| jsdoc | ✓ | | | |
70| jsdoc | ✓ | | | |
82| json | ✓ | | ✓ | `vscode-json-language-server` |
71| json | ✓ | | ✓ | `vscode-json-language-server` |
83−| json5 | ✓ | | | |
84| jsonnet | ✓ | | | `jsonnet-language-server` |
72| jsonnet | ✓ | | | `jsonnet-language-server` |
85| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
73| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
86| julia | ✓ | ✓ | ✓ | `julia` |
74| julia | ✓ | ✓ | ✓ | `julia` |
87| just | ✓ | ✓ | ✓ | |
75| just | ✓ | ✓ | ✓ | |
88−| kdl | ✓ | ✓ | ✓ | |
76+| kdl | ✓ | | | |
89| kotlin | ✓ | | | `kotlin-language-server` |
77| kotlin | ✓ | | | `kotlin-language-server` |
90| latex | ✓ | ✓ | | `texlab` |
78| latex | ✓ | ✓ | | `texlab` |
91| lean | ✓ | | | `lean` |
79| lean | ✓ | | | `lean` |
@@ -93,14 +81,12 @@
93| llvm | ✓ | ✓ | ✓ | |
81| llvm | ✓ | ✓ | ✓ | |
94| llvm-mir | ✓ | ✓ | ✓ | |
82| llvm-mir | ✓ | ✓ | ✓ | |
95| llvm-mir-yaml | ✓ | | ✓ | |
83| llvm-mir-yaml | ✓ | | ✓ | |
96−| log | ✓ | | | |
97−| lpf | ✓ | | | |
98| lua | ✓ | ✓ | ✓ | `lua-language-server` |
84| lua | ✓ | ✓ | ✓ | `lua-language-server` |
99| make | ✓ | | | |
85| make | ✓ | | | |
100| markdoc | ✓ | | | `markdoc-ls` |
86| markdoc | ✓ | | | `markdoc-ls` |
101| markdown | ✓ | | | `marksman` |
87| markdown | ✓ | | | `marksman` |
102| markdown.inline | ✓ | | | |
88| markdown.inline | ✓ | | | |
103−| matlab | ✓ | ✓ | ✓ | |
89+| matlab | ✓ | | | |
104| mermaid | ✓ | | | |
90| mermaid | ✓ | | | |
105| meson | ✓ | | ✓ | |
91| meson | ✓ | | ✓ | |
106| mint | | | | `mint` |
92| mint | | | | `mint` |
@@ -109,8 +95,7 @@
109| nickel | ✓ | | ✓ | `nls` |
95| nickel | ✓ | | ✓ | `nls` |
110| nim | ✓ | ✓ | ✓ | `nimlangserver` |
96| nim | ✓ | ✓ | ✓ | `nimlangserver` |
111| nix | ✓ | | | `nil` |
97| nix | ✓ | | | `nil` |
112−| nu | ✓ | | | `nu` |
98+| nu | ✓ | | | |
113−| nunjucks | ✓ | | | |
114| ocaml | ✓ | | ✓ | `ocamllsp` |
99| ocaml | ✓ | | ✓ | `ocamllsp` |
115| ocaml-interface | ✓ | | | `ocamllsp` |
100| ocaml-interface | ✓ | | | `ocamllsp` |
116| odin | ✓ | | ✓ | `ols` |
101| odin | ✓ | | ✓ | `ols` |
@@ -123,17 +108,16 @@
123| perl | ✓ | ✓ | ✓ | `perlnavigator` |
108| perl | ✓ | ✓ | ✓ | `perlnavigator` |
124| php | ✓ | ✓ | ✓ | `intelephense` |
109| php | ✓ | ✓ | ✓ | `intelephense` |
125| po | ✓ | ✓ | | |
110| po | ✓ | ✓ | | |
126−| pod | ✓ | | | |
127| ponylang | ✓ | ✓ | ✓ | |
111| ponylang | ✓ | ✓ | ✓ | |
128| prisma | ✓ | | | `prisma-language-server` |
112| prisma | ✓ | | | `prisma-language-server` |
129| prolog | | | | `swipl` |
113| prolog | | | | `swipl` |
130−| protobuf | ✓ | | ✓ | `bufls`, `pb` |
114+| protobuf | ✓ | | ✓ | |
131| prql | ✓ | | | |
115| prql | ✓ | | | |
132−| purescript | ✓ | ✓ | | `purescript-language-server` |
116+| purescript | ✓ | | | `purescript-language-server` |
133| python | ✓ | ✓ | ✓ | `pylsp` |
117| python | ✓ | ✓ | ✓ | `pylsp` |
134| qml | ✓ | | ✓ | `qmlls` |
118| qml | ✓ | | ✓ | `qmlls` |
135| r | ✓ | | | `R` |
119| r | ✓ | | | `R` |
136−| racket | ✓ | | ✓ | `racket` |
120+| racket | ✓ | | | `racket` |
137| regex | ✓ | | | |
121| regex | ✓ | | | |
138| rego | ✓ | | | `regols` |
122| rego | ✓ | | | `regols` |
139| rescript | ✓ | ✓ | | `rescript-language-server` |
123| rescript | ✓ | ✓ | | `rescript-language-server` |
@@ -145,49 +129,40 @@
145| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
129| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
146| sage | ✓ | ✓ | | |
130| sage | ✓ | ✓ | | |
147| scala | ✓ | | ✓ | `metals` |
131| scala | ✓ | | ✓ | `metals` |
148−| scheme | ✓ | | ✓ | |
132+| scheme | ✓ | | | |
149| scss | ✓ | | | `vscode-css-language-server` |
133| scss | ✓ | | | `vscode-css-language-server` |
150| slint | ✓ | | ✓ | `slint-lsp` |
134| slint | ✓ | | ✓ | `slint-lsp` |
151−| smali | ✓ | | ✓ | |
152| smithy | ✓ | | | `cs` |
135| smithy | ✓ | | | `cs` |
153| sml | ✓ | | | |
136| sml | ✓ | | | |
154| solidity | ✓ | | | `solc` |
137| solidity | ✓ | | | `solc` |
155| sql | ✓ | | | |
138| sql | ✓ | | | |
156| sshclientconfig | ✓ | | | |
139| sshclientconfig | ✓ | | | |
157| starlark | ✓ | ✓ | | |
140| starlark | ✓ | ✓ | | |
158−| strace | ✓ | | | |
159−| svelte | ✓ | | ✓ | `svelteserver` |
141+| svelte | ✓ | | | `svelteserver` |
160| sway | ✓ | ✓ | ✓ | `forc` |
142| sway | ✓ | ✓ | ✓ | `forc` |
161| swift | ✓ | | | `sourcekit-lsp` |
143| swift | ✓ | | | `sourcekit-lsp` |
162−| t32 | ✓ | | | |
163| tablegen | ✓ | ✓ | ✓ | |
144| tablegen | ✓ | ✓ | ✓ | |
164| task | ✓ | | | |
145| task | ✓ | | | |
165−| templ | ✓ | | | `templ` |
166| tfvars | ✓ | | ✓ | `terraform-ls` |
146| tfvars | ✓ | | ✓ | `terraform-ls` |
167−| todotxt | ✓ | | | |
168| toml | ✓ | | | `taplo` |
147| toml | ✓ | | | `taplo` |
169| tsq | ✓ | | | |
148| tsq | ✓ | | | |
170| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
149| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
171| twig | ✓ | | | |
150| twig | ✓ | | | |
172| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
151| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
173−| typst | ✓ | | | `typst-lsp` |
174| ungrammar | ✓ | | | |
152| ungrammar | ✓ | | | |
175−| unison | ✓ | | | |
176| uxntal | ✓ | | | |
153| uxntal | ✓ | | | |
177−| v | ✓ | ✓ | ✓ | `v-analyzer` |
154+| v | ✓ | ✓ | ✓ | `v` |
178| vala | ✓ | | | `vala-language-server` |
155| vala | ✓ | | | `vala-language-server` |
179| verilog | ✓ | ✓ | | `svlangserver` |
156| verilog | ✓ | ✓ | | `svlangserver` |
180| vhdl | ✓ | | | `vhdl_ls` |
157| vhdl | ✓ | | | `vhdl_ls` |
181| vhs | ✓ | | | |
158| vhs | ✓ | | | |
182−| vue | ✓ | | | `vue-language-server` |
159+| vue | ✓ | | | `vls` |
183| wast | ✓ | | | |
160| wast | ✓ | | | |
184| wat | ✓ | | | |
161| wat | ✓ | | | |
185−| webc | ✓ | | | |
186| wgsl | ✓ | | | `wgsl_analyzer` |
162| wgsl | ✓ | | | `wgsl_analyzer` |
187| wit | ✓ | | ✓ | |
163| wit | ✓ | | ✓ | |
188−| wren | ✓ | ✓ | ✓ | |
189| xit | ✓ | | | |
164| xit | ✓ | | | |
190| xml | ✓ | | ✓ | |
165| xml | ✓ | | ✓ | |
191−| yaml | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
166+| yaml | ✓ | | ✓ | `yaml-language-server` |
192| yuck | ✓ | | | |
167| yuck | ✓ | | | |
193| zig | ✓ | ✓ | ✓ | `zls` |
168| zig | ✓ | ✓ | ✓ | `zls` |
book/src/generated/typable-cmd.md+6−13
@@ -12,19 +12,16 @@
12| `:buffer-next`, `:bn`, `:bnext` | Goto next buffer. |
12| `:buffer-next`, `:bn`, `:bnext` | Goto next buffer. |
13| `:buffer-previous`, `:bp`, `:bprev` | Goto previous buffer. |
13| `:buffer-previous`, `:bp`, `:bprev` | Goto previous buffer. |
14| `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) |
14| `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) |
15−| `:write!`, `:w!` | Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt) |
15+| `:write!`, `:w!` | Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write some/path.txt) |
16−| `:write-buffer-close`, `:wbc` | Write changes to disk 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. |
16| `:new`, `:n` | Create a new scratch buffer. |
19| `:format`, `:fmt` | Format the file using the LSP formatter. |
17| `: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.) |
18+| `: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. |
19| `: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. |
20| `: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. |
21| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
24| `:write-quit`, `:wq`, `:x` | Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) |
22| `:write-quit`, `:wq`, `:x` | Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) |
25| `:write-quit!`, `:wq!`, `:x!` | Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) |
23| `:write-quit!`, `:wq!`, `:x!` | Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) |
26| `:write-all`, `:wa` | Write changes from all buffers to disk. |
24| `:write-all`, `:wa` | Write changes from all buffers to disk. |
27−| `:write-all!`, `:wa!` | Forcefully write changes from all buffers to disk creating necessary subdirectories. |
28| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. |
25| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. |
29| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes). |
26| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes). |
30| `:quit-all`, `:qa` | Close all views. |
27| `:quit-all`, `:qa` | Close all views. |
@@ -32,7 +29,6 @@
32| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
29| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
33| `:cquit!`, `:cq!` | Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2). |
30| `:cquit!`, `:cq!` | Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2). |
34| `:theme` | Change the editor theme (show current theme if no name specified). |
31| `:theme` | Change the editor theme (show current theme if no name specified). |
35−| `:yank-join` | Yank joined selections. A separator can be provided as first argument. Default value is newline. |
36| `:clipboard-yank` | Yank main selection into system clipboard. |
32| `:clipboard-yank` | Yank main selection into system clipboard. |
37| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
33| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
38| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
34| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
@@ -48,14 +44,13 @@
48| `:show-directory`, `:pwd` | Show the current working directory. |
44| `:show-directory`, `:pwd` | Show the current working directory. |
49| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
45| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
50| `:character-info`, `:char` | Get info about the character under the primary cursor. |
46| `:character-info`, `:char` | Get info about the character under the primary cursor. |
51−| `:reload`, `:rl` | Discard changes and reload from the source file. |
47+| `:reload` | Discard changes and reload from the source file. |
52−| `:reload-all`, `:rla` | Discard changes and reload all documents from the source files. |
48+| `:reload-all` | Discard changes and reload all documents from the source files. |
53| `:update`, `:u` | Write changes only if the file has been modified. |
49| `:update`, `:u` | Write changes only if the file has been modified. |
54| `:lsp-workspace-command` | Open workspace command picker |
50| `:lsp-workspace-command` | Open workspace command picker |
55−| `:lsp-restart` | Restarts the language servers used by the current doc |
51+| `:lsp-restart` | Restarts the Language Server that is in use by the current doc |
56−| `:lsp-stop` | Stops the language servers that are used by the current doc |
52+| `:lsp-stop` | Stops the Language Server that is in use by the current doc |
57| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
53| `: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. |
54| `: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. |
55| `: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. |
56| `:debug-eval` | Evaluate expression in current debug context. |
@@ -84,5 +79,3 @@
84| `:run-shell-command`, `:sh` | Run a shell command |
79| `:run-shell-command`, `:sh` | Run a shell command |
85| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
80| `: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. |
81| `: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−3
@@ -9,7 +9,6 @@ below.
9 necessary configuration for the new language. For more information on
9 necessary configuration for the new language. For more information on
10 language configuration, refer to the
10 language configuration, refer to the
11 [language configuration section](../languages.md) of the documentation.
11 [language configuration section](../languages.md) of the documentation.
12− A new language server can be added by extending the `[language-server]` table in the same file.
132. If you are adding a new language or updating an existing language server
122. If you are adding a new language or updating an existing language server
14 configuration, run the command `cargo xtask docgen` to update the
13 configuration, run the command `cargo xtask docgen` to update the
15 [Language Support](../lang-support.md) documentation.
14 [Language Support](../lang-support.md) documentation.
@@ -36,7 +35,6 @@ below.
363. Refer to the
353. Refer to the
37 [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
36 [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
38 for more information on writing queries.
37 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
38
41> 💡 In Helix, the first matching query takes precedence when evaluating
39> 💡 In Helix, the first matching query takes precedence when evaluating
42> queries, which is different from other editors such as Neovim where the last
40> queries, which is different from other editors such as Neovim where the last
@@ -52,4 +50,3 @@ below.
52 grammars.
50 grammars.
53- If a parser is causing a segfault, or you want to remove it, make sure to
51- 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`.
52 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+23−82
@@ -6,14 +6,11 @@
6- [Linux](#linux)
6- [Linux](#linux)
7 - [Ubuntu](#ubuntu)
7 - [Ubuntu](#ubuntu)
8 - [Fedora/RHEL](#fedorarhel)
8 - [Fedora/RHEL](#fedorarhel)
9− - [Arch Linux extra](#arch-linux-extra)
9+ - [Arch Linux community](#arch-linux-community)
10 - [NixOS](#nixos)
10 - [NixOS](#nixos)
11− - [Flatpak](#flatpak)
12− - [Snap](#snap)
13 - [AppImage](#appimage)
11 - [AppImage](#appimage)
14- [macOS](#macos)
12- [macOS](#macos)
15 - [Homebrew Core](#homebrew-core)
13 - [Homebrew Core](#homebrew-core)
16− - [MacPorts](#macports)
17- [Windows](#windows)
14- [Windows](#windows)
18 - [Winget](#winget)
15 - [Winget](#winget)
19 - [Scoop](#scoop)
16 - [Scoop](#scoop)
@@ -21,9 +18,6 @@
21 - [MSYS2](#msys2)
18 - [MSYS2](#msys2)
22- [Building from source](#building-from-source)
19- [Building from source](#building-from-source)
23 - [Configuring Helix's runtime files](#configuring-helixs-runtime-files)
20 - [Configuring Helix's runtime files](#configuring-helixs-runtime-files)
24− - [Linux and macOS](#linux-and-macos)
25− - [Windows](#windows)
26− - [Multiple runtime directories](#multiple-runtime-directories)
27 - [Validating the installation](#validating-the-installation)
21 - [Validating the installation](#validating-the-installation)
28 - [Configure the desktop shortcut](#configure-the-desktop-shortcut)
22 - [Configure the desktop shortcut](#configure-the-desktop-shortcut)
29<!--toc:end-->
23<!--toc:end-->
@@ -65,13 +59,16 @@ sudo apt install helix
65
59
66### Fedora/RHEL
60### Fedora/RHEL
67
61
62+Enable the `COPR` repository for Helix:
63+
68```sh
64```sh
65+sudo dnf copr enable varlad/helix
69sudo dnf install helix
66sudo dnf install helix
70```
67```
71
68
72−### Arch Linux extra
69+### Arch Linux community
73
70
74−Releases are available in the `extra` repository:
71+Releases are available in the `community` repository:
75
72
76```sh
73```sh
77sudo pacman -S helix
74sudo pacman -S helix
@@ -81,10 +78,7 @@ in the AUR, which builds the master branch.
81
78
82### NixOS
79### NixOS
83
80
84−Helix is available in [nixpkgs](https://github.com/nixos/nixpkgs) through the `helix` attribute,
85−the unstable channel usually carries the latest release.
86−
87−Helix is also available as a [flake](https://nixos.wiki/wiki/Flakes) in the project
81+Helix is available as a [flake](https://nixos.wiki/wiki/Flakes) in the project
88root. Use `nix develop` to spin up a reproducible development shell. Outputs are
82root. Use `nix develop` to spin up a reproducible development shell. Outputs are
89cached for each push to master using [Cachix](https://www.cachix.org/). The
83cached for each push to master using [Cachix](https://www.cachix.org/). The
90flake is configured to automatically make use of this cache assuming the user
84flake is configured to automatically make use of this cache assuming the user
@@ -94,25 +88,6 @@ If you are using a version of Nix without flakes enabled,
94[install Cachix CLI](https://docs.cachix.org/installation) and use
88[install Cachix CLI](https://docs.cachix.org/installation) and use
95`cachix use helix` to configure Nix to use cached outputs when possible.
89`cachix use helix` to configure Nix to use cached outputs when possible.
96
90
97−### Flatpak
98−
99−Helix is available on [Flathub](https://flathub.org/en-GB/apps/com.helix_editor.Helix):
100−
101−```sh
102−flatpak install flathub com.helix_editor.Helix
103−flatpak run com.helix_editor.Helix
104−```
105−
106−### Snap
107−
108−Helix is available on [Snapcraft](https://snapcraft.io/helix) and can be installed with:
109−
110−```sh
111−snap install --classic helix
112−```
113−
114−This will install Helix as both `/snap/bin/helix` and `/snap/bin/hx`, so make sure `/snap/bin` is in your `PATH`.
115−
116### AppImage
91### AppImage
117
92
118Install Helix using the Linux [AppImage](https://appimage.org/) format.
93Install Helix using the Linux [AppImage](https://appimage.org/) format.
@@ -131,12 +106,6 @@ chmod +x helix-*.AppImage # change permission for executable mode
131brew install helix
106brew install helix
132```
107```
133
108
134−### MacPorts
135−
136−```sh
137−port install helix
138−```
139−
140## Windows
109## Windows
141
110
142Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
111Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
@@ -174,13 +143,9 @@ pacman -S mingw-w64-ucrt-x86_64-helix
174
143
175Requirements:
144Requirements:
176
145
177−Clone the Helix GitHub repository into a directory of your choice. The
178−examples in this documentation assume installation into either `~/src/` on
179−Linux and macOS, or `%userprofile%\src\` on Windows.
180−
181- The [Rust toolchain](https://www.rust-lang.org/tools/install)
146- The [Rust toolchain](https://www.rust-lang.org/tools/install)
182- The [Git version control system](https://git-scm.com/)
147- The [Git version control system](https://git-scm.com/)
183−- A C++14 compatible compiler to build the tree-sitter grammars, for example GCC or Clang
148+- A c++14 compatible compiler to build the tree-sitter grammars, for example GCC or Clang
184
149
185If you are using the `musl-libc` standard library instead of `glibc` the following environment variable must be set during the build to ensure tree-sitter grammars can be loaded correctly:
150If you are using the `musl-libc` standard library instead of `glibc` the following environment variable must be set during the build to ensure tree-sitter grammars can be loaded correctly:
186
151
@@ -190,19 +155,19 @@ RUSTFLAGS="-C target-feature=-crt-static"
190
155
1911. Clone the repository:
1561. Clone the repository:
192
157
193− ```sh
158+```sh
194− git clone https://github.com/helix-editor/helix
159+git clone https://github.com/helix-editor/helix
195− cd helix
160+cd helix
196− ```
161+```
197
162
1982. Compile from source:
1632. Compile from source:
199
164
200− ```sh
165+```sh
201− cargo install --path helix-term --locked
166+cargo install --path helix-term --locked
202− ```
167+```
203
168
204− This command will create the `hx` executable and construct the tree-sitter
169+This command will create the `hx` executable and construct the tree-sitter
205− grammars in the local `runtime` folder.
170+grammars in the local `runtime` folder.
206
171
207> 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch
172> 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch
208> grammars with `hx --grammar fetch` and compile them with
173> grammars with `hx --grammar fetch` and compile them with
@@ -214,22 +179,18 @@ RUSTFLAGS="-C target-feature=-crt-static"
214
179
215#### Linux and macOS
180#### Linux and macOS
216
181
217−The **runtime** directory is one below the Helix source, so either set a
218−`HELIX_RUNTIME` environment variable to point to that directory and add it to
182+Either set the `HELIX_RUNTIME` environment variable to point to the runtime files and add it to your `~/.bashrc` or equivalent:
219−your `~/.bashrc` or equivalent:
220
183
221```sh
184```sh
222−HELIX_RUNTIME=~/src/helix/runtime
185+HELIX_RUNTIME=/home/user-name/src/helix/runtime
223```
186```
224
187
225−Or, create a symbolic link:
188+Or, create a symlink in `~/.config/helix` that links to the source code directory:
226
189
227```sh
190```sh
228−ln -Ts $PWD/runtime ~/.config/helix/runtime
191+ln -s $PWD/runtime ~/.config/helix/runtime
229```
192```
230
193
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.
232−
233#### Windows
194#### Windows
234
195
235Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
196Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
@@ -261,32 +222,12 @@ following order:
2611. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
2221. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
262 developing and testing helix only).
223 developing and testing helix only).
2632. `runtime/` subdirectory of OS-dependent helix user config directory.
2242. `runtime/` subdirectory of OS-dependent helix user config directory.
264−3. `$HELIX_RUNTIME`
225+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.
226+4. `runtime/` subdirectory of path to Helix executable.
268
227
269This order also sets the priority for selecting which file will be used if multiple runtime
228This order also sets the priority for selecting which file will be used if multiple runtime
270directories have files with the same name.
229directories have files with the same name.
271
230
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
231### Validating the installation
291
232
292To make sure everything is set up as expected you should run the Helix health
233To make sure everything is set up as expected you should run the Helix health
book/src/keymap.md+16−24
@@ -25,8 +25,6 @@
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.
29−
30### Movement
28### Movement
31
29
32> NOTE: Unlike Vim, `f`, `F`, `t` and `T` are not confined to the current line.
30> NOTE: Unlike Vim, `f`, `F`, `t` and `T` are not confined to the current line.
@@ -113,8 +111,7 @@ Normal mode is the default mode when you launch helix. You can return to it from
113| `s` | Select all regex matches inside selections | `select_regex` |
111| `s` | Select all regex matches inside selections | `select_regex` |
114| `S` | Split selection into sub selections on regex matches | `split_selection` |
112| `S` | Split selection into sub selections on regex matches | `split_selection` |
115| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
113| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
116−| `Alt-minus` | Merge selections | `merge_selections` |
117−| `Alt-_` | Merge consecutive selections | `merge_consecutive_selections` |
114+| `Alt-_ ` | Merge consecutive selections | `merge_consecutive_selections` |
118| `&` | Align selection in columns | `align_selections` |
115| `&` | Align selection in columns | `align_selections` |
119| `_` | Trim whitespace from the selection | `trim_selections` |
116| `_` | Trim whitespace from the selection | `trim_selections` |
120| `;` | Collapse selection onto a single cursor | `collapse_selection` |
117| `;` | Collapse selection onto a single cursor | `collapse_selection` |
@@ -291,7 +288,7 @@ This layer is a kludge of mappings, mostly pickers.
291| `w` | Enter [window mode](#window-mode) | N/A |
288| `w` | Enter [window mode](#window-mode) | N/A |
292| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
289| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
293| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
290| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
294−| `y` | Yank selections to clipboard | `yank_to_clipboard` |
291+| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` |
295| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
292| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
296| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
293| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
297| `/` | Global search in workspace folder | `global_search` |
294| `/` | Global search in workspace folder | `global_search` |
@@ -339,8 +336,6 @@ These mappings are in the style of [vim-unimpaired](https://github.com/tpope/vim
339
336
340## Insert mode
337## Insert mode
341
338
342−Accessed by typing `i` in [normal mode](#normal-mode).
343−
344Insert mode bindings are minimal by default. Helix is designed to
339Insert mode bindings are minimal by default. Helix is designed to
345be a modal editor, and this is reflected in the user experience and internal
340be a modal editor, and this is reflected in the user experience and internal
346mechanics. Changes to the text are only saved for undos when
341mechanics. Changes to the text are only saved for undos when
@@ -394,11 +389,9 @@ end = "no_op"
394
389
395## Select / extend mode
390## Select / extend mode
396
391
397−Accessed by typing `v` in [normal mode](#normal-mode).
398−
399Select mode echoes Normal mode, but changes any movements to extend
392Select mode echoes Normal mode, but changes any movements to extend
400selections rather than replace them. Goto motions are also changed to
393selections rather than replace them. Goto motions are also changed to
401−extend, so that `vgl`, for example, extends the selection to the end of
394+extend, so that `vgl` for example extends the selection to the end of
402the line.
395the line.
403
396
404Search is also affected. By default, `n` and `N` will remove the current
397Search is also affected. By default, `n` and `N` will remove the current
@@ -411,20 +404,19 @@ you to selectively add search terms to your selections.
411
404
412Keys to use within picker. Remapping currently not supported.
405Keys to use within picker. Remapping currently not supported.
413
406
414−| Key | Description |
407+| Key | Description |
415−| ----- | ------------- |
408+| ----- | ------------- |
416−| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
409+| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
417−| `Tab`, `Down`, `Ctrl-n` | Next entry |
410+| `Tab`, `Down`, `Ctrl-n` | Next entry |
418−| `PageUp`, `Ctrl-u` | Page up |
411+| `PageUp`, `Ctrl-u` | Page up |
419−| `PageDown`, `Ctrl-d` | Page down |
412+| `PageDown`, `Ctrl-d` | Page down |
420−| `Home` | Go to first entry |
413+| `Home` | Go to first entry |
421−| `End` | Go to last entry |
414+| `End` | Go to last entry |
422−| `Enter` | Open selected |
415+| `Enter` | Open selected |
423−| `Alt-Enter` | Open selected in the background without closing the picker |
424−| `Ctrl-s` | Open horizontally |
416+| `Ctrl-s` | Open horizontally |
425−| `Ctrl-v` | Open vertically |
417+| `Ctrl-v` | Open vertically |
426−| `Ctrl-t` | Toggle preview |
418+| `Ctrl-t` | Toggle preview |
427−| `Escape`, `Ctrl-c` | Close picker |
419+| `Escape`, `Ctrl-c` | Close picker |
428
420
429## Prompt
421## Prompt
430
422
book/src/languages.md+27−103
@@ -7,24 +7,21 @@ in `languages.toml` files.
7
7
8There are three possible locations for a `languages.toml` file:
8There are three possible locations for a `languages.toml` file:
9
9
10−1. In the Helix source code, which lives in the
10+1. In the Helix source code, this lives in the
11 [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml).
11 [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml).
12 It provides the default configurations for languages and language servers.
12 It provides the default configurations for languages and language servers.
13
13
142. In your [configuration directory](./configuration.md). This overrides values
142. In your [configuration directory](./configuration.md). This overrides values
15− from the built-in language configuration. For example, to disable
15+ from the built-in language configuration. For example to disable
16 auto-LSP-formatting in Rust:
16 auto-LSP-formatting in Rust:
17
17
18− ```toml
18+```toml
19− # in <config_dir>/helix/languages.toml
19+# in <config_dir>/helix/languages.toml
20−
21− [language-server.mylang-lsp]
22− command = "mylang-lsp"
23
20
24− [[language]]
21+[[language]]
25− name = "rust"
22+name = "rust"
26− auto-format = false
23+auto-format = false
27− ```
24+```
28
25
293. In a `.helix` folder in your project. Language configuration may also be
263. In a `.helix` folder in your project. Language configuration may also be
30 overridden local to a project by creating a `languages.toml` file in a
27 overridden local to a project by creating a `languages.toml` file in a
@@ -44,8 +41,8 @@ injection-regex = "mylang"
44file-types = ["mylang", "myl"]
41file-types = ["mylang", "myl"]
45comment-token = "#"
42comment-token = "#"
46indent = { tab-width = 2, unit = " " }
43indent = { tab-width = 2, unit = " " }
44+language-server = { command = "mylang-lsp", args = ["--stdio"], environment = { "ENV1" = "value1", "ENV2" = "value2" } }
47formatter = { command = "mylang-formatter" , args = ["--stdin"] }
45formatter = { command = "mylang-formatter" , args = ["--stdin"] }
48−language-servers = [ "mylang-lsp" ]
49```
46```
50
47
51These configuration keys are available:
48These configuration keys are available:
@@ -53,7 +50,6 @@ These configuration keys are available:
53| Key | Description |
50| Key | Description |
54| ---- | ----------- |
51| ---- | ----------- |
55| `name` | The name of the language |
52| `name` | The name of the language |
56−| `language-id` | The language-id for language servers, checkout the table at [TextDocumentItem](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem) for the right id |
57| `scope` | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.<name>` or `text.<name>` in case of markup languages |
53| `scope` | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.<name>` or `text.<name>` in case of markup languages |
58| `injection-regex` | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. |
54| `injection-regex` | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. |
59| `file-types` | The filetypes of the language, for example `["yml", "yaml"]`. See the file-type detection section below. |
55| `file-types` | The filetypes of the language, for example `["yml", "yaml"]`. See the file-type detection section below. |
@@ -63,13 +59,12 @@ These configuration keys are available:
63| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
59| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
64| `comment-token` | The token to use as a comment-token |
60| `comment-token` | The token to use as a comment-token |
65| `indent` | The indent to use. Has sub keys `unit` (the text inserted into the document when indenting; usually set to N spaces or `"\t"` for tabs) and `tab-width` (the number of spaces rendered for a tab) |
61| `indent` | The indent to use. Has sub keys `unit` (the text inserted into the document when indenting; usually set to N spaces or `"\t"` for tabs) and `tab-width` (the number of spaces rendered for a tab) |
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) |
62+| `language-server` | The Language Server to run. See the Language Server configuration section below. |
63+| `config` | Language Server configuration |
67| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
64| `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 |
65| `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` |
66| `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. |
67| `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
68
74### File-type detection and the `file-types` key
69### File-type detection and the `file-types` key
75
70
@@ -97,102 +92,31 @@ with the following priorities:
97 replaced at runtime with the appropriate path separator for the operating
92 replaced at runtime with the appropriate path separator for the operating
98 system, so this rule would match against `.git\config` files on Windows.
93 system, so this rule would match against `.git\config` files on Windows.
99
94
100−## Language Server configuration
95+### Language Server configuration
101
96
102−Language servers are configured separately in the table `language-server` in the same file as the languages `languages.toml`
97+The `language-server` field takes the following keys:
103−
104−For example:
105−
106−```toml
107−[language-server.mylang-lsp]
108−command = "mylang-lsp"
109−args = ["--stdio"]
110−config = { provideFormatter = true }
111−environment = { "ENV1" = "value1", "ENV2" = "value2" }
112−
113−[language-server.efm-lsp-prettier]
114−command = "efm-langserver"
115−
116−[language-server.efm-lsp-prettier.config]
117−documentFormatting = true
118−languages = { typescript = [ { formatCommand ="prettier --stdin-filepath ${INPUT}", formatStdin = true } ] }
119−```
120
98
121−These are the available options for a language server.
99+| Key | Description |
100+| --- | ----------- |
101+| `command` | The name of the language server binary to execute. Binaries must be in `$PATH` |
102+| `args` | A list of arguments to pass to the language server binary |
103+| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
104+| `language-id` | The language name to pass to the language server. Some language servers support multiple languages and use this field to determine which one is being served in a buffer |
105+| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
122
106
123−| Key | Description |
124−| ---- | ----------- |
125−| `command` | The name or path of the language server binary to execute. Binaries must be in `$PATH` |
126−| `args` | A list of arguments to pass to the language server binary |
127−| `config` | LSP initialization options |
107+The top-level `config` field is used to configure the LSP initialization options. A `format`
128−| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
129−| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
130−
131−A `format` sub-table within `config` can be used to pass extra formatting options to
108+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).
109+[Document Formatting Requests](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-16.md#document-formatting-request--leftwards_arrow_with_hook).
133−For example, with typescript:
110+For example with typescript:
134−
135−```toml
136−[language-server.typescript-language-server]
137−# pass format options according to https://github.com/typescript-language-server/typescript-language-server#workspacedidchangeconfiguration omitting the "[language].format." prefix.
138−config = { format = { "semicolons" = "insert", "insertSpaceBeforeFunctionParenthesis" = true } }
139−```
140−
141−### Configuring Language Servers for a language
142−
143−The `language-servers` attribute in a language tells helix which language servers are used for this language.
144−
145−They have to be defined in the `[language-server]` table as described in the previous section.
146−
147−Different languages can use the same language server instance, e.g. `typescript-language-server` is used for javascript, jsx, tsx and typescript by default.
148−
149−In case multiple language servers are specified in the `language-servers` attribute of a `language`,
150−it's often useful to only enable/disable certain language-server features for these language servers.
151−
152−As an example, `efm-lsp-prettier` of the previous example is used only with a formatting command `prettier`,
153−so everything else should be handled by the `typescript-language-server` (which is configured by default).
154−The language configuration for typescript could look like this:
155−
156−```toml
157−[[language]]
158−name = "typescript"
159−language-servers = [ { name = "efm-lsp-prettier", only-features = [ "format" ] }, "typescript-language-server" ]
160−```
161−
162−or equivalent:
163
111
164```toml
112```toml
165[[language]]
113[[language]]
166name = "typescript"
114name = "typescript"
167−language-servers = [ { name = "typescript-language-server", except-features = [ "format" ] }, "efm-lsp-prettier" ]
115+auto-format = true
116+# pass format options according to https://github.com/typescript-language-server/typescript-language-server#workspacedidchangeconfiguration omitting the "[language].format." prefix.
117+config = { format = { "semicolons" = "insert", "insertSpaceBeforeFunctionParenthesis" = true } }
168```
118```
169
119
170−Each requested LSP feature is prioritized in the order of the `language-servers` array.
171−For example, the first `goto-definition` supported language server (in this case `typescript-language-server`) will be taken for the relevant LSP request (command `goto_definition`).
172−The features `diagnostics`, `code-action`, `completion`, `document-symbols` and `workspace-symbols` are an exception to that rule, as they are working for all language servers at the same time and are merged together, if enabled for the language.
173−If no `except-features` or `only-features` is given, all features for the language server are enabled.
174−If a language server itself doesn't support a feature, the next language server array entry will be tried (and so on).
175−
176−The list of supported features is:
177−
178−- `format`
179−- `goto-definition`
180−- `goto-declaration`
181−- `goto-type-definition`
182−- `goto-reference`
183−- `goto-implementation`
184−- `signature-help`
185−- `hover`
186−- `document-highlight`
187−- `completion`
188−- `code-action`
189−- `workspace-command`
190−- `document-symbols`
191−- `workspace-symbols`
192−- `diagnostics`
193−- `rename-symbol`
194−- `inlay-hints`
195−
196## Tree-sitter grammar configuration
120## Tree-sitter grammar configuration
197
121
198The source for a language's tree-sitter grammar is specified in a `[[grammar]]`
122The source for a language's tree-sitter grammar is specified in a `[[grammar]]`
book/src/themes.md+2−10
@@ -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,15 +291,12 @@ 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` | |
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 |
310| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
302| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
book/src/usage.md+10−26
@@ -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−
52−### Special registers
53−
54−Some registers have special behavior when read from and written to.
55
48
56−| Register character | When read | When written |
49+The system clipboard is not directly supported by a special register. Instead, special commands and keybindings are provided. Refer to the
57−| --- | --- | --- |
50+[key map](keymap.md#space-mode) for more details.
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
51
65−When yanking multiple selections to the clipboard registers, the selections
52+The black hole register is a no-op register, meaning that no data will be read or written to it.
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
@@ -112,13 +96,13 @@ function or block of code.
112| `(`, `[`, `'`, etc. | Specified surround pairs |
96| `(`, `[`, `'`, etc. | Specified surround pairs |
113| `m` | The closest surround pair |
97| `m` | The closest surround pair |
114| `f` | Function |
98| `f` | Function |
115−| `t` | Type (or Class) |
99+| `c` | Class |
116| `a` | Argument/parameter |
100| `a` | Argument/parameter |
117−| `c` | Comment |
101+| `o` | Comment |
118−| `T` | Test |
102+| `t` | Test |
119| `g` | Change |
103| `g` | Change |
120
104
121−> 💡 `f`, `t`, etc. need a tree-sitter grammar active for the current
105+> 💡 `f`, `c`, etc. need a tree-sitter grammar active for the current
122document and a special tree-sitter query file to work properly. [Only
106document and a special tree-sitter query file to work properly. [Only
123some grammars][lang-support] currently have the query file implemented.
107some grammars][lang-support] currently have the query file implemented.
124Contributions are welcome!
108Contributions are welcome!
@@ -128,7 +112,7 @@ Contributions are welcome!
128Navigating between functions, classes, parameters, and other elements is
112Navigating between functions, classes, parameters, and other elements is
129possible using tree-sitter and textobject queries. For
113possible using tree-sitter and textobject queries. For
130example to move to the next function use `]f`, to move to previous
114example to move to the next function use `]f`, to move to previous
131−type use `[t`, and so on.
115+class use `[c`, and so on.
132
116
133![Tree-sitter-nav-demo][tree-sitter-nav-demo]
117![Tree-sitter-nav-demo][tree-sitter-nav-demo]
134
118
book/theme/book.jsnew file+660
@@ -0,0 +1,660 @@
1+"use strict";
2+
3+// Fix back button cache problem
4+window.onunload = function () { };
5+
6+// Global variable, shared between modules
7+function playground_text(playground) {
8+ let code_block = playground.querySelector("code");
9+
10+ if (window.ace && code_block.classList.contains("editable")) {
11+ let editor = window.ace.edit(code_block);
12+ return editor.getValue();
13+ } else {
14+ return code_block.textContent;
15+ }
16+}
17+
18+(function codeSnippets() {
19+ function fetch_with_timeout(url, options, timeout = 6000) {
20+ return Promise.race([
21+ fetch(url, options),
22+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
23+ ]);
24+ }
25+
26+ var playgrounds = Array.from(document.querySelectorAll(".playground"));
27+ if (playgrounds.length > 0) {
28+ fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
29+ headers: {
30+ 'Content-Type': "application/json",
31+ },
32+ method: 'POST',
33+ mode: 'cors',
34+ })
35+ .then(response => response.json())
36+ .then(response => {
37+ // get list of crates available in the rust playground
38+ let playground_crates = response.crates.map(item => item["id"]);
39+ playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));
40+ });
41+ }
42+
43+ function handle_crate_list_update(playground_block, playground_crates) {
44+ // update the play buttons after receiving the response
45+ update_play_button(playground_block, playground_crates);
46+
47+ // and install on change listener to dynamically update ACE editors
48+ if (window.ace) {
49+ let code_block = playground_block.querySelector("code");
50+ if (code_block.classList.contains("editable")) {
51+ let editor = window.ace.edit(code_block);
52+ editor.addEventListener("change", function (e) {
53+ update_play_button(playground_block, playground_crates);
54+ });
55+ // add Ctrl-Enter command to execute rust code
56+ editor.commands.addCommand({
57+ name: "run",
58+ bindKey: {
59+ win: "Ctrl-Enter",
60+ mac: "Ctrl-Enter"
61+ },
62+ exec: _editor => run_rust_code(playground_block)
63+ });
64+ }
65+ }
66+ }
67+
68+ // updates the visibility of play button based on `no_run` class and
69+ // used crates vs ones available on http://play.rust-lang.org
70+ function update_play_button(pre_block, playground_crates) {
71+ var play_button = pre_block.querySelector(".play-button");
72+
73+ // skip if code is `no_run`
74+ if (pre_block.querySelector('code').classList.contains("no_run")) {
75+ play_button.classList.add("hidden");
76+ return;
77+ }
78+
79+ // get list of `extern crate`'s from snippet
80+ var txt = playground_text(pre_block);
81+ var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
82+ var snippet_crates = [];
83+ var item;
84+ while (item = re.exec(txt)) {
85+ snippet_crates.push(item[1]);
86+ }
87+
88+ // check if all used crates are available on play.rust-lang.org
89+ var all_available = snippet_crates.every(function (elem) {
90+ return playground_crates.indexOf(elem) > -1;
91+ });
92+
93+ if (all_available) {
94+ play_button.classList.remove("hidden");
95+ } else {
96+ play_button.classList.add("hidden");
97+ }
98+ }
99+
100+ function run_rust_code(code_block) {
101+ var result_block = code_block.querySelector(".result");
102+ if (!result_block) {
103+ result_block = document.createElement('code');
104+ result_block.className = 'result hljs language-bash';
105+
106+ code_block.append(result_block);
107+ }
108+
109+ let text = playground_text(code_block);
110+ let classes = code_block.querySelector('code').classList;
111+ let has_2018 = classes.contains("edition2018");
112+ let edition = has_2018 ? "2018" : "2015";
113+
114+ var params = {
115+ version: "stable",
116+ optimize: "0",
117+ code: text,
118+ edition: edition
119+ };
120+
121+ if (text.indexOf("#![feature") !== -1) {
122+ params.version = "nightly";
123+ }
124+
125+ result_block.innerText = "Running...";
126+
127+ fetch_with_timeout("https://play.rust-lang.org/evaluate.json", {
128+ headers: {
129+ 'Content-Type': "application/json",
130+ },
131+ method: 'POST',
132+ mode: 'cors',
133+ body: JSON.stringify(params)
134+ })
135+ .then(response => response.json())
136+ .then(response => result_block.innerText = response.result)
137+ .catch(error => result_block.innerText = "Playground Communication: " + error.message);
138+ }
139+
140+ // Syntax highlighting Configuration
141+ hljs.configure({
142+ tabReplace: ' ', // 4 spaces
143+ languages: [], // Languages used for auto-detection
144+ });
145+
146+ let code_nodes = Array
147+ .from(document.querySelectorAll('code'))
148+ // Don't highlight `inline code` blocks in headers.
149+ .filter(function (node) {return !node.parentElement.classList.contains("header"); });
150+
151+ if (window.ace) {
152+ // language-rust class needs to be removed for editable
153+ // blocks or highlightjs will capture events
154+ Array
155+ .from(document.querySelectorAll('code.editable'))
156+ .forEach(function (block) { block.classList.remove('language-rust'); });
157+
158+ Array
159+ .from(document.querySelectorAll('code:not(.editable)'))
160+ .forEach(function (block) { hljs.highlightBlock(block); });
161+ } else {
162+ code_nodes.forEach(function (block) { hljs.highlightBlock(block); });
163+ }
164+
165+ // Adding the hljs class gives code blocks the color css
166+ // even if highlighting doesn't apply
167+ code_nodes.forEach(function (block) { block.classList.add('hljs'); });
168+
169+ Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
170+
171+ var lines = Array.from(block.querySelectorAll('.boring'));
172+ // If no lines were hidden, return
173+ if (!lines.length) { return; }
174+ block.classList.add("hide-boring");
175+
176+ var buttons = document.createElement('div');
177+ buttons.className = 'buttons';
178+ buttons.innerHTML = "<button class=\"fa fa-eye\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
179+
180+ // add expand button
181+ var pre_block = block.parentNode;
182+ pre_block.insertBefore(buttons, pre_block.firstChild);
183+
184+ pre_block.querySelector('.buttons').addEventListener('click', function (e) {
185+ if (e.target.classList.contains('fa-eye')) {
186+ e.target.classList.remove('fa-eye');
187+ e.target.classList.add('fa-eye-slash');
188+ e.target.title = 'Hide lines';
189+ e.target.setAttribute('aria-label', e.target.title);
190+
191+ block.classList.remove('hide-boring');
192+ } else if (e.target.classList.contains('fa-eye-slash')) {
193+ e.target.classList.remove('fa-eye-slash');
194+ e.target.classList.add('fa-eye');
195+ e.target.title = 'Show hidden lines';
196+ e.target.setAttribute('aria-label', e.target.title);
197+
198+ block.classList.add('hide-boring');
199+ }
200+ });
201+ });
202+
203+ if (window.playground_copyable) {
204+ Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
205+ var pre_block = block.parentNode;
206+ if (!pre_block.classList.contains('playground')) {
207+ var buttons = pre_block.querySelector(".buttons");
208+ if (!buttons) {
209+ buttons = document.createElement('div');
210+ buttons.className = 'buttons';
211+ pre_block.insertBefore(buttons, pre_block.firstChild);
212+ }
213+
214+ var clipButton = document.createElement('button');
215+ clipButton.className = 'fa fa-copy clip-button';
216+ clipButton.title = 'Copy to clipboard';
217+ clipButton.setAttribute('aria-label', clipButton.title);
218+ clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
219+
220+ buttons.insertBefore(clipButton, buttons.firstChild);
221+ }
222+ });
223+ }
224+
225+ // Process playground code blocks
226+ Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) {
227+ // Add play button
228+ var buttons = pre_block.querySelector(".buttons");
229+ if (!buttons) {
230+ buttons = document.createElement('div');
231+ buttons.className = 'buttons';
232+ pre_block.insertBefore(buttons, pre_block.firstChild);
233+ }
234+
235+ var runCodeButton = document.createElement('button');
236+ runCodeButton.className = 'fa fa-play play-button';
237+ runCodeButton.hidden = true;
238+ runCodeButton.title = 'Run this code';
239+ runCodeButton.setAttribute('aria-label', runCodeButton.title);
240+
241+ buttons.insertBefore(runCodeButton, buttons.firstChild);
242+ runCodeButton.addEventListener('click', function (e) {
243+ run_rust_code(pre_block);
244+ });
245+
246+ if (window.playground_copyable) {
247+ var copyCodeClipboardButton = document.createElement('button');
248+ copyCodeClipboardButton.className = 'fa fa-copy clip-button';
249+ copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
250+ copyCodeClipboardButton.title = 'Copy to clipboard';
251+ copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
252+
253+ buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
254+ }
255+
256+ let code_block = pre_block.querySelector("code");
257+ if (window.ace && code_block.classList.contains("editable")) {
258+ var undoChangesButton = document.createElement('button');
259+ undoChangesButton.className = 'fa fa-history reset-button';
260+ undoChangesButton.title = 'Undo changes';
261+ undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
262+
263+ buttons.insertBefore(undoChangesButton, buttons.firstChild);
264+
265+ undoChangesButton.addEventListener('click', function () {
266+ let editor = window.ace.edit(code_block);
267+ editor.setValue(editor.originalCode);
268+ editor.clearSelection();
269+ });
270+ }
271+ });
272+})();
273+
274+(function themes() {
275+ var html = document.querySelector('html');
276+ var themeToggleButton = document.getElementById('theme-toggle');
277+ var themePopup = document.getElementById('theme-list');
278+ var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
279+ var stylesheets = {
280+ ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
281+ tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
282+ highlight: document.querySelector("[href$='highlight.css']"),
283+ };
284+
285+ function showThemes() {
286+ themePopup.style.display = 'block';
287+ themeToggleButton.setAttribute('aria-expanded', true);
288+ themePopup.querySelector("button#" + get_theme()).focus();
289+ }
290+
291+ function hideThemes() {
292+ themePopup.style.display = 'none';
293+ themeToggleButton.setAttribute('aria-expanded', false);
294+ themeToggleButton.focus();
295+ }
296+
297+ function get_theme() {
298+ var theme;
299+ try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
300+ if (theme === null || theme === undefined) {
301+ return default_theme;
302+ } else {
303+ return theme;
304+ }
305+ }
306+
307+ function set_theme(theme, store = true) {
308+ let ace_theme;
309+
310+ if (theme == 'coal' || theme == 'navy') {
311+ stylesheets.ayuHighlight.disabled = true;
312+ stylesheets.tomorrowNight.disabled = false;
313+ stylesheets.highlight.disabled = true;
314+
315+ ace_theme = "ace/theme/tomorrow_night";
316+ } else if (theme == 'ayu') {
317+ stylesheets.ayuHighlight.disabled = false;
318+ stylesheets.tomorrowNight.disabled = true;
319+ stylesheets.highlight.disabled = true;
320+ ace_theme = "ace/theme/tomorrow_night";
321+ } else {
322+ stylesheets.ayuHighlight.disabled = true;
323+ stylesheets.tomorrowNight.disabled = true;
324+ stylesheets.highlight.disabled = false;
325+ ace_theme = "ace/theme/dawn";
326+ }
327+
328+ setTimeout(function () {
329+ themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;
330+ }, 1);
331+
332+ if (window.ace && window.editors) {
333+ window.editors.forEach(function (editor) {
334+ editor.setTheme(ace_theme);
335+ });
336+ }
337+
338+ var previousTheme = get_theme();
339+
340+ if (store) {
341+ try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
342+ }
343+
344+ html.classList.remove(previousTheme);
345+ html.classList.add(theme);
346+ }
347+
348+ // Set theme
349+ var theme = get_theme();
350+
351+ set_theme(theme, false);
352+
353+ themeToggleButton.addEventListener('click', function () {
354+ if (themePopup.style.display === 'block') {
355+ hideThemes();
356+ } else {
357+ showThemes();
358+ }
359+ });
360+
361+ themePopup.addEventListener('click', function (e) {
362+ var theme = e.target.id || e.target.parentElement.id;
363+ set_theme(theme);
364+ });
365+
366+ themePopup.addEventListener('focusout', function(e) {
367+ // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
368+ if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
369+ hideThemes();
370+ }
371+ });
372+
373+ // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
374+ document.addEventListener('click', function(e) {
375+ if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
376+ hideThemes();
377+ }
378+ });
379+
380+ document.addEventListener('keydown', function (e) {
381+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
382+ if (!themePopup.contains(e.target)) { return; }
383+
384+ switch (e.key) {
385+ case 'Escape':
386+ e.preventDefault();
387+ hideThemes();
388+ break;
389+ case 'ArrowUp':
390+ e.preventDefault();
391+ var li = document.activeElement.parentElement;
392+ if (li && li.previousElementSibling) {
393+ li.previousElementSibling.querySelector('button').focus();
394+ }
395+ break;
396+ case 'ArrowDown':
397+ e.preventDefault();
398+ var li = document.activeElement.parentElement;
399+ if (li && li.nextElementSibling) {
400+ li.nextElementSibling.querySelector('button').focus();
401+ }
402+ break;
403+ case 'Home':
404+ e.preventDefault();
405+ themePopup.querySelector('li:first-child button').focus();
406+ break;
407+ case 'End':
408+ e.preventDefault();
409+ themePopup.querySelector('li:last-child button').focus();
410+ break;
411+ }
412+ });
413+})();
414+
415+(function sidebar() {
416+ var html = document.querySelector("html");
417+ var sidebar = document.getElementById("sidebar");
418+ var sidebarLinks = document.querySelectorAll('#sidebar a');
419+ var sidebarToggleButton = document.getElementById("sidebar-toggle");
420+ var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
421+ var firstContact = null;
422+
423+ function showSidebar() {
424+ html.classList.remove('sidebar-hidden')
425+ html.classList.add('sidebar-visible');
426+ Array.from(sidebarLinks).forEach(function (link) {
427+ link.setAttribute('tabIndex', 0);
428+ });
429+ sidebarToggleButton.setAttribute('aria-expanded', true);
430+ sidebar.setAttribute('aria-hidden', false);
431+ try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
432+ }
433+
434+
435+ var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
436+
437+ function toggleSection(ev) {
438+ ev.currentTarget.parentElement.classList.toggle('expanded');
439+ }
440+
441+ Array.from(sidebarAnchorToggles).forEach(function (el) {
442+ el.addEventListener('click', toggleSection);
443+ });
444+
445+ function hideSidebar() {
446+ html.classList.remove('sidebar-visible')
447+ html.classList.add('sidebar-hidden');
448+ Array.from(sidebarLinks).forEach(function (link) {
449+ link.setAttribute('tabIndex', -1);
450+ });
451+ sidebarToggleButton.setAttribute('aria-expanded', false);
452+ sidebar.setAttribute('aria-hidden', true);
453+ try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
454+ }
455+
456+ // Toggle sidebar
457+ sidebarToggleButton.addEventListener('click', function sidebarToggle() {
458+ if (html.classList.contains("sidebar-hidden")) {
459+ var current_width = parseInt(
460+ document.documentElement.style.getPropertyValue('--sidebar-width'), 10);
461+ if (current_width < 150) {
462+ document.documentElement.style.setProperty('--sidebar-width', '150px');
463+ }
464+ showSidebar();
465+ } else if (html.classList.contains("sidebar-visible")) {
466+ hideSidebar();
467+ } else {
468+ if (getComputedStyle(sidebar)['transform'] === 'none') {
469+ hideSidebar();
470+ } else {
471+ showSidebar();
472+ }
473+ }
474+ });
475+
476+ sidebarResizeHandle.addEventListener('mousedown', initResize, false);
477+
478+ function initResize(e) {
479+ window.addEventListener('mousemove', resize, false);
480+ window.addEventListener('mouseup', stopResize, false);
481+ html.classList.add('sidebar-resizing');
482+ }
483+ function resize(e) {
484+ var pos = (e.clientX - sidebar.offsetLeft);
485+ if (pos < 20) {
486+ hideSidebar();
487+ } else {
488+ if (html.classList.contains("sidebar-hidden")) {
489+ showSidebar();
490+ }
491+ pos = Math.min(pos, window.innerWidth - 100);
492+ document.documentElement.style.setProperty('--sidebar-width', pos + 'px');
493+ }
494+ }
495+ //on mouseup remove windows functions mousemove & mouseup
496+ function stopResize(e) {
497+ html.classList.remove('sidebar-resizing');
498+ window.removeEventListener('mousemove', resize, false);
499+ window.removeEventListener('mouseup', stopResize, false);
500+ }
501+
502+ document.addEventListener('touchstart', function (e) {
503+ firstContact = {
504+ x: e.touches[0].clientX,
505+ time: Date.now()
506+ };
507+ }, { passive: true });
508+
509+ document.addEventListener('touchmove', function (e) {
510+ if (!firstContact)
511+ return;
512+
513+ var curX = e.touches[0].clientX;
514+ var xDiff = curX - firstContact.x,
515+ tDiff = Date.now() - firstContact.time;
516+
517+ if (tDiff < 250 && Math.abs(xDiff) >= 150) {
518+ if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
519+ showSidebar();
520+ else if (xDiff < 0 && curX < 300)
521+ hideSidebar();
522+
523+ firstContact = null;
524+ }
525+ }, { passive: true });
526+
527+ // Scroll sidebar to current active section
528+ var activeSection = document.getElementById("sidebar").querySelector(".active");
529+ if (activeSection) {
530+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
531+ activeSection.scrollIntoView({ block: 'center' });
532+ }
533+})();
534+
535+(function chapterNavigation() {
536+ document.addEventListener('keydown', function (e) {
537+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
538+ if (window.search && window.search.hasFocus()) { return; }
539+
540+ switch (e.key) {
541+ case 'ArrowRight':
542+ e.preventDefault();
543+ var nextButton = document.querySelector('.nav-chapters.next');
544+ if (nextButton) {
545+ window.location.href = nextButton.href;
546+ }
547+ break;
548+ case 'ArrowLeft':
549+ e.preventDefault();
550+ var previousButton = document.querySelector('.nav-chapters.previous');
551+ if (previousButton) {
552+ window.location.href = previousButton.href;
553+ }
554+ break;
555+ }
556+ });
557+})();
558+
559+(function clipboard() {
560+ var clipButtons = document.querySelectorAll('.clip-button');
561+
562+ function hideTooltip(elem) {
563+ elem.firstChild.innerText = "";
564+ elem.className = 'fa fa-copy clip-button';
565+ }
566+
567+ function showTooltip(elem, msg) {
568+ elem.firstChild.innerText = msg;
569+ elem.className = 'fa fa-copy tooltipped';
570+ }
571+
572+ var clipboardSnippets = new ClipboardJS('.clip-button', {
573+ text: function (trigger) {
574+ hideTooltip(trigger);
575+ let playground = trigger.closest("pre");
576+ return playground_text(playground);
577+ }
578+ });
579+
580+ Array.from(clipButtons).forEach(function (clipButton) {
581+ clipButton.addEventListener('mouseout', function (e) {
582+ hideTooltip(e.currentTarget);
583+ });
584+ });
585+
586+ clipboardSnippets.on('success', function (e) {
587+ e.clearSelection();
588+ showTooltip(e.trigger, "Copied!");
589+ });
590+
591+ clipboardSnippets.on('error', function (e) {
592+ showTooltip(e.trigger, "Clipboard error!");
593+ });
594+})();
595+
596+(function scrollToTop () {
597+ var menuTitle = document.querySelector('.menu-title');
598+
599+ menuTitle.addEventListener('click', function () {
600+ document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
601+ });
602+})();
603+
604+(function controlMenu() {
605+ var menu = document.getElementById('menu-bar');
606+
607+ (function controlPosition() {
608+ var scrollTop = document.scrollingElement.scrollTop;
609+ var prevScrollTop = scrollTop;
610+ var minMenuY = -menu.clientHeight - 50;
611+ // When the script loads, the page can be at any scroll (e.g. if you reforesh it).
612+ menu.style.top = scrollTop + 'px';
613+ // Same as parseInt(menu.style.top.slice(0, -2), but faster
614+ var topCache = menu.style.top.slice(0, -2);
615+ menu.classList.remove('sticky');
616+ var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster
617+ document.addEventListener('scroll', function () {
618+ scrollTop = Math.max(document.scrollingElement.scrollTop, 0);
619+ // `null` means that it doesn't need to be updated
620+ var nextSticky = null;
621+ var nextTop = null;
622+ var scrollDown = scrollTop > prevScrollTop;
623+ var menuPosAbsoluteY = topCache - scrollTop;
624+ if (scrollDown) {
625+ nextSticky = false;
626+ if (menuPosAbsoluteY > 0) {
627+ nextTop = prevScrollTop;
628+ }
629+ } else {
630+ if (menuPosAbsoluteY > 0) {
631+ nextSticky = true;
632+ } else if (menuPosAbsoluteY < minMenuY) {
633+ nextTop = prevScrollTop + minMenuY;
634+ }
635+ }
636+ if (nextSticky === true && stickyCache === false) {
637+ menu.classList.add('sticky');
638+ stickyCache = true;
639+ } else if (nextSticky === false && stickyCache === true) {
640+ menu.classList.remove('sticky');
641+ stickyCache = false;
642+ }
643+ if (nextTop !== null) {
644+ menu.style.top = nextTop + 'px';
645+ topCache = nextTop;
646+ }
647+ prevScrollTop = scrollTop;
648+ }, { passive: true });
649+ })();
650+ (function controlBorder() {
651+ menu.classList.remove('bordered');
652+ document.addEventListener('scroll', function () {
653+ if (menu.offsetTop === 0) {
654+ menu.classList.remove('bordered');
655+ } else {
656+ menu.classList.add('bordered');
657+ }
658+ }, { passive: true });
659+ })();
660+})();
book/theme/css/chrome.cssnew file+499
@@ -0,0 +1,499 @@
1+/* CSS for UI elements (a.k.a. chrome) */
2+
3+@import 'variables.css';
4+
5+::-webkit-scrollbar {
6+ background: var(--bg);
7+}
8+::-webkit-scrollbar-thumb {
9+ background: var(--scrollbar);
10+}
11+html {
12+ scrollbar-color: var(--scrollbar) var(--bg);
13+}
14+#searchresults a,
15+.content a:link,
16+a:visited,
17+a > .hljs {
18+ color: var(--links);
19+}
20+.content a:hover {
21+ text-decoration: underline;
22+}
23+
24+/* Menu Bar */
25+
26+#menu-bar,
27+#menu-bar-hover-placeholder {
28+ z-index: 101;
29+ margin: auto calc(0px - var(--page-padding));
30+}
31+#menu-bar {
32+ position: relative;
33+ display: flex;
34+ flex-wrap: wrap;
35+ background-color: var(--bg);
36+ border-bottom-color: var(--bg);
37+ border-bottom-width: 1px;
38+ border-bottom-style: solid;
39+}
40+#menu-bar.sticky,
41+.js #menu-bar-hover-placeholder:hover + #menu-bar,
42+.js #menu-bar:hover,
43+.js.sidebar-visible #menu-bar {
44+ position: -webkit-sticky;
45+ position: sticky;
46+ top: 0 !important;
47+}
48+#menu-bar-hover-placeholder {
49+ position: sticky;
50+ position: -webkit-sticky;
51+ top: 0;
52+ height: var(--menu-bar-height);
53+}
54+#menu-bar.bordered {
55+ border-bottom-color: var(--table-border-color);
56+}
57+#menu-bar i, #menu-bar .icon-button {
58+ position: relative;
59+ padding: 0 8px;
60+ z-index: 10;
61+ line-height: var(--menu-bar-height);
62+ cursor: pointer;
63+ transition: color 0.5s;
64+}
65+@media only screen and (max-width: 420px) {
66+ #menu-bar i, #menu-bar .icon-button {
67+ padding: 0 5px;
68+ }
69+}
70+
71+.icon-button {
72+ border: none;
73+ background: none;
74+ padding: 0;
75+ color: inherit;
76+}
77+.icon-button i {
78+ margin: 0;
79+}
80+
81+.right-buttons {
82+ margin: 0 15px;
83+}
84+.right-buttons a {
85+ text-decoration: none;
86+}
87+
88+.left-buttons {
89+ display: flex;
90+ margin: 0 5px;
91+}
92+.no-js .left-buttons {
93+ display: none;
94+}
95+
96+.menu-title {
97+ display: inline-block;
98+ font-weight: 200;
99+ font-size: 2.4rem;
100+ line-height: var(--menu-bar-height);
101+ text-align: center;
102+ margin: 0;
103+ flex: 1;
104+ white-space: nowrap;
105+ overflow: hidden;
106+ text-overflow: ellipsis;
107+}
108+.js .menu-title {
109+ cursor: pointer;
110+}
111+
112+.menu-bar,
113+.menu-bar:visited,
114+.nav-chapters,
115+.nav-chapters:visited,
116+.mobile-nav-chapters,
117+.mobile-nav-chapters:visited,
118+.menu-bar .icon-button,
119+.menu-bar a i {
120+ color: var(--icons);
121+}
122+
123+.menu-bar i:hover,
124+.menu-bar .icon-button:hover,
125+.nav-chapters:hover,
126+.mobile-nav-chapters i:hover {
127+ color: var(--icons-hover);
128+}
129+
130+/* Nav Icons */
131+
132+.nav-chapters {
133+ font-size: 2.5em;
134+ text-align: center;
135+ text-decoration: none;
136+
137+ position: fixed;
138+ top: 0;
139+ bottom: 0;
140+ margin: 0;
141+ max-width: 150px;
142+ min-width: 90px;
143+
144+ display: flex;
145+ justify-content: center;
146+ align-content: center;
147+ flex-direction: column;
148+
149+ transition: color 0.5s, background-color 0.5s;
150+}
151+
152+.nav-chapters:hover {
153+ text-decoration: none;
154+ background-color: var(--theme-hover);
155+ transition: background-color 0.15s, color 0.15s;
156+}
157+
158+.nav-wrapper {
159+ margin-top: 50px;
160+ display: none;
161+}
162+
163+.mobile-nav-chapters {
164+ font-size: 2.5em;
165+ text-align: center;
166+ text-decoration: none;
167+ width: 90px;
168+ border-radius: 5px;
169+ background-color: var(--sidebar-bg);
170+}
171+
172+.previous {
173+ float: left;
174+}
175+
176+.next {
177+ float: right;
178+ right: var(--page-padding);
179+}
180+
181+@media only screen and (max-width: 1080px) {
182+ .nav-wide-wrapper { display: none; }
183+ .nav-wrapper { display: block; }
184+}
185+
186+@media only screen and (max-width: 1380px) {
187+ .sidebar-visible .nav-wide-wrapper { display: none; }
188+ .sidebar-visible .nav-wrapper { display: block; }
189+}
190+
191+/* Inline code */
192+
193+:not(pre) > .hljs {
194+ display: inline;
195+ padding: 0.1em 0.3em;
196+ border-radius: 3px;
197+}
198+
199+:not(pre):not(a):not(td):not(p) > .hljs {
200+ color: var(--inline-code-color);
201+ overflow-x: initial;
202+}
203+
204+a:hover > .hljs {
205+ text-decoration: underline;
206+}
207+
208+pre {
209+ position: relative;
210+}
211+pre > .buttons {
212+ position: absolute;
213+ z-index: 100;
214+ right: 5px;
215+ top: 5px;
216+
217+ color: var(--sidebar-fg);
218+ cursor: pointer;
219+}
220+pre > .buttons :hover {
221+ color: var(--sidebar-active);
222+}
223+pre > .buttons i {
224+ margin-left: 8px;
225+}
226+pre > .buttons button {
227+ color: inherit;
228+ background: transparent;
229+ border: none;
230+ cursor: inherit;
231+}
232+pre > .result {
233+ margin-top: 10px;
234+}
235+
236+/* Search */
237+
238+#searchresults a {
239+ text-decoration: none;
240+}
241+
242+mark {
243+ border-radius: 2px;
244+ padding: 0 3px 1px 3px;
245+ margin: 0 -3px -1px -3px;
246+ background-color: var(--search-mark-bg);
247+ transition: background-color 300ms linear;
248+ cursor: pointer;
249+}
250+
251+mark.fade-out {
252+ background-color: rgba(0,0,0,0) !important;
253+ cursor: auto;
254+}
255+
256+.searchbar-outer {
257+ margin-left: auto;
258+ margin-right: auto;
259+ max-width: var(--content-max-width);
260+}
261+
262+#searchbar {
263+ width: 100%;
264+ margin: 5px auto 0px auto;
265+ padding: 10px 16px;
266+ transition: box-shadow 300ms ease-in-out;
267+ border: 1px solid var(--searchbar-border-color);
268+ border-radius: 3px;
269+ background-color: var(--searchbar-bg);
270+ color: var(--searchbar-fg);
271+}
272+#searchbar:focus,
273+#searchbar.active {
274+ box-shadow: 0 0 3px var(--searchbar-shadow-color);
275+}
276+
277+.searchresults-header {
278+ font-weight: bold;
279+ font-size: 1em;
280+ padding: 18px 0 0 5px;
281+ color: var(--searchresults-header-fg);
282+}
283+
284+.searchresults-outer {
285+ margin-left: auto;
286+ margin-right: auto;
287+ max-width: var(--content-max-width);
288+ border-bottom: 1px dashed var(--searchresults-border-color);
289+}
290+
291+ul#searchresults {
292+ list-style: none;
293+ padding-left: 20px;
294+}
295+ul#searchresults li {
296+ margin: 10px 0px;
297+ padding: 2px;
298+ border-radius: 2px;
299+}
300+ul#searchresults li.focus {
301+ background-color: var(--searchresults-li-bg);
302+}
303+ul#searchresults span.teaser {
304+ display: block;
305+ clear: both;
306+ margin: 5px 0 0 20px;
307+ font-size: 0.8em;
308+}
309+ul#searchresults span.teaser em {
310+ font-weight: bold;
311+ font-style: normal;
312+}
313+
314+/* Sidebar */
315+
316+.sidebar {
317+ position: fixed;
318+ left: 0;
319+ top: 0;
320+ bottom: 0;
321+ width: var(--sidebar-width);
322+ font-size: 0.875em;
323+ box-sizing: border-box;
324+ -webkit-overflow-scrolling: touch;
325+ overscroll-behavior-y: contain;
326+ background-color: var(--sidebar-bg);
327+ color: var(--sidebar-fg);
328+}
329+.sidebar-resizing {
330+ -moz-user-select: none;
331+ -webkit-user-select: none;
332+ -ms-user-select: none;
333+ user-select: none;
334+}
335+.js:not(.sidebar-resizing) .sidebar {
336+ transition: transform 0.3s; /* Animation: slide away */
337+}
338+.sidebar code {
339+ line-height: 2em;
340+}
341+.sidebar .sidebar-scrollbox {
342+ overflow-y: auto;
343+ position: absolute;
344+ top: 0;
345+ bottom: 0;
346+ left: 0;
347+ right: 0;
348+}
349+.sidebar .sidebar-resize-handle {
350+ position: absolute;
351+ cursor: col-resize;
352+ width: 0;
353+ right: 0;
354+ top: 0;
355+ bottom: 0;
356+}
357+.js .sidebar .sidebar-resize-handle {
358+ cursor: col-resize;
359+ width: 5px;
360+}
361+.sidebar-hidden .sidebar {
362+ transform: translateX(calc(0px - var(--sidebar-width)));
363+}
364+.sidebar::-webkit-scrollbar {
365+ background: var(--sidebar-bg);
366+}
367+.sidebar::-webkit-scrollbar-thumb {
368+ background: var(--scrollbar);
369+}
370+
371+.sidebar-visible .page-wrapper {
372+ transform: translateX(var(--sidebar-width));
373+}
374+@media only screen and (min-width: 620px) {
375+ .sidebar-visible .page-wrapper {
376+ transform: none;
377+ margin-left: var(--sidebar-width);
378+ }
379+}
380+
381+.chapter {
382+ list-style: none outside none;
383+ padding-left: 0;
384+ margin: .25rem 0;
385+}
386+
387+.chapter ol {
388+ width: 100%;
389+}
390+
391+.chapter li {
392+ display: flex;
393+ color: var(--sidebar-non-existent);
394+}
395+.chapter li a {
396+ display: block;
397+ text-decoration: none;
398+ color: var(--sidebar-fg);
399+}
400+
401+.chapter li a:hover {
402+ color: var(--sidebar-active);
403+}
404+
405+.chapter li a.active {
406+ color: var(--sidebar-active);
407+}
408+
409+.chapter li > a.toggle {
410+ cursor: pointer;
411+ display: block;
412+ margin-left: auto;
413+ padding: 0 10px;
414+ user-select: none;
415+ opacity: 0.68;
416+}
417+
418+.chapter li > a.toggle div {
419+ transition: transform 0.5s;
420+}
421+
422+/* collapse the section */
423+.chapter li:not(.expanded) + li > ol {
424+ display: none;
425+}
426+
427+.chapter li.chapter-item {
428+ padding: 1rem 1.5rem;
429+}
430+
431+.chapter .section li.chapter-item {
432+ padding: .5rem .5rem 0 .5rem;
433+}
434+
435+.chapter li.expanded > a.toggle div {
436+ transform: rotate(90deg);
437+}
438+
439+.spacer {
440+ width: 100%;
441+ height: 3px;
442+ margin: 5px 0px;
443+}
444+.chapter .spacer {
445+ background-color: var(--sidebar-spacer);
446+}
447+
448+@media (-moz-touch-enabled: 1), (pointer: coarse) {
449+ .chapter li a { padding: 5px 0; }
450+ .spacer { margin: 10px 0; }
451+}
452+
453+.section {
454+ list-style: none outside none;
455+ padding-left: 2rem;
456+ line-height: 1.9em;
457+}
458+
459+/* Theme Menu Popup */
460+
461+.theme-popup {
462+ position: absolute;
463+ left: 10px;
464+ top: var(--menu-bar-height);
465+ z-index: 1000;
466+ border-radius: 4px;
467+ font-size: 0.7em;
468+ color: var(--fg);
469+ background: var(--theme-popup-bg);
470+ border: 1px solid var(--theme-popup-border);
471+ margin: 0;
472+ padding: 0;
473+ list-style: none;
474+ display: none;
475+}
476+.theme-popup .default {
477+ color: var(--icons);
478+}
479+.theme-popup .theme {
480+ width: 100%;
481+ border: 0;
482+ margin: 0;
483+ padding: 2px 10px;
484+ line-height: 25px;
485+ white-space: nowrap;
486+ text-align: left;
487+ cursor: pointer;
488+ color: inherit;
489+ background: inherit;
490+ font-size: inherit;
491+}
492+.theme-popup .theme:hover {
493+ background-color: var(--theme-hover);
494+}
495+.theme-popup .theme:hover:first-child,
496+.theme-popup .theme:hover:last-child {
497+ border-top-left-radius: inherit;
498+ border-top-right-radius: inherit;
499+}
book/theme/css/general.cssnew file+233
@@ -0,0 +1,233 @@
1+/* Base styles and content styles */
2+
3+@import 'variables.css';
4+
5+:root {
6+ /* Browser default font-size is 16px, this way 1 rem = 10px */
7+ font-size: 62.5%;
8+}
9+
10+/* TODO: replace with self hosted fonts */
11+
12+html {
13+ font-family: "Inter", sans-serif;
14+ color: var(--fg);
15+ background-color: var(--bg);
16+ text-size-adjust: none;
17+}
18+
19+/* @supports (font-variation-settings: normal) { */
20+/* html { font-family: 'Inter var', sans-serif; } */
21+/* } */
22+
23+body {
24+ margin: 0;
25+ font-size: 1.6rem;
26+ overflow-x: hidden;
27+}
28+
29+code {
30+ font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
31+ font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
32+}
33+
34+/* Don't change font size in headers. */
35+h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
36+ font-size: unset;
37+}
38+
39+.left { float: left; }
40+.right { float: right; }
41+.boring { opacity: 0.6; }
42+.hide-boring .boring { display: none; }
43+.hidden { display: none !important; }
44+
45+h2, h3 { margin-top: 2.5em; }
46+h4, h5 { margin-top: 2em; }
47+
48+.header + .header h3,
49+.header + .header h4,
50+.header + .header h5 {
51+ margin-top: 1em;
52+}
53+
54+h1:target::before,
55+h2:target::before,
56+h3:target::before,
57+h4:target::before,
58+h5:target::before,
59+h6:target::before {
60+ display: inline-block;
61+ content: "»";
62+ margin-left: -30px;
63+ width: 30px;
64+}
65+
66+/* This is broken on Safari as of version 14, but is fixed
67+ in Safari Technology Preview 117 which I think will be Safari 14.2.
68+ https://bugs.webkit.org/show_bug.cgi?id=218076
69+*/
70+:target {
71+ scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
72+}
73+
74+.page {
75+ outline: 0;
76+ padding: 0 var(--page-padding);
77+ margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */
78+}
79+.page-wrapper {
80+ box-sizing: border-box;
81+}
82+.js:not(.sidebar-resizing) .page-wrapper {
83+ transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */
84+}
85+
86+.content {
87+ overflow-y: auto;
88+ padding: 0 15px;
89+ padding-bottom: 50px;
90+}
91+.content main {
92+ margin-left: auto;
93+ margin-right: auto;
94+ max-width: var(--content-max-width);
95+}
96+
97+
98+/* 2 1.75 1.5 1.25 1 .875 */
99+
100+.content h1 { font-size: 2em }
101+.content h2 { font-size: 1.75em }
102+.content h3 { font-size: 1.5em }
103+.content h4 { font-size: 1.25em }
104+.content h5 { font-size: 1em }
105+.content h6 { font-size: .875em }
106+
107+.content h1, .content h2, .content h3, .content h4 {
108+ font-weight: 500;
109+ margin-top: 1.275em;
110+ margin-bottom: .875em;
111+}
112+.content p, .content ol, .content ul, .content table {
113+ margin-top: 0;
114+ margin-bottom: .875em;
115+}
116+
117+.content ul li {
118+margin-bottom: .25rem;
119+}
120+.content ul {
121+ list-style-type: square;
122+}
123+.content ul ul, .content ol ul {
124+ margin-bottom: .5rem;
125+}
126+.content li p {
127+ margin-bottom: .5em;
128+}
129+
130+.content p { line-height: 1.45em; }
131+.content ol { line-height: 1.45em; }
132+.content ul { line-height: 1.45em; }
133+.content a { text-decoration: none; }
134+.content a:hover { text-decoration: underline; }
135+.content img { max-width: 100%; }
136+.content .header:link,
137+.content .header:visited {
138+ color: var(--fg);
139+ color: var(--heading-fg);
140+}
141+.content .header:link,
142+.content .header:visited:hover {
143+ text-decoration: none;
144+}
145+
146+table {
147+ margin: 0 auto;
148+ border-collapse: collapse;
149+ width: 100%;
150+}
151+table td {
152+ padding: .75rem;
153+ width: auto;
154+}
155+table thead {
156+ background: var(--table-header-bg);
157+}
158+table thead td {
159+ font-weight: 700;
160+ border: none;
161+}
162+table thead th {
163+ padding: .75rem;
164+ text-align: left;
165+ font-weight: 500;
166+ line-height: 1.5;
167+ width: auto;
168+}
169+table thead tr {
170+ border-bottom: 2px var(--table-border-color) solid;
171+}
172+table tbody tr {
173+ border-bottom: 1px var(--table-border-line) solid;
174+}
175+/* Alternate background colors for rows */
176+table tbody tr:nth-child(2n) {
177+ /* background: var(--table-alternate-bg); */
178+}
179+
180+
181+blockquote {
182+ margin: 1.5rem 0;
183+ padding: 1rem 1.5rem;
184+ color: var(--fg);
185+ opacity: .9;
186+ background-color: var(--quote-bg);
187+ border-left: 4px solid var(--quote-border);
188+}
189+blockquote *:last-child {
190+ margin-bottom: 0;
191+}
192+
193+
194+:not(.footnote-definition) + .footnote-definition,
195+.footnote-definition + :not(.footnote-definition) {
196+ margin-top: 2em;
197+}
198+.footnote-definition {
199+ font-size: 0.9em;
200+ margin: 0.5em 0;
201+}
202+.footnote-definition p {
203+ display: inline;
204+}
205+
206+.tooltiptext {
207+ position: absolute;
208+ visibility: hidden;
209+ color: #fff;
210+ background-color: #333;
211+ transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */
212+ left: -8px; /* Half of the width of the icon */
213+ top: -35px;
214+ font-size: 0.8em;
215+ text-align: center;
216+ border-radius: 6px;
217+ padding: 5px 8px;
218+ margin: 5px;
219+ z-index: 1000;
220+}
221+.tooltipped .tooltiptext {
222+ visibility: visible;
223+}
224+
225+.chapter li.part-title {
226+ color: var(--sidebar-fg);
227+ margin: 5px 0px;
228+ font-weight: bold;
229+}
230+
231+.result-no-output {
232+ font-style: italic;
233+}
book/theme/css/print.cssnew file+54
@@ -0,0 +1,54 @@
1+
2+#sidebar,
3+#menu-bar,
4+.nav-chapters,
5+.mobile-nav-chapters {
6+ display: none;
7+}
8+
9+#page-wrapper.page-wrapper {
10+ transform: none;
11+ margin-left: 0px;
12+ overflow-y: initial;
13+}
14+
15+#content {
16+ max-width: none;
17+ margin: 0;
18+ padding: 0;
19+}
20+
21+.page {
22+ overflow-y: initial;
23+}
24+
25+code {
26+ background-color: #666666;
27+ border-radius: 5px;
28+
29+ /* Force background to be printed in Chrome */
30+ -webkit-print-color-adjust: exact;
31+}
32+
33+pre > .buttons {
34+ z-index: 2;
35+}
36+
37+a, a:visited, a:active, a:hover {
38+ color: #4183c4;
39+ text-decoration: none;
40+}
41+
42+h1, h2, h3, h4, h5, h6 {
43+ page-break-inside: avoid;
44+ page-break-after: avoid;
45+}
46+
47+pre, code {
48+ page-break-inside: avoid;
49+ white-space: pre-wrap;
50+}
51+
52+.fa {
53+ display: none !important;
54+}
book/theme/css/variables.cssnew file+411
@@ -0,0 +1,411 @@
1+
2+/* Globals */
3+
4+:root {
5+ --sidebar-width: 300px;
6+ --page-padding: 15px;
7+ --content-max-width: 750px;
8+ --menu-bar-height: 50px;
9+}
10+
11+/* Themes */
12+
13+.ayu {
14+ --bg: hsl(210, 25%, 8%);
15+ --fg: #c5c5c5;
16+
17+ --sidebar-bg: #14191f;
18+ --sidebar-fg: #c8c9db;
19+ --sidebar-non-existent: #5c6773;
20+ --sidebar-active: #ffb454;
21+ --sidebar-spacer: #2d334f;
22+
23+ --scrollbar: var(--sidebar-fg);
24+
25+ --icons: #737480;
26+ --icons-hover: #b7b9cc;
27+
28+ --links: #0096cf;
29+
30+ --inline-code-color: #ffb454;
31+
32+ --theme-popup-bg: #14191f;
33+ --theme-popup-border: #5c6773;
34+ --theme-hover: #191f26;
35+
36+ --quote-bg: hsl(226, 15%, 17%);
37+ --quote-border: hsl(226, 15%, 22%);
38+
39+ --table-border-color: hsl(210, 25%, 13%);
40+ --table-header-bg: hsl(210, 25%, 28%);
41+ --table-alternate-bg: hsl(210, 25%, 11%);
42+
43+ --searchbar-border-color: #848484;
44+ --searchbar-bg: #424242;
45+ --searchbar-fg: #fff;
46+ --searchbar-shadow-color: #d4c89f;
47+ --searchresults-header-fg: #666;
48+ --searchresults-border-color: #888;
49+ --searchresults-li-bg: #252932;
50+ --search-mark-bg: #e3b171;
51+ --hljs-background: #191f26;
52+ --hljs-color: #e6e1cf;
53+ --hljs-quote: #5c6773;
54+ --hljs-variable: #ff7733;
55+ --hljs-type: #ffee99;
56+ --hljs-title: #b8cc52;
57+ --hljs-symbol: #ffb454;
58+ --hljs-selector-tag: #ff7733;
59+ --hljs-selector-tag: #36a3d9;
60+ --hljs-selector-tag: #00568d;
61+ --hljs-selector-tag: #91b362;
62+ --hljs-selector-tag: #d96c75;
63+}
64+
65+.coal {
66+ --bg: hsl(200, 7%, 8%);
67+ --fg: #98a3ad;
68+
69+ --sidebar-bg: #292c2f;
70+ --sidebar-fg: #a1adb8;
71+ --sidebar-non-existent: #505254;
72+ --sidebar-active: #3473ad;
73+ --sidebar-spacer: #393939;
74+
75+ --scrollbar: var(--sidebar-fg);
76+
77+ --icons: #43484d;
78+ --icons-hover: #b3c0cc;
79+
80+ --links: #2b79a2;
81+
82+ --inline-code-color: #c5c8c6;
83+
84+ --theme-popup-bg: #141617;
85+ --theme-popup-border: #43484d;
86+ --theme-hover: #1f2124;
87+
88+ --quote-bg: hsl(234, 21%, 18%);
89+ --quote-border: hsl(234, 21%, 23%);
90+
91+ --table-border-color: hsl(200, 7%, 13%);
92+ --table-header-bg: hsl(200, 7%, 28%);
93+ --table-alternate-bg: hsl(200, 7%, 11%);
94+
95+ --searchbar-border-color: #aaa;
96+ --searchbar-bg: #b7b7b7;
97+ --searchbar-fg: #000;
98+ --searchbar-shadow-color: #aaa;
99+ --searchresults-header-fg: #666;
100+ --searchresults-border-color: #98a3ad;
101+ --searchresults-li-bg: #2b2b2f;
102+ --search-mark-bg: #355c7d;
103+ --hljs-background: #969896;
104+ --hljs-color: #cc6666;
105+ --hljs-quote: #de935f;
106+ --hljs-variable: #f0c674;
107+ --hljs-type: #b5bd68;
108+ --hljs-title: #8abeb7;
109+ --hljs-symbol: #81a2be;
110+ --hljs-selector-tag: #b294bb;
111+ --hljs-selector-tag: #1d1f21;
112+ --hljs-selector-tag: #c5c8c6;
113+ --hljs-selector-tag: #718c00;
114+ --hljs-selector-tag: #c82829;
115+}
116+
117+.light {
118+ --bg: hsl(0, 0%, 100%);
119+ --fg: hsl(0, 0%, 0%);
120+
121+ --sidebar-bg: #fafafa;
122+ --sidebar-fg: hsl(0, 0%, 0%);
123+ --sidebar-non-existent: #aaaaaa;
124+ --sidebar-active: #1f1fff;
125+ --sidebar-spacer: #f4f4f4;
126+
127+ --scrollbar: #8F8F8F;
128+
129+ --icons: #747474;
130+ --icons-hover: #000000;
131+
132+ --links: #20609f;
133+
134+ --inline-code-color: #301900;
135+
136+ --theme-popup-bg: #fafafa;
137+ --theme-popup-border: #cccccc;
138+ --theme-hover: #e6e6e6;
139+
140+ --quote-bg: hsl(197, 37%, 96%);
141+ --quote-border: hsl(197, 37%, 91%);
142+
143+ --table-border-color: hsl(0, 0%, 95%);
144+ --table-header-bg: hsl(0, 0%, 80%);
145+ --table-alternate-bg: hsl(0, 0%, 97%);
146+
147+ --searchbar-border-color: #aaa;
148+ --searchbar-bg: #fafafa;
149+ --searchbar-fg: #000;
150+ --searchbar-shadow-color: #aaa;
151+ --searchresults-header-fg: #666;
152+ --searchresults-border-color: #888;
153+ --searchresults-li-bg: #e4f2fe;
154+ --search-mark-bg: #a2cff5;
155+ --hljs-background: #f6f7f6;
156+ --hljs-color: #000;
157+ --hljs-quote: #575757;
158+ --hljs-variable: #d70025;
159+ --hljs-type: #b21e00;
160+ --hljs-title: #0030f2;
161+ --hljs-symbol: #008200;
162+ --hljs-selector-tag: #9d00ec;
163+}
164+
165+.navy {
166+ --bg: hsl(226, 23%, 11%);
167+ --fg: #bcbdd0;
168+
169+ --sidebar-bg: #282d3f;
170+ --sidebar-fg: #c8c9db;
171+ --sidebar-non-existent: #505274;
172+ --sidebar-active: #2b79a2;
173+ --sidebar-spacer: #2d334f;
174+
175+ --scrollbar: var(--sidebar-fg);
176+
177+ --icons: #737480;
178+ --icons-hover: #b7b9cc;
179+
180+ --links: #2b79a2;
181+
182+ --inline-code-color: #c5c8c6;
183+
184+ --theme-popup-bg: #161923;
185+ --theme-popup-border: #737480;
186+ --theme-hover: #282e40;
187+
188+ --quote-bg: hsl(226, 15%, 17%);
189+ --quote-border: hsl(226, 15%, 22%);
190+
191+ --table-border-color: hsl(226, 23%, 16%);
192+ --table-header-bg: hsl(226, 23%, 31%);
193+ --table-alternate-bg: hsl(226, 23%, 14%);
194+
195+ --searchbar-border-color: #aaa;
196+ --searchbar-bg: #aeaec6;
197+ --searchbar-fg: #000;
198+ --searchbar-shadow-color: #aaa;
199+ --searchresults-header-fg: #5f5f71;
200+ --searchresults-border-color: #5c5c68;
201+ --searchresults-li-bg: #242430;
202+ --search-mark-bg: #a2cff5;
203+
204+ --hljs-background: #969896;
205+ --hljs-color: #cc6666;
206+ --hljs-quote: #de935f;
207+ --hljs-variable: #f0c674;
208+ --hljs-type: #b5bd68;
209+ --hljs-title: #8abeb7;
210+ --hljs-symbol: #81a2be;
211+ --hljs-selector-tag: #b294bb;
212+ --hljs-selector-tag: #1d1f21;
213+ --hljs-selector-tag: #c5c8c6;
214+ --hljs-selector-tag: #718c00;
215+ --hljs-selector-tag: #c82829;
216+}
217+
218+.rust {
219+ --bg: hsl(60, 9%, 87%);
220+ --fg: #262625;
221+
222+ --sidebar-bg: #3b2e2a;
223+ --sidebar-fg: #c8c9db;
224+ --sidebar-non-existent: #505254;
225+ --sidebar-active: #e69f67;
226+ --sidebar-spacer: #45373a;
227+
228+ --scrollbar: var(--sidebar-fg);
229+
230+ --icons: #737480;
231+ --icons-hover: #262625;
232+
233+ --links: #2b79a2;
234+
235+ --inline-code-color: #6e6b5e;
236+
237+ --theme-popup-bg: #e1e1db;
238+ --theme-popup-border: #b38f6b;
239+ --theme-hover: #99908a;
240+
241+ --quote-bg: hsl(60, 5%, 75%);
242+ --quote-border: hsl(60, 5%, 70%);
243+
244+ --table-border-color: hsl(60, 9%, 82%);
245+ --table-header-bg: #b3a497;
246+ --table-alternate-bg: hsl(60, 9%, 84%);
247+
248+ --searchbar-border-color: #aaa;
249+ --searchbar-bg: #fafafa;
250+ --searchbar-fg: #000;
251+ --searchbar-shadow-color: #aaa;
252+ --searchresults-header-fg: #666;
253+ --searchresults-border-color: #888;
254+ --searchresults-li-bg: #dec2a2;
255+ --search-mark-bg: #e69f67;
256+ --hljs-background: #f6f7f6;
257+ --hljs-color: #000;
258+ --hljs-quote: #575757;
259+ --hljs-variable: #d70025;
260+ --hljs-type: #b21e00;
261+ --hljs-title: #0030f2;
262+ --hljs-symbol: #008200;
263+ --hljs-selector-tag: #9d00ec;
264+}
265+
266+@media (prefers-color-scheme: dark) {
267+ .light.no-js {
268+ --bg: hsl(200, 7%, 8%);
269+ --fg: #98a3ad;
270+
271+ --sidebar-bg: #292c2f;
272+ --sidebar-fg: #a1adb8;
273+ --sidebar-non-existent: #505254;
274+ --sidebar-active: #3473ad;
275+ --sidebar-spacer: #393939;
276+
277+ --scrollbar: var(--sidebar-fg);
278+
279+ --icons: #43484d;
280+ --icons-hover: #b3c0cc;
281+
282+ --links: #2b79a2;
283+
284+ --inline-code-color: #c5c8c6;
285+
286+ --theme-popup-bg: #141617;
287+ --theme-popup-border: #43484d;
288+ --theme-hover: #1f2124;
289+
290+ --quote-bg: hsl(234, 21%, 18%);
291+ --quote-border: hsl(234, 21%, 23%);
292+
293+ --table-border-color: hsl(200, 7%, 13%);
294+ --table-header-bg: hsl(200, 7%, 28%);
295+ --table-alternate-bg: hsl(200, 7%, 11%);
296+
297+ --searchbar-border-color: #aaa;
298+ --searchbar-bg: #b7b7b7;
299+ --searchbar-fg: #000;
300+ --searchbar-shadow-color: #aaa;
301+ --searchresults-header-fg: #666;
302+ --searchresults-border-color: #98a3ad;
303+ --searchresults-li-bg: #2b2b2f;
304+ --search-mark-bg: #355c7d;
305+ }
306+}
307+
308+.colibri {
309+ --bg: #3b224c;
310+ --fg: #bcbdd0;
311+ --heading-fg: #fff;
312+
313+ --sidebar-bg: #281733;
314+ --sidebar-fg: #c8c9db;
315+ --sidebar-non-existent: #505274;
316+ --sidebar-active: #a4a0e8;
317+ --sidebar-spacer: #2d334f;
318+
319+ --scrollbar: var(--sidebar-fg);
320+
321+ --icons: #737480;
322+ --icons-hover: #b7b9cc;
323+
324+ /* --links: #a4a0e8; */
325+ --links: #ECCDBA;
326+
327+ --inline-code-color: hsl(48.7, 7.8%, 70%);
328+
329+ --theme-popup-bg: #161923;
330+ --theme-popup-border: #737480;
331+ --theme-hover: rgba(0,0,0, .2);
332+
333+ --quote-bg: #281733;
334+ --quote-border: hsl(226, 15%, 22%);
335+
336+ --table-border-color: hsl(226, 23%, 76%);
337+ --table-header-bg: hsla(226, 23%, 31%, 0);
338+ --table-alternate-bg: hsl(226, 23%, 14%);
339+ --table-border-line: hsla(201deg, 20%, 92%, 0.2);
340+
341+ --searchbar-border-color: #aaa;
342+ --searchbar-bg: #aeaec6;
343+ --searchbar-fg: #000;
344+ --searchbar-shadow-color: #aaa;
345+ --searchresults-header-fg: #5f5f71;
346+ --searchresults-border-color: #5c5c68;
347+ --searchresults-li-bg: #242430;
348+ --search-mark-bg: #acff5;
349+ --hljs-background: #2f1e2e;
350+ --hljs-color: #a39e9b;
351+ --hljs-quote: #8d8687;
352+ --hljs-variable: #ef6155;
353+ --hljs-type: #f99b15;
354+ --hljs-title: #fec418;
355+ --hljs-symbol: #48b685;
356+ --hljs-selector-tag: #815ba4;
357+}
358+
359+.colibri {
360+/*
361+ --bg: #ffffff;
362+ --fg: #452859;
363+ --fg: #5a5977;
364+ --heading-fg: #281733;
365+
366+ --sidebar-bg: #281733;
367+ --sidebar-fg: #c8c9db;
368+ --sidebar-non-existent: #505274;
369+ --sidebar-active: #a4a0e8;
370+ --sidebar-spacer: #2d334f;
371+
372+ --scrollbar: var(--sidebar-fg);
373+
374+ --icons: #737480;
375+ --icons-hover: #b7b9cc;
376+
377+ --links: #6F44F0;
378+
379+ --inline-code-color: #a39e9b;
380+
381+ --theme-popup-bg: #161923;
382+ --theme-popup-border: #737480;
383+ --theme-hover: rgba(0,0,0, .2);
384+
385+ --quote-bg: rgba(0, 0, 0, 0);
386+ --quote-border: hsl(226, 15%, 75%);
387+
388+ --table-border-color: #5a5977;
389+ --table-border-color: hsl(201deg 10% 67%);
390+ --table-header-bg: hsl(0, 0%, 100%);
391+ --table-alternate-bg: hsl(0, 0%, 97%);
392+ --table-border-line: hsl(201deg, 20%, 92%);
393+
394+ --searchbar-border-color: #aaa;
395+ --searchbar-bg: #aeaec6;
396+ --searchbar-fg: #000;
397+ --searchbar-shadow-color: #aaa;
398+ --searchresults-header-fg: #5f5f71;
399+ --searchresults-border-color: #5c5c68;
400+ --searchresults-li-bg: #242430;
401+ --search-mark-bg: #a2cff5;
402+ --hljs-background: #TODO;
403+ --hljs-color: #TODO;
404+ --hljs-quote: #TODO;
405+ --hljs-variable: #TODO;
406+ --hljs-type: #TODO;
407+ --hljs-title: #TODO;
408+ --hljs-symbol: #TODO;
409+ --hljs-selector-tag: #TODO;
410+*/
411+}
book/theme/highlight.cssnew file+56
@@ -0,0 +1,56 @@
1+pre code.hljs {
2+ display:block;
3+ overflow-x:auto;
4+ padding:1em
5+}
6+code.hljs {
7+ padding:3px 5px
8+}
9+.hljs {
10+ background: var(--hljs-background);
11+ color: var(--hljs-color);
12+}
13+.hljs-comment,
14+.hljs-quote {
15+ color: var(--hljs-quote)
16+}
17+.hljs-link,
18+.hljs-meta,
19+.hljs-name,
20+.hljs-regexp,
21+.hljs-selector-class,
22+.hljs-selector-id,
23+.hljs-tag,
24+.hljs-template-variable,
25+.hljs-variable {
26+ color: var(--hljs-variable)
27+}
28+.hljs-built_in,
29+.hljs-deletion,
30+.hljs-literal,
31+.hljs-number,
32+.hljs-params,
33+.hljs-type {
34+ color: var(--hljs-type)
35+}
36+.hljs-attribute,
37+.hljs-section,
38+.hljs-title {
39+ color: var(--hljs-title)
40+}
41+.hljs-addition,
42+.hljs-bullet,
43+.hljs-string,
44+.hljs-symbol {
45+ color: var(--hljs-symbol)
46+}
47+.hljs-keyword,
48+.hljs-selector-tag {
49+ color: var(--hljs-selector-tag)
50+}
51+.hljs-emphasis {
52+ font-style:italic
53+}
54+.hljs-strong {
55+ font-weight:700
56+}
book/theme/highlight.jsnew file+6
@@ -0,0 +1,6 @@
1+/*
2+ Highlight.js 10.1.1 (93fd0d73)
3+ License: BSD-3-Clause
4+ Copyright (c) 2006-2020, Ivan Sagalaev
5+*/
6+var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset<n[0].offset?e:n:"start"===n[0].event?e:n:e.length?e:n}function c(e){s+="<"+a(e)+[].map.call(e.attributes,(function(e){return" "+e.nodeName+'="'+t(e.value)+'"'})).join("")+">"}function u(e){s+="</"+a(e)+">"}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="</span>",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=`<span class="${e}">`}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:k,mergeStreams:O}=i,M=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void O.addText(A);e=m(y.subLanguage,A,!0,k[y.subLanguage]),k[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void O.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),O.addText(t)}(),A=""}function h(e){return e.className&&O.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"<unnamed>")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&O.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i<e.length;i++){var s=r+=1,o=d(e[i]);for(i>0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"<br>":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ /]*>/g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:"</?",end:">"}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}());hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin:/</,end:/>/},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%<?\^\+\*]/}]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,i]},a={className:"variable",begin:/\$\([\w-]+\s/,end:/\)/,keywords:{built_in:"subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value"},contains:[i]},r={begin:"^"+e.UNDERSCORE_IDENT_RE+"\\s*(?=[:+?]?=)"},s={className:"section",begin:/^[^\s]+:/,end:/$/,contains:[i]};return{name:"Makefile",aliases:["mk","mak"],keywords:{$pattern:/[\w-]+/,keyword:"define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath"},contains:[e.HASH_COMMENT_MODE,i,n,a,r,{className:"meta",begin:/^\.PHONY:/,end:/$/,keywords:{$pattern:/[\.\w]+/,"meta-keyword":".PHONY"}},s]}}}());hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("xml",function(){"use strict";return function(e){var n={className:"symbol",begin:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},a={begin:"\\s",contains:[{className:"meta-keyword",begin:"#?[a-z_][a-z1-9_-]+",illegal:"\\n"}]},s=e.inherit(a,{begin:"\\(",end:"\\)"}),t=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),i=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),c={endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:"attr",begin:"[A-Za-z0-9\\._:-]+",relevance:0},{begin:/=\s*/,relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/,contains:[n]},{begin:/'/,end:/'/,contains:[n]},{begin:/[^\s"'=<>`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"<![a-z]",end:">",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"<![a-z]",end:">",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:"<style(?=\\s|>)",end:">",keywords:{name:"style"},contains:[c],starts:{end:"</style>",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:"<script(?=\\s|>)",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"</?",end:"/?>",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"</",contains:[].concat(_,u,d,[s,{begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",end:">",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin:/</,end:/>/,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}());hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}());hljs.registerLanguage("d",function(){"use strict";return function(e){var a={$pattern:e.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("haskell",function(){"use strict";return function(e){var n={variants:[e.COMMENT("--","$"),e.COMMENT("{-","-}",{contains:["self"]})]},i={className:"meta",begin:"{-#",end:"#-}"},a={className:"meta",begin:"^#",end:"$"},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",end:"\\)",illegal:'"',contains:[i,a,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[s,l,n]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[i,s,l,{begin:"{",end:"}",contains:l.contains},n]},{beginKeywords:"default",end:"$",contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}}());hljs.registerLanguage("handlebars",function(){"use strict";function e(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(n){const a={"builtin-name":"action bindattr collection component concat debugger each each-in get hash if in input link-to loc log lookup mut outlet partial query-params render template textarea unbound unless view with yield"},t=/\[.*?\]/,s=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,i=e("(",/'.*?'/,"|",/".*?"/,"|",t,"|",s,"|",/\.|\//,")+"),r=e("(",t,"|",s,")(?==)"),l={begin:i,lexemes:/[\w.\/]+/},c=n.inherit(l,{keywords:{literal:"true false undefined null"}}),o={begin:/\(/,end:/\)/},m={className:"attr",begin:r,relevance:0,starts:{begin:/=/,end:/=/,starts:{contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,c,o]}}},d={contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{begin:/as\s+\|/,keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},m,c,o],returnEnd:!0},g=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/\)/})});o.contains=[g];const u=n.inherit(l,{keywords:a,className:"name",starts:n.inherit(d,{end:/}}/})}),b=n.inherit(l,{keywords:a,className:"name"}),h=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/}}/})});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},n.COMMENT(/\{\{!--/,/--\}\}/),n.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[u],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[b]},{className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[u]},{className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[b]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[h]},{className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[h]}]}}}());hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:"</",contains:[e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*","\\*/",{contains:["self"]}),e.inherit(e.QUOTE_STRING_MODE,{begin:/b?"/,illegal:null}),{className:"string",variants:[{begin:/r(#*)"(.|\n)*?"\1(?!#)/},{begin:/b?'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/}]},{className:"symbol",begin:/'[a-zA-Z_][a-zA-Z0-9_]*/},{className:"number",variants:[{begin:"\\b0b([01_]+)"+n},{begin:"\\b0o([0-7_]+)"+n},{begin:"\\b0x([A-Fa-f0-9_]+)"+n},{begin:"\\b(\\d[\\d_]*(\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)"+n}],relevance:0},{className:"function",beginKeywords:"fn",end:"(\\(|<)",excludeEnd:!0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"meta",begin:"#\\!?\\[",end:"\\]",contains:[{className:"meta-string",begin:/"/,end:/"/}]},{className:"class",beginKeywords:"type",end:";",contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"\\S"},{className:"class",beginKeywords:"trait enum struct union",end:"{",contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"[\\w\\d]"},{begin:e.IDENT_RE+"::",keywords:{built_in:t}},{begin:"->"}]}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("ini",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(...n){return n.map(n=>e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}());hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"</",contains:[{className:"built_in",begin:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.C_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"string",variants:[{begin:'@"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]}]},{className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}());hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"</?",end:">",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}());hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}());hljs.registerLanguage("x86asm",function(){"use strict";return function(s){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+s.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}());hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin:/</,end:/>/,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin:/</,end:/>/,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}());hljs.registerLanguage("armasm",function(){"use strict";return function(s){const e={variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),s.COMMENT("[;@]","$",{relevance:0}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}());hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:"</",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"string",variants:[e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{begin:"`",end:"`"}]},{className:"number",variants:[{begin:e.C_NUMBER_RE+"[i]",relevance:1},e.C_NUMBER_MODE]},{begin:/:=/},{className:"function",beginKeywords:"func",end:"\\s*(\\{|$)",excludeEnd:!0,contains:[e.TITLE_MODE,{className:"params",begin:/\(/,end:/\)/,keywords:n,illegal:/["']/}]}]}}}());hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}());hljs.registerLanguage("python",function(){"use strict";return function(e){var n={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}());hljs.registerLanguage("scala",function(){"use strict";return function(e){var n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},a={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},i={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[t]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}());hljs.registerLanguage("julia",function(){"use strict";return function(e){var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},a={keywords:t,illegal:/<\//},n={className:"subst",begin:/\$\(/,end:/\)/,keywords:t},o={className:"variable",begin:"\\$"+r},i={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]},l={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],begin:"`",end:"`"},s={className:"meta",begin:"@"+r};return a.name="Julia",a.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,s,{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}],n.contains=a.contains,a}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("r",function(){"use strict";return function(e){var n="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{name:"R",contains:[e.HASH_COMMENT_MODE,{begin:n,keywords:{$pattern:n,keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}());hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:"</>"},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}());hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}());
book/theme/index.hbs+51−87
@@ -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 }}">
@@ -15,9 +15,10 @@
15 <!-- Custom HTML head -->
15 <!-- Custom HTML head -->
16 {{> head}}
16 {{> head}}
17
17
18+ <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
18 <meta name="description" content="{{ description }}">
19 <meta name="description" content="{{ description }}">
19 <meta name="viewport" content="width=device-width, initial-scale=1">
20 <meta name="viewport" content="width=device-width, initial-scale=1">
20− <meta name="theme-color" content="#ffffff">
21+ <meta name="theme-color" content="#ffffff" />
21
22
22 {{#if favicon_svg}}
23 {{#if favicon_svg}}
23 <link rel="icon" href="{{ path_to_root }}favicon.svg">
24 <link rel="icon" href="{{ path_to_root }}favicon.svg">
@@ -34,6 +35,8 @@
34
35
35 <!-- Fonts -->
36 <!-- Fonts -->
36 <link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
37 <link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
38+ <link rel="preconnect" href="https://fonts.gstatic.com">
39+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;400;500;700&display=swap" rel="stylesheet">
37 {{#if copy_fonts}}
40 {{#if copy_fonts}}
38 <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
41 <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
39 {{/if}}
42 {{/if}}
@@ -50,19 +53,18 @@
50
53
51 {{#if mathjax_support}}
54 {{#if mathjax_support}}
52 <!-- MathJax -->
55 <!-- MathJax -->
53− <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
56+ <script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
54 {{/if}}
57 {{/if}}
55 </head>
58 </head>
56− <body class="sidebar-visible no-js">
59+ <body>
57− <div id="body-container">
58 <!-- Provide site root to javascript -->
60 <!-- Provide site root to javascript -->
59− <script>
61+ <script type="text/javascript">
60 var path_to_root = "{{ path_to_root }}";
62 var path_to_root = "{{ path_to_root }}";
61 var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
63 var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
62 </script>
64 </script>
63
65
64 <!-- Work around some values being stored in localStorage wrapped in quotes -->
66 <!-- Work around some values being stored in localStorage wrapped in quotes -->
65− <script>
67+ <script type="text/javascript">
66 try {
68 try {
67 var theme = localStorage.getItem('mdbook-theme');
69 var theme = localStorage.getItem('mdbook-theme');
68 var sidebar = localStorage.getItem('mdbook-sidebar');
70 var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -78,34 +80,27 @@
78 </script>
80 </script>
79
81
80 <!-- Set the theme before any content is loaded, prevents flash -->
82 <!-- Set the theme before any content is loaded, prevents flash -->
81− <script>
83+ <script type="text/javascript">
82 var theme;
84 var theme;
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 type="text/javascript">
97− var body = document.querySelector('body');
96+ var html = document.querySelector('html');
98− var sidebar = null;
97+ var sidebar = 'hidden';
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 {
104− sidebar = 'hidden';
105 }
101 }
106− sidebar_toggle.checked = sidebar === 'visible';
107− body.classList.remove('sidebar-visible');
102+ html.classList.remove('sidebar-visible');
108− body.classList.add("sidebar-" + sidebar);
103+ html.classList.add("sidebar-" + sidebar);
109 </script>
104 </script>
110
105
111 <nav id="sidebar" class="sidebar" aria-label="Table of contents">
106 <nav id="sidebar" class="sidebar" aria-label="Table of contents">
@@ -115,48 +110,26 @@
115 <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
110 <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
116 </nav>
111 </nav>
117
112
118− <!-- Track and set sidebar scroll position -->
119− <script>
120− var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
121− sidebarScrollbox.addEventListener('click', function(e) {
122− if (e.target.tagName === 'A') {
123− sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
124− }
125− }, { passive: true });
126− var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
127− sessionStorage.removeItem('sidebar-scroll');
128− if (sidebarScrollTop) {
129− // preserve sidebar scroll position when navigating via links within sidebar
130− sidebarScrollbox.scrollTop = sidebarScrollTop;
131− } else {
132− // scroll sidebar to current active section when navigating via "next/previous chapter" buttons
133− var activeSection = document.querySelector('#sidebar .active');
134− if (activeSection) {
135− activeSection.scrollIntoView({ block: 'center' });
136− }
137− }
138− </script>
139−
140 <div id="page-wrapper" class="page-wrapper">
113 <div id="page-wrapper" class="page-wrapper">
141
114
142 <div class="page">
115 <div class="page">
143 {{> header}}
116 {{> header}}
144 <div id="menu-bar-hover-placeholder"></div>
117 <div id="menu-bar-hover-placeholder"></div>
145− <div id="menu-bar" class="menu-bar sticky">
118+ <div id="menu-bar" class="menu-bar sticky bordered">
146 <div class="left-buttons">
119 <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">
120+ <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>
121 <i class="fa fa-bars"></i>
149− </label>
122+ </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">
123 <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>
124 <i class="fa fa-paint-brush"></i>
152 </button>
125 </button>
153 <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
126 <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
154− <li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
127+ <li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li>
155− <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
128+ <li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
156− <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
129+ <li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
157− <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
130+ <li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
158− <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
131+ <li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
159− <li role="none"><button role="menuitem" class="theme" id="colibri">Colibri</button></li>
132+ <li role="none"><button role="menuitem" class="theme" id="colibri">{{ theme_option "Colibri" }}</button></li>
160 </ul>
133 </ul>
161 {{#if search_enabled}}
134 {{#if search_enabled}}
162 <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
135 <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
@@ -178,19 +151,13 @@
178 <i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
151 <i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
179 </a>
152 </a>
180 {{/if}}
153 {{/if}}
181− {{#if git_repository_edit_url}}
182− <a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
183− <i id="git-edit-button" class="fa fa-edit"></i>
184− </a>
185− {{/if}}
186−
187 </div>
154 </div>
188 </div>
155 </div>
189
156
190 {{#if search_enabled}}
157 {{#if search_enabled}}
191 <div id="search-wrapper" class="hidden">
158 <div id="search-wrapper" class="hidden">
192 <form id="searchbar-outer" class="searchbar-outer">
159 <form id="searchbar-outer" class="searchbar-outer">
193− <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
160+ <input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
194 </form>
161 </form>
195 <div id="searchresults-outer" class="searchresults-outer hidden">
162 <div id="searchresults-outer" class="searchresults-outer hidden">
196 <div id="searchresults-header" class="searchresults-header"></div>
163 <div id="searchresults-header" class="searchresults-header"></div>
@@ -201,7 +168,7 @@
201 {{/if}}
168 {{/if}}
202
169
203 <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
170 <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
204− <script>
171+ <script type="text/javascript">
205 document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
172 document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
206 document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
173 document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
207 Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
174 Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -223,7 +190,7 @@
223 {{/previous}}
190 {{/previous}}
224
191
225 {{#next}}
192 {{#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">
193+ <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>
194 <i class="fa fa-angle-right"></i>
228 </a>
195 </a>
229 {{/next}}
196 {{/next}}
@@ -241,7 +208,7 @@
241 {{/previous}}
208 {{/previous}}
242
209
243 {{#next}}
210 {{#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">
211+ <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>
212 <i class="fa fa-angle-right"></i>
246 </a>
213 </a>
247 {{/next}}
214 {{/next}}
@@ -249,12 +216,10 @@
249
216
250 </div>
217 </div>
251
218
252− {{#if live_reload_endpoint}}
219+ {{#if livereload}}
253 <!-- Livereload script (if served using the cli tool) -->
220 <!-- Livereload script (if served using the cli tool) -->
254− <script>
221+ <script type="text/javascript">
255− const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
256− const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
257− const socket = new WebSocket(wsAddress);
222+ var socket = new WebSocket("{{{livereload}}}");
258 socket.onmessage = function (event) {
223 socket.onmessage = function (event) {
259 if (event.data === "reload") {
224 if (event.data === "reload") {
260 socket.close();
225 socket.close();
@@ -270,7 +235,7 @@
270
235
271 {{#if google_analytics}}
236 {{#if google_analytics}}
272 <!-- Google Analytics Tag -->
237 <!-- Google Analytics Tag -->
273− <script>
238+ <script type="text/javascript">
274 var localAddrs = ["localhost", "127.0.0.1", ""];
239 var localAddrs = ["localhost", "127.0.0.1", ""];
275
240
276 // make sure we don't activate google analytics if the developer is
241 // make sure we don't activate google analytics if the developer is
@@ -288,43 +253,43 @@
288 {{/if}}
253 {{/if}}
289
254
290 {{#if playground_line_numbers}}
255 {{#if playground_line_numbers}}
291− <script>
256+ <script type="text/javascript">
292 window.playground_line_numbers = true;
257 window.playground_line_numbers = true;
293 </script>
258 </script>
294 {{/if}}
259 {{/if}}
295
260
296 {{#if playground_copyable}}
261 {{#if playground_copyable}}
297− <script>
262+ <script type="text/javascript">
298 window.playground_copyable = true;
263 window.playground_copyable = true;
299 </script>
264 </script>
300 {{/if}}
265 {{/if}}
301
266
302 {{#if playground_js}}
267 {{#if playground_js}}
303− <script src="{{ path_to_root }}ace.js"></script>
268+ <script src="{{ path_to_root }}ace.js" type="text/javascript" charset="utf-8"></script>
304− <script src="{{ path_to_root }}editor.js"></script>
269+ <script src="{{ path_to_root }}editor.js" type="text/javascript" charset="utf-8"></script>
305− <script src="{{ path_to_root }}mode-rust.js"></script>
270+ <script src="{{ path_to_root }}mode-rust.js" type="text/javascript" charset="utf-8"></script>
306− <script src="{{ path_to_root }}theme-dawn.js"></script>
271+ <script src="{{ path_to_root }}theme-dawn.js" type="text/javascript" charset="utf-8"></script>
307− <script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
272+ <script src="{{ path_to_root }}theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
308 {{/if}}
273 {{/if}}
309
274
310 {{#if search_js}}
275 {{#if search_js}}
311− <script src="{{ path_to_root }}elasticlunr.min.js"></script>
276+ <script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
312− <script src="{{ path_to_root }}mark.min.js"></script>
277+ <script src="{{ path_to_root }}mark.min.js" type="text/javascript" charset="utf-8"></script>
313− <script src="{{ path_to_root }}searcher.js"></script>
278+ <script src="{{ path_to_root }}searcher.js" type="text/javascript" charset="utf-8"></script>
314 {{/if}}
279 {{/if}}
315
280
316− <script src="{{ path_to_root }}clipboard.min.js"></script>
281+ <script src="{{ path_to_root }}clipboard.min.js" type="text/javascript" charset="utf-8"></script>
317− <script src="{{ path_to_root }}highlight.js"></script>
282+ <script src="{{ path_to_root }}highlight.js" type="text/javascript" charset="utf-8"></script>
318− <script src="{{ path_to_root }}book.js"></script>
283+ <script src="{{ path_to_root }}book.js" type="text/javascript" charset="utf-8"></script>
319
284
320 <!-- Custom JS scripts -->
285 <!-- Custom JS scripts -->
321 {{#each additional_js}}
286 {{#each additional_js}}
322− <script src="{{ ../path_to_root }}{{this}}"></script>
287+ <script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
323 {{/each}}
288 {{/each}}
324
289
325 {{#if is_print}}
290 {{#if is_print}}
326 {{#if mathjax_support}}
291 {{#if mathjax_support}}
327− <script>
292+ <script type="text/javascript">
328 window.addEventListener('load', function() {
293 window.addEventListener('load', function() {
329 MathJax.Hub.Register.StartupHook('End', function() {
294 MathJax.Hub.Register.StartupHook('End', function() {
330 window.setTimeout(window.print, 100);
295 window.setTimeout(window.print, 100);
@@ -332,7 +297,7 @@
332 });
297 });
333 </script>
298 </script>
334 {{else}}
299 {{else}}
335− <script>
300+ <script type="text/javascript">
336 window.addEventListener('load', function() {
301 window.addEventListener('load', function() {
337 window.setTimeout(window.print, 100);
302 window.setTimeout(window.print, 100);
338 });
303 });
@@ -340,6 +305,5 @@
340 {{/if}}
305 {{/if}}
341 {{/if}}
306 {{/if}}
342
307
343− </div>
344 </body>
308 </body>
345</html>
309</html>
contrib/Helix.appdata.xml−18
@@ -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,12 +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">
55− <url>https://github.com/helix-editor/helix/releases/tag/23.05</url>
56− </release>
57 <release version="23.03" date="2023-03-31">
39 <release version="23.03" date="2023-03-31">
58 <url>https://helix-editor.com/news/release-23-03-highlights/</url>
40 <url>https://helix-editor.com/news/release-23-03-highlights/</url>
59 </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+6−13
@@ -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
@@ -37,7 +30,7 @@ being published.
37
30
38The changelog is currently created manually by reading through commits in the
31The changelog is currently created manually by reading through commits in the
39log since the last release. GitHub's compare view is a nice way to approach
32log since the last release. GitHub's compare view is a nice way to approach
40−this. For example, when creating the 22.07 release notes, this compare link
33+this. For example when creating the 22.07 release notes, this compare link
41may be used
34may be used
42
35
43```
36```
@@ -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.
docs/vision.md+1−1
@@ -20,5 +20,5 @@ Vision statements are all well and good, but are also vague and subjective. Her
20* **Built-in tools** for working with code bases efficiently. Most projects aren't a single file, and an editor should handle that as a first-class use case. In Helix's case, this means (among other things) a fuzzy-search file navigator and LSP support.
20* **Built-in tools** for working with code bases efficiently. Most projects aren't a single file, and an editor should handle that as a first-class use case. In Helix's case, this means (among other things) a fuzzy-search file navigator and LSP support.
21* **Edit anything** that comes up when coding, within reason. Whether it's a 200 MB XML file, a megabyte of minified javascript on a single line, or Japanese text encoded in ShiftJIS, you should be able to open it and edit it without problems. (Note: this doesn't mean handle every esoteric use case. Sometimes you do just need a specialized tool, and Helix isn't that.)
21* **Edit anything** that comes up when coding, within reason. Whether it's a 200 MB XML file, a megabyte of minified javascript on a single line, or Japanese text encoded in ShiftJIS, you should be able to open it and edit it without problems. (Note: this doesn't mean handle every esoteric use case. Sometimes you do just need a specialized tool, and Helix isn't that.)
22* **Configurable**, within reason. Although the defaults should be good, not everyone will agree on what "good" is. Within the bounds of Helix's core interaction models, it should be reasonably configurable so that it can be "good" for more people. This means, for example, custom key maps among other things.
22* **Configurable**, within reason. Although the defaults should be good, not everyone will agree on what "good" is. Within the bounds of Helix's core interaction models, it should be reasonably configurable so that it can be "good" for more people. This means, for example, custom key maps among other things.
23−* **Extensible**, within reason. Although we want Helix to be productive out-of-the-box, it's not practical or desirable to cram every useful feature and use case into the core editor. The basics should be built-in, but you should be able to extend it with additional functionality as needed.
23+* **Extensible**, within reason. Although we want Helix to be productive out-of-the-box, it's not practical or desirable to cram every useful feature and use case into the core editor. The basics should be built-in, but you should be able to extend it with additional functionality as needed. Right now we're thinking Wasm-based plugins.
24* **Clean code base.** Sometimes other factors (e.g. significant performance gains, important features, correctness, etc.) will trump strict readability, but we nevertheless want to keep the code base straightforward and easy to understand to the extent we can.
24* **Clean code base.** Sometimes other factors (e.g. significant performance gains, important features, correctness, etc.) will trump strict readability, but we nevertheless want to keep the code base straightforward and easy to understand to the extent we can.
flake.lock+235−38
@@ -1,35 +1,135 @@
1{
1{
2 "nodes": {
2 "nodes": {
3 "crane": {
3 "crane": {
4+ "flake": false,
5+ "locked": {
6+ "lastModified": 1670900067,
7+ "narHash": "sha256-VXVa+KBfukhmWizaiGiHRVX/fuk66P8dgSFfkVN4/MY=",
8+ "owner": "ipetkov",
9+ "repo": "crane",
10+ "rev": "59b31b41a589c0a65e4a1f86b0e5eac68081468b",
11+ "type": "github"
12+ },
13+ "original": {
14+ "owner": "ipetkov",
15+ "repo": "crane",
16+ "type": "github"
17+ }
18+ },
19+ "dream2nix": {
4 "inputs": {
20 "inputs": {
21+ "all-cabal-json": [
22+ "nci"
23+ ],
24+ "crane": "crane",
25+ "devshell": [
26+ "nci"
27+ ],
28+ "drv-parts": "drv-parts",
29+ "flake-compat": "flake-compat",
30+ "flake-parts": [
31+ "nci",
32+ "parts"
33+ ],
34+ "flake-utils-pre-commit": [
35+ "nci"
36+ ],
37+ "ghc-utils": [
38+ "nci"
39+ ],
40+ "gomod2nix": [
41+ "nci"
42+ ],
43+ "mach-nix": [
44+ "nci"
45+ ],
46+ "nix-pypi-fetcher": [
47+ "nci"
48+ ],
5 "nixpkgs": [
49 "nixpkgs": [
50+ "nci",
6 "nixpkgs"
51 "nixpkgs"
52+ ],
53+ "nixpkgsV1": "nixpkgsV1",
54+ "poetry2nix": [
55+ "nci"
56+ ],
57+ "pre-commit-hooks": [
58+ "nci"
59+ ],
60+ "pruned-racket-catalog": [
61+ "nci"
7 ]
62 ]
8 },
63 },
9 "locked": {
64 "locked": {
10− "lastModified": 1701025348,
65+ "lastModified": 1680258209,
11− "narHash": "sha256-42GHmYH+GF7VjwGSt+fVT1CQuNpGanJbNgVHTAZppUM=",
66+ "narHash": "sha256-lEo50RXI/17/a9aCIun8Hz62ZJ5JM5RGeTgclIP+Lgc=",
12− "owner": "ipetkov",
67+ "owner": "nix-community",
13− "repo": "crane",
68+ "repo": "dream2nix",
14− "rev": "42afaeb1a0325194a7cdb526332d2cb92fddd07b",
69+ "rev": "6f512b5a220fdb26bd3c659f7b55e4f052ec8b35",
15 "type": "github"
70 "type": "github"
16 },
71 },
17 "original": {
72 "original": {
18− "owner": "ipetkov",
73+ "owner": "nix-community",
19− "repo": "crane",
74+ "repo": "dream2nix",
20 "type": "github"
75 "type": "github"
21 }
76 }
22 },
77 },
23− "flake-utils": {
78+ "drv-parts": {
24 "inputs": {
79 "inputs": {
25− "systems": "systems"
80+ "flake-compat": [
81+ "nci",
82+ "dream2nix",
83+ "flake-compat"
84+ ],
85+ "flake-parts": [
86+ "nci",
87+ "dream2nix",
88+ "flake-parts"
89+ ],
90+ "nixpkgs": [
91+ "nci",
92+ "dream2nix",
93+ "nixpkgs"
94+ ]
26 },
95 },
27 "locked": {
96 "locked": {
28− "lastModified": 1694529238,
97+ "lastModified": 1680172861,
29− "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
98+ "narHash": "sha256-QMyI338xRxaHFDlCXdLCtgelGQX2PdlagZALky4ZXJ8=",
99+ "owner": "davhau",
100+ "repo": "drv-parts",
101+ "rev": "ced8a52f62b0a94244713df2225c05c85b416110",
102+ "type": "github"
103+ },
104+ "original": {
105+ "owner": "davhau",
106+ "repo": "drv-parts",
107+ "type": "github"
108+ }
109+ },
110+ "flake-compat": {
111+ "flake": false,
112+ "locked": {
113+ "lastModified": 1673956053,
114+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
115+ "owner": "edolstra",
116+ "repo": "flake-compat",
117+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
118+ "type": "github"
119+ },
120+ "original": {
121+ "owner": "edolstra",
122+ "repo": "flake-compat",
123+ "type": "github"
124+ }
125+ },
126+ "flake-utils": {
127+ "locked": {
128+ "lastModified": 1659877975,
129+ "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
30 "owner": "numtide",
130 "owner": "numtide",
31 "repo": "flake-utils",
131 "repo": "flake-utils",
32− "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
132+ "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
33 "type": "github"
133 "type": "github"
34 },
134 },
35 "original": {
135 "original": {
@@ -38,13 +138,55 @@
38 "type": "github"
138 "type": "github"
39 }
139 }
40 },
140 },
141+ "mk-naked-shell": {
142+ "flake": false,
143+ "locked": {
144+ "lastModified": 1676572903,
145+ "narHash": "sha256-oQoDHHUTxNVSURfkFcYLuAK+btjs30T4rbEUtCUyKy8=",
146+ "owner": "yusdacra",
147+ "repo": "mk-naked-shell",
148+ "rev": "aeca9f8aa592f5e8f71f407d081cb26fd30c5a57",
149+ "type": "github"
150+ },
151+ "original": {
152+ "owner": "yusdacra",
153+ "repo": "mk-naked-shell",
154+ "type": "github"
155+ }
156+ },
157+ "nci": {
158+ "inputs": {
159+ "dream2nix": "dream2nix",
160+ "mk-naked-shell": "mk-naked-shell",
161+ "nixpkgs": [
162+ "nixpkgs"
163+ ],
164+ "parts": "parts",
165+ "rust-overlay": [
166+ "rust-overlay"
167+ ]
168+ },
169+ "locked": {
170+ "lastModified": 1680329418,
171+ "narHash": "sha256-+KN0eQLSZvL1J0kDO8/fxv0UCHTyZCADLmpIfeeiSGo=",
172+ "owner": "yusdacra",
173+ "repo": "nix-cargo-integration",
174+ "rev": "98c1d2ff5155f0fee5d290f6b982cb990839d540",
175+ "type": "github"
176+ },
177+ "original": {
178+ "owner": "yusdacra",
179+ "repo": "nix-cargo-integration",
180+ "type": "github"
181+ }
182+ },
41 "nixpkgs": {
183 "nixpkgs": {
42 "locked": {
184 "locked": {
43− "lastModified": 1700794826,
185+ "lastModified": 1680213900,
44− "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=",
186+ "narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=",
45 "owner": "nixos",
187 "owner": "nixos",
46 "repo": "nixpkgs",
188 "repo": "nixpkgs",
47− "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8",
189+ "rev": "e3652e0735fbec227f342712f180f4f21f0594f2",
48 "type": "github"
190 "type": "github"
49 },
191 },
50 "original": {
192 "original": {
@@ -54,29 +196,99 @@
54 "type": "github"
196 "type": "github"
55 }
197 }
56 },
198 },
199+ "nixpkgs-lib": {
200+ "locked": {
201+ "dir": "lib",
202+ "lastModified": 1678375444,
203+ "narHash": "sha256-XIgHfGvjFvZQ8hrkfocanCDxMefc/77rXeHvYdzBMc8=",
204+ "owner": "NixOS",
205+ "repo": "nixpkgs",
206+ "rev": "130fa0baaa2b93ec45523fdcde942f6844ee9f6e",
207+ "type": "github"
208+ },
209+ "original": {
210+ "dir": "lib",
211+ "owner": "NixOS",
212+ "ref": "nixos-unstable",
213+ "repo": "nixpkgs",
214+ "type": "github"
215+ }
216+ },
217+ "nixpkgsV1": {
218+ "locked": {
219+ "lastModified": 1678500271,
220+ "narHash": "sha256-tRBLElf6f02HJGG0ZR7znMNFv/Uf7b2fFInpTHiHaSE=",
221+ "owner": "NixOS",
222+ "repo": "nixpkgs",
223+ "rev": "5eb98948b66de29f899c7fe27ae112a47964baf8",
224+ "type": "github"
225+ },
226+ "original": {
227+ "id": "nixpkgs",
228+ "ref": "nixos-22.11",
229+ "type": "indirect"
230+ }
231+ },
232+ "parts": {
233+ "inputs": {
234+ "nixpkgs-lib": [
235+ "nci",
236+ "nixpkgs"
237+ ]
238+ },
239+ "locked": {
240+ "lastModified": 1679737941,
241+ "narHash": "sha256-srSD9CwsVPnUMsIZ7Kt/UegkKUEBcTyU1Rev7mO45S0=",
242+ "owner": "hercules-ci",
243+ "repo": "flake-parts",
244+ "rev": "3502ee99d6dade045bdeaf7b0cd8ec703484c25c",
245+ "type": "github"
246+ },
247+ "original": {
248+ "owner": "hercules-ci",
249+ "repo": "flake-parts",
250+ "type": "github"
251+ }
252+ },
253+ "parts_2": {
254+ "inputs": {
255+ "nixpkgs-lib": "nixpkgs-lib"
256+ },
257+ "locked": {
258+ "lastModified": 1679737941,
259+ "narHash": "sha256-srSD9CwsVPnUMsIZ7Kt/UegkKUEBcTyU1Rev7mO45S0=",
260+ "owner": "hercules-ci",
261+ "repo": "flake-parts",
262+ "rev": "3502ee99d6dade045bdeaf7b0cd8ec703484c25c",
263+ "type": "github"
264+ },
265+ "original": {
266+ "owner": "hercules-ci",
267+ "repo": "flake-parts",
268+ "type": "github"
269+ }
270+ },
57 "root": {
271 "root": {
58 "inputs": {
272 "inputs": {
59− "crane": "crane",
273+ "nci": "nci",
60− "flake-utils": "flake-utils",
61 "nixpkgs": "nixpkgs",
274 "nixpkgs": "nixpkgs",
275+ "parts": "parts_2",
62 "rust-overlay": "rust-overlay"
276 "rust-overlay": "rust-overlay"
63 }
277 }
64 },
278 },
65 "rust-overlay": {
279 "rust-overlay": {
66 "inputs": {
280 "inputs": {
67− "flake-utils": [
281+ "flake-utils": "flake-utils",
68− "flake-utils"
69− ],
70 "nixpkgs": [
282 "nixpkgs": [
71 "nixpkgs"
283 "nixpkgs"
72 ]
284 ]
73 },
285 },
74 "locked": {
286 "locked": {
75− "lastModified": 1701137803,
287+ "lastModified": 1680315536,
76− "narHash": "sha256-0LcPAdql5IhQSUXJx3Zna0dYTgdIoYO7zUrsKgiBd04=",
288+ "narHash": "sha256-0AsBuKssJMbcRcw4HJQwJsUHhZxR5+gaf6xPQayhR44=",
77 "owner": "oxalica",
289 "owner": "oxalica",
78 "repo": "rust-overlay",
290 "repo": "rust-overlay",
79− "rev": "9dd940c967502f844eacea52a61e9596268d4f70",
291+ "rev": "5c8c151bdd639074a0051325c16df1a64ee23497",
80 "type": "github"
292 "type": "github"
81 },
293 },
82 "original": {
294 "original": {
@@ -84,21 +296,6 @@
84 "repo": "rust-overlay",
296 "repo": "rust-overlay",
85 "type": "github"
297 "type": "github"
86 }
298 }
87− },
88− "systems": {
89− "locked": {
90− "lastModified": 1681028828,
91− "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
92− "owner": "nix-systems",
93− "repo": "default",
94− "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
95− "type": "github"
96− },
97− "original": {
98− "owner": "nix-systems",
99− "repo": "default",
100− "type": "github"
101− }
102 }
299 }
103 },
300 },
104 "root": "root",
301 "root": "root",
flake.nix+140−171
@@ -3,193 +3,162 @@
3
3
4 inputs = {
4 inputs = {
5 nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
5 nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
6− flake-utils.url = "github:numtide/flake-utils";
7 rust-overlay = {
6 rust-overlay = {
8 url = "github:oxalica/rust-overlay";
7 url = "github:oxalica/rust-overlay";
9− inputs = {
8+ inputs.nixpkgs.follows = "nixpkgs";
10− nixpkgs.follows = "nixpkgs";
11− flake-utils.follows = "flake-utils";
12− };
13 };
9 };
14− crane = {
10+ nci = {
15− url = "github:ipetkov/crane";
11+ url = "github:yusdacra/nix-cargo-integration";
16 inputs.nixpkgs.follows = "nixpkgs";
12 inputs.nixpkgs.follows = "nixpkgs";
13+ inputs.rust-overlay.follows = "rust-overlay";
17 };
14 };
15+ parts.url = "github:hercules-ci/flake-parts";
18 };
16 };
19
17
20− outputs = {
18+ outputs = inp: let
21− self,
19+ mkRootPath = rel:
22− nixpkgs,
20+ builtins.path {
23− crane,
21+ path = "${toString ./.}/${rel}";
24− flake-utils,
22+ name = rel;
25− rust-overlay,
26− ...
27− }:
28− flake-utils.lib.eachDefaultSystem (system: let
29− pkgs = import nixpkgs {
30− inherit system;
31− overlays = [(import rust-overlay)];
32 };
23 };
33− mkRootPath = rel:
34− builtins.path {
35− path = "${toString ./.}/${rel}";
36− name = rel;
37− };
38− filteredSource = let
24+ filteredSource = let
39− pathsToIgnore = [
25+ pathsToIgnore = [
40− ".envrc"
26+ ".envrc"
41− ".ignore"
27+ ".ignore"
42− ".github"
28+ ".github"
43− ".gitignore"
29+ ".gitignore"
30+ "logo.svg"
44− "logo_dark.svg"
31+ "logo_dark.svg"
45− "logo_light.svg"
32+ "logo_light.svg"
46− "rust-toolchain.toml"
33+ "rust-toolchain.toml"
47− "rustfmt.toml"
34+ "rustfmt.toml"
48− "runtime"
35+ "runtime"
49− "screenshot.png"
36+ "screenshot.png"
50− "book"
37+ "book"
38+ "contrib"
51− "docs"
39+ "docs"
52− "README.md"
40+ "README.md"
53− "CHANGELOG.md"
41+ "CHANGELOG.md"
54− "shell.nix"
42+ "shell.nix"
55− "default.nix"
43+ "default.nix"
56− "grammars.nix"
44+ "grammars.nix"
57− "flake.nix"
45+ "flake.nix"
58− "flake.lock"
46+ "flake.lock"
59− ];
47+ ];
60− ignorePaths = path: type: let
48+ ignorePaths = path: type: let
61− inherit (nixpkgs) lib;
49+ inherit (inp.nixpkgs) lib;
62− # split the nix store path into its components
50+ # split the nix store path into its components
63− components = lib.splitString "/" path;
51+ components = lib.splitString "/" path;
64− # drop off the `/nix/hash-source` section from the path
52+ # drop off the `/nix/hash-source` section from the path
65− relPathComponents = lib.drop 4 components;
53+ relPathComponents = lib.drop 4 components;
66− # reassemble the path components
54+ # reassemble the path components
67− relPath = lib.concatStringsSep "/" relPathComponents;
55+ relPath = lib.concatStringsSep "/" relPathComponents;
68− in
69− lib.all (p: ! (lib.hasPrefix p relPath)) pathsToIgnore;
70 in
56 in
57+ lib.all (p: ! (lib.hasPrefix p relPath)) pathsToIgnore;
58+ in
71− builtins.path {
59+ builtins.path {
72− name = "helix-source";
60+ name = "helix-source";
73− path = toString ./.;
61+ path = toString ./.;
74− # filter out unnecessary paths
62+ # filter out unnecessary paths
75− filter = ignorePaths;
63+ filter = ignorePaths;
76− };
64+ };
65+ in
66+ inp.parts.lib.mkFlake {inputs = inp;} {
67+ imports = [inp.nci.flakeModule];
68+ systems = [
69+ "x86_64-linux"
70+ "x86_64-darwin"
71+ "aarch64-linux"
72+ "aarch64-darwin"
73+ "i686-linux"
74+ ];
75+ perSystem = {
76+ config,
77+ pkgs,
78+ lib,
79+ ...
80+ }: let
77− makeOverridableHelix = old: config: let
81+ makeOverridableHelix = old: config: let
78− grammars = pkgs.callPackage ./grammars.nix config;
82+ grammars = pkgs.callPackage ./grammars.nix config;
79− runtimeDir = pkgs.runCommand "helix-runtime" {} ''
83+ runtimeDir = pkgs.runCommand "helix-runtime" {} ''
80− mkdir -p $out
84+ mkdir -p $out
81− ln -s ${mkRootPath "runtime"}/* $out
85+ ln -s ${mkRootPath "runtime"}/* $out
82− rm -r $out/grammars
86+ rm -r $out/grammars
83− ln -s ${grammars} $out/grammars
87+ ln -s ${grammars} $out/grammars
84− '';
88+ '';
85− helix-wrapped =
89+ helix-wrapped =
86− pkgs.runCommand
90+ pkgs.runCommand
87− old.name
91+ old.name
88− {
92+ {
89− inherit (old) pname version;
93+ inherit (old) pname version;
90− meta = old.meta or {};
94+ meta = old.meta or {};
95+ passthru =
96+ (old.passthru or {})
97+ // {
98+ unwrapped = old;
99+ };
100+ nativeBuildInputs = [pkgs.makeWrapper];
101+ makeWrapperArgs = config.makeWrapperArgs or [];
102+ }
103+ ''
104+ cp -rs --no-preserve=mode,ownership ${old} $out
105+ wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
106+ '';
107+ in
108+ helix-wrapped
109+ // {
110+ override = makeOverridableHelix old;
91 passthru =
111 passthru =
92− (old.passthru or {})
112+ helix-wrapped.passthru
93 // {
113 // {
94− unwrapped = old;
114+ wrapper = old: makeOverridableHelix old config;
95 };
115 };
96− nativeBuildInputs = [pkgs.makeWrapper];
116+ };
97− makeWrapperArgs = config.makeWrapperArgs or [];
117+ stdenv =
98− }
118+ if pkgs.stdenv.isLinux
99− ''
119+ then pkgs.stdenv
100− cp -rs --no-preserve=mode,ownership ${old} $out
120+ else pkgs.clangStdenv;
101− wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
121+ rustFlagsEnv =
102− '';
122+ if stdenv.isLinux
123+ then ''$RUSTFLAGS -C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment''
124+ else "$RUSTFLAGS";
103− in
125+ in {
104− helix-wrapped
126+ nci.projects."helix-project".relPath = "";
105− // {
127+ nci.crates."helix-term" = {
106− override = makeOverridableHelix old;
128+ overrides = {
107− passthru =
129+ add-meta.override = _: {meta.mainProgram = "hx";};
108− helix-wrapped.passthru
130+ add-inputs.overrideAttrs = prev: {
109− // {
131+ buildInputs = (prev.buildInputs or []) ++ [stdenv.cc.cc.lib];
110− wrapper = old: makeOverridableHelix old config;
132+ };
133+ disable-grammar-builds = {
134+ # disable fetching and building of tree-sitter grammars in the helix-term build.rs
135+ HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
111 };
136 };
137+ disable-tests = {checkPhase = ":";};
138+ set-stdenv.override = _: {inherit stdenv;};
139+ set-filtered-src.override = _: {src = filteredSource;};
140+ };
112 };
141 };
113− stdenv =
114− if pkgs.stdenv.isLinux
115− then pkgs.stdenv
116− else pkgs.clangStdenv;
117− rustFlagsEnv =
118− if stdenv.isLinux
119− then ''$RUSTFLAGS -C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment''
120− else "$RUSTFLAGS";
121− rustToolchain = pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
122− craneLibMSRV = (crane.mkLib pkgs).overrideToolchain rustToolchain;
123− craneLibStable = (crane.mkLib pkgs).overrideToolchain pkgs.pkgsBuildHost.rust-bin.stable.latest.default;
124− commonArgs = {
125− inherit stdenv;
126− inherit (craneLibMSRV.crateNameFromCargoToml {cargoToml = ./helix-term/Cargo.toml;}) pname;
127− inherit (craneLibMSRV.crateNameFromCargoToml {cargoToml = ./Cargo.toml;}) version;
128− src = filteredSource;
129− # disable fetching and building of tree-sitter grammars in the helix-term build.rs
130− HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
131− buildInputs = [stdenv.cc.cc.lib];
132− # disable tests
133− doCheck = false;
134− meta.mainProgram = "hx";
135− };
136− cargoArtifacts = craneLibMSRV.buildDepsOnly commonArgs;
137− in {
138− packages = {
139− helix-unwrapped = craneLibStable.buildPackage (commonArgs
140− // {
141− cargoArtifacts = craneLibStable.buildDepsOnly commonArgs;
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− });
149− helix = makeOverridableHelix self.packages.${system}.helix-unwrapped {};
150− default = self.packages.${system}.helix;
151− };
152
142
153− checks = {
143+ packages.helix-unwrapped = config.nci.outputs."helix-term".packages.release;
154− # Build the crate itself
144+ packages.helix-unwrapped-dev = config.nci.outputs."helix-term".packages.dev;
155− inherit (self.packages.${system}) helix;
145+ packages.helix = makeOverridableHelix config.packages.helix-unwrapped {};
146+ packages.helix-dev = makeOverridableHelix config.packages.helix-unwrapped-dev {};
147+ packages.default = config.packages.helix;
156
148
157− clippy = craneLibMSRV.cargoClippy (commonArgs
149+ devShells.default = config.nci.outputs."helix-project".devShell.overrideAttrs (old: {
158− // {
159− inherit cargoArtifacts;
160− cargoClippyExtraArgs = "--all-targets -- --deny warnings";
161− });
162−
163− fmt = craneLibMSRV.cargoFmt commonArgs;
164−
165− doc = craneLibMSRV.cargoDoc (commonArgs
166− // {
167− inherit cargoArtifacts;
168− });
169−
170− test = craneLibMSRV.cargoTest (commonArgs
171− // {
172− inherit cargoArtifacts;
173− });
174− };
175−
176− devShells.default = pkgs.mkShell {
177− inputsFrom = builtins.attrValues self.checks.${system};
178− nativeBuildInputs = with pkgs;
150+ nativeBuildInputs =
151+ (old.nativeBuildInputs or [])
179− [lld_13 cargo-flamegraph rust-analyzer]
152+ ++ (with pkgs; [lld_13 cargo-flamegraph rust-analyzer])
180− ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin)
153+ ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin)
181− ++ (lib.optional stdenv.isLinux pkgs.lldb)
154+ ++ (lib.optional stdenv.isLinux pkgs.lldb)
182− ++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation);
155+ ++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation);
183− shellHook = ''
156+ shellHook = ''
184− export HELIX_RUNTIME="$PWD/runtime"
157+ export HELIX_RUNTIME="$PWD/runtime"
185− export RUST_BACKTRACE="1"
158+ export RUST_BACKTRACE="1"
186− export RUSTFLAGS="${rustFlagsEnv}"
159+ export RUSTFLAGS="${rustFlagsEnv}"
187− '';
160+ '';
188− };
161+ });
189− })
190− // {
191− overlays.default = final: prev: {
192− inherit (self.packages.${final.system}) helix;
193 };
162 };
194 };
163 };
195
164
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+16−20
@@ -1,38 +1,37 @@
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"] }
22−smallvec = "1.11"
21+smallvec = "1.10"
23smartstring = "1.0.1"
22smartstring = "1.0.1"
24unicode-segmentation = "1.10"
23unicode-segmentation = "1.10"
25unicode-width = "0.1"
24unicode-width = "0.1"
26unicode-general-category = "0.6"
25unicode-general-category = "0.6"
27# slab = "0.4.2"
26# slab = "0.4.2"
28slotmap = "1.0"
27slotmap = "1.0"
29−tree-sitter.workspace = true
28+tree-sitter = "0.20"
30−once_cell = "1.19"
29+once_cell = "1.17"
31arc-swap = "1"
30arc-swap = "1"
32regex = "1"
31regex = "1"
33−bitflags = "2.4"
32+bitflags = "2.2"
34−ahash = "0.8.6"
33+ahash = "0.8.3"
35−hashbrown = { version = "0.14.3", features = ["raw"] }
34+hashbrown = { version = "0.13.2", 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.1"
helix-core/src/diagnostic.rs−5
@@ -39,15 +39,10 @@ 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>,
49 pub code: Option<NumberOrString>,
45 pub code: Option<NumberOrString>,
50− pub language_server_id: usize,
51 pub tags: Vec<DiagnosticTag>,
46 pub tags: Vec<DiagnosticTag>,
52 pub source: Option<String>,
47 pub source: Option<String>,
53 pub data: Option<serde_json::Value>,
48 pub data: Option<serde_json::Value>,
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/history.rs+2−2
@@ -72,8 +72,8 @@ impl Default for History {
72 revisions: vec![Revision {
72 revisions: vec![Revision {
73 parent: 0,
73 parent: 0,
74 last_child: None,
74 last_child: None,
75− transaction: Transaction::from(ChangeSet::new("".into())),
75+ transaction: Transaction::from(ChangeSet::new(&Rope::new())),
76− inversion: Transaction::from(ChangeSet::new("".into())),
76+ inversion: Transaction::from(ChangeSet::new(&Rope::new())),
77 timestamp: Instant::now(),
77 timestamp: Instant::now(),
78 }],
78 }],
79 current: 0,
79 current: 0,
helix-core/src/indent.rs+145−617
@@ -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.
@@ -20,10 +19,6 @@ pub enum IndentStyle {
20 Spaces(u8),
19 Spaces(u8),
21}
20}
22
21
23−// 16 spaces
24−const INDENTS: &str = " ";
25−pub const MAX_INDENT: u8 = 16;
26−
27impl IndentStyle {
22impl IndentStyle {
28 /// Creates an `IndentStyle` from an indentation string.
23 /// Creates an `IndentStyle` from an indentation string.
29 ///
24 ///
@@ -32,10 +27,10 @@ impl IndentStyle {
32 #[inline]
27 #[inline]
33 pub fn from_str(indent: &str) -> Self {
28 pub fn from_str(indent: &str) -> Self {
34 // XXX: do we care about validating the input more than this? Probably not...?
29 // XXX: do we care about validating the input more than this? Probably not...?
35− debug_assert!(!indent.is_empty() && indent.len() <= MAX_INDENT as usize);
30+ debug_assert!(!indent.is_empty() && indent.len() <= 8);
36
31
37 if indent.starts_with(' ') {
32 if indent.starts_with(' ') {
38− IndentStyle::Spaces(indent.len().clamp(1, MAX_INDENT as usize) as u8)
33+ IndentStyle::Spaces(indent.len() as u8)
39 } else {
34 } else {
40 IndentStyle::Tabs
35 IndentStyle::Tabs
41 }
36 }
@@ -45,13 +40,20 @@ impl IndentStyle {
45 pub fn as_str(&self) -> &'static str {
40 pub fn as_str(&self) -> &'static str {
46 match *self {
41 match *self {
47 IndentStyle::Tabs => "\t",
42 IndentStyle::Tabs => "\t",
43+ IndentStyle::Spaces(1) => " ",
44+ IndentStyle::Spaces(2) => " ",
45+ IndentStyle::Spaces(3) => " ",
46+ IndentStyle::Spaces(4) => " ",
47+ IndentStyle::Spaces(5) => " ",
48+ IndentStyle::Spaces(6) => " ",
49+ IndentStyle::Spaces(7) => " ",
50+ IndentStyle::Spaces(8) => " ",
51+
52+ // Unsupported indentation style. This should never happen,
53+ // but just in case fall back to two spaces.
48 IndentStyle::Spaces(n) => {
54 IndentStyle::Spaces(n) => {
49− // Unsupported indentation style. This should never happen,
50− debug_assert!(n > 0 && n <= MAX_INDENT);
55+ debug_assert!(n > 0 && n <= 8); // Always triggers. `debug_panic!()` wanted.
51−
56+ " "
52− // Either way, clamp to the nearest supported value
53− let closest_n = n.clamp(1, MAX_INDENT) as usize;
54− &INDENTS[0..closest_n]
55 }
57 }
56 }
58 }
57 }
59 }
@@ -73,9 +75,9 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
73 // Build a histogram of the indentation *increases* between
75 // Build a histogram of the indentation *increases* between
74 // subsequent lines, ignoring lines that are all whitespace.
76 // subsequent lines, ignoring lines that are all whitespace.
75 //
77 //
76− // Index 0 is for tabs, the rest are 1-MAX_INDENT spaces.
78+ // Index 0 is for tabs, the rest are 1-8 spaces.
77− let histogram: [usize; MAX_INDENT as usize + 1] = {
79+ let histogram: [usize; 9] = {
78− let mut histogram = [0; MAX_INDENT as usize + 1];
80+ let mut histogram = [0; 9];
79 let mut prev_line_is_tabs = false;
81 let mut prev_line_is_tabs = false;
80 let mut prev_line_leading_count = 0usize;
82 let mut prev_line_leading_count = 0usize;
81
83
@@ -134,7 +136,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
134 histogram[0] += 1;
136 histogram[0] += 1;
135 } else {
137 } else {
136 let amount = leading_count - prev_line_leading_count;
138 let amount = leading_count - prev_line_leading_count;
137− if amount <= MAX_INDENT as usize {
139+ if amount <= 8 {
138 histogram[amount] += 1;
140 histogram[amount] += 1;
139 }
141 }
140 }
142 }
@@ -197,56 +199,6 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: us
197 len / indent_width
199 len / indent_width
198}
200}
199
201
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.
202/// 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
203/// 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> {
204fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bool> {
@@ -288,140 +240,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:
240/// This is usually constructed in one of 2 ways:
289/// - Successively add indent captures to get the (added) indent from a single line
241/// - Successively add indent captures to get the (added) indent from a single line
290/// - Successively add the indent results for each line
242/// - 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)]
243+#[derive(Default)]
295−pub struct Indentation<'a> {
244+pub struct Indentation {
245+ /// The total indent (the number of indent levels) is defined as max(0, indent-outdent).
246+ /// The string that this results in depends on the indent style (spaces or tabs, etc.)
296 indent: usize,
247 indent: usize,
297− indent_always: usize,
298 outdent: usize,
248 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}
249}
304−
305−impl<'a> Indentation<'a> {
250+impl Indentation {
306 /// Add some other [Indentation] to this.
251 /// Add some other [Indentation] to this.
307− /// The added indent should be the total added indent from one line.
252+ /// 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>) {
253+ fn add_line(&mut self, added: &Indentation) {
310− // Align overrides the indent from outer scopes.
254+ if added.indent > 0 && added.outdent == 0 {
311− if self.align.is_some() {
255+ self.indent += 1;
312− return;
256+ } else if added.outdent > 0 && added.indent == 0 {
257+ self.outdent += 1;
313 }
258 }
314− if added.align.is_some() {
315− self.align = added.align;
316− return;
317− }
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 }
259 }
323−
324 /// Add an indent capture to this indent.
260 /// 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`)
261+ /// 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>) {
262+ fn add_capture(&mut self, added: IndentCaptureType) {
329 match added {
263 match added {
330 IndentCaptureType::Indent => {
264 IndentCaptureType::Indent => {
331− if self.indent_always == 0 {
332− self.indent = 1;
265+ 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 }
266 }
341 IndentCaptureType::Outdent => {
267 IndentCaptureType::Outdent => {
342− if self.outdent_always == 0 {
343− self.outdent = 1;
268+ 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 }
269 }
355 }
270 }
356 }
271 }
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,
272+ fn as_string(&self, indent_style: &IndentStyle) -> String {
367− tab_width: usize,
273+ let indent_level = if self.indent >= self.outdent {
368− ) -> Option<String> {
274+ 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 {
275 } else {
381− // If the alignment of both lines is different, we cannot compare their indentation in any meaningful way
276+ log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
382− None
277+ 0
383− }
278+ };
384− }
279+ 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 }
280 }
394}
281}
395
282
396/// An indent definition which corresponds to a capture from the indent query
283/// An indent definition which corresponds to a capture from the indent query
397−#[derive(Debug)]
398−struct IndentCapture<'a> {
284+struct IndentCapture {
399− capture_type: IndentCaptureType<'a>,
285+ capture_type: IndentCaptureType,
400 scope: IndentScope,
286 scope: IndentScope,
401}
287}
402−#[derive(Debug, Clone, PartialEq)]
288+#[derive(Clone, Copy)]
403−enum IndentCaptureType<'a> {
289+enum IndentCaptureType {
404 Indent,
290 Indent,
405− IndentAlways,
406 Outdent,
291 Outdent,
407− OutdentAlways,
408− /// Alignment given as a string of whitespace
409− Align(RopeSlice<'a>),
410}
292}
411−
412−impl<'a> IndentCaptureType<'a> {
293+impl IndentCaptureType {
413 fn default_scope(&self) -> IndentScope {
294 fn default_scope(&self) -> IndentScope {
414 match self {
295 match self {
415− IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
296+ IndentCaptureType::Indent => IndentScope::Tail,
416− IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All,
297+ IndentCaptureType::Outdent => IndentScope::All,
417− IndentCaptureType::Align(_) => IndentScope::All,
418 }
298 }
419 }
299 }
420}
300}
421/// This defines which part of a node an [IndentCapture] applies to.
301/// This defines which part of a node an [IndentCapture] applies to.
422/// Each [IndentCaptureType] has a default scope, but the scope can be changed
302/// Each [IndentCaptureType] has a default scope, but the scope can be changed
423/// with `#set!` property declarations.
303/// with `#set!` property declarations.
424−#[derive(Debug, Clone, Copy)]
304+#[derive(Clone, Copy)]
425enum IndentScope {
305enum IndentScope {
426 /// The indent applies to the whole node
306 /// The indent applies to the whole node
427 All,
307 All,
@@ -431,7 +311,6 @@ enum IndentScope {
431
311
432/// A capture from the indent query which does not define an indent but extends
312/// 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.
313/// the range of a node. This is used before the indent is calculated.
434−#[derive(Debug)]
435enum ExtendCapture {
314enum ExtendCapture {
436 Extend,
315 Extend,
437 PreventOnce,
316 PreventOnce,
@@ -440,41 +319,24 @@ enum ExtendCapture {
440/// The result of running a tree-sitter indent query. This stores for
319/// The result of running a tree-sitter indent query. This stores for
441/// each node (identified by its ID) the relevant captures (already filtered
320/// each node (identified by its ID) the relevant captures (already filtered
442/// by predicates).
321/// by predicates).
443−#[derive(Debug)]
444−struct IndentQueryResult<'a> {
322+struct IndentQueryResult {
445− indent_captures: HashMap<usize, Vec<IndentCapture<'a>>>,
323+ indent_captures: HashMap<usize, Vec<IndentCapture>>,
446 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
324 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
447}
325}
448
326
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>(
327+fn query_indents(
467 query: &Query,
328 query: &Query,
468 syntax: &Syntax,
329 syntax: &Syntax,
469 cursor: &mut QueryCursor,
330 cursor: &mut QueryCursor,
470− text: RopeSlice<'a>,
331+ text: RopeSlice,
471 range: std::ops::Range<usize>,
332 range: std::ops::Range<usize>,
472− new_line_byte_pos: Option<usize>,
333+ // Position of the (optional) newly inserted line break.
334+ // Given as (line, byte_pos)
335+ new_line_break: Option<(usize, usize)>,
473−) -> IndentQueryResult<'a> {
336+) -> IndentQueryResult {
474 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
337 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
475 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
338 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
476 cursor.set_byte_range(range);
339 cursor.set_byte_range(range);
477−
478 // Iterate over all captures from the query
340 // Iterate over all captures from the query
479 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
341 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
480 // Skip matches where not all custom predicates are fulfilled
342 // Skip matches where not all custom predicates are fulfilled
@@ -501,13 +363,21 @@ fn query_indents<'a>(
501 Some(QueryPredicateArg::Capture(capt1)),
363 Some(QueryPredicateArg::Capture(capt1)),
502 Some(QueryPredicateArg::Capture(capt2))
364 Some(QueryPredicateArg::Capture(capt2))
503 ) => {
365 ) => {
366+ let get_line_num = |node: Node| {
367+ let mut node_line = node.start_position().row;
368+ // Adjust for the new line that will be inserted
369+ if let Some((line, byte)) = new_line_break {
370+ if node_line==line && node.start_byte()>=byte {
371+ node_line += 1;
372+ }
373+ }
374+ node_line
375+ };
504 let n1 = m.nodes_for_capture_index(*capt1).next();
376 let n1 = m.nodes_for_capture_index(*capt1).next();
505 let n2 = m.nodes_for_capture_index(*capt2).next();
377 let n2 = m.nodes_for_capture_index(*capt2).next();
506 match (n1, n2) {
378 match (n1, n2) {
507 (Some(n1), Some(n2)) => {
379 (Some(n1), Some(n2)) => {
508− let n1_line = get_node_start_line(n1, new_line_byte_pos);
380+ 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?")
381 same_line==(pred.operator.as_ref()=="same-line?")
512 }
382 }
513 _ => true,
383 _ => true,
@@ -518,23 +388,6 @@ fn query_indents<'a>(
518 }
388 }
519 }
389 }
520 }
390 }
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 _ => {
391 _ => {
539 panic!(
392 panic!(
540 "Invalid indent query: Unknown predicate (\"{}\")",
393 "Invalid indent query: Unknown predicate (\"{}\")",
@@ -545,28 +398,11 @@ fn query_indents<'a>(
545 }) {
398 }) {
546 continue;
399 continue;
547 }
400 }
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 {
401 for capture in m.captures {
554 let capture_name = query.capture_names()[capture.index as usize].as_str();
402 let capture_name = query.capture_names()[capture.index as usize].as_str();
555 let capture_type = match capture_name {
403 let capture_type = match capture_name {
556 "indent" => IndentCaptureType::Indent,
404 "indent" => IndentCaptureType::Indent,
557− "indent.always" => IndentCaptureType::IndentAlways,
558 "outdent" => IndentCaptureType::Outdent,
405 "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" => {
406 "extend" => {
571 extend_captures
407 extend_captures
572 .entry(capture.node.id())
408 .entry(capture.node.id())
@@ -616,40 +452,17 @@ fn query_indents<'a>(
616 }
452 }
617 }
453 }
618 }
454 }
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
455 indent_captures
639− .entry(node_id)
456+ .entry(capture.node.id())
457+ // Most entries only need to contain a single IndentCapture
640 .or_insert_with(|| Vec::with_capacity(1))
458 .or_insert_with(|| Vec::with_capacity(1))
641− .push(capture);
459+ .push(indent_capture);
642 }
460 }
643 }
461 }
644−
645− let result = IndentQueryResult {
462+ IndentQueryResult {
646 indent_captures,
463 indent_captures,
647 extend_captures,
464 extend_captures,
648− };
465+ }
649−
650− log::trace!("indent result = {:?}", result);
651−
652− result
653}
466}
654
467
655/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
468/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
@@ -719,27 +532,61 @@ fn extend_nodes<'a>(
719 }
532 }
720}
533}
721
534
722−/// Prepare an indent query by computing:
535+/// 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)
536+/// This can be used in 2 ways:
724−/// - The indent captures for all relevant nodes.
537+///
538+/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
539+/// - In this case, pos should be inside the first tree-sitter node on that line.
540+/// In most cases, this can just be the first non-whitespace on that line.
541+/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
542+/// after pos were moved to a new line.
543+///
544+/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
545+/// Each of these nodes produces some [Indentation] for:
546+///
547+/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
548+/// - The line after the node. This is defined by:
549+/// - The scope `tail`.
550+/// - The scope `all` if this node is not the first node on its line.
551+/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
552+/// The indents from different nodes for the same line are then combined.
553+/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
554+///
555+/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
556+/// ```ignore
557+/// some_function(|| {
558+/// // Both the function parameters as well as the contained block should be indented.
559+/// // Because they are on the same line, this only yields one indent level
560+/// });
561+/// ```
562+///
563+/// ```ignore
564+/// some_function(
565+/// param1,
566+/// || {
567+/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
568+/// },
569+/// );
570+/// ```
725#[allow(clippy::too_many_arguments)]
571#[allow(clippy::too_many_arguments)]
726−fn init_indent_query<'a, 'b>(
572+pub fn treesitter_indent_for_pos(
727 query: &Query,
573 query: &Query,
728− syntax: &'a Syntax,
574+ syntax: &Syntax,
729− text: RopeSlice<'b>,
575+ indent_style: &IndentStyle,
730 tab_width: usize,
576 tab_width: usize,
731 indent_width: usize,
577 indent_width: usize,
578+ text: RopeSlice,
732 line: usize,
579 line: usize,
733− byte_pos: usize,
580+ pos: usize,
734− new_line_byte_pos: Option<usize>,
581+ new_line: bool,
735−) -> Option<(Node<'a>, HashMap<usize, Vec<IndentCapture<'b>>>)> {
582+) -> Option<String> {
583+ let byte_pos = text.char_to_byte(pos);
736 // The innermost tree-sitter node which is considered for the indent
584 // The innermost tree-sitter node which is considered for the indent
737 // computation. It may change if some predeceding node is extended
585 // computation. It may change if some predeceding node is extended
738 let mut node = syntax
586 let mut node = syntax
739 .tree()
587 .tree()
740 .root_node()
588 .root_node()
741 .descendant_for_byte_range(byte_pos, byte_pos)?;
589 .descendant_for_byte_range(byte_pos, byte_pos)?;
742−
743 let (query_result, deepest_preceding) = {
590 let (query_result, deepest_preceding) = {
744 // The query range should intersect with all nodes directly preceding
591 // The query range should intersect with all nodes directly preceding
745 // the position of the indent query in case one of them is extended.
592 // the position of the indent query in case one of them is extended.
@@ -770,12 +617,13 @@ fn init_indent_query<'a, 'b>(
770 &mut cursor,
617 &mut cursor,
771 text,
618 text,
772 query_range,
619 query_range,
773− new_line_byte_pos,
620+ new_line.then_some((line, byte_pos)),
774 );
621 );
775 ts_parser.cursors.push(cursor);
622 ts_parser.cursors.push(cursor);
776 (query_result, deepest_preceding)
623 (query_result, deepest_preceding)
777 })
624 })
778 };
625 };
626+ let indent_captures = query_result.indent_captures;
779 let extend_captures = query_result.extend_captures;
627 let extend_captures = query_result.extend_captures;
780
628
781 // Check for extend captures, potentially changing the node that the indent calculation starts with
629 // Check for extend captures, potentially changing the node that the indent calculation starts with
@@ -790,68 +638,6 @@ fn init_indent_query<'a, 'b>(
790 indent_width,
638 indent_width,
791 );
639 );
792 }
640 }
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));
641 let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos));
856
642
857 let mut result = Indentation::default();
643 let mut result = Indentation::default();
@@ -859,16 +645,12 @@ pub fn treesitter_indent_for_pos<'a>(
859 // even if there are multiple "indent" nodes on the same line
645 // even if there are multiple "indent" nodes on the same line
860 let mut indent_for_line = Indentation::default();
646 let mut indent_for_line = Indentation::default();
861 let mut indent_for_line_below = Indentation::default();
647 let mut indent_for_line_below = Indentation::default();
862−
863 loop {
648 loop {
864 // This can safely be unwrapped because `first_in_line` contains
649 // 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)
650 // one entry for each ancestor of the node (which is what we iterate over)
866 let is_first = *first_in_line.last().unwrap();
651 let is_first = *first_in_line.last().unwrap();
867−
868− // Apply all indent definitions for this node.
652+ // 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()) {
653+ if let Some(definitions) = indent_captures.get(&node.id()) {
872 for definition in definitions {
654 for definition in definitions {
873 match definition.scope {
655 match definition.scope {
874 IndentScope::All => {
656 IndentScope::All => {
@@ -886,22 +668,28 @@ pub fn treesitter_indent_for_pos<'a>(
886 }
668 }
887
669
888 if let Some(parent) = node.parent() {
670 if let Some(parent) = node.parent() {
889− let node_line = get_node_start_line(node, new_line_byte_pos);
671+ let mut node_line = node.start_position().row;
890− let parent_line = get_node_start_line(parent, new_line_byte_pos);
672+ let mut parent_line = parent.start_position().row;
891−
673+ if node_line == line && new_line {
674+ // Also consider the line that will be inserted
675+ if node.start_byte() >= byte_pos {
676+ node_line += 1;
677+ }
678+ if parent.start_byte() >= byte_pos {
679+ parent_line += 1;
680+ }
681+ };
892 if node_line != parent_line {
682 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) {
683 if node_line < line + (new_line as usize) {
684+ // Don't add indent for the line below the line of the query
895− result.add_line(indent_for_line_below);
685+ result.add_line(&indent_for_line_below);
896 }
686 }
897−
898 if node_line == parent_line + 1 {
687 if node_line == parent_line + 1 {
899 indent_for_line_below = indent_for_line;
688 indent_for_line_below = indent_for_line;
900 } else {
689 } else {
901− result.add_line(indent_for_line);
690+ result.add_line(&indent_for_line);
902 indent_for_line_below = Indentation::default();
691 indent_for_line_below = Indentation::default();
903 }
692 }
904−
905 indent_for_line = Indentation::default();
693 indent_for_line = Indentation::default();
906 }
694 }
907
695
@@ -913,13 +701,13 @@ pub fn treesitter_indent_for_pos<'a>(
913 if (node.start_position().row < line)
701 if (node.start_position().row < line)
914 || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
702 || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
915 {
703 {
916− result.add_line(indent_for_line_below);
704+ result.add_line(&indent_for_line_below);
917 }
705 }
918− result.add_line(indent_for_line);
706+ result.add_line(&indent_for_line);
919 break;
707 break;
920 }
708 }
921 }
709 }
922− Some(result)
710+ Some(result.as_string(indent_style))
923}
711}
924
712
925/// Returns the indentation for a new line.
713/// Returns the indentation for a new line.
@@ -928,7 +716,6 @@ pub fn treesitter_indent_for_pos<'a>(
928pub fn indent_for_newline(
716pub fn indent_for_newline(
929 language_config: Option<&LanguageConfiguration>,
717 language_config: Option<&LanguageConfiguration>,
930 syntax: Option<&Syntax>,
718 syntax: Option<&Syntax>,
931− indent_heuristic: &IndentationHeuristic,
932 indent_style: &IndentStyle,
719 indent_style: &IndentStyle,
933 tab_width: usize,
720 tab_width: usize,
934 text: RopeSlice,
721 text: RopeSlice,
@@ -937,18 +724,14 @@ pub fn indent_for_newline(
937 current_line: usize,
724 current_line: usize,
938) -> String {
725) -> String {
939 let indent_width = indent_style.indent_width(tab_width);
726 let indent_width = indent_style.indent_width(tab_width);
940− if let (
941− IndentationHeuristic::TreeSitter | IndentationHeuristic::Hybrid,
942− Some(query),
943− Some(syntax),
727+ if let (Some(query), Some(syntax)) = (
944− ) = (
945− indent_heuristic,
946 language_config.and_then(|config| config.indent_query()),
728 language_config.and_then(|config| config.indent_query()),
947 syntax,
729 syntax,
948 ) {
730 ) {
949 if let Some(indent) = treesitter_indent_for_pos(
731 if let Some(indent) = treesitter_indent_for_pos(
950 query,
732 query,
951 syntax,
733 syntax,
734+ indent_style,
952 tab_width,
735 tab_width,
953 indent_width,
736 indent_width,
954 text,
737 text,
@@ -956,57 +739,9 @@ pub fn indent_for_newline(
956 line_before_end_pos,
739 line_before_end_pos,
957 true,
740 true,
958 ) {
741 ) {
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;
742+ 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 };
743 };
1008 }
744 }
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);
745 let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
1011 indent_style.as_str().repeat(indent_level)
746 indent_style.as_str().repeat(indent_level)
1012}
747}
@@ -1062,211 +797,4 @@ mod test {
1062 3
797 3
1063 );
798 );
1064 }
799 }
1065−
1066− #[test]
1067− fn test_large_indent_level() {
1068− let tab_width = 16;
1069− let indent_width = 16;
1070− let line = Rope::from(" fn new"); // 16 spaces
1071− assert_eq!(
1072− indent_level_for_line(line.slice(..), tab_width, indent_width),
1073− 1
1074− );
1075− let line = Rope::from(" fn new"); // 32 spaces
1076− assert_eq!(
1077− indent_level_for_line(line.slice(..), tab_width, indent_width),
1078− 2
1079− );
1080− }
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}
800}
helix-core/src/lib.rs+3−5
@@ -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;
@@ -41,9 +41,7 @@ pub use helix_loader::find_workspace;
41pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
41pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
42 line.chars().position(|ch| !ch.is_whitespace())
42 line.chars().position(|ch| !ch.is_whitespace())
43}
43}
44−mod rope_reader;
45
44
46−pub use rope_reader::RopeReader;
47pub use ropey::{self, str_utils, Rope, RopeBuilder, RopeSlice};
45pub use ropey::{self, str_utils, Rope, RopeBuilder, RopeSlice};
48
46
49// pub use tendril::StrTendril as Tendril;
47// pub use tendril::StrTendril as Tendril;
@@ -68,5 +66,5 @@ pub use syntax::Syntax;
68
66
69pub use diagnostic::Diagnostic;
67pub use diagnostic::Diagnostic;
70
68
71−pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
69+pub use line_ending::{LineEnding, DEFAULT_LINE_ENDING};
72−pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction};
70+pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction};
helix-core/src/line_ending.rs+2−2
@@ -1,9 +1,9 @@
1use crate::{Rope, RopeSlice};
1use crate::{Rope, RopeSlice};
2
2
3#[cfg(target_os = "windows")]
3#[cfg(target_os = "windows")]
4−pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::Crlf;
4+pub const DEFAULT_LINE_ENDING: LineEnding = LineEnding::Crlf;
5#[cfg(not(target_os = "windows"))]
5#[cfg(not(target_os = "windows"))]
6−pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::LF;
6+pub const DEFAULT_LINE_ENDING: LineEnding = LineEnding::LF;
7
7
8/// Represents one of the valid Unicode line endings.
8/// Represents one of the valid Unicode line endings.
9#[derive(PartialEq, Eq, Copy, Clone, Debug)]
9#[derive(PartialEq, Eq, Copy, Clone, Debug)]
helix-core/src/match_brackets.rs+29−232
@@ -1,15 +1,7 @@
1−use std::iter;
2−
3−use ropey::RopeSlice;
4use tree_sitter::Node;
1use tree_sitter::Node;
5
2
6−use crate::movement::Direction::{self, Backward, Forward};
3+use crate::{Rope, Syntax};
7−use crate::Syntax;
8−
9−const MAX_PLAINTEXT_SCAN: usize = 10000;
10−const MATCH_LIMIT: usize = 16;
11
4
12−// Limit matching pairs to only ( ) { } [ ] < > ' ' " "
13const PAIRS: &[(char, char)] = &[
5const PAIRS: &[(char, char)] = &[
14 ('(', ')'),
6 ('(', ')'),
15 ('{', '}'),
7 ('{', '}'),
@@ -19,17 +11,17 @@ const PAIRS: &[(char, char)] = &[
19 ('\"', '\"'),
11 ('\"', '\"'),
20];
12];
21
13
14+// limit matching pairs to only ( ) { } [ ] < > ' ' " "
15+
22−/// Returns the position of the matching bracket under cursor.
16+// Returns the position of the matching bracket under cursor.
23−///
17+//
24−/// If the cursor is on the opening bracket, the position of
18+// If the cursor is one the opening bracket, the position of
25−/// the closing bracket is returned. If the cursor on the closing
19+// the closing bracket is returned. If the cursor in the closing
26−/// bracket, the position of the opening bracket is returned.
20+// bracket, the position of the opening bracket is returned.
27−///
21+//
28−/// If the cursor is not on a bracket, `None` is returned.
22+// If the cursor is not on a bracket, `None` is returned.
29−///
30−/// If no matching bracket is found, `None` is returned.
31#[must_use]
23#[must_use]
32−pub fn find_matching_bracket(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
24+pub fn find_matching_bracket(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
33 if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) {
25 if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) {
34 return None;
26 return None;
35 }
27 }
@@ -47,159 +39,46 @@ pub fn find_matching_bracket(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Opt
47//
39//
48// If no surrounding scope is found, the function returns `None`.
40// If no surrounding scope is found, the function returns `None`.
49#[must_use]
41#[must_use]
50−pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
42+pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
51 find_pair(syntax, doc, pos, true)
43 find_pair(syntax, doc, pos, true)
52}
44}
53
45
54−fn find_pair(
55− syntax: &Syntax,
56− doc: RopeSlice,
57− pos_: usize,
58− traverse_parents: bool,
46+fn find_pair(syntax: &Syntax, doc: &Rope, pos: usize, traverse_parents: bool) -> Option<usize> {
59−) -> Option<usize> {
60 let tree = syntax.tree();
47 let tree = syntax.tree();
61− let pos = doc.char_to_byte(pos_);
48+ let pos = doc.char_to_byte(pos);
62
49
63− let mut node = tree.root_node().descendant_for_byte_range(pos, pos)?;
50+ let mut node = tree.root_node().named_descendant_for_byte_range(pos, pos)?;
64
51
65 loop {
52 loop {
66− if node.is_named() {
67− let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
53+ let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
68− let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
54+ let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
69−
70− if is_valid_pair(doc, start_char, end_char) {
71− if end_byte == pos {
72− return Some(start_char);
73− }
74
55
75− // We return the end char if the cursor is either on the start char
56+ if is_valid_pair(doc, start_char, end_char) {
76− // or at some arbitrary position between start and end char.
57+ if end_byte == pos {
77− if traverse_parents || start_byte == pos {
78− return Some(end_char);
58+ return Some(start_char);
79− }
80 }
59 }
60+ // We return the end char if the cursor is either on the start char
61+ // or at some arbitrary position between start and end char.
62+ return Some(end_char);
81 }
63 }
82− // this node itselt wasn't a pair but maybe its siblings are
83
64
84− // check if we are *on* the pair (special cased so we don't look
65+ if traverse_parents {
85− // at the current node twice and to jump to the start on that case)
66+ node = node.parent()?;
86− if let Some(open) = as_close_pair(doc, &node) {
67+ } else {
87− if let Some(pair_start) = find_pair_end(doc, node.prev_sibling(), open, Backward) {
68+ return None;
88− return Some(pair_start);
89− }
90 }
69 }
91−
92− if !traverse_parents {
93− // check if we are *on* the opening pair (special cased here as
94− // an opptimization since we only care about bracket on the cursor
95− // here)
96− if let Some(close) = as_open_pair(doc, &node) {
97− if let Some(pair_end) = find_pair_end(doc, node.next_sibling(), close, Forward) {
98− return Some(pair_end);
99− }
100− }
101− if node.is_named() {
102− break;
103− }
104− }
105−
106− for close in
107− iter::successors(node.next_sibling(), |node| node.next_sibling()).take(MATCH_LIMIT)
108− {
109− let Some(open) = as_close_pair(doc, &close) else {
110− continue;
111− };
112− if find_pair_end(doc, Some(node), open, Backward).is_some() {
113− return doc.try_byte_to_char(close.start_byte()).ok();
114− }
115− }
116− let Some(parent) = node.parent() else {
117− break;
118− };
119− node = parent;
120− }
121− let node = tree.root_node().named_descendant_for_byte_range(pos, pos)?;
122− if node.child_count() != 0 {
123− return None;
124 }
70 }
125− let node_start = doc.byte_to_char(node.start_byte());
126− find_matching_bracket_plaintext(doc.byte_slice(node.byte_range()), pos_ - node_start)
127− .map(|pos| pos + node_start)
128−}
129−
130−/// Returns the position of the matching bracket under cursor.
131−/// This function works on plain text and ignores tree-sitter grammar.
132−/// The search is limited to `MAX_PLAINTEXT_SCAN` characters
133−///
134−/// If the cursor is on the opening bracket, the position of
135−/// the closing bracket is returned. If the cursor on the closing
136−/// bracket, the position of the opening bracket is returned.
137−///
138−/// If the cursor is not on a bracket, `None` is returned.
139−///
140−/// If no matching bracket is found, `None` is returned.
141−#[must_use]
142−pub fn find_matching_bracket_plaintext(doc: RopeSlice, cursor_pos: usize) -> Option<usize> {
143− // Don't do anything when the cursor is not on top of a bracket.
144− let bracket = doc.char(cursor_pos);
145− if !is_valid_bracket(bracket) {
146− return None;
147− }
148−
149− // Determine the direction of the matching.
150− let is_fwd = is_forward_bracket(bracket);
151− let chars_iter = if is_fwd {
152− doc.chars_at(cursor_pos + 1)
153− } else {
154− doc.chars_at(cursor_pos).reversed()
155− };
156−
157− let mut open_cnt = 1;
158−
159− for (i, candidate) in chars_iter.take(MAX_PLAINTEXT_SCAN).enumerate() {
160− if candidate == bracket {
161− open_cnt += 1;
162− } else if is_valid_pair(
163− doc,
164− if is_fwd {
165− cursor_pos
166− } else {
167− cursor_pos - i - 1
168− },
169− if is_fwd {
170− cursor_pos + i + 1
171− } else {
172− cursor_pos
173− },
174− ) {
175− // Return when all pending brackets have been closed.
176− if open_cnt == 1 {
177− return Some(if is_fwd {
178− cursor_pos + i + 1
179− } else {
180− cursor_pos - i - 1
181− });
182− }
183− open_cnt -= 1;
184− }
185− }
186−
187− None
188}
71}
189
72
190fn is_valid_bracket(c: char) -> bool {
73fn is_valid_bracket(c: char) -> bool {
191 PAIRS.iter().any(|(l, r)| *l == c || *r == c)
74 PAIRS.iter().any(|(l, r)| *l == c || *r == c)
192}
75}
193
76
194−fn is_forward_bracket(c: char) -> bool {
195− PAIRS.iter().any(|(l, _)| *l == c)
196−}
197−
198−fn is_valid_pair(doc: RopeSlice, start_char: usize, end_char: usize) -> bool {
77+fn is_valid_pair(doc: &Rope, start_char: usize, end_char: usize) -> bool {
199 PAIRS.contains(&(doc.char(start_char), doc.char(end_char)))
78 PAIRS.contains(&(doc.char(start_char), doc.char(end_char)))
200}
79}
201
80
202−fn surrounding_bytes(doc: RopeSlice, node: &Node) -> Option<(usize, usize)> {
81+fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> {
203 let len = doc.len_bytes();
82 let len = doc.len_bytes();
204
83
205 let start_byte = node.start_byte();
84 let start_byte = node.start_byte();
@@ -211,85 +90,3 @@ fn surrounding_bytes(doc: RopeSlice, node: &Node) -> Option<(usize, usize)> {
211
90
212 Some((start_byte, end_byte))
91 Some((start_byte, end_byte))
213}
92}
214−
215−/// Tests if this node is a pair close char and returns the expected open char
216−fn as_close_pair(doc: RopeSlice, node: &Node) -> Option<char> {
217− let close = as_char(doc, node)?.1;
218− PAIRS
219− .iter()
220− .find_map(|&(open, close_)| (close_ == close).then_some(open))
221−}
222−
223−/// Checks if `node` or its siblings (at most MATCH_LIMIT nodes) is the specified closing char
224−///
225−/// # Returns
226−///
227−/// The position of the found node or `None` otherwise
228−fn find_pair_end(
229− doc: RopeSlice,
230− node: Option<Node>,
231− end_char: char,
232− direction: Direction,
233−) -> Option<usize> {
234− let advance = match direction {
235− Forward => Node::next_sibling,
236− Backward => Node::prev_sibling,
237− };
238− iter::successors(node, advance)
239− .take(MATCH_LIMIT)
240− .find_map(|node| {
241− let (pos, c) = as_char(doc, &node)?;
242− (end_char == c).then_some(pos)
243− })
244−}
245−
246−/// Tests if this node is a pair close char and returns the expected open char
247−fn as_open_pair(doc: RopeSlice, node: &Node) -> Option<char> {
248− let open = as_char(doc, node)?.1;
249− PAIRS
250− .iter()
251− .find_map(|&(open_, close)| (open_ == open).then_some(close))
252−}
253−
254−/// If node is a single char return it (and its char position)
255−fn as_char(doc: RopeSlice, node: &Node) -> Option<(usize, char)> {
256− // TODO: multi char/non ASCII pairs
257− if node.byte_range().len() != 1 {
258− return None;
259− }
260− let pos = doc.try_byte_to_char(node.start_byte()).ok()?;
261− Some((pos, doc.char(pos)))
262−}
263−
264−#[cfg(test)]
265−mod tests {
266− use super::*;
267−
268− #[test]
269− fn test_find_matching_bracket_current_line_plaintext() {
270− let assert = |input: &str, pos, expected| {
271− let input = RopeSlice::from(input);
272− let actual = find_matching_bracket_plaintext(input, pos);
273− assert_eq!(expected, actual.unwrap());
274−
275− let actual = find_matching_bracket_plaintext(input, expected);
276− assert_eq!(pos, actual.unwrap(), "expected symmetrical behaviour");
277− };
278−
279− assert("(hello)", 0, 6);
280− assert("((hello))", 0, 8);
281− assert("((hello))", 1, 7);
282− assert("(((hello)))", 2, 8);
283−
284− assert("key: ${value}", 6, 12);
285− assert("key: ${value} # (some comment)", 16, 29);
286−
287− assert("(paren (paren {bracket}))", 0, 24);
288− assert("(paren (paren {bracket}))", 7, 23);
289− assert("(paren (paren {bracket}))", 14, 22);
290−
291− assert("(prev line\n ) (middle) ( \n next line)", 0, 12);
292− assert("(prev line\n ) (middle) ( \n next line)", 14, 21);
293− assert("(prev line\n ) (middle) ( \n next line)", 23, 36);
294− }
295−}
helix-core/src/movement.rs+7−187
@@ -1,4 +1,4 @@
1−use std::{cmp::Reverse, iter};
1+use std::iter;
2
2
3use ropey::iter::Chars;
3use ropey::iter::Chars;
4use tree_sitter::{Node, QueryCursor};
4use tree_sitter::{Node, QueryCursor};
@@ -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)]
@@ -177,10 +177,6 @@ pub fn move_prev_word_start(slice: RopeSlice, range: Range, count: usize) -> Ran
177 word_move(slice, range, count, WordMotionTarget::PrevWordStart)
177 word_move(slice, range, count, WordMotionTarget::PrevWordStart)
178}
178}
179
179
180−pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
181− word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
182−}
183−
184pub fn move_next_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
180pub fn move_next_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
185 word_move(slice, range, count, WordMotionTarget::NextLongWordStart)
181 word_move(slice, range, count, WordMotionTarget::NextLongWordStart)
186}
182}
@@ -193,8 +189,8 @@ pub fn move_prev_long_word_start(slice: RopeSlice, range: Range, count: usize) -
193 word_move(slice, range, count, WordMotionTarget::PrevLongWordStart)
189 word_move(slice, range, count, WordMotionTarget::PrevLongWordStart)
194}
190}
195
191
196−pub fn move_prev_long_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
192+pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
197− word_move(slice, range, count, WordMotionTarget::PrevLongWordEnd)
193+ word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
198}
194}
199
195
200fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
196fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
@@ -203,7 +199,6 @@ fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTar
203 WordMotionTarget::PrevWordStart
199 WordMotionTarget::PrevWordStart
204 | WordMotionTarget::PrevLongWordStart
200 | WordMotionTarget::PrevLongWordStart
205 | WordMotionTarget::PrevWordEnd
201 | WordMotionTarget::PrevWordEnd
206− | WordMotionTarget::PrevLongWordEnd
207 );
202 );
208
203
209 // Special-case early-out.
204 // Special-case early-out.
@@ -382,7 +377,6 @@ pub enum WordMotionTarget {
382 NextLongWordStart,
377 NextLongWordStart,
383 NextLongWordEnd,
378 NextLongWordEnd,
384 PrevLongWordStart,
379 PrevLongWordStart,
385− PrevLongWordEnd,
386}
380}
387
381
388pub trait CharHelpers {
382pub trait CharHelpers {
@@ -399,7 +393,6 @@ impl CharHelpers for Chars<'_> {
399 WordMotionTarget::PrevWordStart
393 WordMotionTarget::PrevWordStart
400 | WordMotionTarget::PrevLongWordStart
394 | WordMotionTarget::PrevLongWordStart
401 | WordMotionTarget::PrevWordEnd
395 | WordMotionTarget::PrevWordEnd
402− | WordMotionTarget::PrevLongWordEnd
403 );
396 );
404
397
405 // Reverse the iterator if needed for the motion direction.
398 // Reverse the iterator if needed for the motion direction.
@@ -486,7 +479,7 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
486 is_word_boundary(prev_ch, next_ch)
479 is_word_boundary(prev_ch, next_ch)
487 && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
480 && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
488 }
481 }
489− WordMotionTarget::NextLongWordStart | WordMotionTarget::PrevLongWordEnd => {
482+ WordMotionTarget::NextLongWordStart => {
490 is_long_word_boundary(prev_ch, next_ch)
483 is_long_word_boundary(prev_ch, next_ch)
491 && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
484 && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
492 }
485 }
@@ -527,10 +520,10 @@ pub fn goto_treesitter_object(
527 let node = match dir {
520 let node = match dir {
528 Direction::Forward => nodes
521 Direction::Forward => nodes
529 .filter(|n| n.start_byte() > byte_pos)
522 .filter(|n| n.start_byte() > byte_pos)
530− .min_by_key(|n| (n.start_byte(), Reverse(n.end_byte())))?,
523+ .min_by_key(|n| n.start_byte())?,
531 Direction::Backward => nodes
524 Direction::Backward => nodes
532 .filter(|n| n.end_byte() < byte_pos)
525 .filter(|n| n.end_byte() < byte_pos)
533− .max_by_key(|n| (n.end_byte(), Reverse(n.start_byte())))?,
526+ .max_by_key(|n| n.end_byte())?,
534 };
527 };
535
528
536 let len = slice.len_bytes();
529 let len = slice.len_bytes();
@@ -556,85 +549,6 @@ pub fn goto_treesitter_object(
556 last_range
549 last_range
557}
550}
558
551
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)]
552#[cfg(test)]
639mod test {
553mod test {
640 use ropey::Rope;
554 use ropey::Rope;
@@ -1531,100 +1445,6 @@ mod test {
1531 }
1445 }
1532 }
1446 }
1533
1447
1534− #[test]
1535− fn test_behaviour_when_moving_to_end_of_prev_long_words() {
1536− let tests = [
1537− (
1538− "Basic backward motion from the middle of a word",
1539− vec![(1, Range::new(3, 3), Range::new(4, 0))],
1540− ),
1541− ("Starting from after boundary retreats the anchor",
1542− vec![(1, Range::new(0, 9), Range::new(8, 0))],
1543− ),
1544− (
1545− "Jump to end of a word succeeded by whitespace",
1546− vec![(1, Range::new(10, 10), Range::new(10, 4))],
1547− ),
1548− (
1549− " Jump to start of line from end of word preceded by whitespace",
1550− vec![(1, Range::new(3, 4), Range::new(4, 0))],
1551− ),
1552− ("Previous anchor is irrelevant for backward motions",
1553− vec![(1, Range::new(12, 5), Range::new(6, 0))]),
1554− (
1555− " Starting from whitespace moves to first space in sequence",
1556− vec![(1, Range::new(0, 4), Range::new(4, 0))],
1557− ),
1558− ("Identifiers_with_underscores are considered a single word",
1559− vec![(1, Range::new(0, 20), Range::new(20, 0))]),
1560− (
1561− "Jumping\n \nback through a newline selects whitespace",
1562− vec![(1, Range::new(0, 13), Range::new(12, 8))],
1563− ),
1564− (
1565− "Jumping to start of word from the end selects the word",
1566− vec![(1, Range::new(6, 7), Range::new(7, 0))],
1567− ),
1568− (
1569− "alphanumeric.!,and.?=punctuation are treated exactly the same",
1570− vec![(1, Range::new(29, 30), Range::new(30, 0))],
1571− ),
1572− (
1573− "... ... punctuation and spaces behave as expected",
1574− vec![
1575− (1, Range::new(0, 10), Range::new(9, 3)),
1576− (1, Range::new(10, 6), Range::new(7, 3)),
1577− ],
1578− ),
1579− (".._.._ punctuation is joined by underscores into a single block",
1580− vec![(1, Range::new(0, 6), Range::new(6, 0))]),
1581− (
1582− "Newlines\n\nare bridged seamlessly.",
1583− vec![(1, Range::new(0, 10), Range::new(8, 0))],
1584− ),
1585− (
1586− "Jumping \n\n\n\n\nback from within a newline group selects previous block",
1587− vec![(1, Range::new(0, 13), Range::new(11, 7))],
1588− ),
1589− (
1590− "Failed motions do not modify the range",
1591− vec![(0, Range::new(3, 0), Range::new(3, 0))],
1592− ),
1593− (
1594− "Multiple motions at once resolve correctly",
1595− vec![(3, Range::new(19, 19), Range::new(8, 0))],
1596− ),
1597− (
1598− "Excessive motions are performed partially",
1599− vec![(999, Range::new(40, 40), Range::new(9, 0))],
1600− ),
1601− (
1602− "", // Edge case of moving backwards in empty string
1603− vec![(1, Range::new(0, 0), Range::new(0, 0))],
1604− ),
1605− (
1606− "\n\n\n\n\n", // Edge case of moving backwards in all newlines
1607− vec![(1, Range::new(5, 5), Range::new(0, 0))],
1608− ),
1609− (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
1610− vec![
1611− (1, Range::new(0, 8), Range::new(7, 4)),
1612− (1, Range::new(7, 4), Range::new(3, 0)),
1613− ]),
1614− ("ヒーリ..クス multibyte characters behave as normal characters, including when interacting with punctuation",
1615− vec![
1616− (1, Range::new(0, 8), Range::new(7, 0)),
1617− ]),
1618− ];
1619−
1620− for (sample, scenario) in tests {
1621− for (count, begin, expected_end) in scenario.into_iter() {
1622− let range = move_prev_long_word_end(Rope::from(sample).slice(..), begin, count);
1623− assert_eq!(range, expected_end, "Case failed: [{}]", sample);
1624− }
1625− }
1626− }
1627−
1628 #[test]
1448 #[test]
1629 fn test_behaviour_when_moving_to_prev_paragraph_single() {
1449 fn test_behaviour_when_moving_to_prev_paragraph_single() {
1630 let tests = [
1450 let tests = [
helix-core/src/path.rs+7−5
@@ -85,21 +85,23 @@ 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+ std::env::current_dir().map(|current_dir| current_dir.join(path))?
92 } else {
92 } else {
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 {
100 let path = PathBuf::from(path);
100 let path = PathBuf::from(path);
101 let path = if path.is_absolute() {
101 let path = if path.is_absolute() {
102+ let cwdir = std::env::current_dir()
102− let cwdir = get_normalized_path(&helix_loader::current_working_dir());
103+ .map(|path| get_normalized_path(&path))
104+ .expect("couldn't determine current directory");
103 get_normalized_path(&path)
105 get_normalized_path(&path)
104 .strip_prefix(cwdir)
106 .strip_prefix(cwdir)
105 .map(PathBuf::from)
107 .map(PathBuf::from)
@@ -140,7 +142,7 @@ pub fn get_relative_path(path: &Path) -> PathBuf {
140/// ```
142/// ```
141///
143///
142pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
144pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
143− let cwd = helix_loader::current_working_dir();
145+ let cwd = std::env::current_dir().unwrap_or_default();
144 let path = path
146 let path = path
145 .as_ref()
147 .as_ref()
146 .strip_prefix(cwd)
148 .strip_prefix(cwd)
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/rope_reader.rsdeleted−37
@@ -1,37 +0,0 @@
1−use std::io;
2−
3−use ropey::iter::Chunks;
4−use ropey::RopeSlice;
5−
6−pub struct RopeReader<'a> {
7− current_chunk: &'a [u8],
8− chunks: Chunks<'a>,
9−}
10−
11−impl<'a> RopeReader<'a> {
12− pub fn new(rope: RopeSlice<'a>) -> RopeReader<'a> {
13− RopeReader {
14− current_chunk: &[],
15− chunks: rope.chunks(),
16− }
17− }
18−}
19−
20−impl io::Read for RopeReader<'_> {
21− fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
22− let buf_len = buf.len();
23− loop {
24− let read_bytes = self.current_chunk.read(buf)?;
25− buf = &mut buf[read_bytes..];
26− if buf.is_empty() {
27− return Ok(buf_len);
28− }
29−
30− if let Some(next_chunk) = self.chunks.next() {
31− self.current_chunk = next_chunk.as_bytes();
32− } else {
33− return Ok(buf_len - buf.len());
34− }
35− }
36− }
37−}
helix-core/src/selection.rs+43−76
@@ -161,35 +161,34 @@ impl Range {
161 self.from() <= pos && pos < self.to()
161 self.from() <= pos && pos < self.to()
162 }
162 }
163
163
164− /// Map a range through a set of changes. Returns a new range representing
164+ /// Map a range through a set of changes. Returns a new range representing the same position
165− /// the same position after the changes are applied. Note that this
165+ /// after the changes are applied.
166− /// function runs in O(N) (N is number of changes) and can therefore
167− /// cause performance problems if run for a large number of ranges as the
168− /// complexity is then O(MN) (for multicuror M=N usually). Instead use
169− /// [Selection::map] or [ChangeSet::update_positions] instead
170− pub fn map(mut self, changes: &ChangeSet) -> Self {
166+ pub fn map(self, changes: &ChangeSet) -> Self {
171 use std::cmp::Ordering;
167 use std::cmp::Ordering;
172− if changes.is_empty() {
173− return self;
174− }
175−
176− let positions_to_map = match self.anchor.cmp(&self.head) {
168+ let (anchor, head) = match self.anchor.cmp(&self.head) {
177− Ordering::Equal => [
169+ Ordering::Equal => (
178− (&mut self.anchor, Assoc::After),
170+ changes.map_pos(self.anchor, Assoc::After),
179− (&mut self.head, Assoc::After),
171+ changes.map_pos(self.head, Assoc::After),
180− ],
172+ ),
181− Ordering::Less => [
173+ Ordering::Less => (
182− (&mut self.anchor, Assoc::After),
174+ changes.map_pos(self.anchor, Assoc::After),
183− (&mut self.head, Assoc::Before),
175+ changes.map_pos(self.head, Assoc::Before),
184− ],
176+ ),
185− Ordering::Greater => [
177+ Ordering::Greater => (
186− (&mut self.head, Assoc::After),
178+ changes.map_pos(self.anchor, Assoc::Before),
187− (&mut self.anchor, Assoc::Before),
179+ changes.map_pos(self.head, Assoc::After),
188− ],
180+ ),
189 };
181 };
190− changes.update_positions(positions_to_map.into_iter());
182+
183+ // We want to return a new `Range` with `horiz == None` every time,
184+ // even if the anchor and head haven't changed, because we don't
185+ // know if the *visual* position hasn't changed due to
186+ // character-width or grapheme changes earlier in the text.
187+ Self {
188+ anchor,
189+ head,
191− self.old_visual_position = None;
190+ old_visual_position: None,
192− self
191+ }
193 }
192 }
194
193
195 /// Extend the range to cover at least `from` `to`.
194 /// Extend the range to cover at least `from` `to`.
@@ -452,36 +451,17 @@ impl Selection {
452 /// Map selections over a set of changes. Useful for adjusting the selection position after
451 /// Map selections over a set of changes. Useful for adjusting the selection position after
453 /// applying changes to a document.
452 /// applying changes to a document.
454 pub fn map(self, changes: &ChangeSet) -> Self {
453 pub fn map(self, changes: &ChangeSet) -> Self {
455− self.map_no_normalize(changes).normalize()
456− }
457−
458− /// Map selections over a set of changes. Useful for adjusting the selection position after
459− /// applying changes to a document. Doesn't normalize the selection
460− pub fn map_no_normalize(mut self, changes: &ChangeSet) -> Self {
461 if changes.is_empty() {
454 if changes.is_empty() {
462 return self;
455 return self;
463 }
456 }
464
457
465− let positions_to_map = self.ranges.iter_mut().flat_map(|range| {
458+ Self::new(
466− use std::cmp::Ordering;
459+ self.ranges
467− range.old_visual_position = None;
460+ .into_iter()
468− match range.anchor.cmp(&range.head) {
461+ .map(|range| range.map(changes))
469− Ordering::Equal => [
462+ .collect(),
470− (&mut range.anchor, Assoc::After),
463+ self.primary_index,
471− (&mut range.head, Assoc::After),
464+ )
472− ],
473− Ordering::Less => [
474− (&mut range.anchor, Assoc::After),
475− (&mut range.head, Assoc::Before),
476− ],
477− Ordering::Greater => [
478− (&mut range.head, Assoc::After),
479− (&mut range.anchor, Assoc::Before),
480− ],
481− }
482− });
483− changes.update_positions(positions_to_map);
484− self
485 }
465 }
486
466
487 pub fn ranges(&self) -> &[Range] {
467 pub fn ranges(&self) -> &[Range] {
@@ -517,9 +497,6 @@ impl Selection {
517
497
518 /// Normalizes a `Selection`.
498 /// Normalizes a `Selection`.
519 fn normalize(mut self) -> Self {
499 fn normalize(mut self) -> Self {
520− if self.len() < 2 {
521− return self;
522− }
523 let mut primary = self.ranges[self.primary_index];
500 let mut primary = self.ranges[self.primary_index];
524 self.ranges.sort_unstable_by_key(Range::from);
501 self.ranges.sort_unstable_by_key(Range::from);
525
502
@@ -545,14 +522,7 @@ impl Selection {
545 self
522 self
546 }
523 }
547
524
548− /// Replaces ranges with one spanning from first to last range.
549− pub fn merge_ranges(self) -> Self {
550− let first = self.ranges.first().unwrap();
551− let last = self.ranges.last().unwrap();
552− Selection::new(smallvec![first.merge(*last)], 0)
553− }
554−
555− /// Merges all ranges that are consecutive.
525+ // Merges all ranges that are consecutive
556 pub fn merge_consecutive_ranges(mut self) -> Self {
526 pub fn merge_consecutive_ranges(mut self) -> Self {
557 let mut primary = self.ranges[self.primary_index];
527 let mut primary = self.ranges[self.primary_index];
558
528
@@ -584,12 +554,17 @@ impl Selection {
584 assert!(!ranges.is_empty());
554 assert!(!ranges.is_empty());
585 debug_assert!(primary_index < ranges.len());
555 debug_assert!(primary_index < ranges.len());
586
556
587− let selection = Self {
557+ let mut selection = Self {
588 ranges,
558 ranges,
589 primary_index,
559 primary_index,
590 };
560 };
591
561
592− selection.normalize()
562+ if selection.ranges.len() > 1 {
563+ // TODO: only normalize if needed (any ranges out of order)
564+ selection = selection.normalize();
565+ }
566+
567+ selection
593 }
568 }
594
569
595 /// Takes a closure and maps each `Range` over the closure.
570 /// Takes a closure and maps each `Range` over the closure.
@@ -630,19 +605,11 @@ impl Selection {
630 self.transform(|range| Range::point(range.cursor(text)))
605 self.transform(|range| Range::point(range.cursor(text)))
631 }
606 }
632
607
633− pub fn fragments<'a>(
608+ 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))
609 self.ranges.iter().map(move |range| range.fragment(text))
639 }
610 }
640
611
641− pub fn slices<'a>(
612+ 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))
613 self.ranges.iter().map(move |range| range.slice(text))
647 }
614 }
648
615
helix-core/src/surround.rs+6−1
@@ -397,10 +397,15 @@ mod test {
397
397
398 let selections: SmallVec<[Range; 1]> = spec
398 let selections: SmallVec<[Range; 1]> = spec
399 .match_indices('^')
399 .match_indices('^')
400+ .into_iter()
400 .map(|(i, _)| Range::point(i))
401 .map(|(i, _)| Range::point(i))
401 .collect();
402 .collect();
402
403
403− let expectations: Vec<usize> = spec.match_indices('_').map(|(i, _)| i).collect();
404+ let expectations: Vec<usize> = spec
405+ .match_indices('_')
406+ .into_iter()
407+ .map(|(i, _)| i)
408+ .collect();
404
409
405 (rope, Selection::new(selections, 0), expectations)
410 (rope, Selection::new(selections, 0), expectations)
406 }
411 }
helix-core/src/syntax.rs+96−282
@@ -4,7 +4,7 @@ use crate::{
4 diagnostic::Severity,
4 diagnostic::Severity,
5 regex::Regex,
5 regex::Regex,
6 transaction::{ChangeSet, Operation},
6 transaction::{ChangeSet, Operation},
7− RopeSlice, Tendril,
7+ Rope, RopeSlice, Tendril,
8};
8};
9
9
10use ahash::RandomState;
10use ahash::RandomState;
@@ -16,8 +16,8 @@ use slotmap::{DefaultKey as LayerId, HopSlotMap};
16use std::{
16use std::{
17 borrow::Cow,
17 borrow::Cow,
18 cell::RefCell,
18 cell::RefCell,
19− collections::{HashMap, HashSet, VecDeque},
19+ collections::{HashMap, VecDeque},
20− fmt::{self, Display},
20+ fmt,
21 hash::{Hash, Hasher},
21 hash::{Hash, Hasher},
22 mem::{replace, transmute},
22 mem::{replace, transmute},
23 path::{Path, PathBuf},
23 path::{Path, PathBuf},
@@ -26,7 +26,7 @@ use std::{
26};
26};
27
27
28use once_cell::sync::{Lazy, OnceCell};
28use once_cell::sync::{Lazy, OnceCell};
29−use serde::{ser::SerializeSeq, Deserialize, Serialize};
29+use serde::{Deserialize, Serialize};
30
30
31use helix_loader::grammar::{get_language, load_runtime_file};
31use helix_loader::grammar::{get_language, load_runtime_file};
32
32
@@ -48,21 +48,6 @@ where
48 .transpose()
48 .transpose()
49}
49}
50
50
51−fn deserialize_tab_width<'de, D>(deserializer: D) -> Result<usize, D::Error>
52−where
53− D: serde::Deserializer<'de>,
54−{
55− usize::deserialize(deserializer).and_then(|n| {
56− if n > 0 && n <= 16 {
57− Ok(n)
58− } else {
59− Err(serde::de::Error::custom(
60− "tab width must be a value from 1 to 16 inclusive",
61− ))
62− }
63− })
64−}
65−
66pub fn deserialize_auto_pairs<'de, D>(deserializer: D) -> Result<Option<AutoPairs>, D::Error>
51pub fn deserialize_auto_pairs<'de, D>(deserializer: D) -> Result<Option<AutoPairs>, D::Error>
67where
52where
68 D: serde::Deserializer<'de>,
53 D: serde::Deserializer<'de>,
@@ -75,11 +60,8 @@ fn default_timeout() -> u64 {
75}
60}
76
61
77#[derive(Debug, Serialize, Deserialize)]
62#[derive(Debug, Serialize, Deserialize)]
78−#[serde(rename_all = "kebab-case")]
79pub struct Configuration {
63pub struct Configuration {
80 pub language: Vec<LanguageConfiguration>,
64 pub language: Vec<LanguageConfiguration>,
81− #[serde(default)]
82− pub language_server: HashMap<String, LanguageServerConfiguration>,
83}
65}
84
66
85impl Default for Configuration {
67impl Default for Configuration {
@@ -93,20 +75,19 @@ impl Default for Configuration {
93#[serde(rename_all = "kebab-case", deny_unknown_fields)]
75#[serde(rename_all = "kebab-case", deny_unknown_fields)]
94pub struct LanguageConfiguration {
76pub struct LanguageConfiguration {
95 #[serde(rename = "name")]
77 #[serde(rename = "name")]
96− pub language_id: String, // c-sharp, rust, tsx
78+ pub language_id: String, // c-sharp, rust
97− #[serde(rename = "language-id")]
98− // see the table under https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem
99− pub language_server_language_id: Option<String>, // csharp, rust, typescriptreact, for the language-server
100 pub scope: String, // source.rust
79 pub scope: String, // source.rust
101 pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
80 pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
102 #[serde(default)]
81 #[serde(default)]
103 pub shebangs: Vec<String>, // interpreter(s) associated with language
82 pub shebangs: Vec<String>, // interpreter(s) associated with language
104− #[serde(default)]
105− pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
83+ pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
106 pub comment_token: Option<String>,
84 pub comment_token: Option<String>,
107 pub text_width: Option<usize>,
85 pub text_width: Option<usize>,
108 pub soft_wrap: Option<SoftWrap>,
86 pub soft_wrap: Option<SoftWrap>,
109
87
88+ #[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
89+ pub config: Option<serde_json::Value>,
90+
110 #[serde(default)]
91 #[serde(default)]
111 pub auto_format: bool,
92 pub auto_format: bool,
112
93
@@ -126,13 +107,8 @@ pub struct LanguageConfiguration {
126 #[serde(skip)]
107 #[serde(skip)]
127 pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
108 pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
128 // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
109 // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
129− #[serde(
130− default,
131− skip_serializing_if = "Vec::is_empty",
110+ #[serde(skip_serializing_if = "Option::is_none")]
132− serialize_with = "serialize_lang_features",
111+ pub language_server: Option<LanguageServerConfiguration>,
133− deserialize_with = "deserialize_lang_features"
134− )]
135− pub language_servers: Vec<LanguageServerFeatures>,
136 #[serde(skip_serializing_if = "Option::is_none")]
112 #[serde(skip_serializing_if = "Option::is_none")]
137 pub indent: Option<IndentationConfiguration>,
113 pub indent: Option<IndentationConfiguration>,
138
114
@@ -155,8 +131,6 @@ pub struct LanguageConfiguration {
155 /// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
131 /// 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.
132 /// Falling back to the current working directory if none are configured.
157 pub workspace_lsp_roots: Option<Vec<PathBuf>>,
133 pub workspace_lsp_roots: Option<Vec<PathBuf>>,
158− #[serde(default)]
159− pub persistent_diagnostic_sources: Vec<String>,
160}
134}
161
135
162#[derive(Debug, PartialEq, Eq, Hash)]
136#[derive(Debug, PartialEq, Eq, Hash)]
@@ -213,9 +187,9 @@ impl<'de> Deserialize<'de> for FileType {
213 M: serde::de::MapAccess<'de>,
187 M: serde::de::MapAccess<'de>,
214 {
188 {
215 match map.next_entry::<String, String>()? {
189 match map.next_entry::<String, String>()? {
216− Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
190+ Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix(
217− suffix.replace('/', std::path::MAIN_SEPARATOR_STR)
191+ suffix.replace('/', &std::path::MAIN_SEPARATOR.to_string()),
218− })),
192+ )),
219 Some((key, _value)) => Err(serde::de::Error::custom(format!(
193 Some((key, _value)) => Err(serde::de::Error::custom(format!(
220 "unknown key in `file-types` list: {}",
194 "unknown key in `file-types` list: {}",
221 key
195 key
@@ -231,133 +205,6 @@ impl<'de> Deserialize<'de> for FileType {
231 }
205 }
232}
206}
233
207
234−#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
235−#[serde(rename_all = "kebab-case")]
236−pub enum LanguageServerFeature {
237− Format,
238− GotoDeclaration,
239− GotoDefinition,
240− GotoTypeDefinition,
241− GotoReference,
242− GotoImplementation,
243− // Goto, use bitflags, combining previous Goto members?
244− SignatureHelp,
245− Hover,
246− DocumentHighlight,
247− Completion,
248− CodeAction,
249− WorkspaceCommand,
250− DocumentSymbols,
251− WorkspaceSymbols,
252− // Symbols, use bitflags, see above?
253− Diagnostics,
254− RenameSymbol,
255− InlayHints,
256−}
257−
258−impl Display for LanguageServerFeature {
259− fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260− use LanguageServerFeature::*;
261− let feature = match self {
262− Format => "format",
263− GotoDeclaration => "goto-declaration",
264− GotoDefinition => "goto-definition",
265− GotoTypeDefinition => "goto-type-definition",
266− GotoReference => "goto-type-definition",
267− GotoImplementation => "goto-implementation",
268− SignatureHelp => "signature-help",
269− Hover => "hover",
270− DocumentHighlight => "document-highlight",
271− Completion => "completion",
272− CodeAction => "code-action",
273− WorkspaceCommand => "workspace-command",
274− DocumentSymbols => "document-symbols",
275− WorkspaceSymbols => "workspace-symbols",
276− Diagnostics => "diagnostics",
277− RenameSymbol => "rename-symbol",
278− InlayHints => "inlay-hints",
279− };
280− write!(f, "{feature}",)
281− }
282−}
283−
284−#[derive(Debug, Serialize, Deserialize)]
285−#[serde(untagged, rename_all = "kebab-case", deny_unknown_fields)]
286−enum LanguageServerFeatureConfiguration {
287− #[serde(rename_all = "kebab-case")]
288− Features {
289− #[serde(default, skip_serializing_if = "HashSet::is_empty")]
290− only_features: HashSet<LanguageServerFeature>,
291− #[serde(default, skip_serializing_if = "HashSet::is_empty")]
292− except_features: HashSet<LanguageServerFeature>,
293− name: String,
294− },
295− Simple(String),
296−}
297−
298−#[derive(Debug, Default)]
299−pub struct LanguageServerFeatures {
300− pub name: String,
301− pub only: HashSet<LanguageServerFeature>,
302− pub excluded: HashSet<LanguageServerFeature>,
303−}
304−
305−impl LanguageServerFeatures {
306− pub fn has_feature(&self, feature: LanguageServerFeature) -> bool {
307− (self.only.is_empty() || self.only.contains(&feature)) && !self.excluded.contains(&feature)
308− }
309−}
310−
311−fn deserialize_lang_features<'de, D>(
312− deserializer: D,
313−) -> Result<Vec<LanguageServerFeatures>, D::Error>
314−where
315− D: serde::Deserializer<'de>,
316−{
317− let raw: Vec<LanguageServerFeatureConfiguration> = Deserialize::deserialize(deserializer)?;
318− let res = raw
319− .into_iter()
320− .map(|config| match config {
321− LanguageServerFeatureConfiguration::Simple(name) => LanguageServerFeatures {
322− name,
323− ..Default::default()
324− },
325− LanguageServerFeatureConfiguration::Features {
326− only_features,
327− except_features,
328− name,
329− } => LanguageServerFeatures {
330− name,
331− only: only_features,
332− excluded: except_features,
333− },
334− })
335− .collect();
336− Ok(res)
337−}
338−fn serialize_lang_features<S>(
339− map: &Vec<LanguageServerFeatures>,
340− serializer: S,
341−) -> Result<S::Ok, S::Error>
342−where
343− S: serde::Serializer,
344−{
345− let mut serializer = serializer.serialize_seq(Some(map.len()))?;
346− for features in map {
347− let features = if features.only.is_empty() && features.excluded.is_empty() {
348− LanguageServerFeatureConfiguration::Simple(features.name.to_owned())
349− } else {
350− LanguageServerFeatureConfiguration::Features {
351− only_features: features.only.clone(),
352− except_features: features.excluded.clone(),
353− name: features.name.to_owned(),
354− }
355− };
356− serializer.serialize_element(&features)?;
357− }
358− serializer.end()
359−}
360−
361#[derive(Debug, Serialize, Deserialize)]
208#[derive(Debug, Serialize, Deserialize)]
362#[serde(rename_all = "kebab-case")]
209#[serde(rename_all = "kebab-case")]
363pub struct LanguageServerConfiguration {
210pub struct LanguageServerConfiguration {
@@ -367,10 +214,9 @@ pub struct LanguageServerConfiguration {
367 pub args: Vec<String>,
214 pub args: Vec<String>,
368 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
215 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
369 pub environment: HashMap<String, String>,
216 pub environment: HashMap<String, String>,
370− #[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
371− pub config: Option<serde_json::Value>,
372 #[serde(default = "default_timeout")]
217 #[serde(default = "default_timeout")]
373 pub timeout: u64,
218 pub timeout: u64,
219+ pub language_id: Option<String>,
374}
220}
375
221
376#[derive(Debug, Clone, Serialize, Deserialize)]
222#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -439,27 +285,10 @@ pub struct DebuggerQuirks {
439#[derive(Debug, Serialize, Deserialize)]
285#[derive(Debug, Serialize, Deserialize)]
440#[serde(rename_all = "kebab-case")]
286#[serde(rename_all = "kebab-case")]
441pub struct IndentationConfiguration {
287pub struct IndentationConfiguration {
442− #[serde(deserialize_with = "deserialize_tab_width")]
443 pub tab_width: usize,
288 pub tab_width: usize,
444 pub unit: String,
289 pub unit: String,
445}
290}
446
291
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
292/// Configuration for auto pairs
464#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
465#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
294#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
@@ -762,8 +591,6 @@ pub struct Loader {
762 language_config_ids_by_suffix: HashMap<String, usize>,
591 language_config_ids_by_suffix: HashMap<String, usize>,
763 language_config_ids_by_shebang: HashMap<String, usize>,
592 language_config_ids_by_shebang: HashMap<String, usize>,
764
593
765− language_server_configs: HashMap<String, LanguageServerConfiguration>,
766−
767 scopes: ArcSwap<Vec<String>>,
594 scopes: ArcSwap<Vec<String>>,
768}
595}
769
596
@@ -771,7 +598,6 @@ impl Loader {
771 pub fn new(config: Configuration) -> Self {
598 pub fn new(config: Configuration) -> Self {
772 let mut loader = Self {
599 let mut loader = Self {
773 language_configs: Vec::new(),
600 language_configs: Vec::new(),
774− language_server_configs: config.language_server,
775 language_config_ids_by_extension: HashMap::new(),
601 language_config_ids_by_extension: HashMap::new(),
776 language_config_ids_by_suffix: HashMap::new(),
602 language_config_ids_by_suffix: HashMap::new(),
777 language_config_ids_by_shebang: HashMap::new(),
603 language_config_ids_by_shebang: HashMap::new(),
@@ -834,10 +660,7 @@ impl Loader {
834 // TODO: content_regex handling conflict resolution
660 // TODO: content_regex handling conflict resolution
835 }
661 }
836
662
837− pub fn language_config_for_shebang(
663+ pub fn language_config_for_shebang(&self, source: &Rope) -> Option<Arc<LanguageConfiguration>> {
838− &self,
839− source: RopeSlice,
840− ) -> Option<Arc<LanguageConfiguration>> {
841 let line = Cow::from(source.line(0));
664 let line = Cow::from(source.line(0));
842 static SHEBANG_REGEX: Lazy<Regex> =
665 static SHEBANG_REGEX: Lazy<Regex> =
843 Lazy::new(|| Regex::new(&["^", SHEBANG].concat()).unwrap());
666 Lazy::new(|| Regex::new(&["^", SHEBANG].concat()).unwrap());
@@ -899,10 +722,6 @@ impl Loader {
899 self.language_configs.iter()
722 self.language_configs.iter()
900 }
723 }
901
724
902− pub fn language_server_configs(&self) -> &HashMap<String, LanguageServerConfiguration> {
903− &self.language_server_configs
904− }
905−
906 pub fn set_scopes(&self, scopes: Vec<String>) {
725 pub fn set_scopes(&self, scopes: Vec<String>) {
907 self.scopes.store(Arc::new(scopes));
726 self.scopes.store(Arc::new(scopes));
908
727
@@ -946,11 +765,7 @@ fn byte_range_to_str(range: std::ops::Range<usize>, source: RopeSlice) -> Cow<st
946}
765}
947
766
948impl Syntax {
767impl Syntax {
949− pub fn new(
950− source: RopeSlice,
951− config: Arc<HighlightConfiguration>,
768+ pub fn new(source: &Rope, config: Arc<HighlightConfiguration>, loader: Arc<Loader>) -> Self {
952− loader: Arc<Loader>,
953− ) -> Option<Self> {
954 let root_layer = LanguageLayer {
769 let root_layer = LanguageLayer {
955 tree: None,
770 tree: None,
956 config,
771 config,
@@ -975,19 +790,17 @@ impl Syntax {
975 loader,
790 loader,
976 };
791 };
977
792
793+ syntax
978− let res = syntax.update(source, source, &ChangeSet::new(source));
794+ .update(source, source, &ChangeSet::new(source))
795+ .unwrap();
979
796
980− if res.is_err() {
981− log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
982− return None;
983− }
984− Some(syntax)
797+ syntax
985 }
798 }
986
799
987 pub fn update(
800 pub fn update(
988 &mut self,
801 &mut self,
989− old_source: RopeSlice,
802+ old_source: &Rope,
990− source: RopeSlice,
803+ source: &Rope,
991 changeset: &ChangeSet,
804 changeset: &ChangeSet,
992 ) -> Result<(), Error> {
805 ) -> Result<(), Error> {
993 let mut queue = VecDeque::new();
806 let mut queue = VecDeque::new();
@@ -1109,7 +922,6 @@ impl Syntax {
1109
922
1110 PARSER.with(|ts_parser| {
923 PARSER.with(|ts_parser| {
1111 let ts_parser = &mut ts_parser.borrow_mut();
924 let ts_parser = &mut ts_parser.borrow_mut();
1112− ts_parser.parser.set_timeout_micros(1000 * 500); // half a second is pretty generours
1113 let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new);
925 let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new);
1114 // TODO: might need to set cursor range
926 // TODO: might need to set cursor range
1115 cursor.set_byte_range(0..usize::MAX);
927 cursor.set_byte_range(0..usize::MAX);
@@ -1154,38 +966,12 @@ impl Syntax {
1154 layer.tree().root_node(),
966 layer.tree().root_node(),
1155 RopeProvider(source_slice),
967 RopeProvider(source_slice),
1156 );
968 );
1157− let mut combined_injections = vec![
1158− (None, Vec::new(), IncludedChildren::default());
1159− layer.config.combined_injections_patterns.len()
1160− ];
1161 let mut injections = Vec::new();
969 let mut injections = Vec::new();
1162− let mut last_injection_end = 0;
1163 for mat in matches {
970 for mat in matches {
1164 let (injection_capture, content_node, included_children) = layer
971 let (injection_capture, content_node, included_children) = layer
1165 .config
972 .config
1166 .injection_for_match(&layer.config.injections_query, &mat, source_slice);
973 .injection_for_match(&layer.config.injections_query, &mat, source_slice);
1167
974
1168− // in case this is a combined injection save it for more processing later
1169− if let Some(combined_injection_idx) = layer
1170− .config
1171− .combined_injections_patterns
1172− .iter()
1173− .position(|&pattern| pattern == mat.pattern_index)
1174− {
1175− let entry = &mut combined_injections[combined_injection_idx];
1176− if injection_capture.is_some() {
1177− entry.0 = injection_capture;
1178− }
1179− if let Some(content_node) = content_node {
1180− if content_node.start_byte() >= last_injection_end {
1181− entry.1.push(content_node);
1182− last_injection_end = content_node.end_byte();
1183− }
1184− }
1185− entry.2 = included_children;
1186− continue;
1187− }
1188−
1189 // Explicitly remove this match so that none of its other captures will remain
975 // Explicitly remove this match so that none of its other captures will remain
1190 // in the stream of captures.
976 // in the stream of captures.
1191 mat.remove();
977 mat.remove();
@@ -1200,23 +986,49 @@ impl Syntax {
1200 intersect_ranges(&layer.ranges, &[content_node], included_children);
986 intersect_ranges(&layer.ranges, &[content_node], included_children);
1201
987
1202 if !ranges.is_empty() {
988 if !ranges.is_empty() {
1203− if content_node.start_byte() < last_injection_end {
1204− continue;
1205− }
1206− last_injection_end = content_node.end_byte();
1207 injections.push((config, ranges));
989 injections.push((config, ranges));
1208 }
990 }
1209 }
991 }
1210 }
992 }
1211 }
993 }
1212
994
995+ // Process combined injections.
996+ if let Some(combined_injections_query) = &layer.config.combined_injections_query {
997+ let mut injections_by_pattern_index =
998+ vec![
999+ (None, Vec::new(), IncludedChildren::default());
1000+ combined_injections_query.pattern_count()
1001+ ];
1002+ let matches = cursor.matches(
1003+ combined_injections_query,
1004+ layer.tree().root_node(),
1005+ RopeProvider(source_slice),
1006+ );
1007+ for mat in matches {
1008+ let entry = &mut injections_by_pattern_index[mat.pattern_index];
1009+ let (injection_capture, content_node, included_children) = layer
1010+ .config
1011+ .injection_for_match(combined_injections_query, &mat, source_slice);
1012+ if injection_capture.is_some() {
1013+ entry.0 = injection_capture;
1014+ }
1015+ if let Some(content_node) = content_node {
1016+ entry.1.push(content_node);
1017+ }
1018+ entry.2 = included_children;
1019+ }
1213− for (lang_name, content_nodes, included_children) in combined_injections {
1020+ for (lang_name, content_nodes, included_children) in injections_by_pattern_index
1021+ {
1214− if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) {
1022+ if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) {
1215− if let Some(config) = (injection_callback)(&lang_name) {
1023+ if let Some(config) = (injection_callback)(&lang_name) {
1216− let ranges =
1024+ let ranges = intersect_ranges(
1025+ &layer.ranges,
1026+ &content_nodes,
1217− intersect_ranges(&layer.ranges, &content_nodes, included_children);
1027+ included_children,
1028+ );
1218− if !ranges.is_empty() {
1029+ if !ranges.is_empty() {
1219− injections.push((config, ranges));
1030+ injections.push((config, ranges));
1031+ }
1220 }
1032 }
1221 }
1033 }
1222 }
1034 }
@@ -1406,7 +1218,7 @@ impl LanguageLayer {
1406 self.tree.as_ref().unwrap()
1218 self.tree.as_ref().unwrap()
1407 }
1219 }
1408
1220
1409− fn parse(&mut self, parser: &mut Parser, source: RopeSlice) -> Result<(), Error> {
1221+ fn parse(&mut self, parser: &mut Parser, source: &Rope) -> Result<(), Error> {
1410 parser
1222 parser
1411 .set_included_ranges(&self.ranges)
1223 .set_included_ranges(&self.ranges)
1412 .map_err(|_| Error::InvalidRanges)?;
1224 .map_err(|_| Error::InvalidRanges)?;
@@ -1421,7 +1233,7 @@ impl LanguageLayer {
1421 &mut |byte, _| {
1233 &mut |byte, _| {
1422 if byte <= source.len_bytes() {
1234 if byte <= source.len_bytes() {
1423 let (chunk, start_byte, _, _) = source.chunk_at_byte(byte);
1235 let (chunk, start_byte, _, _) = source.chunk_at_byte(byte);
1424− &chunk.as_bytes()[byte - start_byte..]
1236+ chunk[byte - start_byte..].as_bytes()
1425 } else {
1237 } else {
1426 // out of range
1238 // out of range
1427 &[]
1239 &[]
@@ -1437,7 +1249,7 @@ impl LanguageLayer {
1437}
1249}
1438
1250
1439pub(crate) fn generate_edits(
1251pub(crate) fn generate_edits(
1440− old_text: RopeSlice,
1252+ old_text: &Rope,
1441 changeset: &ChangeSet,
1253 changeset: &ChangeSet,
1442) -> Vec<tree_sitter::InputEdit> {
1254) -> Vec<tree_sitter::InputEdit> {
1443 use Operation::*;
1255 use Operation::*;
@@ -1453,7 +1265,7 @@ pub(crate) fn generate_edits(
1453
1265
1454 // TODO; this is a lot easier with Change instead of Operation.
1266 // TODO; this is a lot easier with Change instead of Operation.
1455
1267
1456− fn point_at_pos(text: RopeSlice, pos: usize) -> (usize, Point) {
1268+ fn point_at_pos(text: &Rope, pos: usize) -> (usize, Point) {
1457 let byte = text.char_to_byte(pos); // <- attempted to index past end
1269 let byte = text.char_to_byte(pos); // <- attempted to index past end
1458 let line = text.char_to_line(pos);
1270 let line = text.char_to_line(pos);
1459 let line_start_byte = text.line_to_byte(line);
1271 let line_start_byte = text.line_to_byte(line);
@@ -1579,7 +1391,7 @@ pub struct HighlightConfiguration {
1579 pub language: Grammar,
1391 pub language: Grammar,
1580 pub query: Query,
1392 pub query: Query,
1581 injections_query: Query,
1393 injections_query: Query,
1582− combined_injections_patterns: Vec<usize>,
1394+ combined_injections_query: Option<Query>,
1583 highlights_pattern_index: usize,
1395 highlights_pattern_index: usize,
1584 highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
1396 highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
1585 non_local_variable_patterns: Vec<bool>,
1397 non_local_variable_patterns: Vec<bool>,
@@ -1630,7 +1442,7 @@ impl<'a> Iterator for ChunksBytes<'a> {
1630}
1442}
1631
1443
1632pub struct RopeProvider<'a>(pub RopeSlice<'a>);
1444pub struct RopeProvider<'a>(pub RopeSlice<'a>);
1633−impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> {
1445+impl<'a> TextProvider<'a> for RopeProvider<'a> {
1634 type I = ChunksBytes<'a>;
1446 type I = ChunksBytes<'a>;
1635
1447
1636 fn text(&mut self, node: Node) -> Self::I {
1448 fn text(&mut self, node: Node) -> Self::I {
@@ -1644,7 +1456,7 @@ impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> {
1644struct HighlightIterLayer<'a> {
1456struct HighlightIterLayer<'a> {
1645 _tree: Option<Tree>,
1457 _tree: Option<Tree>,
1646 cursor: QueryCursor,
1458 cursor: QueryCursor,
1647− captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>, &'a [u8]>>>,
1459+ captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>>>>,
1648 config: &'a HighlightConfiguration,
1460 config: &'a HighlightConfiguration,
1649 highlight_end_stack: Vec<usize>,
1461 highlight_end_stack: Vec<usize>,
1650 scope_stack: Vec<LocalScope<'a>>,
1462 scope_stack: Vec<LocalScope<'a>>,
@@ -1695,15 +1507,26 @@ impl HighlightConfiguration {
1695 }
1507 }
1696 }
1508 }
1697
1509
1698− let injections_query = Query::new(language, injection_query)?;
1510+ let mut injections_query = Query::new(language, injection_query)?;
1511+
1512+ // Construct a separate query just for dealing with the 'combined injections'.
1513+ // Disable the combined injection patterns in the main query.
1514+ let mut combined_injections_query = Query::new(language, injection_query)?;
1515+ let mut has_combined_queries = false;
1699− let combined_injections_patterns = (0..injections_query.pattern_count())
1516+ for pattern_index in 0..injections_query.pattern_count() {
1700− .filter(|&i| {
1701− injections_query
1702− .property_settings(i)
1517+ let settings = injections_query.property_settings(pattern_index);
1703− .iter()
1704− .any(|s| &*s.key == "injection.combined")
1518+ if settings.iter().any(|s| &*s.key == "injection.combined") {
1705− })
1519+ has_combined_queries = true;
1706− .collect();
1520+ injections_query.disable_pattern(pattern_index);
1521+ } else {
1522+ combined_injections_query.disable_pattern(pattern_index);
1523+ }
1524+ }
1525+ let combined_injections_query = if has_combined_queries {
1526+ Some(combined_injections_query)
1527+ } else {
1528+ None
1529+ };
1707
1530
1708 // Find all of the highlighting patterns that are disabled for nodes that
1531 // Find all of the highlighting patterns that are disabled for nodes that
1709 // have been identified as local variables.
1532 // have been identified as local variables.
@@ -1752,7 +1575,7 @@ impl HighlightConfiguration {
1752 language,
1575 language,
1753 query,
1576 query,
1754 injections_query,
1577 injections_query,
1755− combined_injections_patterns,
1578+ combined_injections_query,
1756 highlights_pattern_index,
1579 highlights_pattern_index,
1757 highlight_indices,
1580 highlight_indices,
1758 non_local_variable_patterns,
1581 non_local_variable_patterns,
@@ -2537,10 +2360,7 @@ mod test {
2537 "#,
2360 "#,
2538 );
2361 );
2539
2362
2540− let loader = Loader::new(Configuration {
2363+ let loader = Loader::new(Configuration { language: vec![] });
2541− language: vec![],
2542− language_server: HashMap::new(),
2543− });
2544 let language = get_language("rust").unwrap();
2364 let language = get_language("rust").unwrap();
2545
2365
2546 let query = Query::new(language, query_str).unwrap();
2366 let query = Query::new(language, query_str).unwrap();
@@ -2548,7 +2368,7 @@ mod test {
2548 let mut cursor = QueryCursor::new();
2368 let mut cursor = QueryCursor::new();
2549
2369
2550 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2370 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2551− let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
2371+ let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2552
2372
2553 let root = syntax.tree().root_node();
2373 let root = syntax.tree().root_node();
2554 let mut test = |capture, range| {
2374 let mut test = |capture, range| {
@@ -2599,10 +2419,7 @@ mod test {
2599 .map(String::from)
2419 .map(String::from)
2600 .collect();
2420 .collect();
2601
2421
2602− let loader = Loader::new(Configuration {
2422+ let loader = Loader::new(Configuration { language: vec![] });
2603− language: vec![],
2604− language_server: HashMap::new(),
2605− });
2606
2423
2607 let language = get_language("rust").unwrap();
2424 let language = get_language("rust").unwrap();
2608 let config = HighlightConfiguration::new(
2425 let config = HighlightConfiguration::new(
@@ -2622,7 +2439,7 @@ mod test {
2622 fn main() {}
2439 fn main() {}
2623 ",
2440 ",
2624 );
2441 );
2625− let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
2442+ let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2626 let tree = syntax.tree();
2443 let tree = syntax.tree();
2627 let root = tree.root_node();
2444 let root = tree.root_node();
2628 assert_eq!(root.kind(), "source_file");
2445 assert_eq!(root.kind(), "source_file");
@@ -2649,7 +2466,7 @@ mod test {
2649 &doc,
2466 &doc,
2650 vec![(6, 11, Some("test".into())), (12, 17, None)].into_iter(),
2467 vec![(6, 11, Some("test".into())), (12, 17, None)].into_iter(),
2651 );
2468 );
2652− let edits = generate_edits(doc.slice(..), transaction.changes());
2469+ let edits = generate_edits(&doc, transaction.changes());
2653 // transaction.apply(&mut state);
2470 // transaction.apply(&mut state);
2654
2471
2655 assert_eq!(
2472 assert_eq!(
@@ -2678,7 +2495,7 @@ mod test {
2678 let mut doc = Rope::from("fn test() {}");
2495 let mut doc = Rope::from("fn test() {}");
2679 let transaction =
2496 let transaction =
2680 Transaction::change(&doc, vec![(8, 8, Some("a: u32".into()))].into_iter());
2497 Transaction::change(&doc, vec![(8, 8, Some("a: u32".into()))].into_iter());
2681− let edits = generate_edits(doc.slice(..), transaction.changes());
2498+ let edits = generate_edits(&doc, transaction.changes());
2682 transaction.apply(&mut doc);
2499 transaction.apply(&mut doc);
2683
2500
2684 assert_eq!(doc, "fn test(a: u32) {}");
2501 assert_eq!(doc, "fn test(a: u32) {}");
@@ -2705,14 +2522,11 @@ mod test {
2705 ) {
2522 ) {
2706 let source = Rope::from_str(source);
2523 let source = Rope::from_str(source);
2707
2524
2708− let loader = Loader::new(Configuration {
2525+ let loader = Loader::new(Configuration { language: vec![] });
2709− language: vec![],
2710− language_server: HashMap::new(),
2711− });
2712 let language = get_language(language_name).unwrap();
2526 let language = get_language(language_name).unwrap();
2713
2527
2714 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2528 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2715− let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
2529+ let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2716
2530
2717 let root = syntax
2531 let root = syntax
2718 .tree()
2532 .tree()
helix-core/src/transaction.rs+51−242
@@ -1,12 +1,10 @@
1−use ropey::RopeSlice;
2use smallvec::SmallVec;
1use smallvec::SmallVec;
3
2
4−use crate::{chars::char_is_word, Range, Rope, Selection, Tendril};
3+use crate::{Range, Rope, Selection, Tendril};
5−use std::{borrow::Cow, iter::once};
4+use std::borrow::Cow;
6
5
7/// (from, to, replacement)
6/// (from, to, replacement)
8pub type Change = (usize, usize, Option<Tendril>);
7pub type Change = (usize, usize, Option<Tendril>);
9−pub type Deletion = (usize, usize);
10
8
11// TODO: pub(crate)
9// TODO: pub(crate)
12#[derive(Debug, Clone, PartialEq, Eq)]
10#[derive(Debug, Clone, PartialEq, Eq)]
@@ -23,30 +21,6 @@ pub enum Operation {
23pub enum Assoc {
21pub enum Assoc {
24 Before,
22 Before,
25 After,
23 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}
24}
51
25
52#[derive(Debug, Default, Clone, PartialEq, Eq)]
26#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -67,7 +41,7 @@ impl ChangeSet {
67 }
41 }
68
42
69 #[must_use]
43 #[must_use]
70− pub fn new(doc: RopeSlice) -> Self {
44+ pub fn new(doc: &Rope) -> Self {
71 let len = doc.len_chars();
45 let len = doc.len_chars();
72 Self {
46 Self {
73 changes: Vec::new(),
47 changes: Vec::new(),
@@ -351,75 +325,20 @@ impl ChangeSet {
351 self.changes.is_empty() || self.changes == [Operation::Retain(self.len)]
325 self.changes.is_empty() || self.changes == [Operation::Retain(self.len)]
352 }
326 }
353
327
354− /// Map a (mostly) *sorted* list of positions through the changes.
328+ /// Map a position through the changes.
355− ///
356− /// This is equivalent to updating each position with `map_pos`:
357 ///
329 ///
358− /// ``` no-compile
330+ /// `assoc` indicates which size to associate the position with. `Before` will keep the
359− /// for (pos, assoc) in positions {
331+ /// position close to the character before, and will place it before insertions over that
360− /// *pos = changes.map_pos(*pos, assoc);
332+ /// range, or at that point. `After` will move it forward, placing it at the end of such
361− /// }
333+ /// insertions.
362− /// ```
334+ pub fn map_pos(&self, pos: usize, assoc: Assoc) -> usize {
363− /// However this function is significantly faster for sorted lists running
364− /// in `O(N+M)` instead of `O(NM)`. This function also handles unsorted/
365− /// partially sorted lists. However, in that case worst case complexity is
366− /// again `O(MN)`. For lists that are often/mostly sorted (like the end of diagnostic ranges)
367− /// performance is usally close to `O(N + M)`
368− pub fn update_positions<'a>(&self, positions: impl Iterator<Item = (&'a mut usize, Assoc)>) {
369 use Operation::*;
335 use Operation::*;
370−
371− let mut positions = positions.peekable();
372−
373 let mut old_pos = 0;
336 let mut old_pos = 0;
374 let mut new_pos = 0;
337 let mut new_pos = 0;
375− let mut iter = self.changes.iter().enumerate().peekable();
376−
377− 'outer: loop {
378− macro_rules! map {
379− ($map: expr, $i: expr) => {
380− loop {
381− let Some((pos, assoc)) = positions.peek_mut() else { return; };
382− if **pos < old_pos {
383− // Positions are not sorted, revert to the last Operation that
384− // contains this position and continue iterating from there.
385− // We can unwrap here since `pos` can not be negative
386− // (unsigned integer) and iterating backwards to the start
387− // should always move us back to the start
388− for (i, change) in self.changes[..$i].iter().enumerate().rev() {
389− match change {
390− Retain(i) => {
391− old_pos -= i;
392− new_pos -= i;
393− }
394− Delete(i) => {
395− old_pos -= i;
396− }
397− Insert(ins) => {
398− new_pos -= ins.chars().count();
399− }
400− }
401− if old_pos <= **pos {
402− iter = self.changes[i..].iter().enumerate().peekable();
403− }
404− }
405− debug_assert!(old_pos <= **pos, "Reverse Iter across changeset works");
406− continue 'outer;
407− }
408− let Some(new_pos) = $map(**pos, *assoc) else { break; };
409− **pos = new_pos;
410− positions.next();
411− }
412− };
413− }
414
338
415− let Some((i, change)) = iter.next() else {
416− map!(
417− |pos, _| (old_pos == pos).then_some(new_pos),
418− self.changes.len()
339+ let mut iter = self.changes.iter().peekable();
419− );
420− break;
421− };
422
340
341+ while let Some(change) = iter.next() {
423 let len = match change {
342 let len = match change {
424 Delete(i) | Retain(i) => *i,
343 Delete(i) | Retain(i) => *i,
425 Insert(_) => 0,
344 Insert(_) => 0,
@@ -428,65 +347,61 @@ impl ChangeSet {
428
347
429 match change {
348 match change {
430 Retain(_) => {
349 Retain(_) => {
431− map!(
350+ if old_end > pos {
432− |pos, _| (old_end > pos).then_some(new_pos + (pos - old_pos)),
351+ return new_pos + (pos - old_pos);
433− i
352+ }
434− );
435 new_pos += len;
353 new_pos += len;
436 }
354 }
437 Delete(_) => {
355 Delete(_) => {
438 // in range
356 // in range
439− map!(|pos, _| (old_end > pos).then_some(new_pos), i);
357+ if old_end > pos {
358+ return new_pos;
359+ }
440 }
360 }
441 Insert(s) => {
361 Insert(s) => {
362+ let ins = s.chars().count();
363+
442 // a subsequent delete means a replace, consume it
364 // a subsequent delete means a replace, consume it
443− if let Some((_, Delete(len))) = iter.peek() {
365+ if let Some(Delete(len)) = iter.peek() {
444 iter.next();
366 iter.next();
445
367
446 old_end = old_pos + len;
368 old_end = old_pos + len;
447 // in range of replaced text
369 // in range of replaced text
448− map!(
449− |pos, assoc: Assoc| (old_end > pos).then(|| {
370+ if old_end > pos {
450− // at point or tracking before
371+ // at point or tracking before
451− if pos == old_pos && assoc.stay_at_gaps() {
372+ if pos == old_pos || assoc == Assoc::Before {
452− new_pos
373+ return new_pos;
453− } else {
374+ } else {
454− // place to end of insert
375+ // place to end of insert
455− new_pos + assoc.insert_offset(s)
376+ return new_pos + ins;
456− }
457− }),
377+ }
458− i
378+ }
459− );
460 } else {
379 } else {
461 // at insert point
380 // at insert point
462− map!(
463− |pos, assoc: Assoc| (old_pos == pos).then(|| {
381+ if old_pos == pos {
464− // return position before inserted text
382+ // return position before inserted text
465− new_pos + assoc.insert_offset(s)
383+ if assoc == Assoc::Before {
384+ return new_pos;
385+ } else {
386+ // after text
387+ return new_pos + ins;
466− }),
388+ }
467− i
389+ }
468− );
469 }
390 }
470
391
471− new_pos += s.chars().count();
392+ new_pos += ins;
472 }
393 }
473 }
394 }
474 old_pos = old_end;
395 old_pos = old_end;
475 }
396 }
476− let out_of_bounds: Vec<_> = positions.collect();
477
397
398+ if pos > old_pos {
399+ panic!(
478− panic!("Positions {out_of_bounds:?} are out of range for changeset len {old_pos}!",)
400+ "Position {} is out of range for changeset len {}!",
479− }
401+ pos, old_pos
480−
402+ )
481− /// Map a position through the changes.
403+ }
482− ///
404+ new_pos
483− /// `assoc` indicates which side to associate the position with. `Before` will keep the
484− /// position close to the character before, and will place it before insertions over that
485− /// range, or at that point. `After` will move it forward, placing it at the end of such
486− /// insertions.
487− pub fn map_pos(&self, mut pos: usize, assoc: Assoc) -> usize {
488− self.update_positions(once((&mut pos, assoc)));
489− pos
490 }
405 }
491
406
492 pub fn changes_iter(&self) -> ChangeIterator {
407 pub fn changes_iter(&self) -> ChangeIterator {
@@ -506,7 +421,7 @@ impl Transaction {
506 /// Create a new, empty transaction.
421 /// Create a new, empty transaction.
507 pub fn new(doc: &Rope) -> Self {
422 pub fn new(doc: &Rope) -> Self {
508 Self {
423 Self {
509− changes: ChangeSet::new(doc.slice(..)),
424+ changes: ChangeSet::new(doc),
510 selection: None,
425 selection: None,
511 }
426 }
512 }
427 }
@@ -619,46 +534,6 @@ impl Transaction {
619 Self::from(changeset)
534 Self::from(changeset)
620 }
535 }
621
536
622− /// Generate a transaction from a set of potentially overlapping deletions
623− /// by merging overlapping deletions together.
624− pub fn delete<I>(doc: &Rope, deletions: I) -> Self
625− where
626− I: Iterator<Item = Deletion>,
627− {
628− let len = doc.len_chars();
629−
630− let (lower, upper) = deletions.size_hint();
631− let size = upper.unwrap_or(lower);
632− let mut changeset = ChangeSet::with_capacity(2 * size + 1); // rough estimate
633−
634− let mut last = 0;
635− for (mut from, to) in deletions {
636− if last > to {
637− continue;
638− }
639− if last > from {
640− from = last
641− }
642− debug_assert!(
643− from <= to,
644− "Edit end must end before it starts (should {from} <= {to})"
645− );
646− // Retain from last "to" to current "from"
647− changeset.retain(from - last);
648− changeset.delete(to - from);
649− last = to;
650− }
651−
652− changeset.retain(len - last);
653−
654− Self::from(changeset)
655− }
656−
657− pub fn insert_at_eof(mut self, text: Tendril) -> Transaction {
658− self.changes.insert(text);
659− self
660− }
661−
662 /// Generate a transaction with a change per selection range.
537 /// Generate a transaction with a change per selection range.
663 pub fn change_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
538 pub fn change_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
664 where
539 where
@@ -705,16 +580,6 @@ impl Transaction {
705 )
580 )
706 }
581 }
707
582
708− /// Generate a transaction with a deletion per selection range.
709− /// Compared to using `change_by_selection` directly these ranges may overlap.
710− /// In that case they are merged
711− pub fn delete_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
712− where
713− F: FnMut(&Range) -> Deletion,
714− {
715− Self::delete(doc, selection.iter().map(f))
716− }
717−
718 /// Insert text at each selection head.
583 /// Insert text at each selection head.
719 pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self {
584 pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self {
720 Self::change_by_selection(doc, selection, |range| {
585 Self::change_by_selection(doc, selection, |range| {
@@ -887,62 +752,6 @@ mod test {
887 };
752 };
888 assert_eq!(cs.map_pos(2, Assoc::Before), 2);
753 assert_eq!(cs.map_pos(2, Assoc::Before), 2);
889 assert_eq!(cs.map_pos(2, Assoc::After), 2);
754 assert_eq!(cs.map_pos(2, Assoc::After), 2);
890− // unsorted selection
891− let cs = ChangeSet {
892− changes: vec![
893− Insert("ab".into()),
894− Delete(2),
895− Insert("cd".into()),
896− Delete(2),
897− ],
898− len: 4,
899− len_after: 4,
900− };
901− let mut positions = [4, 2];
902− cs.update_positions(positions.iter_mut().map(|pos| (pos, Assoc::After)));
903− 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 }
755 }
947
756
948 #[test]
757 #[test]
@@ -1009,9 +818,9 @@ mod test {
1009 #[test]
818 #[test]
1010 fn combine_with_empty() {
819 fn combine_with_empty() {
1011 let empty = Rope::from("");
820 let empty = Rope::from("");
1012− let a = ChangeSet::new(empty.slice(..));
821+ let a = ChangeSet::new(&empty);
1013
822
1014− let mut b = ChangeSet::new(empty.slice(..));
823+ let mut b = ChangeSet::new(&empty);
1015 b.insert("a".into());
824 b.insert("a".into());
1016
825
1017 let changes = a.compose(b);
826 let changes = a.compose(b);
@@ -1025,9 +834,9 @@ mod test {
1025 const TEST_CASE: &str = "Hello, これはヘリックスエディターです!";
834 const TEST_CASE: &str = "Hello, これはヘリックスエディターです!";
1026
835
1027 let empty = Rope::from("");
836 let empty = Rope::from("");
1028− let a = ChangeSet::new(empty.slice(..));
837+ let a = ChangeSet::new(&empty);
1029
838
1030− let mut b = ChangeSet::new(empty.slice(..));
839+ let mut b = ChangeSet::new(&empty);
1031 b.insert(TEST_CASE.into());
840 b.insert(TEST_CASE.into());
1032
841
1033 let changes = a.compose(b);
842 let changes = a.compose(b);
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+25−153
@@ -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]
12#[test]
15−fn test_treesitter_indent_cpp() {
13+fn test_treesitter_indent_rust_2() {
16− standard_treesitter_test("cpp.cpp", "source.cpp");
14+ test_treesitter_indent("indent.rs", "source.rust");
17−}
15+ // TODO Use commands.rs as indentation test.
18−
16+ // Currently this fails because we can't align the parameters of a closure yet
19−#[test]
17+ // test_treesitter_indent("commands.rs", "source.rust");
20−fn test_treesitter_indent_rust_helix() {
21− // We pin a specific git revision to prevent unrelated changes from causing the indent tests to fail.
22− // Ideally, someone updates this once in a while and fixes any errors that occur.
23− let rev = "af382768cdaf89ff547dbd8f644a1bddd90e7c8f";
24− let files = Command::new("git")
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,35 +71,30 @@ 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(..);
200− let syntax = Syntax::new(text, highlight_config, std::sync::Arc::new(loader)).unwrap();
75+ let syntax = Syntax::new(&doc, highlight_config, std::sync::Arc::new(loader));
201 let indent_query = language_config.indent_query().unwrap();
76 let indent_query = language_config.indent_query().unwrap();
77+ let text = doc.slice(..);
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−14
@@ -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"
@@ -19,18 +18,16 @@ anyhow = "1"
19serde = { version = "1.0", features = ["derive"] }
18serde = { version = "1.0", features = ["derive"] }
20toml = "0.7"
19toml = "0.7"
21etcetera = "0.8"
20etcetera = "0.8"
22−tree-sitter.workspace = true
21+tree-sitter = "0.20"
23−once_cell = "1.19"
22+once_cell = "1.17"
24log = "0.4"
23log = "0.4"
25−which = "5.0.0"
26
24
27# TODO: these two should be on !wasm32 only
25# TODO: these two should be on !wasm32 only
28
26
29# cloning/compiling tree-sitter grammars
27# cloning/compiling tree-sitter grammars
30cc = { version = "1" }
28cc = { version = "1" }
31threadpool = { version = "1.0" }
29threadpool = { version = "1.0" }
32−tempfile = "3.8.1"
30+tempfile = "3.5.0"
33−dunce = "1.0.4"
34
31
35[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
32[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
36libloading = "0.8"
33libloading = "0.8"
helix-loader/build.rs+6−20
@@ -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() {
@@ -50,9 +40,7 @@ fn main() {
50 .ok()
40 .ok()
51 .filter(|output| output.status.success())
41 .filter(|output| output.status.success())
52 .and_then(|x| String::from_utf8(x.stdout).ok())
42 .and_then(|x| String::from_utf8(x.stdout).ok())
53− else {
54− return;
43+ else{ return; };
55− };
56 // If heads starts pointing at something else (different branch)
44 // If heads starts pointing at something else (different branch)
57 // we need to return
45 // we need to return
58 let head = Path::new(&git_dir).join("HEAD");
46 let head = Path::new(&git_dir).join("HEAD");
@@ -67,9 +55,7 @@ fn main() {
67 .ok()
55 .ok()
68 .filter(|output| output.status.success())
56 .filter(|output| output.status.success())
69 .and_then(|x| String::from_utf8(x.stdout).ok())
57 .and_then(|x| String::from_utf8(x.stdout).ok())
70− else {
71− return;
58+ else{ return; };
72− };
73 let head_ref = Path::new(&git_dir).join(head_ref);
59 let head_ref = Path::new(&git_dir).join(head_ref);
74 if head_ref.exists() {
60 if head_ref.exists() {
75 println!("cargo:rerun-if-changed={}", head_ref.display());
61 println!("cargo:rerun-if-changed={}", head_ref.display());
helix-loader/src/grammar.rs−11
@@ -85,16 +85,7 @@ pub fn get_language(name: &str) -> Result<Language> {
85 Ok(language)
85 Ok(language)
86}
86}
87
87
88−fn ensure_git_is_available() -> Result<()> {
89− match which::which("git") {
90− Ok(_cmd) => Ok(()),
91− Err(err) => Err(anyhow::anyhow!("'git' could not be found ({err})")),
92− }
93−}
94−
95pub fn fetch_grammars() -> Result<()> {
88pub fn fetch_grammars() -> Result<()> {
96− ensure_git_is_available()?;
97−
98 // We do not need to fetch local grammars.
89 // We do not need to fetch local grammars.
99 let mut grammars = get_grammar_configs()?;
90 let mut grammars = get_grammar_configs()?;
100 grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. }));
91 grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. }));
@@ -154,8 +145,6 @@ pub fn fetch_grammars() -> Result<()> {
154}
145}
155
146
156pub fn build_grammars(target: Option<String>) -> Result<()> {
147pub fn build_grammars(target: Option<String>) -> Result<()> {
157− ensure_git_is_available()?;
158−
159 let grammars = get_grammar_configs()?;
148 let grammars = get_grammar_configs()?;
160 println!("Building {} grammars", grammars.len());
149 println!("Building {} grammars", grammars.len());
161 let results = run_parallel(grammars, move |grammar| {
150 let results = run_parallel(grammars, move |grammar| {
helix-loader/src/lib.rs+34−95
@@ -3,56 +3,29 @@ pub mod grammar;
3
3
4use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
4use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
5use std::path::{Path, PathBuf};
5use std::path::{Path, PathBuf};
6−use std::sync::RwLock;
7
6
8pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH");
7pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH");
9
8
10−static CWD: RwLock<Option<PathBuf>> = RwLock::new(None);
11−
12static RUNTIME_DIRS: once_cell::sync::Lazy<Vec<PathBuf>> =
9static RUNTIME_DIRS: once_cell::sync::Lazy<Vec<PathBuf>> =
13 once_cell::sync::Lazy::new(prioritize_runtime_dirs);
10 once_cell::sync::Lazy::new(prioritize_runtime_dirs);
14
11
15static CONFIG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
12static CONFIG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
16
13
17−static LOG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
14+pub fn initialize_config_file(specified_file: Option<PathBuf>) {
18−
15+ let config_file = specified_file.unwrap_or_else(|| {
19−// Get the current working directory.
16+ let config_dir = config_dir();
20−// This information is managed internally as the call to std::env::current_dir
21−// might fail if the cwd has been deleted.
22−pub fn current_working_dir() -> PathBuf {
23− if let Some(path) = &*CWD.read().unwrap() {
24− return path.clone();
25− }
26−
27− let path = std::env::current_dir()
28− .and_then(dunce::canonicalize)
29− .expect("Couldn't determine current working directory");
30− let mut cwd = CWD.write().unwrap();
31− *cwd = Some(path.clone());
32
17
33− path
18+ if !config_dir.exists() {
19+ std::fs::create_dir_all(&config_dir).ok();
34−}
20+ }
35
21
36−pub fn set_current_working_dir(path: impl AsRef<Path>) -> std::io::Result<()> {
22+ config_dir.join("config.toml")
37− let path = dunce::canonicalize(path)?;
38− std::env::set_current_dir(&path)?;
39− let mut cwd = CWD.write().unwrap();
40− *cwd = Some(path);
41− Ok(())
42−}
23+ });
43
24
44−pub fn initialize_config_file(specified_file: Option<PathBuf>) {
25+ // We should only initialize this value once.
45− let config_file = specified_file.unwrap_or_else(default_config_file);
46− ensure_parent_dir(&config_file);
47 CONFIG_FILE.set(config_file).ok();
26 CONFIG_FILE.set(config_file).ok();
48}
27}
49
28
50−pub fn initialize_log_file(specified_file: Option<PathBuf>) {
51− let log_file = specified_file.unwrap_or_else(default_log_file);
52− ensure_parent_dir(&log_file);
53− LOG_FILE.set(log_file).ok();
54−}
55−
56/// A list of runtime directories from highest to lowest priority
29/// A list of runtime directories from highest to lowest priority
57///
30///
58/// The priority is:
31/// The priority is:
@@ -60,8 +33,7 @@ pub fn initialize_log_file(specified_file: Option<PathBuf>) {
60/// 1. sibling directory to `CARGO_MANIFEST_DIR` (if environment variable is set)
33/// 1. sibling directory to `CARGO_MANIFEST_DIR` (if environment variable is set)
61/// 2. subdirectory of user config directory (always included)
34/// 2. subdirectory of user config directory (always included)
62/// 3. `HELIX_RUNTIME` (if environment variable is set)
35/// 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)
36+/// 4. subdirectory of path to helix executable (always included)
65///
37///
66/// Postcondition: returns at least two paths (they might not exist).
38/// Postcondition: returns at least two paths (they might not exist).
67fn prioritize_runtime_dirs() -> Vec<PathBuf> {
39fn prioritize_runtime_dirs() -> Vec<PathBuf> {
@@ -82,14 +54,6 @@ fn prioritize_runtime_dirs() -> Vec<PathBuf> {
82 rt_dirs.push(dir.into());
54 rt_dirs.push(dir.into());
83 }
55 }
84
56
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
57 // fallback to location of the executable being run
94 // canonicalize the path in case the executable is symlinked
58 // canonicalize the path in case the executable is symlinked
95 let exe_rt_dir = std::env::current_exe()
59 let exe_rt_dir = std::env::current_exe()
@@ -158,11 +122,10 @@ pub fn cache_dir() -> PathBuf {
158}
122}
159
123
160pub fn config_file() -> PathBuf {
124pub fn config_file() -> PathBuf {
125+ CONFIG_FILE
126+ .get()
161− CONFIG_FILE.get().map(|path| path.to_path_buf()).unwrap()
127+ .map(|path| path.to_path_buf())
162−}
128+ .unwrap_or_else(|| config_dir().join("config.toml"))
163−
164−pub fn log_file() -> PathBuf {
165− LOG_FILE.get().map(|path| path.to_path_buf()).unwrap()
166}
129}
167
130
168pub fn workspace_config_file() -> PathBuf {
131pub fn workspace_config_file() -> PathBuf {
@@ -173,7 +136,7 @@ pub fn lang_config_file() -> PathBuf {
173 config_dir().join("languages.toml")
136 config_dir().join("languages.toml")
174}
137}
175
138
176−pub fn default_log_file() -> PathBuf {
139+pub fn log_file() -> PathBuf {
177 cache_dir().join("helix.log")
140 cache_dir().join("helix.log")
178}
141}
179
142
@@ -246,55 +209,13 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi
246 }
209 }
247}
210}
248
211
249−/// Finds the current workspace folder.
250−/// Used as a ceiling dir for LSP root resolution, the filepicker and potentially as a future filewatching root
251−///
252−/// This function starts searching the FS upward from the CWD
253−/// and returns the first directory that contains either `.git` or `.helix`.
254−/// If no workspace was found returns (CWD, true).
255−/// Otherwise (workspace, false) is returned
256−pub fn find_workspace() -> (PathBuf, bool) {
257− let current_dir = current_working_dir();
258− for ancestor in current_dir.ancestors() {
259− if ancestor.join(".git").exists() || ancestor.join(".helix").exists() {
260− return (ancestor.to_owned(), false);
261− }
262− }
263−
264− (current_dir, true)
265−}
266−
267−fn default_config_file() -> PathBuf {
268− config_dir().join("config.toml")
269−}
270−
271−fn ensure_parent_dir(path: &Path) {
272− if let Some(parent) = path.parent() {
273− if !parent.exists() {
274− std::fs::create_dir_all(parent).ok();
275− }
276− }
277−}
278−
279#[cfg(test)]
212#[cfg(test)]
280mod merge_toml_tests {
213mod merge_toml_tests {
281 use std::str;
214 use std::str;
282
215
283− use super::{current_working_dir, merge_toml_values, set_current_working_dir};
216+ use super::merge_toml_values;
284 use toml::Value;
217 use toml::Value;
285
218
286− #[test]
287− fn current_dir_is_set() {
288− let new_path = dunce::canonicalize(std::env::temp_dir()).unwrap();
289− let cwd = current_working_dir();
290− assert_ne!(cwd, new_path);
291−
292− set_current_working_dir(&new_path).expect("Couldn't set new path");
293−
294− let cwd = current_working_dir();
295− assert_eq!(cwd, new_path);
296− }
297−
298 #[test]
219 #[test]
299 fn language_toml_map_merges() {
220 fn language_toml_map_merges() {
300 const USER: &str = r#"
221 const USER: &str = r#"
@@ -360,3 +281,21 @@ mod merge_toml_tests {
360 )
281 )
361 }
282 }
362}
283}
284+
285+/// Finds the current workspace folder.
286+/// Used as a ceiling dir for LSP root resolution, the filepicker and potentially as a future filewatching root
287+///
288+/// This function starts searching the FS upward from the CWD
289+/// and returns the first directory that contains either `.git` or `.helix`.
290+/// If no workspace was found returns (CWD, true).
291+/// Otherwise (workspace, false) is returned
292+pub fn find_workspace() -> (PathBuf, bool) {
293+ let current_dir = std::env::current_dir().expect("unable to determine current directory");
294+ for ancestor in current_dir.ancestors() {
295+ if ancestor.join(".git").exists() || ancestor.join(".helix").exists() {
296+ return (ancestor.to_owned(), false);
297+ }
298+ }
299+
300+ (current_dir, true)
301+}
helix-lsp/Cargo.toml+13−15
@@ -1,32 +1,30 @@
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"
24log = "0.4"
22log = "0.4"
25−lsp-types = { version = "0.95" }
23+lsp-types = { version = "0.94" }
26serde = { version = "1.0", features = ["derive"] }
24serde = { version = "1.0", features = ["derive"] }
27serde_json = "1.0"
25serde_json = "1.0"
28thiserror = "1.0"
26thiserror = "1.0"
29−tokio = { version = "1.35", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
27+tokio = { version = "1.28", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
30tokio-stream = "0.1.14"
28tokio-stream = "0.1.14"
31−which = "5.0.0"
29+which = "4.4"
32parking_lot = "0.12.1"
30parking_lot = "0.12.1"
helix-lsp/src/client.rs+24−215
@@ -4,12 +4,11 @@ use crate::{
4 Call, Error, OffsetEncoding, Result,
4 Call, Error, OffsetEncoding, Result,
5};
5};
6
6
7−use helix_core::{find_workspace, path, syntax::LanguageServerFeature, ChangeSet, Rope};
7+use helix_core::{find_workspace, path, ChangeSet, Rope};
8use helix_loader::{self, VERSION_AND_GIT_HASH};
8use helix_loader::{self, VERSION_AND_GIT_HASH};
9use lsp::{
9use lsp::{
10− notification::DidChangeWorkspaceFolders, CodeActionCapabilityResolveSupport,
10+ notification::DidChangeWorkspaceFolders, DidChangeWorkspaceFoldersParams, OneOf,
11− DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, WorkspaceFolder,
12− WorkspaceFoldersChangeEvent,
11+ PositionEncodingKind, WorkspaceFolder, WorkspaceFoldersChangeEvent,
13};
12};
14use lsp_types as lsp;
13use lsp_types as lsp;
15use parking_lot::Mutex;
14use parking_lot::Mutex;
@@ -45,7 +44,6 @@ fn workspace_for_uri(uri: lsp::Url) -> WorkspaceFolder {
45#[derive(Debug)]
44#[derive(Debug)]
46pub struct Client {
45pub struct Client {
47 id: usize,
46 id: usize,
48− name: String,
49 _process: Child,
47 _process: Child,
50 server_tx: UnboundedSender<Payload>,
48 server_tx: UnboundedSender<Payload>,
51 request_counter: AtomicU64,
49 request_counter: AtomicU64,
@@ -168,7 +166,8 @@ impl Client {
168 tokio::spawn(self.did_change_workspace(vec![workspace_for_uri(root_uri)], Vec::new()));
166 tokio::spawn(self.did_change_workspace(vec![workspace_for_uri(root_uri)], Vec::new()));
169 }
167 }
170
168
171− #[allow(clippy::type_complexity, clippy::too_many_arguments)]
169+ #[allow(clippy::type_complexity)]
170+ #[allow(clippy::too_many_arguments)]
172 pub fn start(
171 pub fn start(
173 cmd: &str,
172 cmd: &str,
174 args: &[String],
173 args: &[String],
@@ -177,7 +176,6 @@ impl Client {
177 root_markers: &[String],
176 root_markers: &[String],
178 manual_roots: &[PathBuf],
177 manual_roots: &[PathBuf],
179 id: usize,
178 id: usize,
180− name: String,
181 req_timeout: u64,
179 req_timeout: u64,
182 doc_path: Option<&std::path::PathBuf>,
180 doc_path: Option<&std::path::PathBuf>,
183 ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
181 ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
@@ -202,7 +200,7 @@ impl Client {
202 let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
200 let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
203
201
204 let (server_rx, server_tx, initialize_notify) =
202 let (server_rx, server_tx, initialize_notify) =
205− Transport::start(reader, writer, stderr, id, name.clone());
203+ Transport::start(reader, writer, stderr, id);
206 let (workspace, workspace_is_cwd) = find_workspace();
204 let (workspace, workspace_is_cwd) = find_workspace();
207 let workspace = path::get_normalized_path(&workspace);
205 let workspace = path::get_normalized_path(&workspace);
208 let root = find_lsp_workspace(
206 let root = find_lsp_workspace(
@@ -227,7 +225,6 @@ impl Client {
227
225
228 let client = Self {
226 let client = Self {
229 id,
227 id,
230− name,
231 _process: process,
228 _process: process,
232 server_tx,
229 server_tx,
233 request_counter: AtomicU64::new(0),
230 request_counter: AtomicU64::new(0),
@@ -243,10 +240,6 @@ impl Client {
243 Ok((client, server_rx, initialize_notify))
240 Ok((client, server_rx, initialize_notify))
244 }
241 }
245
242
246− pub fn name(&self) -> &str {
247− &self.name
248− }
249−
250 pub fn id(&self) -> usize {
243 pub fn id(&self) -> usize {
251 self.id
244 self.id
252 }
245 }
@@ -277,87 +270,6 @@ impl Client {
277 .expect("language server not yet initialized!")
270 .expect("language server not yet initialized!")
278 }
271 }
279
272
280− /// Client has to be initialized otherwise this function panics
281− #[inline]
282− pub fn supports_feature(&self, feature: LanguageServerFeature) -> bool {
283− let capabilities = self.capabilities();
284−
285− use lsp::*;
286− match feature {
287− LanguageServerFeature::Format => matches!(
288− capabilities.document_formatting_provider,
289− Some(OneOf::Left(true) | OneOf::Right(_))
290− ),
291− LanguageServerFeature::GotoDeclaration => matches!(
292− capabilities.declaration_provider,
293− Some(
294− DeclarationCapability::Simple(true)
295− | DeclarationCapability::RegistrationOptions(_)
296− | DeclarationCapability::Options(_),
297− )
298− ),
299− LanguageServerFeature::GotoDefinition => matches!(
300− capabilities.definition_provider,
301− Some(OneOf::Left(true) | OneOf::Right(_))
302− ),
303− LanguageServerFeature::GotoTypeDefinition => matches!(
304− capabilities.type_definition_provider,
305− Some(
306− TypeDefinitionProviderCapability::Simple(true)
307− | TypeDefinitionProviderCapability::Options(_),
308− )
309− ),
310− LanguageServerFeature::GotoReference => matches!(
311− capabilities.references_provider,
312− Some(OneOf::Left(true) | OneOf::Right(_))
313− ),
314− LanguageServerFeature::GotoImplementation => matches!(
315− capabilities.implementation_provider,
316− Some(
317− ImplementationProviderCapability::Simple(true)
318− | ImplementationProviderCapability::Options(_),
319− )
320− ),
321− LanguageServerFeature::SignatureHelp => capabilities.signature_help_provider.is_some(),
322− LanguageServerFeature::Hover => matches!(
323− capabilities.hover_provider,
324− Some(HoverProviderCapability::Simple(true) | HoverProviderCapability::Options(_),)
325− ),
326− LanguageServerFeature::DocumentHighlight => matches!(
327− capabilities.document_highlight_provider,
328− Some(OneOf::Left(true) | OneOf::Right(_))
329− ),
330− LanguageServerFeature::Completion => capabilities.completion_provider.is_some(),
331− LanguageServerFeature::CodeAction => matches!(
332− capabilities.code_action_provider,
333− Some(
334− CodeActionProviderCapability::Simple(true)
335− | CodeActionProviderCapability::Options(_),
336− )
337− ),
338− LanguageServerFeature::WorkspaceCommand => {
339− capabilities.execute_command_provider.is_some()
340− }
341− LanguageServerFeature::DocumentSymbols => matches!(
342− capabilities.document_symbol_provider,
343− Some(OneOf::Left(true) | OneOf::Right(_))
344− ),
345− LanguageServerFeature::WorkspaceSymbols => matches!(
346− capabilities.workspace_symbol_provider,
347− Some(OneOf::Left(true) | OneOf::Right(_))
348− ),
349− LanguageServerFeature::Diagnostics => true, // there's no extra server capability
350− LanguageServerFeature::RenameSymbol => matches!(
351− capabilities.rename_provider,
352− Some(OneOf::Left(true)) | Some(OneOf::Right(_))
353− ),
354− LanguageServerFeature::InlayHints => matches!(
355− capabilities.inlay_hint_provider,
356− Some(OneOf::Left(true) | OneOf::Right(InlayHintServerCapabilities::Options(_)))
357− ),
358− }
359− }
360−
361 pub fn offset_encoding(&self) -> OffsetEncoding {
273 pub fn offset_encoding(&self) -> OffsetEncoding {
362 self.capabilities()
274 self.capabilities()
363 .position_encoding
275 .position_encoding
@@ -401,22 +313,12 @@ impl Client {
401 &self,
313 &self,
402 params: R::Params,
314 params: R::Params,
403 ) -> impl Future<Output = Result<Value>>
315 ) -> 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
316 where
416 R::Params: serde::Serialize,
317 R::Params: serde::Serialize,
417 {
318 {
418 let server_tx = self.server_tx.clone();
319 let server_tx = self.server_tx.clone();
419 let id = self.next_request_id();
320 let id = self.next_request_id();
321+ let timeout_secs = self.req_timeout;
420
322
421 async move {
323 async move {
422 use std::time::Duration;
324 use std::time::Duration;
@@ -554,15 +456,6 @@ impl Client {
554 normalizes_line_endings: Some(false),
456 normalizes_line_endings: Some(false),
555 change_annotation_support: None,
457 change_annotation_support: None,
556 }),
458 }),
557− did_change_watched_files: Some(lsp::DidChangeWatchedFilesClientCapabilities {
558− dynamic_registration: Some(true),
559− relative_pattern_support: Some(false),
560− }),
561− file_operations: Some(lsp::WorkspaceFileOperationsClientCapabilities {
562− will_rename: Some(true),
563− did_rename: Some(true),
564− ..Default::default()
565− }),
566 ..Default::default()
459 ..Default::default()
567 }),
460 }),
568 text_document: Some(lsp::TextDocumentClientCapabilities {
461 text_document: Some(lsp::TextDocumentClientCapabilities {
@@ -629,12 +522,6 @@ impl Client {
629 .collect(),
522 .collect(),
630 },
523 },
631 }),
524 }),
632− is_preferred_support: Some(true),
633− disabled_support: Some(true),
634− data_support: Some(true),
635− resolve_support: Some(CodeActionCapabilityResolveSupport {
636− properties: vec!["edit".to_owned(), "command".to_owned()],
637− }),
638 ..Default::default()
525 ..Default::default()
639 }),
526 }),
640 publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
527 publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
@@ -667,7 +554,6 @@ impl Client {
667 version: Some(String::from(VERSION_AND_GIT_HASH)),
554 version: Some(String::from(VERSION_AND_GIT_HASH)),
668 }),
555 }),
669 locale: None, // TODO
556 locale: None, // TODO
670− work_done_progress_params: lsp::WorkDoneProgressParams::default(),
671 };
557 };
672
558
673 self.request::<lsp::request::Initialize>(params).await
559 self.request::<lsp::request::Initialize>(params).await
@@ -716,65 +602,6 @@ impl Client {
716 })
602 })
717 }
603 }
718
604
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 // -------------------------------------------------------------------------------------------
605 // -------------------------------------------------------------------------------------------
779 // Text document
606 // Text document
780 // -------------------------------------------------------------------------------------------
607 // -------------------------------------------------------------------------------------------
@@ -970,19 +797,20 @@ impl Client {
970 ) -> Option<impl Future<Output = Result<()>>> {
797 ) -> Option<impl Future<Output = Result<()>>> {
971 let capabilities = self.capabilities.get().unwrap();
798 let capabilities = self.capabilities.get().unwrap();
972
799
973− let include_text = match &capabilities.text_document_sync.as_ref()? {
800+ let include_text = match &capabilities.text_document_sync {
974− lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
801+ Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
975− save: options,
802+ save: Some(options),
976 ..
803 ..
977− }) => match options.as_ref()? {
804+ })) => match options {
978 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
805 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
979 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
806 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
980 include_text,
807 include_text,
981 }) => include_text.unwrap_or(false),
808 }) => include_text.unwrap_or(false),
982− lsp::TextDocumentSyncSaveOptions::Supported(false) => return None,
809+ // Supported(false)
810+ _ => return None,
983 },
811 },
984− // see: https://github.com/microsoft/language-server-protocol/issues/288
812+ // unsupported
985− lsp::TextDocumentSyncCapability::Kind(..) => false,
813+ _ => return None,
986 };
814 };
987
815
988 Some(self.notify::<lsp::notification::DidSaveTextDocument>(
816 Some(self.notify::<lsp::notification::DidSaveTextDocument>(
@@ -1039,24 +867,6 @@ impl Client {
1039 Some(self.call::<lsp::request::ResolveCompletionItem>(completion_item))
867 Some(self.call::<lsp::request::ResolveCompletionItem>(completion_item))
1040 }
868 }
1041
869
1042− pub fn resolve_code_action(
1043− &self,
1044− code_action: lsp::CodeAction,
1045− ) -> Option<impl Future<Output = Result<Value>>> {
1046− let capabilities = self.capabilities.get().unwrap();
1047−
1048− // Return early if the server does not support resolving code actions.
1049− match capabilities.code_action_provider {
1050− Some(lsp::CodeActionProviderCapability::Options(lsp::CodeActionOptions {
1051− resolve_provider: Some(true),
1052− ..
1053− })) => (),
1054− _ => return None,
1055− }
1056−
1057− Some(self.call::<lsp::request::CodeActionResolveRequest>(code_action))
1058− }
1059−
1060 pub fn text_document_signature_help(
870 pub fn text_document_signature_help(
1061 &self,
871 &self,
1062 text_document: lsp::TextDocumentIdentifier,
872 text_document: lsp::TextDocumentIdentifier,
@@ -1485,13 +1295,21 @@ impl Client {
1485 Some(self.call::<lsp::request::CodeActionRequest>(params))
1295 Some(self.call::<lsp::request::CodeActionRequest>(params))
1486 }
1296 }
1487
1297
1298+ pub fn supports_rename(&self) -> bool {
1299+ let capabilities = self.capabilities.get().unwrap();
1300+ matches!(
1301+ capabilities.rename_provider,
1302+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
1303+ )
1304+ }
1305+
1488 pub fn rename_symbol(
1306 pub fn rename_symbol(
1489 &self,
1307 &self,
1490 text_document: lsp::TextDocumentIdentifier,
1308 text_document: lsp::TextDocumentIdentifier,
1491 position: lsp::Position,
1309 position: lsp::Position,
1492 new_name: String,
1310 new_name: String,
1493 ) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
1311 ) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
1494− if !self.supports_feature(LanguageServerFeature::RenameSymbol) {
1312+ if !self.supports_rename() {
1495 return None;
1313 return None;
1496 }
1314 }
1497
1315
@@ -1531,13 +1349,4 @@ impl Client {
1531
1349
1532 Some(self.call::<lsp::request::ExecuteCommand>(params))
1350 Some(self.call::<lsp::request::ExecuteCommand>(params))
1533 }
1351 }
1534−
1535− pub fn did_change_watched_files(
1536− &self,
1537− changes: Vec<lsp::FileEvent>,
1538− ) -> impl Future<Output = std::result::Result<(), Error>> {
1539− self.notify::<lsp::notification::DidChangeWatchedFiles>(lsp::DidChangeWatchedFilesParams {
1540− changes,
1541− })
1542− }
1543}
1352}
helix-lsp/src/file_event.rsdeleted−193
@@ -1,193 +0,0 @@
1−use std::{collections::HashMap, path::PathBuf, sync::Weak};
2−
3−use globset::{GlobBuilder, GlobSetBuilder};
4−use tokio::sync::mpsc;
5−
6−use crate::{lsp, Client};
7−
8−enum Event {
9− FileChanged {
10− path: PathBuf,
11− },
12− Register {
13− client_id: usize,
14− client: Weak<Client>,
15− registration_id: String,
16− options: lsp::DidChangeWatchedFilesRegistrationOptions,
17− },
18− Unregister {
19− client_id: usize,
20− registration_id: String,
21− },
22− RemoveClient {
23− client_id: usize,
24− },
25−}
26−
27−#[derive(Default)]
28−struct ClientState {
29− client: Weak<Client>,
30− registered: HashMap<String, globset::GlobSet>,
31−}
32−
33−/// The Handler uses a dedicated tokio task to respond to file change events by
34−/// forwarding changes to LSPs that have registered for notifications with a
35−/// matching glob.
36−///
37−/// When an LSP registers for the DidChangeWatchedFiles notification, the
38−/// Handler is notified by sending the registration details in addition to a
39−/// weak reference to the LSP client. This is done so that the Handler can have
40−/// access to the client without preventing the client from being dropped if it
41−/// is closed and the Handler isn't properly notified.
42−#[derive(Clone, Debug)]
43−pub struct Handler {
44− tx: mpsc::UnboundedSender<Event>,
45−}
46−
47−impl Default for Handler {
48− fn default() -> Self {
49− Self::new()
50− }
51−}
52−
53−impl Handler {
54− pub fn new() -> Self {
55− let (tx, rx) = mpsc::unbounded_channel();
56− tokio::spawn(Self::run(rx));
57− Self { tx }
58− }
59−
60− pub fn register(
61− &self,
62− client_id: usize,
63− client: Weak<Client>,
64− registration_id: String,
65− options: lsp::DidChangeWatchedFilesRegistrationOptions,
66− ) {
67− let _ = self.tx.send(Event::Register {
68− client_id,
69− client,
70− registration_id,
71− options,
72− });
73− }
74−
75− pub fn unregister(&self, client_id: usize, registration_id: String) {
76− let _ = self.tx.send(Event::Unregister {
77− client_id,
78− registration_id,
79− });
80− }
81−
82− pub fn file_changed(&self, path: PathBuf) {
83− let _ = self.tx.send(Event::FileChanged { path });
84− }
85−
86− pub fn remove_client(&self, client_id: usize) {
87− let _ = self.tx.send(Event::RemoveClient { client_id });
88− }
89−
90− async fn run(mut rx: mpsc::UnboundedReceiver<Event>) {
91− let mut state: HashMap<usize, ClientState> = HashMap::new();
92− while let Some(event) = rx.recv().await {
93− match event {
94− Event::FileChanged { path } => {
95− log::debug!("Received file event for {:?}", &path);
96−
97− state.retain(|id, client_state| {
98− if !client_state
99− .registered
100− .values()
101− .any(|glob| glob.is_match(&path))
102− {
103− return true;
104− }
105− let Some(client) = client_state.client.upgrade() else {
106− log::warn!("LSP client was dropped: {id}");
107− return false;
108− };
109− let Ok(uri) = lsp::Url::from_file_path(&path) else {
110− return true;
111− };
112− log::debug!(
113− "Sending didChangeWatchedFiles notification to client '{}'",
114− client.name()
115− );
116− if let Err(err) = crate::block_on(client
117− .did_change_watched_files(vec![lsp::FileEvent {
118− uri,
119− // We currently always send the CHANGED state
120− // since we don't actually have more context at
121− // the moment.
122− typ: lsp::FileChangeType::CHANGED,
123− }]))
124− {
125− log::warn!("Failed to send didChangeWatchedFiles notification to client: {err}");
126− }
127− true
128− });
129− }
130− Event::Register {
131− client_id,
132− client,
133− registration_id,
134− options: ops,
135− } => {
136− log::debug!(
137− "Registering didChangeWatchedFiles for client '{}' with id '{}'",
138− client_id,
139− registration_id
140− );
141−
142− let entry = state.entry(client_id).or_insert_with(ClientState::default);
143− entry.client = client;
144−
145− let mut builder = GlobSetBuilder::new();
146− for watcher in ops.watchers {
147− if let lsp::GlobPattern::String(pattern) = watcher.glob_pattern {
148− if let Ok(glob) = GlobBuilder::new(&pattern).build() {
149− builder.add(glob);
150− }
151− }
152− }
153− match builder.build() {
154− Ok(globset) => {
155− entry.registered.insert(registration_id, globset);
156− }
157− Err(err) => {
158− // Remove any old state for that registration id and
159− // remove the entire client if it's now empty.
160− entry.registered.remove(&registration_id);
161− if entry.registered.is_empty() {
162− state.remove(&client_id);
163− }
164− log::warn!(
165− "Unable to build globset for LSP didChangeWatchedFiles {err}"
166− )
167− }
168− }
169− }
170− Event::Unregister {
171− client_id,
172− registration_id,
173− } => {
174− log::debug!(
175− "Unregistering didChangeWatchedFiles with id '{}' for client '{}'",
176− registration_id,
177− client_id
178− );
179− if let Some(client_state) = state.get_mut(&client_id) {
180− client_state.registered.remove(&registration_id);
181− if client_state.registered.is_empty() {
182− state.remove(&client_id);
183− }
184− }
185− }
186− Event::RemoveClient { client_id } => {
187− log::debug!("Removing LSP client: {client_id}");
188− state.remove(&client_id);
189− }
190− }
191− }
192− }
193−}
helix-lsp/src/lib.rs+115−152
@@ -1,5 +1,4 @@
1mod client;
1mod client;
2−pub mod file_event;
3pub mod jsonrpc;
2pub mod jsonrpc;
4pub mod snippet;
3pub mod snippet;
5mod transport;
4mod transport;
@@ -13,21 +12,24 @@ pub use lsp_types as lsp;
13use futures_util::stream::select_all::SelectAll;
12use futures_util::stream::select_all::SelectAll;
14use helix_core::{
13use helix_core::{
15 path,
14 path,
16− syntax::{LanguageConfiguration, LanguageServerConfiguration, LanguageServerFeatures},
15+ syntax::{LanguageConfiguration, LanguageServerConfiguration},
17};
16};
18use tokio::sync::mpsc::UnboundedReceiver;
17use tokio::sync::mpsc::UnboundedReceiver;
19
18
20use std::{
19use std::{
21− collections::HashMap,
20+ collections::{hash_map::Entry, HashMap},
22 path::{Path, PathBuf},
21 path::{Path, PathBuf},
23− sync::Arc,
22+ sync::{
23+ atomic::{AtomicUsize, Ordering},
24+ Arc,
25+ },
24};
26};
25
27
26use thiserror::Error;
28use thiserror::Error;
27use tokio_stream::wrappers::UnboundedReceiverStream;
29use tokio_stream::wrappers::UnboundedReceiverStream;
28
30
29pub type Result<T> = core::result::Result<T, Error>;
31pub type Result<T> = core::result::Result<T, Error>;
30−pub type LanguageServerName = String;
32+type LanguageId = String;
31
33
32#[derive(Error, Debug)]
34#[derive(Error, Debug)]
33pub enum Error {
35pub enum Error {
@@ -47,7 +49,7 @@ pub enum Error {
47 Other(#[from] anyhow::Error),
49 Other(#[from] anyhow::Error),
48}
50}
49
51
50−#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
52+#[derive(Clone, Copy, Debug, Default)]
51pub enum OffsetEncoding {
53pub enum OffsetEncoding {
52 /// UTF-8 code units aka bytes
54 /// UTF-8 code units aka bytes
53 Utf8,
55 Utf8,
@@ -378,7 +380,7 @@ pub mod util {
378 .expect("transaction must be valid for primary selection");
380 .expect("transaction must be valid for primary selection");
379 let removed_text = text.slice(removed_start..removed_end);
381 let removed_text = text.slice(removed_start..removed_end);
380
382
381− let (transaction, mut selection) = Transaction::change_by_selection_ignore_overlapping(
383+ let (transaction, selection) = Transaction::change_by_selection_ignore_overlapping(
382 doc,
384 doc,
383 selection,
385 selection,
384 |range| {
386 |range| {
@@ -421,11 +423,6 @@ pub mod util {
421 return transaction;
423 return transaction;
422 }
424 }
423
425
424− // Don't normalize to avoid merging/reording selections which would
425− // break the association between tabstops and selections. Most ranges
426− // will be replaced by tabstops anyways and the final selection will be
427− // normalized anyways
428− selection = selection.map_no_normalize(changes);
429 let mut mapped_selection = SmallVec::with_capacity(selection.len());
426 let mut mapped_selection = SmallVec::with_capacity(selection.len());
430 let mut mapped_primary_idx = 0;
427 let mut mapped_primary_idx = 0;
431 let primary_range = selection.primary();
428 let primary_range = selection.primary();
@@ -434,8 +431,9 @@ pub mod util {
434 mapped_primary_idx = mapped_selection.len()
431 mapped_primary_idx = mapped_selection.len()
435 }
432 }
436
433
434+ let range = range.map(changes);
437 let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty());
435 let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty());
438− let Some(tabstops) = tabstops else {
436+ let Some(tabstops) = tabstops else{
439 // no tabstop normal mapping
437 // no tabstop normal mapping
440 mapped_selection.push(range);
438 mapped_selection.push(range);
441 continue;
439 continue;
@@ -445,36 +443,36 @@ pub mod util {
445 // the tabstop closest to the range simply replaces `head` while anchor remains in place
443 // the tabstop closest to the range simply replaces `head` while anchor remains in place
446 // the remaining tabstops receive their own single-width cursor
444 // the remaining tabstops receive their own single-width cursor
447 if range.head < range.anchor {
445 if range.head < range.anchor {
448− let last_idx = tabstops.len() - 1;
449− let last_tabstop = tabstop_anchor + tabstops[last_idx].0;
446+ let first_tabstop = tabstop_anchor + tabstops[0].1;
450
447
451 // if selection is forward but was moved to the right it is
448 // if selection is forward but was moved to the right it is
452 // contained entirely in the replacement text, just do a point
449 // contained entirely in the replacement text, just do a point
453 // selection (fallback below)
450 // selection (fallback below)
454− if range.anchor > last_tabstop {
451+ if range.anchor >= first_tabstop {
455− let range = Range::new(range.anchor, last_tabstop);
452+ let range = Range::new(range.anchor, first_tabstop);
456 mapped_selection.push(range);
453 mapped_selection.push(range);
457− let rem_tabstops = tabstops[..last_idx]
454+ let rem_tabstops = tabstops[1..]
458 .iter()
455 .iter()
459− .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
456+ .map(|tabstop| Range::point(tabstop_anchor + tabstop.1));
460 mapped_selection.extend(rem_tabstops);
457 mapped_selection.extend(rem_tabstops);
461 continue;
458 continue;
462 }
459 }
463 } else {
460 } else {
461+ let last_idx = tabstops.len() - 1;
464− let first_tabstop = tabstop_anchor + tabstops[0].0;
462+ let last_tabstop = tabstop_anchor + tabstops[last_idx].1;
465
463
466 // if selection is forward but was moved to the right it is
464 // if selection is forward but was moved to the right it is
467 // contained entirely in the replacement text, just do a point
465 // contained entirely in the replacement text, just do a point
468 // selection (fallback below)
466 // selection (fallback below)
469− if range.anchor < first_tabstop {
467+ if range.anchor <= last_tabstop {
470 // we can't properly compute the the next grapheme
468 // we can't properly compute the the next grapheme
471 // here because the transaction hasn't been applied yet
469 // here because the transaction hasn't been applied yet
472 // that is not a problem because the range gets grapheme aligned anyway
470 // 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
471 // tough so just adding one will always cause head to be grapheme
474 // aligned correctly when applied to the document
472 // aligned correctly when applied to the document
475− let range = Range::new(range.anchor, first_tabstop + 1);
473+ let range = Range::new(range.anchor, last_tabstop + 1);
476 mapped_selection.push(range);
474 mapped_selection.push(range);
477− let rem_tabstops = tabstops[1..]
475+ let rem_tabstops = tabstops[..last_idx]
478 .iter()
476 .iter()
479 .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
477 .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
480 mapped_selection.extend(rem_tabstops);
478 mapped_selection.extend(rem_tabstops);
@@ -548,7 +546,6 @@ pub enum MethodCall {
548 WorkspaceFolders,
546 WorkspaceFolders,
549 WorkspaceConfiguration(lsp::ConfigurationParams),
547 WorkspaceConfiguration(lsp::ConfigurationParams),
550 RegisterCapability(lsp::RegistrationParams),
548 RegisterCapability(lsp::RegistrationParams),
551− UnregisterCapability(lsp::UnregistrationParams),
552}
549}
553
550
554impl MethodCall {
551impl MethodCall {
@@ -572,10 +569,6 @@ impl MethodCall {
572 let params: lsp::RegistrationParams = params.parse()?;
569 let params: lsp::RegistrationParams = params.parse()?;
573 Self::RegisterCapability(params)
570 Self::RegisterCapability(params)
574 }
571 }
575− lsp::request::UnregisterCapability::METHOD => {
576− let params: lsp::UnregistrationParams = params.parse()?;
577− Self::UnregisterCapability(params)
578− }
579 _ => {
572 _ => {
580 return Err(Error::Unhandled);
573 return Err(Error::Unhandled);
581 }
574 }
@@ -631,21 +624,24 @@ impl Notification {
631
624
632#[derive(Debug)]
625#[derive(Debug)]
633pub struct Registry {
626pub struct Registry {
634− inner: HashMap<LanguageServerName, Vec<Arc<Client>>>,
627+ inner: HashMap<LanguageId, Vec<(usize, Arc<Client>)>>,
635− syn_loader: Arc<helix_core::syntax::Loader>,
628+
636− counter: usize,
629+ counter: AtomicUsize,
637 pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
630 pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
638− pub file_event_handler: file_event::Handler,
631+}
632+
633+impl Default for Registry {
634+ fn default() -> Self {
635+ Self::new()
636+ }
639}
637}
640
638
641impl Registry {
639impl Registry {
642− pub fn new(syn_loader: Arc<helix_core::syntax::Loader>) -> Self {
640+ pub fn new() -> Self {
643 Self {
641 Self {
644 inner: HashMap::new(),
642 inner: HashMap::new(),
645− syn_loader,
646− counter: 0,
643+ counter: AtomicUsize::new(0),
647 incoming: SelectAll::new(),
644 incoming: SelectAll::new(),
648− file_event_handler: file_event::Handler::new(),
649 }
645 }
650 }
646 }
651
647
@@ -653,95 +649,65 @@ impl Registry {
653 self.inner
649 self.inner
654 .values()
650 .values()
655 .flatten()
651 .flatten()
656− .find(|client| client.id() == id)
652+ .find(|(client_id, _)| client_id == &id)
657− .map(|client| &**client)
653+ .map(|(_, client)| client.as_ref())
658 }
654 }
659
655
660 pub fn remove_by_id(&mut self, id: usize) {
656 pub fn remove_by_id(&mut self, id: usize) {
661− self.file_event_handler.remove_client(id);
662− self.inner.retain(|_, language_servers| {
657+ self.inner.retain(|_, clients| {
663− language_servers.retain(|ls| id != ls.id());
658+ clients.retain(|&(client_id, _)| client_id != id);
664− !language_servers.is_empty()
659+ !clients.is_empty()
665− });
660+ })
666 }
661 }
667
662
668− fn start_client(
669− &mut self,
670− name: String,
671− ls_config: &LanguageConfiguration,
672− doc_path: Option<&std::path::PathBuf>,
673− root_dirs: &[PathBuf],
674− enable_snippets: bool,
675− ) -> Result<Arc<Client>> {
676− let config = self
677− .syn_loader
678− .language_server_configs()
679− .get(&name)
680− .ok_or_else(|| anyhow::anyhow!("Language server '{name}' not defined"))?;
681− let id = self.counter;
682− self.counter += 1;
683− let NewClient(client, incoming) = start_client(
684− id,
685− name,
686− ls_config,
687− config,
688− doc_path,
689− root_dirs,
690− enable_snippets,
691− )?;
692− self.incoming.push(UnboundedReceiverStream::new(incoming));
693− Ok(client)
694− }
695−
696− /// If this method is called, all documents that have a reference to language servers used by the language config have to refresh their language servers,
697− /// as it could be that language servers of these documents were stopped by this method.
698− /// See helix_view::editor::Editor::refresh_language_servers
699 pub fn restart(
663 pub fn restart(
700 &mut self,
664 &mut self,
701 language_config: &LanguageConfiguration,
665 language_config: &LanguageConfiguration,
702 doc_path: Option<&std::path::PathBuf>,
666 doc_path: Option<&std::path::PathBuf>,
703 root_dirs: &[PathBuf],
667 root_dirs: &[PathBuf],
704 enable_snippets: bool,
668 enable_snippets: bool,
705− ) -> Result<Vec<Arc<Client>>> {
669+ ) -> Result<Option<Arc<Client>>> {
706− language_config
670+ let config = match &language_config.language_server {
707− .language_servers
671+ Some(config) => config,
708− .iter()
672+ None => return Ok(None),
709− .filter_map(|LanguageServerFeatures { name, .. }| {
673+ };
710− if self.inner.contains_key(name) {
674+
675+ let scope = language_config.scope.clone();
676+
677+ match self.inner.entry(scope) {
678+ Entry::Vacant(_) => Ok(None),
679+ Entry::Occupied(mut entry) => {
680+ // initialize a new client
681+ let id = self.counter.fetch_add(1, Ordering::Relaxed);
682+
711− let client = match self.start_client(
683+ let NewClientResult(client, incoming) = start_client(
712− name.clone(),
684+ id,
713− language_config,
685+ language_config,
686+ config,
714− doc_path,
687+ doc_path,
715− root_dirs,
688+ root_dirs,
716− enable_snippets,
689+ enable_snippets,
717− ) {
690+ )?;
718− Ok(client) => client,
691+ self.incoming.push(UnboundedReceiverStream::new(incoming));
719− error => return Some(error),
692+
720− };
721− let old_clients = self
722− .inner
723− .insert(name.clone(), vec![client.clone()])
693+ let old_clients = entry.insert(vec![(id, client.clone())]);
724− .unwrap();
694+
725−
726− for old_client in old_clients {
695+ for (_, old_client) in old_clients {
727− self.file_event_handler.remove_client(old_client.id());
728− tokio::spawn(async move {
696+ tokio::spawn(async move {
729− let _ = old_client.force_shutdown().await;
697+ let _ = old_client.force_shutdown().await;
730− });
731− }
698+ });
732−
733− Some(Ok(client))
734− } else {
735− None
736 }
699 }
700+
701+ Ok(Some(client))
737− })
702+ }
738− .collect()
703+ }
739 }
704 }
740
705
741− pub fn stop(&mut self, name: &str) {
706+ pub fn stop(&mut self, language_config: &LanguageConfiguration) {
707+ let scope = language_config.scope.clone();
708+
742− if let Some(clients) = self.inner.remove(name) {
709+ if let Some(clients) = self.inner.remove(&scope) {
743− for client in clients {
710+ for (_, client) in clients {
744− self.file_event_handler.remove_client(client.id());
745 tokio::spawn(async move {
711 tokio::spawn(async move {
746 let _ = client.force_shutdown().await;
712 let _ = client.force_shutdown().await;
747 });
713 });
@@ -749,44 +715,43 @@ impl Registry {
749 }
715 }
750 }
716 }
751
717
752− pub fn get<'a>(
718+ pub fn get(
753− &'a mut self,
719+ &mut self,
754− language_config: &'a LanguageConfiguration,
720+ language_config: &LanguageConfiguration,
755− doc_path: Option<&'a std::path::PathBuf>,
721+ doc_path: Option<&std::path::PathBuf>,
756− root_dirs: &'a [PathBuf],
722+ root_dirs: &[PathBuf],
757 enable_snippets: bool,
723 enable_snippets: bool,
758− ) -> impl Iterator<Item = (LanguageServerName, Result<Arc<Client>>)> + 'a {
724+ ) -> Result<Option<Arc<Client>>> {
759− language_config.language_servers.iter().map(
725+ let config = match &language_config.language_server {
760− move |LanguageServerFeatures { name, .. }| {
726+ Some(config) => config,
727+ None => return Ok(None),
728+ };
729+
761− if let Some(clients) = self.inner.get(name) {
730+ let clients = self.inner.entry(language_config.scope.clone()).or_default();
731+ // check if we already have a client for this documents root that we can reuse
762− if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| {
732+ if let Some((_, client)) = clients.iter_mut().enumerate().find(|(i, (_, client))| {
763− client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
733+ client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
764− }) {
734+ }) {
765− return (name.to_owned(), Ok(client.clone()));
735+ return Ok(Some(client.1.clone()));
766− }
736+ }
767− }
737+ // initialize a new client
768− match self.start_client(
738+ let id = self.counter.fetch_add(1, Ordering::Relaxed);
769− name.clone(),
739+
740+ let NewClientResult(client, incoming) = start_client(
741+ id,
770− language_config,
742+ language_config,
743+ config,
771− doc_path,
744+ doc_path,
772− root_dirs,
745+ root_dirs,
773− enable_snippets,
746+ enable_snippets,
774− ) {
747+ )?;
775− Ok(client) => {
748+ clients.push((id, client.clone()));
776− self.inner
749+ self.incoming.push(UnboundedReceiverStream::new(incoming));
777− .entry(name.to_owned())
750+ Ok(Some(client))
778− .or_default()
779− .push(client.clone());
780− (name.clone(), Ok(client))
781− }
782− Err(err) => (name.to_owned(), Err(err)),
783− }
784− },
785− )
786 }
751 }
787
752
788 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
753 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
789− self.inner.values().flatten()
754+ self.inner.values().flatten().map(|(_, client)| client)
790 }
755 }
791}
756}
792
757
@@ -868,28 +833,26 @@ impl LspProgressMap {
868 }
833 }
869}
834}
870
835
871−struct NewClient(Arc<Client>, UnboundedReceiver<(usize, Call)>);
836+struct NewClientResult(Arc<Client>, UnboundedReceiver<(usize, Call)>);
872
837
873/// start_client takes both a LanguageConfiguration and a LanguageServerConfiguration to ensure that
838/// start_client takes both a LanguageConfiguration and a LanguageServerConfiguration to ensure that
874/// it is only called when it makes sense.
839/// it is only called when it makes sense.
875fn start_client(
840fn start_client(
876 id: usize,
841 id: usize,
877− name: String,
878 config: &LanguageConfiguration,
842 config: &LanguageConfiguration,
879 ls_config: &LanguageServerConfiguration,
843 ls_config: &LanguageServerConfiguration,
880 doc_path: Option<&std::path::PathBuf>,
844 doc_path: Option<&std::path::PathBuf>,
881 root_dirs: &[PathBuf],
845 root_dirs: &[PathBuf],
882 enable_snippets: bool,
846 enable_snippets: bool,
883−) -> Result<NewClient> {
847+) -> Result<NewClientResult> {
884 let (client, incoming, initialize_notify) = Client::start(
848 let (client, incoming, initialize_notify) = Client::start(
885 &ls_config.command,
849 &ls_config.command,
886 &ls_config.args,
850 &ls_config.args,
887− ls_config.config.clone(),
851+ config.config.clone(),
888 ls_config.environment.clone(),
852 ls_config.environment.clone(),
889 &config.roots,
853 &config.roots,
890 config.workspace_lsp_roots.as_deref().unwrap_or(root_dirs),
854 config.workspace_lsp_roots.as_deref().unwrap_or(root_dirs),
891 id,
855 id,
892− name,
893 ls_config.timeout,
856 ls_config.timeout,
894 doc_path,
857 doc_path,
895 )?;
858 )?;
@@ -923,7 +886,7 @@ fn start_client(
923 initialize_notify.notify_one();
886 initialize_notify.notify_one();
924 });
887 });
925
888
926− Ok(NewClient(client, incoming))
889+ Ok(NewClientResult(client, incoming))
927}
890}
928
891
929/// Find an LSP workspace of a file using the following mechanism:
892/// Find an LSP workspace of a file using the following mechanism:
@@ -946,7 +909,7 @@ pub fn find_lsp_workspace(
946 let mut file = if file.is_absolute() {
909 let mut file = if file.is_absolute() {
947 file.to_path_buf()
910 file.to_path_buf()
948 } else {
911 } else {
949− let current_dir = helix_loader::current_working_dir();
912+ let current_dir = std::env::current_dir().expect("unable to determine current directory");
950 current_dir.join(file)
913 current_dir.join(file)
951 };
914 };
952 file = path::get_normalized_path(&file);
915 file = path::get_normalized_path(&file);
helix-lsp/src/transport.rs+21−52
@@ -38,7 +38,6 @@ enum ServerMessage {
38#[derive(Debug)]
38#[derive(Debug)]
39pub struct Transport {
39pub struct Transport {
40 id: usize,
40 id: usize,
41− name: String,
42 pending_requests: Mutex<HashMap<jsonrpc::Id, Sender<Result<Value>>>>,
41 pending_requests: Mutex<HashMap<jsonrpc::Id, Sender<Result<Value>>>>,
43}
42}
44
43
@@ -48,7 +47,6 @@ impl Transport {
48 server_stdin: BufWriter<ChildStdin>,
47 server_stdin: BufWriter<ChildStdin>,
49 server_stderr: BufReader<ChildStderr>,
48 server_stderr: BufReader<ChildStderr>,
50 id: usize,
49 id: usize,
51− name: String,
52 ) -> (
50 ) -> (
53 UnboundedReceiver<(usize, jsonrpc::Call)>,
51 UnboundedReceiver<(usize, jsonrpc::Call)>,
54 UnboundedSender<Payload>,
52 UnboundedSender<Payload>,
@@ -60,7 +58,6 @@ impl Transport {
60
58
61 let transport = Self {
59 let transport = Self {
62 id,
60 id,
63− name,
64 pending_requests: Mutex::new(HashMap::default()),
61 pending_requests: Mutex::new(HashMap::default()),
65 };
62 };
66
63
@@ -86,7 +83,6 @@ impl Transport {
86 async fn recv_server_message(
83 async fn recv_server_message(
87 reader: &mut (impl AsyncBufRead + Unpin + Send),
84 reader: &mut (impl AsyncBufRead + Unpin + Send),
88 buffer: &mut String,
85 buffer: &mut String,
89− language_server_name: &str,
90 ) -> Result<ServerMessage> {
86 ) -> Result<ServerMessage> {
91 let mut content_length = None;
87 let mut content_length = None;
92 loop {
88 loop {
@@ -128,7 +124,7 @@ impl Transport {
128 reader.read_exact(&mut content).await?;
124 reader.read_exact(&mut content).await?;
129 let msg = std::str::from_utf8(&content).context("invalid utf8 from server")?;
125 let msg = std::str::from_utf8(&content).context("invalid utf8 from server")?;
130
126
131− info!("{language_server_name} <- {msg}");
127+ info!("<- {}", msg);
132
128
133 // try parsing as output (server response) or call (server request)
129 // try parsing as output (server response) or call (server request)
134 let output: serde_json::Result<ServerMessage> = serde_json::from_str(msg);
130 let output: serde_json::Result<ServerMessage> = serde_json::from_str(msg);
@@ -139,13 +135,12 @@ impl Transport {
139 async fn recv_server_error(
135 async fn recv_server_error(
140 err: &mut (impl AsyncBufRead + Unpin + Send),
136 err: &mut (impl AsyncBufRead + Unpin + Send),
141 buffer: &mut String,
137 buffer: &mut String,
142− language_server_name: &str,
143 ) -> Result<()> {
138 ) -> Result<()> {
144 buffer.truncate(0);
139 buffer.truncate(0);
145 if err.read_line(buffer).await? == 0 {
140 if err.read_line(buffer).await? == 0 {
146 return Err(Error::StreamClosed);
141 return Err(Error::StreamClosed);
147 };
142 };
148− error!("{language_server_name} err <- {buffer:?}");
143+ error!("err <- {:?}", buffer);
149
144
150 Ok(())
145 Ok(())
151 }
146 }
@@ -167,17 +162,15 @@ impl Transport {
167 Payload::Notification(value) => serde_json::to_string(&value)?,
162 Payload::Notification(value) => serde_json::to_string(&value)?,
168 Payload::Response(error) => serde_json::to_string(&error)?,
163 Payload::Response(error) => serde_json::to_string(&error)?,
169 };
164 };
170− self.send_string_to_server(server_stdin, json, &self.name)
165+ self.send_string_to_server(server_stdin, json).await
171− .await
172 }
166 }
173
167
174 async fn send_string_to_server(
168 async fn send_string_to_server(
175 &self,
169 &self,
176 server_stdin: &mut BufWriter<ChildStdin>,
170 server_stdin: &mut BufWriter<ChildStdin>,
177 request: String,
171 request: String,
178− language_server_name: &str,
179 ) -> Result<()> {
172 ) -> Result<()> {
180− info!("{language_server_name} -> {request}");
173+ info!("-> {}", request);
181
174
182 // send the headers
175 // send the headers
183 server_stdin
176 server_stdin
@@ -196,13 +189,9 @@ impl Transport {
196 &self,
189 &self,
197 client_tx: &UnboundedSender<(usize, jsonrpc::Call)>,
190 client_tx: &UnboundedSender<(usize, jsonrpc::Call)>,
198 msg: ServerMessage,
191 msg: ServerMessage,
199− language_server_name: &str,
200 ) -> Result<()> {
192 ) -> Result<()> {
201 match msg {
193 match msg {
202− ServerMessage::Output(output) => {
203− self.process_request_response(output, language_server_name)
194+ ServerMessage::Output(output) => self.process_request_response(output).await?,
204− .await?
205− }
206 ServerMessage::Call(call) => {
195 ServerMessage::Call(call) => {
207 client_tx
196 client_tx
208 .send((self.id, call))
197 .send((self.id, call))
@@ -213,18 +202,14 @@ impl Transport {
213 Ok(())
202 Ok(())
214 }
203 }
215
204
216− async fn process_request_response(
205+ async fn process_request_response(&self, output: jsonrpc::Output) -> Result<()> {
217− &self,
218− output: jsonrpc::Output,
219− language_server_name: &str,
220− ) -> Result<()> {
221 let (id, result) = match output {
206 let (id, result) = match output {
222 jsonrpc::Output::Success(jsonrpc::Success { id, result, .. }) => {
207 jsonrpc::Output::Success(jsonrpc::Success { id, result, .. }) => {
223− info!("{language_server_name} <- {}", result);
208+ info!("<- {}", result);
224 (id, Ok(result))
209 (id, Ok(result))
225 }
210 }
226 jsonrpc::Output::Failure(jsonrpc::Failure { id, error, .. }) => {
211 jsonrpc::Output::Failure(jsonrpc::Failure { id, error, .. }) => {
227− error!("{language_server_name} <- {error}");
212+ error!("<- {}", error);
228 (id, Err(error.into()))
213 (id, Err(error.into()))
229 }
214 }
230 };
215 };
@@ -255,17 +240,12 @@ impl Transport {
255 ) {
240 ) {
256 let mut recv_buffer = String::new();
241 let mut recv_buffer = String::new();
257 loop {
242 loop {
258− match Self::recv_server_message(&mut server_stdout, &mut recv_buffer, &transport.name)
243+ match Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await {
259− .await
260− {
261 Ok(msg) => {
244 Ok(msg) => {
262− match transport
263− .process_server_message(&client_tx, msg, &transport.name)
245+ match transport.process_server_message(&client_tx, msg).await {
264− .await
265− {
266 Ok(_) => {}
246 Ok(_) => {}
267 Err(err) => {
247 Err(err) => {
268− error!("{} err: <- {err:?}", transport.name);
248+ error!("err: <- {:?}", err);
269 break;
249 break;
270 }
250 }
271 };
251 };
@@ -290,7 +270,7 @@ impl Transport {
290 params: jsonrpc::Params::None,
270 params: jsonrpc::Params::None,
291 }));
271 }));
292 match transport
272 match transport
293− .process_server_message(&client_tx, notification, &transport.name)
273+ .process_server_message(&client_tx, notification)
294 .await
274 .await
295 {
275 {
296 Ok(_) => {}
276 Ok(_) => {}
@@ -301,22 +281,20 @@ impl Transport {
301 break;
281 break;
302 }
282 }
303 Err(err) => {
283 Err(err) => {
304− error!("{} err: <- {err:?}", transport.name);
284+ error!("err: <- {:?}", err);
305 break;
285 break;
306 }
286 }
307 }
287 }
308 }
288 }
309 }
289 }
310
290
311− async fn err(transport: Arc<Self>, mut server_stderr: BufReader<ChildStderr>) {
291+ async fn err(_transport: Arc<Self>, mut server_stderr: BufReader<ChildStderr>) {
312 let mut recv_buffer = String::new();
292 let mut recv_buffer = String::new();
313 loop {
293 loop {
314− match Self::recv_server_error(&mut server_stderr, &mut recv_buffer, &transport.name)
294+ match Self::recv_server_error(&mut server_stderr, &mut recv_buffer).await {
315− .await
316− {
317 Ok(_) => {}
295 Ok(_) => {}
318 Err(err) => {
296 Err(err) => {
319− error!("{} err: <- {err:?}", transport.name);
297+ error!("err: <- {:?}", err);
320 break;
298 break;
321 }
299 }
322 }
300 }
@@ -353,11 +331,6 @@ impl Transport {
353 }
331 }
354 }
332 }
355
333
356− fn is_shutdown(payload: &Payload) -> bool {
357− use lsp_types::request::{Request, Shutdown};
358− matches!(payload, Payload::Request { value: jsonrpc::MethodCall { method, .. }, .. } if method == Shutdown::METHOD)
359− }
360−
361 // TODO: events that use capabilities need to do the right thing
334 // TODO: events that use capabilities need to do the right thing
362
335
363 loop {
336 loop {
@@ -375,11 +348,10 @@ impl Transport {
375 method: lsp_types::notification::Initialized::METHOD.to_string(),
348 method: lsp_types::notification::Initialized::METHOD.to_string(),
376 params: jsonrpc::Params::None,
349 params: jsonrpc::Params::None,
377 }));
350 }));
378− let language_server_name = &transport.name;
379− match transport.process_server_message(&client_tx, notification, language_server_name).await {
351+ match transport.process_server_message(&client_tx, notification).await {
380 Ok(_) => {}
352 Ok(_) => {}
381 Err(err) => {
353 Err(err) => {
382− error!("{language_server_name} err: <- {err:?}");
354+ error!("err: <- {:?}", err);
383 }
355 }
384 }
356 }
385
357
@@ -389,17 +361,14 @@ impl Transport {
389 match transport.send_payload_to_server(&mut server_stdin, msg).await {
361 match transport.send_payload_to_server(&mut server_stdin, msg).await {
390 Ok(_) => {}
362 Ok(_) => {}
391 Err(err) => {
363 Err(err) => {
392− error!("{language_server_name} err: <- {err:?}");
364+ error!("err: <- {:?}", err);
393 }
365 }
394 }
366 }
395 }
367 }
396 }
368 }
397 msg = client_rx.recv() => {
369 msg = client_rx.recv() => {
398 if let Some(msg) = msg {
370 if let Some(msg) = msg {
399− if is_pending && is_shutdown(&msg) {
400− log::info!("Language server not initialized, shutting down");
401− break;
402− } else if is_pending && !is_initialize(&msg) {
371+ if is_pending && !is_initialize(&msg) {
403 // ignore notifications
372 // ignore notifications
404 if let Payload::Notification(_) = msg {
373 if let Payload::Notification(_) = msg {
405 continue;
374 continue;
@@ -411,7 +380,7 @@ impl Transport {
411 match transport.send_payload_to_server(&mut server_stdin, msg).await {
380 match transport.send_payload_to_server(&mut server_stdin, msg).await {
412 Ok(_) => {}
381 Ok(_) => {}
413 Err(err) => {
382 Err(err) => {
414− error!("{} err: <- {err:?}", transport.name);
383+ error!("err: <- {:?}", err);
415 }
384 }
416 }
385 }
417 }
386 }
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−33
@@ -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.17"
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,17 @@ 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.142"
77−
78−[target.'cfg(target_os = "macos")'.dependencies]
79−crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] }
80
72
81[build-dependencies]
73[build-dependencies]
82−helix-loader = { path = "../helix-loader" }
74+helix-loader = { version = "0.6", path = "../helix-loader" }
83
75
84[dev-dependencies]
76[dev-dependencies]
85−smallvec = "1.11"
77+smallvec = "1.10"
86−indoc = "2.0.4"
78+indoc = "2.0.1"
87−tempfile = "3.8.1"
79+tempfile = "3.4.0"
helix-term/src/application.rs+189−285
@@ -1,16 +1,11 @@
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,
8};
7};
9−use helix_lsp::{
10− lsp::{self, notification::Notification},
11− util::lsp_pos_to_pos,
8+use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
12− LspProgressMap,
13−};
14use helix_view::{
9use helix_view::{
15 align_view,
10 align_view,
16 document::DocumentSavedEventResult,
11 document::DocumentSavedEventResult,
@@ -34,9 +29,12 @@ use crate::{
34};
29};
35
30
36use log::{debug, error, warn};
31use log::{debug, error, warn};
37−#[cfg(not(feature = "integration"))]
38−use std::io::stdout;
32+use std::{
39−use std::{collections::btree_map::Entry, io::stdin, path::Path, sync::Arc};
33+ io::{stdin, stdout},
34+ path::Path,
35+ sync::Arc,
36+ time::{Duration, Instant},
37+};
40
38
41use anyhow::{Context, Error};
39use anyhow::{Context, Error};
42
40
@@ -46,6 +44,8 @@ use {signal_hook::consts::signal, signal_hook_tokio::Signals};
46#[cfg(windows)]
44#[cfg(windows)]
47type Signals = futures_util::stream::Empty<()>;
45type Signals = futures_util::stream::Empty<()>;
48
46
47+const LSP_DEADLINE: Duration = Duration::from_millis(16);
48+
49#[cfg(not(feature = "integration"))]
49#[cfg(not(feature = "integration"))]
50use tui::backend::CrosstermBackend;
50use tui::backend::CrosstermBackend;
51
51
@@ -75,6 +75,7 @@ pub struct Application {
75 signals: Signals,
75 signals: Signals,
76 jobs: Jobs,
76 jobs: Jobs,
77 lsp_progress: LspProgressMap,
77 lsp_progress: LspProgressMap,
78+ last_render: Instant,
78}
79}
79
80
80#[cfg(feature = "integration")]
81#[cfg(feature = "integration")]
@@ -161,21 +162,17 @@ impl Application {
161 let path = helix_loader::runtime_file(Path::new("tutor"));
162 let path = helix_loader::runtime_file(Path::new("tutor"));
162 editor.open(&path, Action::VerticalSplit)?;
163 editor.open(&path, Action::VerticalSplit)?;
163 // Unset path to prevent accidentally saving to the original tutor file.
164 // Unset path to prevent accidentally saving to the original tutor file.
164− doc_mut!(editor).set_path(None);
165+ doc_mut!(editor).set_path(None)?;
165 } else if !args.files.is_empty() {
166 } else if !args.files.is_empty() {
166− let mut files_it = args.files.into_iter().peekable();
167+ 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()) {
168+ if first.is_dir() {
169+ std::env::set_current_dir(first).context("set current dir")?;
170+ editor.new_file(Action::VerticalSplit);
170− let picker = ui::file_picker(first, &config.load().editor);
171+ let picker = ui::file_picker(".".into(), &config.load().editor);
171 compositor.push(Box::new(overlaid(picker)));
172 compositor.push(Box::new(overlaid(picker)));
172− }
173+ } 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;
174+ let nr_of_files = args.files.len();
177− for (file, pos) in files_it {
175+ for (i, (file, pos)) in args.files.into_iter().enumerate() {
178− nr_of_files += 1;
179 if file.is_dir() {
176 if file.is_dir() {
180 return Err(anyhow::anyhow!(
177 return Err(anyhow::anyhow!(
181 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
178 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
@@ -187,7 +184,7 @@ impl Application {
187 // option. If neither of those two arguments are passed
184 // option. If neither of those two arguments are passed
188 // in, just load the files normally.
185 // in, just load the files normally.
189 let action = match args.split {
186 let action = match args.split {
190− _ if nr_of_files == 1 => Action::VerticalSplit,
187+ _ if i == 0 => Action::VerticalSplit,
191 Some(Layout::Vertical) => Action::VerticalSplit,
188 Some(Layout::Vertical) => Action::VerticalSplit,
192 Some(Layout::Horizontal) => Action::HorizontalSplit,
189 Some(Layout::Horizontal) => Action::HorizontalSplit,
193 None => Action::Load,
190 None => Action::Load,
@@ -214,11 +211,14 @@ impl Application {
214 // does not affect views without pos since it is at the top
211 // does not affect views without pos since it is at the top
215 let (view, doc) = current!(editor);
212 let (view, doc) = current!(editor);
216 align_view(doc, view, Align::Center);
213 align_view(doc, view, Align::Center);
217− } else {
218− editor.new_file(Action::VerticalSplit);
219 }
214 }
220 } else if stdin().is_tty() || cfg!(feature = "integration") {
215 } else if stdin().is_tty() || cfg!(feature = "integration") {
221 editor.new_file(Action::VerticalSplit);
216 editor.new_file(Action::VerticalSplit);
217+ } else if cfg!(target_os = "macos") {
218+ // On Linux and Windows, we allow the output of a command to be piped into the new buffer.
219+ // This doesn't currently work on macOS because of the following issue:
220+ // https://github.com/crossterm-rs/crossterm/issues/500
221+ anyhow::bail!("Piping into helix-term is currently not supported on macOS");
222 } else {
222 } else {
223 editor
223 editor
224 .new_file_from_stdin(Action::VerticalSplit)
224 .new_file_from_stdin(Action::VerticalSplit)
@@ -230,14 +230,8 @@ impl Application {
230 #[cfg(windows)]
230 #[cfg(windows)]
231 let signals = futures_util::stream::empty();
231 let signals = futures_util::stream::empty();
232 #[cfg(not(windows))]
232 #[cfg(not(windows))]
233− let signals = Signals::new([
233+ let signals = Signals::new([signal::SIGTSTP, signal::SIGCONT, signal::SIGUSR1])
234− signal::SIGTSTP,
235− signal::SIGCONT,
236− signal::SIGUSR1,
237− signal::SIGTERM,
238− signal::SIGINT,
239− ])
240− .context("build signal handler")?;
234+ .context("build signal handler")?;
241
235
242 let app = Self {
236 let app = Self {
243 compositor,
237 compositor,
@@ -252,25 +246,29 @@ impl Application {
252 signals,
246 signals,
253 jobs: Jobs::new(),
247 jobs: Jobs::new(),
254 lsp_progress: LspProgressMap::new(),
248 lsp_progress: LspProgressMap::new(),
249+ last_render: Instant::now(),
255 };
250 };
256
251
257 Ok(app)
252 Ok(app)
258 }
253 }
259
254
260 async fn render(&mut self) {
255 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 {
256 let mut cx = crate::compositor::Context {
267 editor: &mut self.editor,
257 editor: &mut self.editor,
268 jobs: &mut self.jobs,
258 jobs: &mut self.jobs,
269 scroll: None,
259 scroll: None,
270 };
260 };
271
261
272− helix_event::start_frame();
262+ // Acquire mutable access to the redraw_handle lock
263+ // to ensure that there are no tasks running that want to block rendering
264+ drop(cx.editor.redraw_handle.1.write().await);
273 cx.editor.needs_redraw = false;
265 cx.editor.needs_redraw = false;
266+ {
267+ // exhaust any leftover redraw notifications
268+ let notify = cx.editor.redraw_handle.0.notified();
269+ tokio::pin!(notify);
270+ notify.enable();
271+ }
274
272
275 let area = self
273 let area = self
276 .terminal
274 .terminal
@@ -292,9 +290,10 @@ impl Application {
292
290
293 pub async fn event_loop<S>(&mut self, input_stream: &mut S)
291 pub async fn event_loop<S>(&mut self, input_stream: &mut S)
294 where
292 where
295− S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
293+ S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
296 {
294 {
297 self.render().await;
295 self.render().await;
296+ self.last_render = Instant::now();
298
297
299 loop {
298 loop {
300 if !self.event_loop_until_idle(input_stream).await {
299 if !self.event_loop_until_idle(input_stream).await {
@@ -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() {
@@ -318,9 +317,7 @@ impl Application {
318 biased;
317 biased;
319
318
320 Some(signal) = self.signals.next() => {
319 Some(signal) = self.signals.next() => {
321− if !self.handle_signals(signal).await {
320+ self.handle_signals(signal).await;
322− return false;
323− };
324 }
321 }
325 Some(event) = input_stream.next() => {
322 Some(event) = input_stream.next() => {
326 self.handle_terminal_events(event).await;
323 self.handle_terminal_events(event).await;
@@ -444,12 +441,10 @@ impl Application {
444
441
445 #[cfg(windows)]
442 #[cfg(windows)]
446 // no signal handling available on windows
443 // no signal handling available on windows
447− pub async fn handle_signals(&mut self, _signal: ()) -> bool {
444+ pub async fn handle_signals(&mut self, _signal: ()) {}
448− true
449− }
450
445
451 #[cfg(not(windows))]
446 #[cfg(not(windows))]
452− pub async fn handle_signals(&mut self, signal: i32) -> bool {
447+ pub async fn handle_signals(&mut self, signal: i32) {
453 match signal {
448 match signal {
454 signal::SIGTSTP => {
449 signal::SIGTSTP => {
455 self.restore_term().unwrap();
450 self.restore_term().unwrap();
@@ -503,14 +498,8 @@ impl Application {
503 self.refresh_config();
498 self.refresh_config();
504 self.render().await;
499 self.render().await;
505 }
500 }
506− signal::SIGTERM | signal::SIGINT => {
507− self.restore_term().unwrap();
508− return false;
509− }
510 _ => unreachable!(),
501 _ => unreachable!(),
511 }
502 }
512−
513− true
514 }
503 }
515
504
516 pub async fn handle_idle_timeout(&mut self) {
505 pub async fn handle_idle_timeout(&mut self) {
@@ -558,7 +547,16 @@ impl Application {
558 let bytes = doc_save_event.text.len_bytes();
547 let bytes = doc_save_event.text.len_bytes();
559
548
560 if doc.path() != Some(&doc_save_event.path) {
549 if doc.path() != Some(&doc_save_event.path) {
561− doc.set_path(Some(&doc_save_event.path));
550+ if let Err(err) = doc.set_path(Some(&doc_save_event.path)) {
551+ log::error!(
552+ "error setting path for doc '{:?}': {}",
553+ doc.path(),
554+ err.to_string(),
555+ );
556+
557+ self.editor.set_error(err.to_string());
558+ return;
559+ }
562
560
563 let loader = self.editor.syn_loader.clone();
561 let loader = self.editor.syn_loader.clone();
564
562
@@ -566,7 +564,7 @@ impl Application {
566 let doc = doc_mut!(self.editor, &doc_save_event.doc_id);
564 let doc = doc_mut!(self.editor, &doc_save_event.doc_id);
567 let id = doc.id();
565 let id = doc.id();
568 doc.detect_language(loader);
566 doc.detect_language(loader);
569− self.editor.refresh_language_servers(id);
567+ let _ = self.editor.refresh_language_server(id);
570 }
568 }
571
569
572 // TODO: fix being overwritten by lsp
570 // TODO: fix being overwritten by lsp
@@ -594,7 +592,12 @@ impl Application {
594 EditorEvent::LanguageServerMessage((id, call)) => {
592 EditorEvent::LanguageServerMessage((id, call)) => {
595 self.handle_language_server_message(call, id).await;
593 self.handle_language_server_message(call, id).await;
596 // limit render calls for fast language server messages
594 // limit render calls for fast language server messages
597− helix_event::request_redraw();
595+ let last = self.editor.language_servers.incoming.is_empty();
596+
597+ if last || self.last_render.elapsed() > LSP_DEADLINE {
598+ self.render().await;
599+ self.last_render = Instant::now();
600+ }
598 }
601 }
599 EditorEvent::DebuggerEvent(payload) => {
602 EditorEvent::DebuggerEvent(payload) => {
600 let needs_render = self.editor.handle_debugger_message(payload).await;
603 let needs_render = self.editor.handle_debugger_message(payload).await;
@@ -602,9 +605,6 @@ impl Application {
602 self.render().await;
605 self.render().await;
603 }
606 }
604 }
607 }
605− EditorEvent::Redraw => {
606− self.render().await;
607− }
608 EditorEvent::IdleTimer => {
608 EditorEvent::IdleTimer => {
609 self.editor.clear_idle_timer();
609 self.editor.clear_idle_timer();
610 self.handle_idle_timeout().await;
610 self.handle_idle_timeout().await;
@@ -619,7 +619,10 @@ impl Application {
619 false
619 false
620 }
620 }
621
621
622− pub async fn handle_terminal_events(&mut self, event: std::io::Result<CrosstermEvent>) {
622+ pub async fn handle_terminal_events(
623+ &mut self,
624+ event: Result<CrosstermEvent, crossterm::ErrorKind>,
625+ ) {
623 let mut cx = crate::compositor::Context {
626 let mut cx = crate::compositor::Context {
624 editor: &mut self.editor,
627 editor: &mut self.editor,
625 jobs: &mut self.jobs,
628 jobs: &mut self.jobs,
@@ -659,18 +662,6 @@ impl Application {
659 ) {
662 ) {
660 use helix_lsp::{Call, MethodCall, Notification};
663 use helix_lsp::{Call, MethodCall, Notification};
661
664
662− macro_rules! language_server {
663− () => {
664− match self.editor.language_server_by_id(server_id) {
665− Some(language_server) => language_server,
666− None => {
667− warn!("can't find language server with id `{}`", server_id);
668− return;
669− }
670− }
671− };
672− }
673−
674 match call {
665 match call {
675 Call::Notification(helix_lsp::jsonrpc::Notification { method, params, .. }) => {
666 Call::Notification(helix_lsp::jsonrpc::Notification { method, params, .. }) => {
676 let notification = match Notification::parse(&method, params) {
667 let notification = match Notification::parse(&method, params) {
@@ -686,7 +677,14 @@ impl Application {
686
677
687 match notification {
678 match notification {
688 Notification::Initialized => {
679 Notification::Initialized => {
689− let language_server = language_server!();
680+ let language_server =
681+ match self.editor.language_servers.get_by_id(server_id) {
682+ Some(language_server) => language_server,
683+ None => {
684+ warn!("can't find language server with id `{}`", server_id);
685+ return;
686+ }
687+ };
690
688
691 // Trigger a workspace/didChangeConfiguration notification after initialization.
689 // Trigger a workspace/didChangeConfiguration notification after initialization.
692 // This might not be required by the spec but Neovim does this as well, so it's
690 // This might not be required by the spec but Neovim does this as well, so it's
@@ -695,10 +693,9 @@ impl Application {
695 tokio::spawn(language_server.did_change_configuration(config.clone()));
693 tokio::spawn(language_server.did_change_configuration(config.clone()));
696 }
694 }
697
695
698− let docs = self
696+ let docs = self.editor.documents().filter(|doc| {
699− .editor
697+ doc.language_server().map(|server| server.id()) == Some(server_id)
700− .documents()
698+ });
701− .filter(|doc| doc.supports_language_server(server_id));
702
699
703 // trigger textDocument/didOpen for docs that are already open
700 // trigger textDocument/didOpen for docs that are already open
704 for doc in docs {
701 for doc in docs {
@@ -726,77 +723,40 @@ impl Application {
726 return;
723 return;
727 }
724 }
728 };
725 };
729− let language_server = language_server!();
726+ let doc = self.editor.document_by_path_mut(&path).filter(|doc| {
730− if !language_server.is_initialized() {
731− log::error!("Discarding publishDiagnostic notification sent by an uninitialized server: {}", language_server.name());
732− return;
733− }
734− let offset_encoding = language_server.offset_encoding();
735− // have to inline the function because of borrow checking...
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 {
727+ if let Some(version) = params.version {
740− if version != doc.version() {
728+ if version != doc.version() {
741− log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
729+ log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
742− return false;
730+ return false;
743− }
744 }
731 }
745− true
746− });
732+ }
747
733
748− if let Some(doc) = doc {
734+ true
749− let lang_conf = doc.language.clone();
735+ });
750− let text = doc.text().clone();
751
736
752− let mut unchaged_diag_sources_ = Vec::new();
753− if let Some(lang_conf) = &lang_conf {
737+ if let Some(doc) = doc {
754− if let Some(old_diagnostics) =
738+ let lang_conf = doc.language_config();
755− self.editor.diagnostics.get(&params.uri)
739+ let text = doc.text();
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
740
783− let unchaged_diag_sources = &unchaged_diag_sources_;
741+ let diagnostics = params
784− let diagnostics =
742+ .diagnostics
743+ .iter()
785− params.diagnostics.iter().filter_map(move |diagnostic| {
744+ .filter_map(|diagnostic| {
786 use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
745 use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
787 use lsp::DiagnosticSeverity;
746 use lsp::DiagnosticSeverity;
788
747
789− if diagnostic.source.as_ref().map_or(false, |source| {
748+ let language_server = if let Some(language_server) = doc.language_server() {
790− unchaged_diag_sources.contains(source)
749+ language_server
791− }) {
750+ } else {
751+ log::warn!("Discarding diagnostic because language server is not initialized: {:?}", diagnostic);
792 return None;
752 return None;
793− }
753+ };
794
754
795 // TODO: convert inside server
755 // TODO: convert inside server
796 let start = if let Some(start) = lsp_pos_to_pos(
756 let start = if let Some(start) = lsp_pos_to_pos(
797− &text,
757+ text,
798 diagnostic.range.start,
758 diagnostic.range.start,
799− offset_encoding,
759+ language_server.offset_encoding(),
800 ) {
760 ) {
801 start
761 start
802 } else {
762 } else {
@@ -804,14 +764,17 @@ impl Application {
804 return None;
764 return None;
805 };
765 };
806
766
807− let end = if let Some(end) =
767+ let end = if let Some(end) = lsp_pos_to_pos(
768+ text,
808− lsp_pos_to_pos(&text, diagnostic.range.end, offset_encoding)
769+ diagnostic.range.end,
770+ language_server.offset_encoding(),
809− {
771+ ) {
810 end
772 end
811 } else {
773 } else {
812 log::warn!("lsp position out of bounds - {:?}", diagnostic);
774 log::warn!("lsp position out of bounds - {:?}", diagnostic);
813 return None;
775 return None;
814 };
776 };
777+
815 let severity =
778 let severity =
816 diagnostic.severity.map(|severity| match severity {
779 diagnostic.severity.map(|severity| match severity {
817 DiagnosticSeverity::ERROR => Error,
780 DiagnosticSeverity::ERROR => Error,
@@ -824,7 +787,7 @@ impl Application {
824 ),
787 ),
825 });
788 });
826
789
827− if let Some(lang_conf) = &lang_conf {
790+ if let Some(lang_conf) = lang_conf {
828 if let Some(severity) = severity {
791 if let Some(severity) = severity {
829 if severity < lang_conf.diagnostic_severity {
792 if severity < lang_conf.diagnostic_severity {
830 return None;
793 return None;
@@ -844,36 +807,22 @@ impl Application {
844 None => None,
807 None => None,
845 };
808 };
846
809
847− let tags = if let Some(tags) = &diagnostic.tags {
810+ let tags = if let Some(ref tags) = diagnostic.tags {
848− let new_tags = tags
811+ let new_tags = tags.iter().filter_map(|tag| {
849− .iter()
850− .filter_map(|tag| match *tag {
812+ match *tag {
851− lsp::DiagnosticTag::DEPRECATED => {
852− Some(DiagnosticTag::Deprecated)
813+ lsp::DiagnosticTag::DEPRECATED => Some(DiagnosticTag::Deprecated),
853− }
854− lsp::DiagnosticTag::UNNECESSARY => {
855− Some(DiagnosticTag::Unnecessary)
814+ lsp::DiagnosticTag::UNNECESSARY => Some(DiagnosticTag::Unnecessary),
856− }
857− _ => None,
815+ _ => None
858− })
816+ }
859− .collect();
817+ }).collect();
860
818
861 new_tags
819 new_tags
862 } else {
820 } else {
863 Vec::new()
821 Vec::new()
864 };
822 };
865
823
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 {
824 Some(Diagnostic {
873 range: Range { start, end },
825 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,
826 line: diagnostic.range.start.line as usize,
878 message: diagnostic.message.clone(),
827 message: diagnostic.message.clone(),
879 severity,
828 severity,
@@ -881,35 +830,25 @@ impl Application {
881 tags,
830 tags,
882 source: diagnostic.source.clone(),
831 source: diagnostic.source.clone(),
883 data: diagnostic.data.clone(),
832 data: diagnostic.data.clone(),
884− language_server_id: server_id,
885 })
833 })
886− });
834+ })
835+ .collect();
887
836
888− doc.replace_diagnostics(diagnostics, unchaged_diag_sources, server_id);
837+ doc.set_diagnostics(diagnostics);
889 }
838 }
890
839
891− let diagnostics = params.diagnostics.into_iter().map(|d| (d, server_id));
840+ // Sort diagnostics first by severity and then by line numbers.
841+ // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
842+ params
843+ .diagnostics
844+ .sort_unstable_by_key(|d| (d.severity, d.range.start));
892
845
893 // Insert the original lsp::Diagnostics here because we may have no open document
846 // 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.
847 // 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.
848 // When using them later in the diagnostics picker, we calculate them on-demand.
896− let diagnostics = match self.editor.diagnostics.entry(params.uri) {
849+ self.editor
897− Entry::Occupied(o) => {
850+ .diagnostics
898− let current_diagnostics = o.into_mut();
851+ .insert(params.uri, params.diagnostics);
899− // 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);
901− current_diagnostics.extend(diagnostics);
902− current_diagnostics
903− // Sort diagnostics first by severity and then by line numbers.
904− }
905− Entry::Vacant(v) => v.insert(diagnostics.collect()),
906− };
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 }
852 }
914 Notification::ShowMessage(params) => {
853 Notification::ShowMessage(params) => {
915 log::warn!("unhandled window/showMessage: {:?}", params);
854 log::warn!("unhandled window/showMessage: {:?}", params);
@@ -1006,18 +945,24 @@ impl Application {
1006 Notification::Exit => {
945 Notification::Exit => {
1007 self.editor.set_status("Language server exited");
946 self.editor.set_status("Language server exited");
1008
947
1009− // LSPs may produce diagnostics for files that haven't been opened in helix,
1010− // we need to clear those and remove the entries from the list if this leads to
1011− // an empty diagnostic list for said files
1012− for diags in self.editor.diagnostics.values_mut() {
1013− diags.retain(|(_, lsp_id)| *lsp_id != server_id);
1014− }
1015−
1016− self.editor.diagnostics.retain(|_, diags| !diags.is_empty());
1017−
1018 // Clear any diagnostics for documents with this server open.
948 // Clear any diagnostics for documents with this server open.
1019− for doc in self.editor.documents_mut() {
949+ let urls: Vec<_> = self
1020− doc.clear_diagnostics(server_id);
950+ .editor
951+ .documents_mut()
952+ .filter_map(|doc| {
953+ if doc.language_server().map(|server| server.id())
954+ == Some(server_id)
955+ {
956+ doc.set_diagnostics(Vec::new());
957+ doc.url()
958+ } else {
959+ None
960+ }
961+ })
962+ .collect();
963+
964+ for url in urls {
965+ self.editor.diagnostics.remove(&url);
1021 }
966 }
1022
967
1023 // Remove the language server from the registry.
968 // Remove the language server from the registry.
@@ -1068,48 +1013,47 @@ impl Application {
1068 Ok(serde_json::Value::Null)
1013 Ok(serde_json::Value::Null)
1069 }
1014 }
1070 Ok(MethodCall::ApplyWorkspaceEdit(params)) => {
1015 Ok(MethodCall::ApplyWorkspaceEdit(params)) => {
1071− let language_server = language_server!();
1072− if language_server.is_initialized() {
1073− let offset_encoding = language_server.offset_encoding();
1074− let res = apply_workspace_edit(
1016+ let res = apply_workspace_edit(
1075− &mut self.editor,
1017+ &mut self.editor,
1076− offset_encoding,
1018+ helix_lsp::OffsetEncoding::Utf8,
1077− &params.edit,
1019+ &params.edit,
1078− );
1020+ );
1079−
1021+
1080− Ok(json!(lsp::ApplyWorkspaceEditResponse {
1022+ Ok(json!(lsp::ApplyWorkspaceEditResponse {
1081− applied: res.is_ok(),
1023+ applied: res.is_ok(),
1082− failure_reason: res.as_ref().err().map(|err| err.kind.to_string()),
1024+ failure_reason: res.as_ref().err().map(|err| err.kind.to_string()),
1083− failed_change: res
1025+ failed_change: res
1084− .as_ref()
1026+ .as_ref()
1085− .err()
1027+ .err()
1086− .map(|err| err.failed_change_idx as u32),
1028+ .map(|err| err.failed_change_idx as u32),
1087− }))
1088− } else {
1089− Err(helix_lsp::jsonrpc::Error {
1090− code: helix_lsp::jsonrpc::ErrorCode::InvalidRequest,
1091− message: "Server must be initialized to request workspace edits"
1092− .to_string(),
1093− data: None,
1094− })
1029+ }))
1095− }
1096 }
1030 }
1097 Ok(MethodCall::WorkspaceFolders) => {
1031 Ok(MethodCall::WorkspaceFolders) => {
1032+ let language_server =
1033+ self.editor.language_servers.get_by_id(server_id).unwrap();
1034+
1098− Ok(json!(&*language_server!().workspace_folders().await))
1035+ Ok(json!(&*language_server.workspace_folders().await))
1099 }
1036 }
1100 Ok(MethodCall::WorkspaceConfiguration(params)) => {
1037 Ok(MethodCall::WorkspaceConfiguration(params)) => {
1101− let language_server = language_server!();
1102 let result: Vec<_> = params
1038 let result: Vec<_> = params
1103 .items
1039 .items
1104 .iter()
1040 .iter()
1105 .map(|item| {
1041 .map(|item| {
1106− let mut config = language_server.config()?;
1042+ let mut config = match &item.scope_uri {
1043+ Some(scope) => {
1044+ let path = scope.to_file_path().ok()?;
1045+ let doc = self.editor.document_by_path(path)?;
1046+ doc.language_config()?.config.as_ref()?
1047+ }
1048+ None => self
1049+ .editor
1050+ .language_servers
1051+ .get_by_id(server_id)?
1052+ .config()?,
1053+ };
1107 if let Some(section) = item.section.as_ref() {
1054 if let Some(section) = item.section.as_ref() {
1108− // for some reason some lsps send an empty string (observed in 'vscode-eslint-language-server')
1109− if !section.is_empty() {
1110− for part in section.split('.') {
1055+ for part in section.split('.') {
1111− config = config.get(part)?;
1056+ config = config.get(part)?;
1112− }
1113 }
1057 }
1114 }
1058 }
1115 Some(config)
1059 Some(config)
@@ -1117,68 +1061,28 @@ impl Application {
1117 .collect();
1061 .collect();
1118 Ok(json!(result))
1062 Ok(json!(result))
1119 }
1063 }
1120− Ok(MethodCall::RegisterCapability(params)) => {
1064+ Ok(MethodCall::RegisterCapability(_params)) => {
1121− if let Some(client) = self
1065+ log::warn!("Ignoring a client/registerCapability request because dynamic capability registration is not enabled. Please report this upstream to the language server");
1122− .editor
1123− .language_servers
1124− .iter_clients()
1125− .find(|client| client.id() == server_id)
1126− {
1127− for reg in params.registrations {
1128− match reg.method.as_str() {
1129− lsp::notification::DidChangeWatchedFiles::METHOD => {
1130− let Some(options) = reg.register_options else {
1131− continue;
1132− };
1133− let ops: lsp::DidChangeWatchedFilesRegistrationOptions =
1134− match serde_json::from_value(options) {
1135− Ok(ops) => ops,
1136− Err(err) => {
1137− log::warn!("Failed to deserialize DidChangeWatchedFilesRegistrationOptions: {err}");
1138− continue;
1139− }
1140− };
1141− self.editor.language_servers.file_event_handler.register(
1142− client.id(),
1143− Arc::downgrade(client),
1144− reg.id,
1145− ops,
1146− )
1147− }
1148− _ => {
1149− // Language Servers based on the `vscode-languageserver-node` library often send
1066+ // Language Servers based on the `vscode-languageserver-node` library often send
1150− // client/registerCapability even though we do not enable dynamic registration
1067+ // client/registerCapability even though we do not enable dynamic registration
1151− // for most capabilities. We should send a MethodNotFound JSONRPC error in this
1068+ // for any capabilities. We should send a MethodNotFound JSONRPC error in this
1152− // case but that rejects the registration promise in the server which causes an
1069+ // case but that rejects the registration promise in the server which causes an
1153− // exit. So we work around this by ignoring the request and sending back an OK
1070+ // exit. So we work around this by ignoring the request and sending back an OK
1154− // response.
1071+ // response.
1155− log::warn!("Ignoring a client/registerCapability request because dynamic capability registration is not enabled. Please report this upstream to the language server");
1156− }
1157− }
1158− }
1159− }
1160
1072
1161 Ok(serde_json::Value::Null)
1073 Ok(serde_json::Value::Null)
1162 }
1074 }
1163− Ok(MethodCall::UnregisterCapability(params)) => {
1075+ };
1164− for unreg in params.unregisterations {
1076+
1165− match unreg.method.as_str() {
1077+ let language_server = match self.editor.language_servers.get_by_id(server_id) {
1166− lsp::notification::DidChangeWatchedFiles::METHOD => {
1078+ Some(language_server) => language_server,
1167− self.editor
1079+ None => {
1168− .language_servers
1080+ warn!("can't find language server with id `{}`", server_id);
1169− .file_event_handler
1081+ return;
1170− .unregister(server_id, unreg.id);
1171− }
1172− _ => {
1173− log::warn!("Received unregistration request for unsupported method: {}", unreg.method);
1174− }
1175− }
1176− }
1177− Ok(serde_json::Value::Null)
1178 }
1082 }
1179 };
1083 };
1180
1084
1181− tokio::spawn(language_server!().reply(id, reply));
1085+ tokio::spawn(language_server.reply(id, reply));
1182 }
1086 }
1183 Call::Invalid { id } => log::error!("LSP invalid method call id={:?}", id),
1087 Call::Invalid { id } => log::error!("LSP invalid method call id={:?}", id),
1184 }
1088 }
@@ -1201,7 +1105,7 @@ impl Application {
1201
1105
1202 pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
1106 pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
1203 where
1107 where
1204− S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
1108+ S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
1205 {
1109 {
1206 self.claim_term().await?;
1110 self.claim_term().await?;
1207
1111
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+623−1048
@@ -23,17 +23,17 @@ use helix_core::{
23 regex::{self, Regex, RegexBuilder},
23 regex::{self, Regex, RegexBuilder},
24 search::{self, CharMatcher},
24 search::{self, CharMatcher},
25 selection, shellwords, surround,
25 selection, shellwords, surround,
26− syntax::LanguageServerFeature,
27 text_annotations::TextAnnotations,
26 text_annotations::TextAnnotations,
28 textobject,
27 textobject,
29 tree_sitter::Node,
28 tree_sitter::Node,
30 unicode::width::UnicodeWidthChar,
29 unicode::width::UnicodeWidthChar,
31− visual_offset_from_block, Deletion, LineEnding, Position, Range, Rope, RopeGraphemes,
30+ visual_offset_from_block, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice,
32− RopeReader, RopeSlice, Selection, SmallVec, Tendril, Transaction,
31+ Selection, SmallVec, Tendril, Transaction,
33};
32};
34use helix_view::{
33use helix_view::{
34+ clipboard::ClipboardType,
35 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
35 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
36− editor::{Action, CompleteAction},
36+ editor::{Action, Motion},
37 info::Info,
37 info::Info,
38 input::KeyEvent,
38 input::KeyEvent,
39 keyboard::KeyCode,
39 keyboard::KeyCode,
@@ -43,6 +43,7 @@ use helix_view::{
43};
43};
44
44
45use anyhow::{anyhow, bail, ensure, Context as _};
45use anyhow::{anyhow, bail, ensure, Context as _};
46+use fuzzy_matcher::FuzzyMatcher;
46use insert::*;
47use insert::*;
47use movement::Movement;
48use movement::Movement;
48
49
@@ -53,20 +54,15 @@ use crate::{
53 job::Callback,
54 job::Callback,
54 keymap::ReverseKeymap,
55 keymap::ReverseKeymap,
55 ui::{
56 ui::{
56− self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, CompletionItem, Picker,
57+ self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, FilePicker, Picker,
57 Popup, Prompt, PromptEvent,
58 Popup, Prompt, PromptEvent,
58 },
59 },
59};
60};
60
61
61use crate::job::{self, Jobs};
62use crate::job::{self, Jobs};
62−use futures_util::{stream::FuturesUnordered, TryStreamExt};
63+use futures_util::StreamExt;
63−use std::{
64− collections::{HashMap, HashSet},
64+use std::{collections::HashMap, fmt, future::Future};
65− fmt,
66− future::Future,
67− io::Read,
68− num::NonZeroUsize,
65+use std::{collections::HashSet, num::NonZeroUsize};
69−};
70
66
71use std::{
67use std::{
72 borrow::Cow,
68 borrow::Cow,
@@ -75,11 +71,11 @@ use std::{
75
71
76use once_cell::sync::Lazy;
72use once_cell::sync::Lazy;
77use serde::de::{self, Deserialize, Deserializer};
73use serde::de::{self, Deserialize, Deserializer};
78−use url::Url;
79
74
80use grep_regex::RegexMatcherBuilder;
75use grep_regex::RegexMatcherBuilder;
81use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
76use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
82use ignore::{DirEntry, WalkBuilder, WalkState};
77use ignore::{DirEntry, WalkBuilder, WalkState};
78+use tokio_stream::wrappers::UnboundedReceiverStream;
83
79
84pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
80pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
85
81
@@ -101,13 +97,6 @@ impl<'a> Context<'a> {
101 }));
97 }));
102 }
98 }
103
99
104− /// Call `replace_or_push` on the Compositor
105− pub fn replace_or_push_layer<T: Component>(&mut self, id: &'static str, component: T) {
106− self.callback = Some(Box::new(move |compositor: &mut Compositor, _| {
107− compositor.replace_or_push(id, component);
108− }));
109− }
110−
111 #[inline]
100 #[inline]
112 pub fn on_next_key(
101 pub fn on_next_key(
113 &mut self,
102 &mut self,
@@ -250,9 +239,6 @@ impl MappableCommand {
250 move_next_long_word_start, "Move to start of next long word",
239 move_next_long_word_start, "Move to start of next long word",
251 move_prev_long_word_start, "Move to start of previous long word",
240 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",
241 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",
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",
242 extend_next_word_start, "Extend to start of next word",
257 extend_prev_word_start, "Extend to start of previous word",
243 extend_prev_word_start, "Extend to start of previous word",
258 extend_next_word_end, "Extend to end of next word",
244 extend_next_word_end, "Extend to end of next word",
@@ -260,9 +246,6 @@ impl MappableCommand {
260 extend_next_long_word_start, "Extend to start of next long word",
246 extend_next_long_word_start, "Extend to start of next long word",
261 extend_prev_long_word_start, "Extend to start of previous long word",
247 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",
248 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",
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",
249 find_till_char, "Move till next occurrence of char",
267 find_next_char, "Move to next occurrence of char",
250 find_next_char, "Move to next occurrence of char",
268 extend_till_char, "Extend till next occurrence of char",
251 extend_till_char, "Extend till next occurrence of char",
@@ -284,7 +267,6 @@ impl MappableCommand {
284 select_regex, "Select all regex matches inside selections",
267 select_regex, "Select all regex matches inside selections",
285 split_selection, "Split selections on regex matches",
268 split_selection, "Split selections on regex matches",
286 split_selection_on_newline, "Split selection on newlines",
269 split_selection_on_newline, "Split selection on newlines",
287− merge_selections, "Merge selections",
288 merge_consecutive_selections, "Merge consecutive selections",
270 merge_consecutive_selections, "Merge consecutive selections",
289 search, "Search for regex pattern",
271 search, "Search for regex pattern",
290 rsearch, "Reverse search for regex pattern",
272 rsearch, "Reverse search for regex pattern",
@@ -337,7 +319,7 @@ impl MappableCommand {
337 goto_implementation, "Goto implementation",
319 goto_implementation, "Goto implementation",
338 goto_file_start, "Goto line number <n> else file start",
320 goto_file_start, "Goto line number <n> else file start",
339 goto_file_end, "Goto file end",
321 goto_file_end, "Goto file end",
340− goto_file, "Goto files/URLs in selection",
322+ goto_file, "Goto files in selection",
341 goto_file_hsplit, "Goto files in selection (hsplit)",
323 goto_file_hsplit, "Goto files in selection (hsplit)",
342 goto_file_vsplit, "Goto files in selection (vsplit)",
324 goto_file_vsplit, "Goto files in selection (vsplit)",
343 goto_reference, "Goto references",
325 goto_reference, "Goto references",
@@ -369,7 +351,6 @@ impl MappableCommand {
369 extend_to_line_end, "Extend to line end",
351 extend_to_line_end, "Extend to line end",
370 extend_to_line_end_newline, "Extend to line end",
352 extend_to_line_end_newline, "Extend to line end",
371 signature_help, "Show signature help",
353 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",
354 insert_tab, "Insert tab char",
374 insert_newline, "Insert newline char",
355 insert_newline, "Insert newline char",
375 delete_char_backward, "Delete previous char",
356 delete_char_backward, "Delete previous char",
@@ -384,9 +365,6 @@ impl MappableCommand {
384 later, "Move forward in history",
365 later, "Move forward in history",
385 commit_undo_checkpoint, "Commit changes to new checkpoint",
366 commit_undo_checkpoint, "Commit changes to new checkpoint",
386 yank, "Yank selection",
367 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",
390 yank_joined_to_clipboard, "Join and yank selections to clipboard",
368 yank_joined_to_clipboard, "Join and yank selections to clipboard",
391 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
369 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
392 yank_joined_to_primary_clipboard, "Join and yank selections to primary clipboard",
370 yank_joined_to_primary_clipboard, "Join and yank selections to primary clipboard",
@@ -417,7 +395,6 @@ impl MappableCommand {
417 rotate_selections_backward, "Rotate selections backward",
395 rotate_selections_backward, "Rotate selections backward",
418 rotate_selection_contents_forward, "Rotate selection contents forward",
396 rotate_selection_contents_forward, "Rotate selection contents forward",
419 rotate_selection_contents_backward, "Rotate selections contents backward",
397 rotate_selection_contents_backward, "Rotate selections contents backward",
420− reverse_selection_contents, "Reverse selections contents",
421 expand_selection, "Expand selection to parent syntax node",
398 expand_selection, "Expand selection to parent syntax node",
422 shrink_selection, "Shrink selection to previously expanded syntax node",
399 shrink_selection, "Shrink selection to previously expanded syntax node",
423 select_next_sibling, "Select next sibling in syntax tree",
400 select_next_sibling, "Select next sibling in syntax tree",
@@ -811,50 +788,54 @@ fn extend_to_line_start(cx: &mut Context) {
811}
788}
812
789
813fn kill_to_line_start(cx: &mut Context) {
790fn kill_to_line_start(cx: &mut Context) {
814− delete_by_selection_insert_mode(
791+ let (view, doc) = current!(cx.editor);
815− cx,
792+ let text = doc.text().slice(..);
816− move |text, range| {
793+
794+ let selection = doc.selection(view.id).clone().transform(|range| {
817− let line = range.cursor_line(text);
795+ let line = range.cursor_line(text);
818− let first_char = text.line_to_char(line);
796+ let first_char = text.line_to_char(line);
819− let anchor = range.cursor(text);
797+ let anchor = range.cursor(text);
820− let head = if anchor == first_char && line != 0 {
798+ let head = if anchor == first_char && line != 0 {
821− // select until previous line
799+ // select until previous line
822− line_end_char_index(&text, line - 1)
800+ line_end_char_index(&text, line - 1)
823− } else if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
801+ } else if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
824− if first_char + pos < anchor {
802+ if first_char + pos < anchor {
825− // select until first non-blank in line if cursor is after it
803+ // select until first non-blank in line if cursor is after it
826− first_char + pos
804+ first_char + pos
827− } else {
828− // select until start of line
829− first_char
830− }
831 } else {
805 } else {
832 // select until start of line
806 // select until start of line
833 first_char
807 first_char
834− };
808+ }
809+ } else {
810+ // select until start of line
811+ first_char
812+ };
835− (head, anchor)
813+ Range::new(head, anchor)
836− },
814+ });
837− Direction::Backward,
815+ delete_selection_insert_mode(doc, view, &selection);
838− );
816+
817+ lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
839}
818}
840
819
841fn kill_to_line_end(cx: &mut Context) {
820fn kill_to_line_end(cx: &mut Context) {
842− delete_by_selection_insert_mode(
821+ let (view, doc) = current!(cx.editor);
843− cx,
822+ let text = doc.text().slice(..);
844− |text, range| {
845− let line = range.cursor_line(text);
846− let line_end_pos = line_end_char_index(&text, line);
847− let pos = range.cursor(text);
848
823
849− // if the cursor is on the newline char delete that
824+ let selection = doc.selection(view.id).clone().transform(|range| {
825+ let line = range.cursor_line(text);
826+ let line_end_pos = line_end_char_index(&text, line);
827+ let pos = range.cursor(text);
828+
829+ let mut new_range = range.put_cursor(text, line_end_pos, true);
830+ // don't want to remove the line separator itself if the cursor doesn't reach the end of line.
850− if pos == line_end_pos {
831+ if pos != line_end_pos {
851− (pos, text.line_to_char(line + 1))
832+ new_range.head = line_end_pos;
852− } else {
833+ }
853− (pos, line_end_pos)
834+ new_range
854− }
835+ });
855− },
836+ delete_selection_insert_mode(doc, view, &selection);
856− Direction::Forward,
837+
857− );
838+ lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
858}
839}
859
840
860fn goto_first_nonwhitespace(cx: &mut Context) {
841fn goto_first_nonwhitespace(cx: &mut Context) {
@@ -990,7 +971,6 @@ fn align_selections(cx: &mut Context) {
990
971
991 let transaction = Transaction::change(doc.text(), changes.into_iter());
972 let transaction = Transaction::change(doc.text(), changes.into_iter());
992 doc.apply(&transaction, view.id);
973 doc.apply(&transaction, view.id);
993− exit_select_mode(cx);
994}
974}
995
975
996fn goto_window(cx: &mut Context, align: Align) {
976fn goto_window(cx: &mut Context, align: Align) {
@@ -1082,10 +1062,6 @@ fn move_prev_long_word_start(cx: &mut Context) {
1082 move_word_impl(cx, movement::move_prev_long_word_start)
1062 move_word_impl(cx, movement::move_prev_long_word_start)
1083}
1063}
1084
1064
1085−fn move_prev_long_word_end(cx: &mut Context) {
1086− move_word_impl(cx, movement::move_prev_long_word_end)
1087−}
1088−
1089fn move_next_long_word_end(cx: &mut Context) {
1065fn move_next_long_word_end(cx: &mut Context) {
1090 move_word_impl(cx, movement::move_next_long_word_end)
1066 move_word_impl(cx, movement::move_next_long_word_end)
1091}
1067}
@@ -1110,7 +1086,8 @@ where
1110 .transform(|range| move_fn(text, range, count, behavior));
1086 .transform(|range| move_fn(text, range, count, behavior));
1111 doc.set_selection(view.id, selection);
1087 doc.set_selection(view.id, selection);
1112 };
1088 };
1089+ motion(cx.editor);
1113− cx.editor.apply_motion(motion)
1090+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
1114}
1091}
1115
1092
1116fn goto_prev_paragraph(cx: &mut Context) {
1093fn goto_prev_paragraph(cx: &mut Context) {
@@ -1165,10 +1142,6 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
1165 let (view, doc) = current_ref!(cx.editor);
1142 let (view, doc) = current_ref!(cx.editor);
1166 let text = doc.text();
1143 let text = doc.text();
1167 let selections = doc.selection(view.id);
1144 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
1145 let mut paths: Vec<_> = selections
1173 .iter()
1146 .iter()
1174 .map(|r| text.slice(r.from()..r.to()).to_string())
1147 .map(|r| text.slice(r.from()..r.to()).to_string())
@@ -1196,80 +1169,16 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
1196 .to_string(),
1169 .to_string(),
1197 );
1170 );
1198 }
1171 }
1199−
1200 for sel in paths {
1172 for sel in paths {
1201 let p = sel.trim();
1173 let p = sel.trim();
1202− if p.is_empty() {
1174+ 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) {
1175+ 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));
1176 cx.editor.set_error(format!("Open file failed: {:?}", e));
1251 }
1177 }
1252 }
1178 }
1253 }
1179 }
1254}
1180}
1255
1181
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)
1182fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
1274where
1183where
1275 F: Fn(RopeSlice, Range, usize) -> Range,
1184 F: Fn(RopeSlice, Range, usize) -> Range,
@@ -1310,74 +1219,14 @@ fn extend_prev_long_word_start(cx: &mut Context) {
1310 extend_word_impl(cx, movement::move_prev_long_word_start)
1219 extend_word_impl(cx, movement::move_prev_long_word_start)
1311}
1220}
1312
1221
1313−fn extend_prev_long_word_end(cx: &mut Context) {
1314− extend_word_impl(cx, movement::move_prev_long_word_end)
1315−}
1316−
1317fn extend_next_long_word_end(cx: &mut Context) {
1222fn extend_next_long_word_end(cx: &mut Context) {
1318 extend_word_impl(cx, movement::move_next_long_word_end)
1223 extend_word_impl(cx, movement::move_next_long_word_end)
1319}
1224}
1320
1225
1321−/// Separate branch to find_char designed only for `<ret>` char.
1226+fn will_find_char<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool)
1322−//
1227+where
1323−// This is necessary because the one document can have different line endings inside. And we
1228+ F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,
1324−// cannot predict what character to find when <ret> is pressed. On the current line it can be `lf`
1229+{
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−
1380−fn 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.
1230 // TODO: count is reset to 1 before next key so we move it into the closure here.
1382 // Would be nice to carry over.
1231 // Would be nice to carry over.
1383 let count = cx.count();
1232 let count = cx.count();
@@ -1390,9 +1239,13 @@ fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bo
1390 KeyEvent {
1239 KeyEvent {
1391 code: KeyCode::Enter,
1240 code: KeyCode::Enter,
1392 ..
1241 ..
1393− } => {
1242+ } =>
1394− find_char_line_ending(cx, count, direction, inclusive, extend);
1243+ // TODO: this isn't quite correct when CRLF is involved.
1395− return;
1244+ // This hack will work in most cases, since documents don't
1245+ // usually mix line endings. But we should fix it eventually
1246+ // anyway.
1247+ {
1248+ doc!(cx.editor).line_ending.as_str().chars().next().unwrap()
1396 }
1249 }
1397
1250
1398 KeyEvent {
1251 KeyEvent {
@@ -1405,18 +1258,11 @@ fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bo
1405 } => ch,
1258 } => ch,
1406 _ => return,
1259 _ => return,
1407 };
1260 };
1408− let motion = move |editor: &mut Editor| {
1409− match direction {
1410− Direction::Forward => {
1411− find_char_impl(editor, &find_next_char_impl, inclusive, extend, ch, count)
1412− }
1413− Direction::Backward => {
1414− find_char_impl(editor, &find_prev_char_impl, inclusive, extend, ch, count)
1415− }
1416− };
1417− };
1418
1261
1419− cx.editor.apply_motion(motion);
1262+ find_char_impl(cx.editor, &search_fn, inclusive, extend, ch, count);
1263+ cx.editor.last_motion = Some(Motion(Box::new(move |editor: &mut Editor| {
1264+ find_char_impl(editor, &search_fn, inclusive, true, ch, 1);
1265+ })));
1420 })
1266 })
1421}
1267}
1422
1268
@@ -1495,39 +1341,46 @@ fn find_prev_char_impl(
1495}
1341}
1496
1342
1497fn find_till_char(cx: &mut Context) {
1343fn find_till_char(cx: &mut Context) {
1498− find_char(cx, Direction::Forward, false, false);
1344+ will_find_char(cx, find_next_char_impl, false, false)
1499}
1345}
1500
1346
1501fn find_next_char(cx: &mut Context) {
1347fn find_next_char(cx: &mut Context) {
1502− find_char(cx, Direction::Forward, true, false)
1348+ will_find_char(cx, find_next_char_impl, true, false)
1503}
1349}
1504
1350
1505fn extend_till_char(cx: &mut Context) {
1351fn extend_till_char(cx: &mut Context) {
1506− find_char(cx, Direction::Forward, false, true)
1352+ will_find_char(cx, find_next_char_impl, false, true)
1507}
1353}
1508
1354
1509fn extend_next_char(cx: &mut Context) {
1355fn extend_next_char(cx: &mut Context) {
1510− find_char(cx, Direction::Forward, true, true)
1356+ will_find_char(cx, find_next_char_impl, true, true)
1511}
1357}
1512
1358
1513fn till_prev_char(cx: &mut Context) {
1359fn till_prev_char(cx: &mut Context) {
1514− find_char(cx, Direction::Backward, false, false)
1360+ will_find_char(cx, find_prev_char_impl, false, false)
1515}
1361}
1516
1362
1517fn find_prev_char(cx: &mut Context) {
1363fn find_prev_char(cx: &mut Context) {
1518− find_char(cx, Direction::Backward, true, false)
1364+ will_find_char(cx, find_prev_char_impl, true, false)
1519}
1365}
1520
1366
1521fn extend_till_prev_char(cx: &mut Context) {
1367fn extend_till_prev_char(cx: &mut Context) {
1522− find_char(cx, Direction::Backward, false, true)
1368+ will_find_char(cx, find_prev_char_impl, false, true)
1523}
1369}
1524
1370
1525fn extend_prev_char(cx: &mut Context) {
1371fn extend_prev_char(cx: &mut Context) {
1526− find_char(cx, Direction::Backward, true, true)
1372+ will_find_char(cx, find_prev_char_impl, true, true)
1527}
1373}
1528
1374
1529fn repeat_last_motion(cx: &mut Context) {
1375fn repeat_last_motion(cx: &mut Context) {
1530− cx.editor.repeat_last_motion(cx.count())
1376+ let count = cx.count();
1377+ let last_motion = cx.editor.last_motion.take();
1378+ if let Some(m) = &last_motion {
1379+ for _ in 0..count {
1380+ m.run(cx.editor);
1381+ }
1382+ cx.editor.last_motion = last_motion;
1383+ }
1531}
1384}
1532
1385
1533fn replace(cx: &mut Context) {
1386fn replace(cx: &mut Context) {
@@ -1594,7 +1447,6 @@ where
1594 });
1447 });
1595
1448
1596 doc.apply(&transaction, view.id);
1449 doc.apply(&transaction, view.id);
1597− exit_select_mode(cx);
1598}
1450}
1599
1451
1600fn switch_case(cx: &mut Context) {
1452fn switch_case(cx: &mut Context) {
@@ -1836,8 +1688,8 @@ fn select_regex(cx: &mut Context) {
1836 "select:".into(),
1688 "select:".into(),
1837 Some(reg),
1689 Some(reg),
1838 ui::completers::none,
1690 ui::completers::none,
1839− move |cx, regex, event| {
1691+ move |editor, regex, event| {
1840− let (view, doc) = current!(cx.editor);
1692+ let (view, doc) = current!(editor);
1841 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1693 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1842 return;
1694 return;
1843 }
1695 }
@@ -1858,8 +1710,8 @@ fn split_selection(cx: &mut Context) {
1858 "split:".into(),
1710 "split:".into(),
1859 Some(reg),
1711 Some(reg),
1860 ui::completers::none,
1712 ui::completers::none,
1861− move |cx, regex, event| {
1713+ move |editor, regex, event| {
1862− let (view, doc) = current!(cx.editor);
1714+ let (view, doc) = current!(editor);
1863 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1715 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1864 return;
1716 return;
1865 }
1717 }
@@ -1881,12 +1733,6 @@ fn split_selection_on_newline(cx: &mut Context) {
1881 doc.set_selection(view.id, selection);
1733 doc.set_selection(view.id, selection);
1882}
1734}
1883
1735
1884−fn merge_selections(cx: &mut Context) {
1885− let (view, doc) = current!(cx.editor);
1886− let selection = doc.selection(view.id).clone().merge_ranges();
1887− doc.set_selection(view.id, selection);
1888−}
1889−
1890fn merge_consecutive_selections(cx: &mut Context) {
1736fn merge_consecutive_selections(cx: &mut Context) {
1891 let (view, doc) = current!(cx.editor);
1737 let (view, doc) = current!(cx.editor);
1892 let selection = doc.selection(view.id).clone().merge_consecutive_ranges();
1738 let selection = doc.selection(view.id).clone().merge_consecutive_ranges();
@@ -1982,11 +1828,11 @@ fn search_impl(
1982
1828
1983fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
1829fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
1984 let mut items = reg
1830 let mut items = reg
1985− .and_then(|reg| cx.editor.registers.read(reg, cx.editor))
1831+ .and_then(|reg| cx.editor.registers.get(reg))
1986− .map_or(Vec::new(), |reg| reg.take(200).collect());
1832+ .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect());
1987 items.sort_unstable();
1833 items.sort_unstable();
1988 items.dedup();
1834 items.dedup();
1989− items.into_iter().map(|value| value.to_string()).collect()
1835+ items.into_iter().cloned().collect()
1990}
1836}
1991
1837
1992fn search(cx: &mut Context) {
1838fn search(cx: &mut Context) {
@@ -2023,14 +1869,12 @@ fn searcher(cx: &mut Context, direction: Direction) {
2023 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
1869 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
2024 .collect()
1870 .collect()
2025 },
1871 },
2026− move |cx, regex, event| {
1872+ move |editor, regex, event| {
2027− if event == PromptEvent::Validate {
1873+ if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
2028− cx.editor.registers.last_search_register = reg;
2029− } else if event != PromptEvent::Update {
2030 return;
1874 return;
2031 }
1875 }
2032 search_impl(
1876 search_impl(
2033− cx.editor,
1877+ editor,
2034 &contents,
1878 &contents,
2035 &regex,
1879 &regex,
2036 Movement::Move,
1880 Movement::Move,
@@ -2045,13 +1889,11 @@ fn searcher(cx: &mut Context, direction: Direction) {
2045
1889
2046fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
1890fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
2047 let count = cx.count();
1891 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();
1892 let config = cx.editor.config();
2052 let scrolloff = config.scrolloff;
1893 let scrolloff = config.scrolloff;
2053− if let Some(query) = cx.editor.registers.first(register, cx.editor) {
1894+ let (_, doc) = current!(cx.editor);
2054− let doc = doc!(cx.editor);
1895+ let registers = &cx.editor.registers;
1896+ if let Some(query) = registers.read('/').and_then(|query| query.last()) {
2055 let contents = doc.text().slice(..).to_string();
1897 let contents = doc.text().slice(..).to_string();
2056 let search_config = &config.search;
1898 let search_config = &config.search;
2057 let case_insensitive = if search_config.smart_case {
1899 let case_insensitive = if search_config.smart_case {
@@ -2060,7 +1902,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
2060 false
1902 false
2061 };
1903 };
2062 let wrap_around = search_config.wrap_around;
1904 let wrap_around = search_config.wrap_around;
2063− if let Ok(regex) = RegexBuilder::new(&query)
1905+ if let Ok(regex) = RegexBuilder::new(query)
2064 .case_insensitive(case_insensitive)
1906 .case_insensitive(case_insensitive)
2065 .multi_line(true)
1907 .multi_line(true)
2066 .build()
1908 .build()
@@ -2100,7 +1942,6 @@ fn extend_search_prev(cx: &mut Context) {
2100}
1942}
2101
1943
2102fn search_selection(cx: &mut Context) {
1944fn search_selection(cx: &mut Context) {
2103− let register = cx.register.unwrap_or('/');
2104 let (view, doc) = current!(cx.editor);
1945 let (view, doc) = current!(cx.editor);
2105 let contents = doc.text().slice(..);
1946 let contents = doc.text().slice(..);
2106
1947
@@ -2113,24 +1954,13 @@ fn search_selection(cx: &mut Context) {
2113 .collect::<Vec<_>>()
1954 .collect::<Vec<_>>()
2114 .join("|");
1955 .join("|");
2115
1956
2116− let msg = format!("register '{}' set to '{}'", register, &regex);
1957+ let msg = format!("register '{}' set to '{}'", '/', &regex);
2117− match cx.editor.registers.push(register, regex) {
1958+ cx.editor.registers.push('/', regex);
2118− Ok(_) => {
2119− cx.editor.registers.last_search_register = register;
2120− cx.editor.set_status(msg)
1959+ cx.editor.set_status(msg);
2121− }
2122− Err(err) => cx.editor.set_error(err.to_string()),
2123− }
2124}
1960}
2125
1961
2126fn make_search_word_bounded(cx: &mut Context) {
1962fn 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) {
1963+ let regex = match cx.editor.registers.last('/') {
2134 Some(regex) => regex,
1964 Some(regex) => regex,
2135 None => return,
1965 None => return,
2136 };
1966 };
@@ -2148,19 +1978,14 @@ fn make_search_word_bounded(cx: &mut Context) {
2148 if !start_anchored {
1978 if !start_anchored {
2149 new_regex.push_str("\\b");
1979 new_regex.push_str("\\b");
2150 }
1980 }
2151− new_regex.push_str(&regex);
1981+ new_regex.push_str(regex);
2152 if !end_anchored {
1982 if !end_anchored {
2153 new_regex.push_str("\\b");
1983 new_regex.push_str("\\b");
2154 }
1984 }
2155
1985
2156− let msg = format!("register '{}' set to '{}'", register, &new_regex);
1986+ let msg = format!("register '{}' set to '{}'", '/', &new_regex);
2157− match cx.editor.registers.push(register, new_regex) {
1987+ cx.editor.registers.push('/', new_regex);
2158− Ok(_) => {
2159− cx.editor.registers.last_search_register = register;
2160− cx.editor.set_status(msg)
1988+ cx.editor.set_status(msg);
2161− }
2162− Err(err) => cx.editor.set_error(err.to_string()),
2163− }
2164}
1989}
2165
1990
2166fn global_search(cx: &mut Context) {
1991fn global_search(cx: &mut Context) {
@@ -2199,11 +2024,13 @@ fn global_search(cx: &mut Context) {
2199 }
2024 }
2200 }
2025 }
2201
2026
2027+ let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<FileResult>();
2202 let config = cx.editor.config();
2028 let config = cx.editor.config();
2203 let smart_case = config.search.smart_case;
2029 let smart_case = config.search.smart_case;
2204 let file_picker_config = config.file_picker.clone();
2030 let file_picker_config = config.file_picker.clone();
2205
2031
2206 let reg = cx.register.unwrap_or('/');
2032 let reg = cx.register.unwrap_or('/');
2033+
2207 let completions = search_completions(cx, Some(reg));
2034 let completions = search_completions(cx, Some(reg));
2208 ui::regex_prompt(
2035 ui::regex_prompt(
2209 cx,
2036 cx,
@@ -2216,67 +2043,43 @@ fn global_search(cx: &mut Context) {
2216 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
2043 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
2217 .collect()
2044 .collect()
2218 },
2045 },
2219− move |cx, regex, event| {
2046+ move |_editor, regex, event| {
2220 if event != PromptEvent::Validate {
2047 if event != PromptEvent::Validate {
2221 return;
2048 return;
2222 }
2049 }
2223− cx.editor.registers.last_search_register = reg;
2224−
2225− let current_path = doc_mut!(cx.editor).path().cloned();
2226− let documents: Vec<_> = cx
2227− .editor
2228− .documents()
2229− .map(|doc| (doc.path().cloned(), doc.text().to_owned()))
2230− .collect();
2231
2050
2232 if let Ok(matcher) = RegexMatcherBuilder::new()
2051 if let Ok(matcher) = RegexMatcherBuilder::new()
2233 .case_smart(smart_case)
2052 .case_smart(smart_case)
2234 .build(regex.as_str())
2053 .build(regex.as_str())
2235 {
2054 {
2236− let search_root = helix_loader::current_working_dir();
2055+ let searcher = SearcherBuilder::new()
2237− if !search_root.exists() {
2056+ .binary_detection(BinaryDetection::quit(b'\x00'))
2238− cx.editor
2057+ .build();
2239− .set_error("Current working directory does not exist");
2240− return;
2241− }
2242−
2243− let (picker, injector) = Picker::stream(current_path);
2244
2058
2059+ let search_root = std::env::current_dir()
2060+ .expect("Global search error: Failed to get current dir");
2245 let dedup_symlinks = file_picker_config.deduplicate_links;
2061 let dedup_symlinks = file_picker_config.deduplicate_links;
2246 let absolute_root = search_root
2062 let absolute_root = search_root
2247 .canonicalize()
2063 .canonicalize()
2248 .unwrap_or_else(|_| search_root.clone());
2064 .unwrap_or_else(|_| search_root.clone());
2249− let injector_ = injector.clone();
2065+
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);
2066+ WalkBuilder::new(search_root)
2257−
2258− walk_builder
2259− .hidden(file_picker_config.hidden)
2067+ .hidden(file_picker_config.hidden)
2260− .parents(file_picker_config.parents)
2068+ .parents(file_picker_config.parents)
2261− .ignore(file_picker_config.ignore)
2069+ .ignore(file_picker_config.ignore)
2262− .follow_links(file_picker_config.follow_symlinks)
2070+ .follow_links(file_picker_config.follow_symlinks)
2263− .git_ignore(file_picker_config.git_ignore)
2071+ .git_ignore(file_picker_config.git_ignore)
2264− .git_global(file_picker_config.git_global)
2072+ .git_global(file_picker_config.git_global)
2265− .git_exclude(file_picker_config.git_exclude)
2073+ .git_exclude(file_picker_config.git_exclude)
2266− .max_depth(file_picker_config.max_depth)
2074+ .max_depth(file_picker_config.max_depth)
2267− .filter_entry(move |entry| {
2075+ .filter_entry(move |entry| {
2268− filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2076+ filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2269− });
2077+ })
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(|| {
2078+ .build_parallel()
2079+ .run(|| {
2276 let mut searcher = searcher.clone();
2080 let mut searcher = searcher.clone();
2277 let matcher = matcher.clone();
2081 let matcher = matcher.clone();
2278− let injector = injector_.clone();
2082+ let all_matches_sx = all_matches_sx.clone();
2279− let documents = &documents;
2280 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
2083 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
2281 let entry = match entry {
2084 let entry = match entry {
2282 Ok(entry) => entry,
2085 Ok(entry) => entry,
@@ -2289,39 +2092,17 @@ fn global_search(cx: &mut Context) {
2289 _ => return WalkState::Continue,
2092 _ => return WalkState::Continue,
2290 };
2093 };
2291
2094
2292− let mut stop = false;
2095+ let result = searcher.search_path(
2096+ &matcher,
2097+ entry.path(),
2293− let sink = sinks::UTF8(|line_num, _| {
2098+ sinks::UTF8(|line_num, _| {
2294− stop = injector
2099+ all_matches_sx
2295− .push(FileResult::new(entry.path(), line_num as usize - 1))
2100+ .send(FileResult::new(entry.path(), line_num as usize - 1))
2296− .is_err();
2101+ .unwrap();
2297−
2102+
2298− Ok(!stop)
2103+ Ok(true)
2299− });
2104+ }),
2300− let doc = documents.iter().find(|&(doc_path, _)| {
2301− doc_path
2302− .as_ref()
2303− .map_or(false, |doc_path| doc_path == entry.path())
2304− });
2305−
2306− let result = if let Some((_, doc)) = doc {
2307− // there is already a buffer for this file
2308− // search the buffer instead of the file because it's faster
2309− // and captures new edits without requiring a save
2310− if searcher.multi_line_with_matcher(&matcher) {
2311− // in this case a continous buffer is required
2312− // convert the rope to a string
2313− let text = doc.to_string();
2314− searcher.search_slice(&matcher, text.as_bytes(), sink)
2315− } else {
2316− searcher.search_reader(
2317− &matcher,
2318− RopeReader::new(doc.slice(..)),
2319− sink,
2320− )
2321− }
2322− } else {
2323− searcher.search_path(&matcher, entry.path(), sink)
2324− };
2105+ );
2325
2106
2326 if let Err(err) = result {
2107 if let Err(err) = result {
2327 log::error!(
2108 log::error!(
@@ -2330,66 +2111,67 @@ fn global_search(cx: &mut Context) {
2330 err
2111 err
2331 );
2112 );
2332 }
2113 }
2333− if stop {
2334− WalkState::Quit
2335− } else {
2336− WalkState::Continue
2114+ WalkState::Continue
2337− }
2338 })
2115 })
2339 });
2116 });
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 {
2117 } else {
2388 // Otherwise do nothing
2118 // Otherwise do nothing
2389 // log::warn!("Global Search Invalid Pattern")
2119 // log::warn!("Global Search Invalid Pattern")
2390 }
2120 }
2391 },
2121 },
2392 );
2122 );
2123+
2124+ let current_path = doc_mut!(cx.editor).path().cloned();
2125+
2126+ let show_picker = async move {
2127+ let all_matches: Vec<FileResult> =
2128+ UnboundedReceiverStream::new(all_matches_rx).collect().await;
2129+ let call: job::Callback = Callback::EditorCompositor(Box::new(
2130+ move |editor: &mut Editor, compositor: &mut Compositor| {
2131+ if all_matches.is_empty() {
2132+ editor.set_status("No matches found");
2133+ return;
2134+ }
2135+
2136+ let picker = FilePicker::new(
2137+ all_matches,
2138+ current_path,
2139+ move |cx, FileResult { path, line_num }, action| {
2140+ match cx.editor.open(path, action) {
2141+ Ok(_) => {}
2142+ Err(e) => {
2143+ cx.editor.set_error(format!(
2144+ "Failed to open file '{}': {}",
2145+ path.display(),
2146+ e
2147+ ));
2148+ return;
2149+ }
2150+ }
2151+
2152+ let line_num = *line_num;
2153+ let (view, doc) = current!(cx.editor);
2154+ let text = doc.text();
2155+ if line_num >= text.len_lines() {
2156+ cx.editor.set_error("The line you jumped to does not exist anymore because the file has changed.");
2157+ return;
2158+ }
2159+ let start = text.line_to_char(line_num);
2160+ let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2161+
2162+ doc.set_selection(view.id, Selection::single(start, end));
2163+ align_view(doc, view, Align::Center);
2164+ },
2165+ |_editor, FileResult { path, line_num }| {
2166+ Some((path.clone().into(), Some((*line_num, *line_num))))
2167+ },
2168+ );
2169+ compositor.push(Box::new(overlaid(picker)));
2170+ },
2171+ ));
2172+ Ok(call)
2173+ };
2174+ cx.jobs.callback(show_picker);
2393}
2175}
2394
2176
2395enum Extend {
2177enum Extend {
@@ -2512,40 +2294,23 @@ enum Operation {
2512 Change,
2294 Change,
2513}
2295}
2514
2296
2515−fn selection_is_linewise(selection: &Selection, text: &Rope) -> bool {
2516− selection.ranges().iter().all(|range| {
2517− let text = text.slice(..);
2518− if range.slice(text).len_lines() < 2 {
2519− return false;
2520− }
2521− // If the start of the selection is at the start of a line and the end at the end of a line.
2522− let (start_line, end_line) = range.line_range(text);
2523− let start = text.line_to_char(start_line);
2524− let end = text.line_to_char((end_line + 1).min(text.len_lines()));
2525− start == range.from() && end == range.to()
2526− })
2527−}
2528−
2529fn delete_selection_impl(cx: &mut Context, op: Operation) {
2297fn delete_selection_impl(cx: &mut Context, op: Operation) {
2530 let (view, doc) = current!(cx.editor);
2298 let (view, doc) = current!(cx.editor);
2531
2299
2532 let selection = doc.selection(view.id);
2300 let selection = doc.selection(view.id);
2533− let only_whole_lines = selection_is_linewise(selection, doc.text());
2534
2301
2535 if cx.register != Some('_') {
2302 if cx.register != Some('_') {
2536 // first yank the selection
2303 // first yank the selection
2537 let text = doc.text().slice(..);
2304 let text = doc.text().slice(..);
2538 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
2305 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
2539 let reg_name = cx.register.unwrap_or('"');
2306 let reg_name = cx.register.unwrap_or('"');
2540− if let Err(err) = cx.editor.registers.write(reg_name, values) {
2307+ cx.editor.registers.write(reg_name, values);
2541− cx.editor.set_error(err.to_string());
2542− return;
2543− }
2544 };
2308 };
2545
2309
2546 // then delete
2310 // then delete
2547− let transaction =
2548− Transaction::delete_by_selection(doc.text(), selection, |range| (range.from(), range.to()));
2311+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
2312+ (range.from(), range.to(), None)
2313+ });
2549 doc.apply(&transaction, view.id);
2314 doc.apply(&transaction, view.id);
2550
2315
2551 match op {
2316 match op {
@@ -2554,59 +2319,17 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
2554 exit_select_mode(cx);
2319 exit_select_mode(cx);
2555 }
2320 }
2556 Operation::Change => {
2321 Operation::Change => {
2557− if only_whole_lines {
2558− open_above(cx);
2559− } else {
2560− enter_insert_mode(cx);
2322+ enter_insert_mode(cx);
2561− }
2562 }
2323 }
2563 }
2324 }
2564}
2325}
2565
2326
2566#[inline]
2327#[inline]
2567−fn delete_by_selection_insert_mode(
2328+fn delete_selection_insert_mode(doc: &mut Document, view: &mut View, selection: &Selection) {
2568− cx: &mut Context,
2569− mut f: impl FnMut(RopeSlice, &Range) -> Deletion,
2570− direction: Direction,
2571−) {
2572− let (view, doc) = current!(cx.editor);
2573− let text = doc.text().slice(..);
2574− let mut selection = SmallVec::new();
2575− let mut insert_newline = false;
2576− let text_len = text.len_chars();
2577− let mut transaction =
2578− Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| {
2329+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
2579− let (start, end) = f(text, range);
2330+ (range.from(), range.to(), None)
2580− if direction == Direction::Forward {
2581− let mut range = *range;
2582− if range.head > range.anchor {
2583− insert_newline |= end == text_len;
2584− // move the cursor to the right so that the selection
2585− // doesn't shrink when deleting forward (so the text appears to
2586− // move to left)
2587− // += 1 is enough here as the range is normalized to grapheme boundaries
2588− // later anyway
2589− range.head += 1;
2590− }
2591− selection.push(range);
2592− }
2593− (start, end)
2594− });
2595−
2596− // in case we delete the last character and the cursor would be moved to the EOF char
2597− // insert a newline, just like when entering append mode
2598− if insert_newline {
2599− transaction = transaction.insert_at_eof(doc.line_ending.as_str().into());
2600− }
2331+ });
2601−
2602− if direction == Direction::Forward {
2603− doc.set_selection(
2604− view.id,
2605− Selection::new(selection, doc.selection(view.id).primary_index()),
2606− );
2607− }
2608 doc.apply(&transaction, view.id);
2332 doc.apply(&transaction, view.id);
2609− lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
2610}
2333}
2611
2334
2612fn delete_selection(cx: &mut Context) {
2335fn delete_selection(cx: &mut Context) {
@@ -2680,10 +2403,6 @@ fn insert_mode(cx: &mut Context) {
2680 .transform(|range| Range::new(range.to(), range.from()));
2403 .transform(|range| Range::new(range.to(), range.from()));
2681
2404
2682 doc.set_selection(view.id, selection);
2405 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}
2406}
2688
2407
2689// inserts at the end of each selection
2408// inserts at the end of each selection
@@ -2720,10 +2439,6 @@ fn append_mode(cx: &mut Context) {
2720
2439
2721fn file_picker(cx: &mut Context) {
2440fn file_picker(cx: &mut Context) {
2722 let root = find_workspace().0;
2441 let root = find_workspace().0;
2723− if !root.exists() {
2724− cx.editor.set_error("Workspace directory does not exist");
2725− return;
2726− }
2727 let picker = ui::file_picker(root, &cx.editor.config());
2442 let picker = ui::file_picker(root, &cx.editor.config());
2728 cx.push_layer(Box::new(overlaid(picker)));
2443 cx.push_layer(Box::new(overlaid(picker)));
2729}
2444}
@@ -2744,14 +2459,8 @@ fn file_picker_in_current_buffer_directory(cx: &mut Context) {
2744 let picker = ui::file_picker(path, &cx.editor.config());
2459 let picker = ui::file_picker(path, &cx.editor.config());
2745 cx.push_layer(Box::new(overlaid(picker)));
2460 cx.push_layer(Box::new(overlaid(picker)));
2746}
2461}
2747−
2748fn file_picker_in_current_directory(cx: &mut Context) {
2462fn file_picker_in_current_directory(cx: &mut Context) {
2749− let cwd = helix_loader::current_working_dir();
2463+ let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("./"));
2750− if !cwd.exists() {
2751− cx.editor
2752− .set_error("Current working directory does not exist");
2753− return;
2754− }
2755 let picker = ui::file_picker(cwd, &cx.editor.config());
2464 let picker = ui::file_picker(cwd, &cx.editor.config());
2756 cx.push_layer(Box::new(overlaid(picker)));
2465 cx.push_layer(Box::new(overlaid(picker)));
2757}
2466}
@@ -2810,18 +2519,22 @@ fn buffer_picker(cx: &mut Context) {
2810 // mru
2519 // mru
2811 items.sort_unstable_by_key(|item| std::cmp::Reverse(item.focused_at));
2520 items.sort_unstable_by_key(|item| std::cmp::Reverse(item.focused_at));
2812
2521
2522+ let picker = FilePicker::new(
2523+ items,
2524+ (),
2813− let picker = Picker::new(items, (), |cx, meta, action| {
2525+ |cx, meta, action| {
2814− cx.editor.switch(meta.id, action);
2526+ cx.editor.switch(meta.id, action);
2815− })
2527+ },
2816− .with_preview(|editor, meta| {
2528+ |editor, meta| {
2817− let doc = &editor.documents.get(&meta.id)?;
2529+ let doc = &editor.documents.get(&meta.id)?;
2818− let &view_id = doc.selections().keys().next()?;
2530+ let &view_id = doc.selections().keys().next()?;
2819− let line = doc
2531+ let line = doc
2820− .selection(view_id)
2532+ .selection(view_id)
2821− .primary()
2533+ .primary()
2822− .cursor_line(doc.text().slice(..));
2534+ .cursor_line(doc.text().slice(..));
2823− Some((meta.id.into(), Some((line, line))))
2535+ Some((meta.id.into(), Some((line, line))))
2536+ },
2824− });
2537+ );
2825 cx.push_layer(Box::new(overlaid(picker)));
2538 cx.push_layer(Box::new(overlaid(picker)));
2826}
2539}
2827
2540
@@ -2887,7 +2600,7 @@ fn jumplist_picker(cx: &mut Context) {
2887 }
2600 }
2888 };
2601 };
2889
2602
2890− let picker = Picker::new(
2603+ let picker = FilePicker::new(
2891 cx.editor
2604 cx.editor
2892 .tree
2605 .tree
2893 .views()
2606 .views()
@@ -2901,18 +2614,16 @@ fn jumplist_picker(cx: &mut Context) {
2901 |cx, meta, action| {
2614 |cx, meta, action| {
2902 cx.editor.switch(meta.id, action);
2615 cx.editor.switch(meta.id, action);
2903 let config = cx.editor.config();
2616 let config = cx.editor.config();
2904− let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id));
2617+ let (view, doc) = current!(cx.editor);
2905 doc.set_selection(view.id, meta.selection.clone());
2618 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);
2619+ view.ensure_cursor_in_view_center(doc, config.scrolloff);
2908− }
2909 },
2620 },
2910− )
2911− .with_preview(|editor, meta| {
2621+ |editor, meta| {
2912− let doc = &editor.documents.get(&meta.id)?;
2622+ let doc = &editor.documents.get(&meta.id)?;
2913− let line = meta.selection.primary().cursor_line(doc.text().slice(..));
2623+ let line = meta.selection.primary().cursor_line(doc.text().slice(..));
2914− Some((meta.id.into(), Some((line, line))))
2624+ Some((meta.path.clone()?.into(), Some((line, line))))
2625+ },
2915− });
2626+ );
2916 cx.push_layer(Box::new(overlaid(picker)));
2627 cx.push_layer(Box::new(overlaid(picker)));
2917}
2628}
2918
2629
@@ -2946,9 +2657,6 @@ impl ui::menu::Item for MappableCommand {
2946}
2657}
2947
2658
2948pub fn command_palette(cx: &mut Context) {
2659pub fn command_palette(cx: &mut Context) {
2949− let register = cx.register;
2950− let count = cx.count;
2951−
2952 cx.callback = Some(Box::new(
2660 cx.callback = Some(Box::new(
2953 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
2661 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
2954 let keymap = compositor.find::<ui::EditorView>().unwrap().keymaps.map()
2662 let keymap = compositor.find::<ui::EditorView>().unwrap().keymaps.map()
@@ -2966,8 +2674,8 @@ pub fn command_palette(cx: &mut Context) {
2966
2674
2967 let picker = Picker::new(commands, keymap, move |cx, command, _action| {
2675 let picker = Picker::new(commands, keymap, move |cx, command, _action| {
2968 let mut ctx = Context {
2676 let mut ctx = Context {
2969− register,
2677+ register: None,
2970− count,
2678+ count: std::num::NonZeroUsize::new(1),
2971 editor: cx.editor,
2679 editor: cx.editor,
2972 callback: None,
2680 callback: None,
2973 on_next_key_callback: None,
2681 on_next_key_callback: None,
@@ -3006,88 +2714,24 @@ fn last_picker(cx: &mut Context) {
3006 }));
2714 }));
3007}
2715}
3008
2716
3009−/// Fallback position to use for [`insert_with_indent`].
3010−enum IndentFallbackPos {
3011− LineStart,
3012− LineEnd,
3013−}
3014−
3015−// `I` inserts at the first nonwhitespace character of each line with a selection.
2717+// I inserts at the first nonwhitespace character of each line with a selection
3016−// If the line is empty, automatically indent.
3017fn insert_at_line_start(cx: &mut Context) {
2718fn insert_at_line_start(cx: &mut Context) {
3018− insert_with_indent(cx, IndentFallbackPos::LineStart);
2719+ goto_first_nonwhitespace(cx);
2720+ enter_insert_mode(cx);
3019}
2721}
3020
2722
3021−// `A` inserts at the end of each line with a selection.
2723+// A inserts at the end of each line with a selection
3022−// If the line is empty, automatically indent.
3023fn insert_at_line_end(cx: &mut Context) {
2724fn insert_at_line_end(cx: &mut Context) {
3024− insert_with_indent(cx, IndentFallbackPos::LineEnd);
3025−}
3026−
3027−// Enter insert mode and auto-indent the current line if it is empty.
3028−// If the line is not empty, move the cursor to the specified fallback position.
3029−fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) {
3030 enter_insert_mode(cx);
2725 enter_insert_mode(cx);
3031−
3032 let (view, doc) = current!(cx.editor);
2726 let (view, doc) = current!(cx.editor);
3033
2727
3034− let text = doc.text().slice(..);
3035− let contents = doc.text();
3036− let selection = doc.selection(view.id);
2728+ let selection = doc.selection(view.id).clone().transform(|range| {
3037−
2729+ let text = doc.text().slice(..);
3038− let language_config = doc.language_config();
3039− let syntax = doc.syntax();
3040− let tab_width = doc.tab_width();
3041−
3042− let mut ranges = SmallVec::with_capacity(selection.len());
3043− let mut offs = 0;
3044−
3045− let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
3046− let cursor_line = range.cursor_line(text);
2730+ let line = range.cursor_line(text);
3047− let cursor_line_start = text.line_to_char(cursor_line);
3048−
3049− if line_end_char_index(&text, cursor_line) == cursor_line_start {
2731+ let pos = line_end_char_index(&text, line);
3050− // line is empty => auto indent
2732+ Range::new(pos, pos)
3051− let line_end_index = cursor_line_start;
3052−
3053− let indent = indent::indent_for_newline(
3054− language_config,
3055− syntax,
3056− &doc.config.load().indent_heuristic,
3057− &doc.indent_style,
3058− tab_width,
3059− text,
3060− cursor_line,
3061− line_end_index,
3062− cursor_line,
3063− );
3064−
3065− // calculate new selection ranges
3066− let pos = offs + cursor_line_start;
3067− let indent_width = indent.chars().count();
3068− ranges.push(Range::point(pos + indent_width));
3069− offs += indent_width;
3070−
3071− (line_end_index, line_end_index, Some(indent.into()))
3072− } else {
3073− // move cursor to the fallback position
3074− let pos = match cursor_fallback {
3075− IndentFallbackPos::LineStart => {
3076− find_first_non_whitespace_char(text.line(cursor_line))
3077− .map(|ws_offset| ws_offset + cursor_line_start)
3078− .unwrap_or(cursor_line_start)
3079− }
3080− IndentFallbackPos::LineEnd => line_end_char_index(&text, cursor_line),
3081− };
3082−
3083− ranges.push(range.put_cursor(text, pos + offs, cx.editor.mode == Mode::Select));
3084−
3085− (cursor_line_start, cursor_line_start, None)
3086− }
3087 });
2733 });
3088−
2734+ doc.set_selection(view.id, selection);
3089− transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
3090− doc.apply(&transaction, view.id);
3091}
2735}
3092
2736
3093// Creates an LspCallback that waits for formatting changes to be computed. When they're done,
2737// Creates an LspCallback that waits for formatting changes to be computed. When they're done,
@@ -3158,7 +2802,6 @@ fn open(cx: &mut Context, open: Open) {
3158 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
2802 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
3159 Open::Above => range.from(),
2803 Open::Above => range.from(),
3160 });
2804 });
3161−
3162 let new_line = match open {
2805 let new_line = match open {
3163 // adjust position to the end of the line (next line - 1)
2806 // adjust position to the end of the line (next line - 1)
3164 Open::Below => cursor_line + 1,
2807 Open::Below => cursor_line + 1,
@@ -3166,15 +2809,13 @@ fn open(cx: &mut Context, open: Open) {
3166 Open::Above => cursor_line,
2809 Open::Above => cursor_line,
3167 };
2810 };
3168
2811
3169− let line_num = new_line.saturating_sub(1);
3170−
3171 // Index to insert newlines after, as well as the char width
2812 // Index to insert newlines after, as well as the char width
3172 // to use to compensate for those inserted newlines.
2813 // to use to compensate for those inserted newlines.
3173 let (line_end_index, line_end_offset_width) = if new_line == 0 {
2814 let (line_end_index, line_end_offset_width) = if new_line == 0 {
3174 (0, 0)
2815 (0, 0)
3175 } else {
2816 } else {
3176 (
2817 (
3177− line_end_char_index(&text, line_num),
2818+ line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
3178 doc.line_ending.len_chars(),
2819 doc.line_ending.len_chars(),
3179 )
2820 )
3180 };
2821 };
@@ -3182,15 +2823,13 @@ fn open(cx: &mut Context, open: Open) {
3182 let indent = indent::indent_for_newline(
2823 let indent = indent::indent_for_newline(
3183 doc.language_config(),
2824 doc.language_config(),
3184 doc.syntax(),
2825 doc.syntax(),
3185− &doc.config.load().indent_heuristic,
3186 &doc.indent_style,
2826 &doc.indent_style,
3187 doc.tab_width(),
2827 doc.tab_width(),
3188 text,
2828 text,
3189− line_num,
2829+ new_line.saturating_sub(1),
3190 line_end_index,
2830 line_end_index,
3191 cursor_line,
2831 cursor_line,
3192 );
2832 );
3193−
3194 let indent_len = indent.len();
2833 let indent_len = indent.len();
3195 let mut text = String::with_capacity(1 + indent_len);
2834 let mut text = String::with_capacity(1 + indent_len);
3196 text.push_str(doc.line_ending.as_str());
2835 text.push_str(doc.line_ending.as_str());
@@ -3350,7 +2989,7 @@ fn exit_select_mode(cx: &mut Context) {
3350
2989
3351fn goto_first_diag(cx: &mut Context) {
2990fn goto_first_diag(cx: &mut Context) {
3352 let (view, doc) = current!(cx.editor);
2991 let (view, doc) = current!(cx.editor);
3353− let selection = match doc.shown_diagnostics().next() {
2992+ let selection = match doc.diagnostics().first() {
3354 Some(diag) => Selection::single(diag.range.start, diag.range.end),
2993 Some(diag) => Selection::single(diag.range.start, diag.range.end),
3355 None => return,
2994 None => return,
3356 };
2995 };
@@ -3359,7 +2998,7 @@ fn goto_first_diag(cx: &mut Context) {
3359
2998
3360fn goto_last_diag(cx: &mut Context) {
2999fn goto_last_diag(cx: &mut Context) {
3361 let (view, doc) = current!(cx.editor);
3000 let (view, doc) = current!(cx.editor);
3362− let selection = match doc.shown_diagnostics().last() {
3001+ let selection = match doc.diagnostics().last() {
3363 Some(diag) => Selection::single(diag.range.start, diag.range.end),
3002 Some(diag) => Selection::single(diag.range.start, diag.range.end),
3364 None => return,
3003 None => return,
3365 };
3004 };
@@ -3375,9 +3014,10 @@ fn goto_next_diag(cx: &mut Context) {
3375 .cursor(doc.text().slice(..));
3014 .cursor(doc.text().slice(..));
3376
3015
3377 let diag = doc
3016 let diag = doc
3378− .shown_diagnostics()
3017+ .diagnostics()
3018+ .iter()
3379 .find(|diag| diag.range.start > cursor_pos)
3019 .find(|diag| diag.range.start > cursor_pos)
3380− .or_else(|| doc.shown_diagnostics().next());
3020+ .or_else(|| doc.diagnostics().first());
3381
3021
3382 let selection = match diag {
3022 let selection = match diag {
3383 Some(diag) => Selection::single(diag.range.start, diag.range.end),
3023 Some(diag) => Selection::single(diag.range.start, diag.range.end),
@@ -3395,10 +3035,11 @@ fn goto_prev_diag(cx: &mut Context) {
3395 .cursor(doc.text().slice(..));
3035 .cursor(doc.text().slice(..));
3396
3036
3397 let diag = doc
3037 let diag = doc
3398− .shown_diagnostics()
3038+ .diagnostics()
3039+ .iter()
3399 .rev()
3040 .rev()
3400 .find(|diag| diag.range.start < cursor_pos)
3041 .find(|diag| diag.range.start < cursor_pos)
3401− .or_else(|| doc.shown_diagnostics().last());
3042+ .or_else(|| doc.diagnostics().last());
3402
3043
3403 let selection = match diag {
3044 let selection = match diag {
3404 // NOTE: the selection is reversed because we're jumping to the
3045 // NOTE: the selection is reversed because we're jumping to the
@@ -3489,7 +3130,8 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
3489
3130
3490 doc.set_selection(view.id, selection)
3131 doc.set_selection(view.id, selection)
3491 };
3132 };
3133+ motion(cx.editor);
3492− cx.editor.apply_motion(motion);
3134+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
3493}
3135}
3494
3136
3495/// Returns the [Range] for a [Hunk] in the given text.
3137/// Returns the [Range] for a [Hunk] in the given text.
@@ -3552,19 +3194,23 @@ pub mod insert {
3552 use helix_lsp::lsp;
3194 use helix_lsp::lsp;
3553 // if ch matches completion char, trigger completion
3195 // if ch matches completion char, trigger completion
3554 let doc = doc_mut!(cx.editor);
3196 let doc = doc_mut!(cx.editor);
3555− let trigger_completion = doc
3197+ let language_server = match doc.language_server() {
3556− .language_servers_with_feature(LanguageServerFeature::Completion)
3198+ Some(language_server) => language_server,
3557− .any(|ls| {
3199+ None => return,
3558− // TODO: what if trigger is multiple chars long
3200+ };
3559− matches!(&ls.capabilities().completion_provider, Some(lsp::CompletionOptions {
3201+
3560− trigger_characters: Some(triggers),
3202+ let capabilities = language_server.capabilities();
3561− ..
3562− }) if triggers.iter().any(|trigger| trigger.contains(ch)))
3563− });
3564
3203
3565− if trigger_completion {
3204+ if let Some(lsp::CompletionOptions {
3205+ trigger_characters: Some(triggers),
3206+ ..
3207+ }) = &capabilities.completion_provider
3208+ {
3209+ // TODO: what if trigger is multiple chars long
3210+ if triggers.iter().any(|trigger| trigger.contains(ch)) {
3566− cx.editor.clear_idle_timer();
3211+ cx.editor.clear_idle_timer();
3567− super::completion(cx);
3212+ super::completion(cx);
3213+ }
3568 }
3214 }
3569 }
3215 }
3570
3216
@@ -3572,12 +3218,12 @@ pub mod insert {
3572 use helix_lsp::lsp;
3218 use helix_lsp::lsp;
3573 // if ch matches signature_help char, trigger
3219 // if ch matches signature_help char, trigger
3574 let doc = doc_mut!(cx.editor);
3220 let doc = doc_mut!(cx.editor);
3575− // TODO support multiple language servers (not just the first that is found), likely by merging UI somehow
3221+ // The language_server!() macro is not used here since it will
3222+ // print an "LSP not active for current buffer" message on
3223+ // every keypress.
3576− let Some(language_server) = doc
3224+ let language_server = match doc.language_server() {
3577− .language_servers_with_feature(LanguageServerFeature::SignatureHelp)
3225+ Some(language_server) => language_server,
3578− .next()
3579− else {
3580− return;
3226+ None => return,
3581 };
3227 };
3582
3228
3583 let capabilities = language_server.capabilities();
3229 let capabilities = language_server.capabilities();
@@ -3616,7 +3262,6 @@ pub mod insert {
3616 }
3262 }
3617
3263
3618 use helix_core::auto_pairs;
3264 use helix_core::auto_pairs;
3619− use helix_view::editor::SmartTabConfig;
3620
3265
3621 pub fn insert_char(cx: &mut Context, c: char) {
3266 pub fn insert_char(cx: &mut Context, c: char) {
3622 let (view, doc) = current_ref!(cx.editor);
3267 let (view, doc) = current_ref!(cx.editor);
@@ -3642,31 +3287,6 @@ pub mod insert {
3642 }
3287 }
3643 }
3288 }
3644
3289
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) {
3290 pub fn insert_tab(cx: &mut Context) {
3671 let (view, doc) = current!(cx.editor);
3291 let (view, doc) = current!(cx.editor);
3672 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
3292 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
@@ -3722,7 +3342,6 @@ pub mod insert {
3722 let indent = indent::indent_for_newline(
3342 let indent = indent::indent_for_newline(
3723 doc.language_config(),
3343 doc.language_config(),
3724 doc.syntax(),
3344 doc.syntax(),
3725− &doc.config.load().indent_heuristic,
3726 &doc.indent_style,
3345 &doc.indent_style,
3727 doc.tab_width(),
3346 doc.tab_width(),
3728 text,
3347 text,
@@ -3796,10 +3415,10 @@ pub mod insert {
3796 let auto_pairs = doc.auto_pairs(cx.editor);
3415 let auto_pairs = doc.auto_pairs(cx.editor);
3797
3416
3798 let transaction =
3417 let transaction =
3799− Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| {
3418+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3800 let pos = range.cursor(text);
3419 let pos = range.cursor(text);
3801 if pos == 0 {
3420 if pos == 0 {
3802− return (pos, pos);
3421+ return (pos, pos, None);
3803 }
3422 }
3804 let line_start_pos = text.line_to_char(range.cursor_line(text));
3423 let line_start_pos = text.line_to_char(range.cursor_line(text));
3805 // consider to delete by indent level if all characters before `pos` are indent units.
3424 // consider to delete by indent level if all characters before `pos` are indent units.
@@ -3807,7 +3426,11 @@ pub mod insert {
3807 if !fragment.is_empty() && fragment.chars().all(|ch| ch == ' ' || ch == '\t') {
3426 if !fragment.is_empty() && fragment.chars().all(|ch| ch == ' ' || ch == '\t') {
3808 if text.get_char(pos.saturating_sub(1)) == Some('\t') {
3427 if text.get_char(pos.saturating_sub(1)) == Some('\t') {
3809 // fast path, delete one char
3428 // fast path, delete one char
3429+ (
3810− (graphemes::nth_prev_grapheme_boundary(text, pos, 1), pos)
3430+ graphemes::nth_prev_grapheme_boundary(text, pos, 1),
3431+ pos,
3432+ None,
3433+ )
3811 } else {
3434 } else {
3812 let width: usize = fragment
3435 let width: usize = fragment
3813 .chars()
3436 .chars()
@@ -3834,7 +3457,7 @@ pub mod insert {
3834 _ => break,
3457 _ => break,
3835 }
3458 }
3836 }
3459 }
3837− (start, pos) // delete!
3460+ (start, pos, None) // delete!
3838 }
3461 }
3839 } else {
3462 } else {
3840 match (
3463 match (
@@ -3852,12 +3475,17 @@ pub mod insert {
3852 (
3475 (
3853 graphemes::nth_prev_grapheme_boundary(text, pos, count),
3476 graphemes::nth_prev_grapheme_boundary(text, pos, count),
3854 graphemes::nth_next_grapheme_boundary(text, pos, count),
3477 graphemes::nth_next_grapheme_boundary(text, pos, count),
3478+ None,
3855 )
3479 )
3856 }
3480 }
3857 _ =>
3481 _ =>
3858 // delete 1 char
3482 // delete 1 char
3859 {
3483 {
3484+ (
3860− (graphemes::nth_prev_grapheme_boundary(text, pos, count), pos)
3485+ graphemes::nth_prev_grapheme_boundary(text, pos, count),
3486+ pos,
3487+ None,
3488+ )
3861 }
3489 }
3862 }
3490 }
3863 }
3491 }
@@ -3870,40 +3498,50 @@ pub mod insert {
3870
3498
3871 pub fn delete_char_forward(cx: &mut Context) {
3499 pub fn delete_char_forward(cx: &mut Context) {
3872 let count = cx.count();
3500 let count = cx.count();
3873− delete_by_selection_insert_mode(
3501+ let (view, doc) = current!(cx.editor);
3874− cx,
3502+ let text = doc.text().slice(..);
3875− |text, range| {
3503+ let transaction =
3504+ Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3876 let pos = range.cursor(text);
3505 let pos = range.cursor(text);
3506+ (
3507+ pos,
3877− (pos, graphemes::nth_next_grapheme_boundary(text, pos, count))
3508+ graphemes::nth_next_grapheme_boundary(text, pos, count),
3509+ None,
3510+ )
3878− },
3511+ });
3879− Direction::Forward,
3512+ doc.apply(&transaction, view.id);
3880− )
3513+
3514+ lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
3881 }
3515 }
3882
3516
3883 pub fn delete_word_backward(cx: &mut Context) {
3517 pub fn delete_word_backward(cx: &mut Context) {
3884 let count = cx.count();
3518 let count = cx.count();
3885− delete_by_selection_insert_mode(
3519+ let (view, doc) = current!(cx.editor);
3886− cx,
3520+ let text = doc.text().slice(..);
3887− |text, range| {
3521+
3522+ let selection = doc.selection(view.id).clone().transform(|range| {
3888− let anchor = movement::move_prev_word_start(text, *range, count).from();
3523+ let anchor = movement::move_prev_word_start(text, range, count).from();
3889− let next = Range::new(anchor, range.cursor(text));
3524+ let next = Range::new(anchor, range.cursor(text));
3890− let range = exclude_cursor(text, next, *range);
3525+ exclude_cursor(text, next, range)
3891− (range.from(), range.to())
3892− },
3526+ });
3893− Direction::Backward,
3527+ delete_selection_insert_mode(doc, view, &selection);
3894− );
3528+
3529+ lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
3895 }
3530 }
3896
3531
3897 pub fn delete_word_forward(cx: &mut Context) {
3532 pub fn delete_word_forward(cx: &mut Context) {
3898 let count = cx.count();
3533 let count = cx.count();
3899− delete_by_selection_insert_mode(
3534+ let (view, doc) = current!(cx.editor);
3900− cx,
3535+ let text = doc.text().slice(..);
3901− |text, range| {
3536+
3537+ let selection = doc.selection(view.id).clone().transform(|range| {
3902− let head = movement::move_next_word_end(text, *range, count).to();
3538+ let head = movement::move_next_word_end(text, range, count).to();
3903− (range.cursor(text), head)
3539+ Range::new(range.cursor(text), head)
3904− },
3540+ });
3905− Direction::Forward,
3541+
3906− );
3542+ delete_selection_insert_mode(doc, view, &selection);
3543+
3544+ lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
3907 }
3545 }
3908}
3546}
3909
3547
@@ -3963,21 +3601,34 @@ fn commit_undo_checkpoint(cx: &mut Context) {
3963// Yank / Paste
3601// Yank / Paste
3964
3602
3965fn yank(cx: &mut Context) {
3603fn yank(cx: &mut Context) {
3966− yank_impl(cx.editor, cx.register.unwrap_or('"'));
3604+ let (view, doc) = current!(cx.editor);
3967− exit_select_mode(cx);
3605+ let text = doc.text().slice(..);
3968−}
3969
3606
3970−fn yank_to_clipboard(cx: &mut Context) {
3607+ let values: Vec<String> = doc
3971− yank_impl(cx.editor, '+');
3608+ .selection(view.id)
3972− exit_select_mode(cx);
3609+ .fragments(text)
3973−}
3610+ .map(Cow::into_owned)
3611+ .collect();
3974
3612
3975−fn yank_to_primary_clipboard(cx: &mut Context) {
3613+ let msg = format!(
3614+ "yanked {} selection(s) to register {}",
3615+ values.len(),
3616+ cx.register.unwrap_or('"')
3617+ );
3618+
3976− yank_impl(cx.editor, '*');
3619+ cx.editor
3620+ .registers
3621+ .write(cx.register.unwrap_or('"'), values);
3622+
3623+ cx.editor.set_status(msg);
3977 exit_select_mode(cx);
3624 exit_select_mode(cx);
3978}
3625}
3979
3626
3627+fn yank_joined_to_clipboard_impl(
3980−fn yank_impl(editor: &mut Editor, register: char) {
3628+ editor: &mut Editor,
3629+ separator: &str,
3630+ clipboard_type: ClipboardType,
3631+) -> anyhow::Result<()> {
3981 let (view, doc) = current!(editor);
3632 let (view, doc) = current!(editor);
3982 let text = doc.text().slice(..);
3633 let text = doc.text().slice(..);
3983
3634
@@ -3986,79 +3637,74 @@ fn yank_impl(editor: &mut Editor, register: char) {
3986 .fragments(text)
3637 .fragments(text)
3987 .map(Cow::into_owned)
3638 .map(Cow::into_owned)
3988 .collect();
3639 .collect();
3989− let selections = values.len();
3990
3640
3991− match editor.registers.write(register, values) {
3641+ let clipboard_text = match clipboard_type {
3992− Ok(_) => editor.set_status(format!(
3642+ ClipboardType::Clipboard => "system clipboard",
3993− "yanked {selections} selection{} to register {register}",
3643+ ClipboardType::Selection => "primary clipboard",
3994− if selections == 1 { "" } else { "s" }
3995− )),
3996− Err(err) => editor.set_error(err.to_string()),
3997− }
3998−}
3644+ };
3999
3645
4000−fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
3646+ let msg = format!(
4001− let (view, doc) = current!(editor);
3647+ "joined and yanked {} selection(s) to {}",
4002− let text = doc.text().slice(..);
3648+ values.len(),
3649+ clipboard_text,
3650+ );
4003
3651
4004− let selection = doc.selection(view.id);
3652+ let joined = values.join(separator);
4005− let selections = selection.len();
4006− let joined = selection
4007− .fragments(text)
4008− .fold(String::new(), |mut acc, fragment| {
4009− if !acc.is_empty() {
4010− acc.push_str(separator);
4011− }
4012− acc.push_str(&fragment);
4013− acc
4014− });
4015
3653
4016− match editor.registers.write(register, vec![joined]) {
3654+ editor
4017− Ok(_) => editor.set_status(format!(
3655+ .clipboard_provider
4018− "joined and yanked {selections} selection{} to register {register}",
3656+ .set_contents(joined, clipboard_type)
4019− if selections == 1 { "" } else { "s" }
3657+ .context("Couldn't set system clipboard content")?;
4020− )),
4021− Err(err) => editor.set_error(err.to_string()),
4022− }
4023−}
4024
3658
4025−fn yank_joined(cx: &mut Context) {
3659+ editor.set_status(msg);
4026− let separator = doc!(cx.editor).line_ending.as_str();
4027− yank_joined_impl(cx.editor, separator, cx.register.unwrap_or('"'));
4028− exit_select_mode(cx);
4029−}
4030
3660
4031−fn yank_joined_to_clipboard(cx: &mut Context) {
3661+ Ok(())
4032− let line_ending = doc!(cx.editor).line_ending;
4033− yank_joined_impl(cx.editor, line_ending.as_str(), '+');
4034− exit_select_mode(cx);
4035}
3662}
4036
3663
4037−fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3664+fn yank_joined_to_clipboard(cx: &mut Context) {
4038 let line_ending = doc!(cx.editor).line_ending;
3665 let line_ending = doc!(cx.editor).line_ending;
3666+ let _ =
4039− yank_joined_impl(cx.editor, line_ending.as_str(), '*');
3667+ yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Clipboard);
4040 exit_select_mode(cx);
3668 exit_select_mode(cx);
4041}
3669}
4042
3670
3671+fn yank_main_selection_to_clipboard_impl(
4043−fn yank_primary_selection_impl(editor: &mut Editor, register: char) {
3672+ editor: &mut Editor,
3673+ clipboard_type: ClipboardType,
3674+) -> anyhow::Result<()> {
4044 let (view, doc) = current!(editor);
3675 let (view, doc) = current!(editor);
4045 let text = doc.text().slice(..);
3676 let text = doc.text().slice(..);
4046
3677
4047− let selection = doc.selection(view.id).primary().fragment(text).to_string();
3678+ let message_text = match clipboard_type {
3679+ ClipboardType::Clipboard => "yanked main selection to system clipboard",
3680+ ClipboardType::Selection => "yanked main selection to primary clipboard",
3681+ };
4048
3682
4049− match editor.registers.write(register, vec![selection]) {
3683+ let value = doc.selection(view.id).primary().fragment(text);
4050− Ok(_) => editor.set_status(format!("yanked primary selection to register {register}",)),
3684+
4051− Err(err) => editor.set_error(err.to_string()),
3685+ if let Err(e) = editor
3686+ .clipboard_provider
3687+ .set_contents(value.into_owned(), clipboard_type)
3688+ {
3689+ bail!("Couldn't set system clipboard content: {}", e);
4052 }
3690 }
3691+
3692+ editor.set_status(message_text);
3693+ Ok(())
4053}
3694}
4054
3695
4055fn yank_main_selection_to_clipboard(cx: &mut Context) {
3696fn yank_main_selection_to_clipboard(cx: &mut Context) {
4056− yank_primary_selection_impl(cx.editor, '+');
3697+ let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard);
4057− exit_select_mode(cx);
3698+}
3699+
3700+fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3701+ let line_ending = doc!(cx.editor).line_ending;
3702+ let _ =
3703+ yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Selection);
4058}
3704}
4059
3705
4060fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
3706fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
4061− yank_primary_selection_impl(cx.editor, '*');
3707+ let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection);
4062 exit_select_mode(cx);
3708 exit_select_mode(cx);
4063}
3709}
4064
3710
@@ -4156,99 +3802,146 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
4156 };
3802 };
4157 let (view, doc) = current!(cx.editor);
3803 let (view, doc) = current!(cx.editor);
4158 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
3804 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
4159− exit_select_mode(cx);
3805+}
3806+
3807+fn paste_clipboard_impl(
3808+ editor: &mut Editor,
3809+ action: Paste,
3810+ clipboard_type: ClipboardType,
3811+ count: usize,
3812+) -> anyhow::Result<()> {
3813+ let (view, doc) = current!(editor);
3814+ match editor.clipboard_provider.get_contents(clipboard_type) {
3815+ Ok(contents) => {
3816+ paste_impl(&[contents], doc, view, action, count, editor.mode);
3817+ Ok(())
3818+ }
3819+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
3820+ }
4160}
3821}
4161
3822
4162fn paste_clipboard_after(cx: &mut Context) {
3823fn paste_clipboard_after(cx: &mut Context) {
3824+ let _ = paste_clipboard_impl(
3825+ cx.editor,
4163− paste(cx.editor, '+', Paste::After, cx.count());
3826+ Paste::After,
4164− exit_select_mode(cx);
3827+ ClipboardType::Clipboard,
3828+ cx.count(),
3829+ );
4165}
3830}
4166
3831
4167fn paste_clipboard_before(cx: &mut Context) {
3832fn paste_clipboard_before(cx: &mut Context) {
3833+ let _ = paste_clipboard_impl(
3834+ cx.editor,
4168− paste(cx.editor, '+', Paste::Before, cx.count());
3835+ Paste::Before,
4169− exit_select_mode(cx);
3836+ ClipboardType::Clipboard,
3837+ cx.count(),
3838+ );
4170}
3839}
4171
3840
4172fn paste_primary_clipboard_after(cx: &mut Context) {
3841fn paste_primary_clipboard_after(cx: &mut Context) {
3842+ let _ = paste_clipboard_impl(
3843+ cx.editor,
4173− paste(cx.editor, '*', Paste::After, cx.count());
3844+ Paste::After,
4174− exit_select_mode(cx);
3845+ ClipboardType::Selection,
3846+ cx.count(),
3847+ );
4175}
3848}
4176
3849
4177fn paste_primary_clipboard_before(cx: &mut Context) {
3850fn paste_primary_clipboard_before(cx: &mut Context) {
3851+ let _ = paste_clipboard_impl(
3852+ cx.editor,
4178− paste(cx.editor, '*', Paste::Before, cx.count());
3853+ Paste::Before,
4179− exit_select_mode(cx);
3854+ ClipboardType::Selection,
3855+ cx.count(),
3856+ );
4180}
3857}
4181
3858
4182fn replace_with_yanked(cx: &mut Context) {
3859fn replace_with_yanked(cx: &mut Context) {
3860+ let count = cx.count();
4183− replace_with_yanked_impl(cx.editor, cx.register.unwrap_or('"'), cx.count());
3861+ let reg_name = cx.register.unwrap_or('"');
3862+ let (view, doc) = current!(cx.editor);
3863+ let registers = &mut cx.editor.registers;
3864+
3865+ if let Some(values) = registers.read(reg_name) {
3866+ if !values.is_empty() {
3867+ let repeat = std::iter::repeat(
3868+ values
3869+ .last()
3870+ .map(|value| Tendril::from(&value.repeat(count)))
3871+ .unwrap(),
3872+ );
3873+ let mut values = values
3874+ .iter()
3875+ .map(|value| Tendril::from(&value.repeat(count)))
3876+ .chain(repeat);
3877+ let selection = doc.selection(view.id);
3878+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3879+ if !range.is_empty() {
3880+ (range.from(), range.to(), Some(values.next().unwrap()))
3881+ } else {
3882+ (range.from(), range.to(), None)
3883+ }
3884+ });
3885+
3886+ doc.apply(&transaction, view.id);
4184− exit_select_mode(cx);
3887+ exit_select_mode(cx);
3888+ }
3889+ }
4185}
3890}
4186
3891
4187−fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
3892+fn replace_selections_with_clipboard_impl(
4188− let Some(values) = editor.registers
3893+ cx: &mut Context,
4189− .read(register, editor)
3894+ clipboard_type: ClipboardType,
4190− .filter(|values| values.len() > 0) else { return };
3895+) -> anyhow::Result<()> {
4191− let values: Vec<_> = values.map(|value| value.to_string()).collect();
3896+ let count = cx.count();
3897+ let (view, doc) = current!(cx.editor);
4192
3898
4193− let (view, doc) = current!(editor);
3899+ match cx.editor.clipboard_provider.get_contents(clipboard_type) {
4194− let repeat = std::iter::repeat(
3900+ 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);
3901+ let selection = doc.selection(view.id);
4205− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3902+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4206− if !range.is_empty() {
3903+ (
4207− (range.from(), range.to(), Some(values.next().unwrap()))
3904+ range.from(),
4208− } else {
3905+ range.to(),
4209− (range.from(), range.to(), None)
3906+ Some(contents.repeat(count).as_str().into()),
3907+ )
3908+ });
3909+
3910+ doc.apply(&transaction, view.id);
3911+ doc.append_changes_to_history(view);
4210 }
3912 }
3913+ Err(e) => return Err(e.context("Couldn't get system clipboard contents")),
4211− });
3914+ }
4212
3915
4213− doc.apply(&transaction, view.id);
3916+ exit_select_mode(cx);
3917+ Ok(())
4214}
3918}
4215
3919
4216fn replace_selections_with_clipboard(cx: &mut Context) {
3920fn replace_selections_with_clipboard(cx: &mut Context) {
4217− replace_with_yanked_impl(cx.editor, '+', cx.count());
3921+ let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard);
4218− exit_select_mode(cx);
4219}
3922}
4220
3923
4221fn replace_selections_with_primary_clipboard(cx: &mut Context) {
3924fn replace_selections_with_primary_clipboard(cx: &mut Context) {
4222− replace_with_yanked_impl(cx.editor, '*', cx.count());
3925+ let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Selection);
4223− exit_select_mode(cx);
4224}
3926}
4225
3927
4226−fn paste(editor: &mut Editor, register: char, pos: Paste, count: usize) {
3928+fn paste(cx: &mut Context, pos: Paste) {
4227− let Some(values) = editor.registers.read(register, editor) else { return };
3929+ let count = cx.count();
4228− let values: Vec<_> = values.map(|value| value.to_string()).collect();
3930+ let reg_name = cx.register.unwrap_or('"');
3931+ let (view, doc) = current!(cx.editor);
3932+ let registers = &mut cx.editor.registers;
4229
3933
4230− let (view, doc) = current!(editor);
3934+ if let Some(values) = registers.read(reg_name) {
4231− paste_impl(&values, doc, view, pos, count, editor.mode);
3935+ paste_impl(values, doc, view, pos, count, cx.editor.mode);
3936+ }
4232}
3937}
4233
3938
4234fn paste_after(cx: &mut Context) {
3939fn paste_after(cx: &mut Context) {
4235− paste(
4236− cx.editor,
4237− cx.register.unwrap_or('"'),
4238− Paste::After,
3940+ paste(cx, Paste::After)
4239− cx.count(),
4240− );
4241− exit_select_mode(cx);
4242}
3941}
4243
3942
4244fn paste_before(cx: &mut Context) {
3943fn paste_before(cx: &mut Context) {
4245− paste(
4246− cx.editor,
4247− cx.register.unwrap_or('"'),
4248− Paste::Before,
3944+ paste(cx, Paste::Before)
4249− cx.count(),
4250− );
4251− exit_select_mode(cx);
4252}
3945}
4253
3946
4254fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
3947fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
@@ -4287,7 +3980,6 @@ fn indent(cx: &mut Context) {
4287 }),
3980 }),
4288 );
3981 );
4289 doc.apply(&transaction, view.id);
3982 doc.apply(&transaction, view.id);
4290− exit_select_mode(cx);
4291}
3983}
4292
3984
4293fn unindent(cx: &mut Context) {
3985fn unindent(cx: &mut Context) {
@@ -4327,76 +4019,71 @@ fn unindent(cx: &mut Context) {
4327 let transaction = Transaction::change(doc.text(), changes.into_iter());
4019 let transaction = Transaction::change(doc.text(), changes.into_iter());
4328
4020
4329 doc.apply(&transaction, view.id);
4021 doc.apply(&transaction, view.id);
4330− exit_select_mode(cx);
4331}
4022}
4332
4023
4333fn format_selections(cx: &mut Context) {
4024fn format_selections(cx: &mut Context) {
4334 use helix_lsp::{lsp, util::range_to_lsp_range};
4025 use helix_lsp::{lsp, util::range_to_lsp_range};
4335
4026
4336 let (view, doc) = current!(cx.editor);
4027 let (view, doc) = current!(cx.editor);
4337− let view_id = view.id;
4338
4028
4339 // via lsp if available
4029 // via lsp if available
4340 // TODO: else via tree-sitter indentation calculations
4030 // TODO: else via tree-sitter indentation calculations
4341
4031
4342− if doc.selection(view_id).len() != 1 {
4343− cx.editor
4344− .set_error("format_selections only supports a single selection for now");
4345− return;
4346− }
4347−
4348− // TODO extra LanguageServerFeature::FormatSelections?
4349− // maybe such that LanguageServerFeature::Format contains it as well
4350− let Some(language_server) = doc
4032+ let language_server = match doc.language_server() {
4351− .language_servers_with_feature(LanguageServerFeature::Format)
4033+ Some(language_server) => language_server,
4352− .find(|ls| {
4353− matches!(
4354− ls.capabilities().document_range_formatting_provider,
4355− Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
4356− )
4357− })
4358− else {
4359− cx.editor
4360− .set_error("No configured language server supports range formatting");
4361− return;
4034+ None => return,
4362 };
4035 };
4363
4036
4364− let offset_encoding = language_server.offset_encoding();
4365 let ranges: Vec<lsp::Range> = doc
4037 let ranges: Vec<lsp::Range> = doc
4366− .selection(view_id)
4038+ .selection(view.id)
4367 .iter()
4039 .iter()
4368− .map(|range| range_to_lsp_range(doc.text(), *range, offset_encoding))
4040+ .map(|range| range_to_lsp_range(doc.text(), *range, language_server.offset_encoding()))
4369 .collect();
4041 .collect();
4370
4042
4043+ if ranges.len() != 1 {
4044+ cx.editor
4045+ .set_error("format_selections only supports a single selection for now");
4046+ return;
4047+ }
4048+
4371 // TODO: handle fails
4049 // TODO: handle fails
4372 // TODO: concurrent map over all ranges
4050 // TODO: concurrent map over all ranges
4373
4051
4374 let range = ranges[0];
4052 let range = ranges[0];
4375
4053
4376− let future = language_server
4377− .text_document_range_formatting(
4054+ let request = match language_server.text_document_range_formatting(
4378− doc.identifier(),
4055+ doc.identifier(),
4379− range,
4056+ range,
4380− lsp::FormattingOptions::default(),
4057+ lsp::FormattingOptions::default(),
4381− None,
4058+ None,
4382− )
4059+ ) {
4383− .unwrap();
4060+ Some(future) => future,
4061+ None => {
4062+ cx.editor
4063+ .set_error("Language server does not support range formatting");
4064+ return;
4065+ }
4066+ };
4384
4067
4385− let edits = tokio::task::block_in_place(|| helix_lsp::block_on(future)).unwrap_or_default();
4068+ let edits = tokio::task::block_in_place(|| helix_lsp::block_on(request)).unwrap_or_default();
4386
4069
4387− let transaction =
4388− helix_lsp::util::generate_transaction_from_edits(doc.text(), edits, offset_encoding);
4070+ let transaction = helix_lsp::util::generate_transaction_from_edits(
4071+ doc.text(),
4072+ edits,
4073+ language_server.offset_encoding(),
4074+ );
4389
4075
4390− doc.apply(&transaction, view_id);
4076+ doc.apply(&transaction, view.id);
4391}
4077}
4392
4078
4393fn join_selections_impl(cx: &mut Context, select_space: bool) {
4079fn join_selections_impl(cx: &mut Context, select_space: bool) {
4394 use movement::skip_while;
4080 use movement::skip_while;
4395 let (view, doc) = current!(cx.editor);
4081 let (view, doc) = current!(cx.editor);
4396 let text = doc.text();
4082 let text = doc.text();
4397− let slice = text.slice(..);
4083+ let slice = doc.text().slice(..);
4398
4084
4399 let mut changes = Vec::new();
4085 let mut changes = Vec::new();
4086+ let fragment = Tendril::from(" ");
4400
4087
4401 for selection in doc.selection(view.id) {
4088 for selection in doc.selection(view.id) {
4402 let (start, mut end) = selection.line_range(slice);
4089 let (start, mut end) = selection.line_range(slice);
@@ -4412,13 +4099,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4412 let mut end = text.line_to_char(line + 1);
4099 let mut end = text.line_to_char(line + 1);
4413 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
4100 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
4414
4101
4415− let separator = if end == line_end_char_index(&slice, line + 1) {
4102+ // need to skip from start, not end
4416− // the joining line contains only space-characters => don't include a whitespace when joining
4103+ let change = (start, end, Some(fragment.clone()));
4417− None
4418− } else {
4419− Some(Tendril::from(" "))
4420− };
4421− changes.push((start, end, separator));
4104+ changes.push(change);
4422 }
4105 }
4423 }
4106 }
4424
4107
@@ -4430,6 +4113,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4430 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
4113 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
4431 changes.dedup();
4114 changes.dedup();
4432
4115
4116+ // TODO: joining multiple empty lines should be replaced by a single space.
4117+ // need to merge change ranges that touch
4118+
4433 // select inserted spaces
4119 // select inserted spaces
4434 let transaction = if select_space {
4120 let transaction = if select_space {
4435 let ranges: SmallVec<_> = changes
4121 let ranges: SmallVec<_> = changes
@@ -4441,9 +4127,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4441 })
4127 })
4442 .collect();
4128 .collect();
4443 let selection = Selection::new(ranges, 0);
4129 let selection = Selection::new(ranges, 0);
4444− Transaction::change(text, changes.into_iter()).with_selection(selection)
4130+ Transaction::change(doc.text(), changes.into_iter()).with_selection(selection)
4445 } else {
4131 } else {
4446− Transaction::change(text, changes.into_iter())
4132+ Transaction::change(doc.text(), changes.into_iter())
4447 };
4133 };
4448
4134
4449 doc.apply(&transaction, view.id);
4135 doc.apply(&transaction, view.id);
@@ -4457,8 +4143,8 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
4457 if remove { "remove:" } else { "keep:" }.into(),
4143 if remove { "remove:" } else { "keep:" }.into(),
4458 Some(reg),
4144 Some(reg),
4459 ui::completers::none,
4145 ui::completers::none,
4460− move |cx, regex, event| {
4146+ move |editor, regex, event| {
4461− let (view, doc) = current!(cx.editor);
4147+ let (view, doc) = current!(editor);
4462 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
4148 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
4463 return;
4149 return;
4464 }
4150 }
@@ -4517,53 +4203,21 @@ pub fn completion(cx: &mut Context) {
4517
4203
4518 let (view, doc) = current!(cx.editor);
4204 let (view, doc) = current!(cx.editor);
4519
4205
4520− let savepoint = if let Some(CompleteAction::Selected { savepoint }) = &cx.editor.last_completion
4206+ let language_server = match doc.language_server() {
4521− {
4207+ Some(language_server) => language_server,
4522− savepoint.clone()
4208+ None => return,
4523− } else {
4524− doc.savepoint(view)
4525 };
4209 };
4526
4210
4527− let text = savepoint.text.clone();
4528− let cursor = savepoint.cursor();
4529−
4530− let mut seen_language_servers = HashSet::new();
4531−
4532− let mut futures: FuturesUnordered<_> = doc
4533− .language_servers_with_feature(LanguageServerFeature::Completion)
4534− .filter(|ls| seen_language_servers.insert(ls.id()))
4535− .map(|language_server| {
4536− let language_server_id = language_server.id();
4537− let offset_encoding = language_server.offset_encoding();
4211+ let offset_encoding = language_server.offset_encoding();
4538− let pos = pos_to_lsp_pos(&text, cursor, offset_encoding);
4212+ let text = doc.text().slice(..);
4539− let doc_id = doc.identifier();
4213+ let cursor = doc.selection(view.id).primary().cursor(text);
4540− let completion_request = language_server.completion(doc_id, pos, None).unwrap();
4541−
4542− async move {
4543− let json = completion_request.await?;
4544− let response: Option<lsp::CompletionResponse> = serde_json::from_value(json)?;
4545−
4546− let items = match response {
4547− Some(lsp::CompletionResponse::Array(items)) => items,
4548− // TODO: do something with is_incomplete
4549− Some(lsp::CompletionResponse::List(lsp::CompletionList {
4550− is_incomplete: _is_incomplete,
4551− items,
4552− })) => items,
4553− None => Vec::new(),
4554− }
4555− .into_iter()
4556− .map(|item| CompletionItem {
4557− item,
4558− language_server_id,
4559− resolved: false,
4560− })
4561− .collect();
4562
4214
4563− anyhow::Ok(items)
4215+ let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
4564− }
4216+
4565− })
4217+ let future = match language_server.completion(doc.identifier(), pos, None) {
4566− .collect();
4218+ Some(future) => future,
4219+ None => return,
4220+ };
4567
4221
4568 // setup a channel that allows the request to be canceled
4222 // setup a channel that allows the request to be canceled
4569 let (tx, rx) = oneshot::channel();
4223 let (tx, rx) = oneshot::channel();
@@ -4572,20 +4226,12 @@ pub fn completion(cx: &mut Context) {
4572 // and the associated request is automatically dropped
4226 // and the associated request is automatically dropped
4573 cx.editor.completion_request_handle = Some(tx);
4227 cx.editor.completion_request_handle = Some(tx);
4574 let future = async move {
4228 let future = async move {
4575− let items_future = async move {
4576− let mut items = Vec::new();
4577− // TODO if one completion request errors, all other completion requests are discarded (even if they're valid)
4578− while let Some(mut lsp_items) = futures.try_next().await? {
4579− items.append(&mut lsp_items);
4580− }
4581− anyhow::Ok(items)
4582− };
4583 tokio::select! {
4229 tokio::select! {
4584 biased;
4230 biased;
4585 _ = rx => {
4231 _ = rx => {
4586− Ok(Vec::new())
4232+ Ok(serde_json::Value::Null)
4587 }
4233 }
4588− res = items_future => {
4234+ res = future => {
4589 res
4235 res
4590 }
4236 }
4591 }
4237 }
@@ -4601,6 +4247,7 @@ pub fn completion(cx: &mut Context) {
4601 iter.reverse();
4247 iter.reverse();
4602 let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count();
4248 let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count();
4603 let start_offset = cursor.saturating_sub(offset);
4249 let start_offset = cursor.saturating_sub(offset);
4250+ let savepoint = doc.savepoint(view);
4604
4251
4605 let trigger_doc = doc.id();
4252 let trigger_doc = doc.id();
4606 let trigger_view = view.id;
4253 let trigger_view = view.id;
@@ -4619,9 +4266,9 @@ pub fn completion(cx: &mut Context) {
4619 },
4266 },
4620 ));
4267 ));
4621
4268
4622− cx.jobs.callback(async move {
4269+ cx.callback(
4623− let items = future.await?;
4270+ future,
4624− let call = move |editor: &mut Editor, compositor: &mut Compositor| {
4271+ move |editor, compositor, response: Option<lsp::CompletionResponse>| {
4625 let (view, doc) = current_ref!(editor);
4272 let (view, doc) = current_ref!(editor);
4626 // check if the completion request is stale.
4273 // check if the completion request is stale.
4627 //
4274 //
@@ -4632,6 +4279,16 @@ pub fn completion(cx: &mut Context) {
4632 return;
4279 return;
4633 }
4280 }
4634
4281
4282+ let items = match response {
4283+ Some(lsp::CompletionResponse::Array(items)) => items,
4284+ // TODO: do something with is_incomplete
4285+ Some(lsp::CompletionResponse::List(lsp::CompletionList {
4286+ is_incomplete: _is_incomplete,
4287+ items,
4288+ })) => items,
4289+ None => Vec::new(),
4290+ };
4291+
4635 if items.is_empty() {
4292 if items.is_empty() {
4636 // editor.set_error("No completion available");
4293 // editor.set_error("No completion available");
4637 return;
4294 return;
@@ -4642,6 +4299,7 @@ pub fn completion(cx: &mut Context) {
4642 editor,
4299 editor,
4643 savepoint,
4300 savepoint,
4644 items,
4301 items,
4302+ offset_encoding,
4645 start_offset,
4303 start_offset,
4646 trigger_offset,
4304 trigger_offset,
4647 size,
4305 size,
@@ -4655,9 +4313,8 @@ pub fn completion(cx: &mut Context) {
4655 {
4313 {
4656 compositor.remove(SignatureHelp::ID);
4314 compositor.remove(SignatureHelp::ID);
4657 }
4315 }
4658− };
4316+ },
4659− Ok(Callback::EditorCompositor(Box::new(call)))
4660− });
4317+ );
4661}
4318}
4662
4319
4663// comments
4320// comments
@@ -4692,13 +4349,7 @@ fn rotate_selections_backward(cx: &mut Context) {
4692 rotate_selections(cx, Direction::Backward)
4349 rotate_selections(cx, Direction::Backward)
4693}
4350}
4694
4351
4695−enum ReorderStrategy {
4352+fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
4696− RotateForward,
4697− RotateBackward,
4698− Reverse,
4699−}
4700−
4701−fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
4702 let count = cx.count;
4353 let count = cx.count;
4703 let (view, doc) = current!(cx.editor);
4354 let (view, doc) = current!(cx.editor);
4704 let text = doc.text().slice(..);
4355 let text = doc.text().slice(..);
@@ -4716,10 +4367,9 @@ fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
4716
4367
4717 for chunk in fragments.chunks_mut(group) {
4368 for chunk in fragments.chunks_mut(group) {
4718 // TODO: also modify main index
4369 // TODO: also modify main index
4719− match strategy {
4370+ match direction {
4720− ReorderStrategy::RotateForward => chunk.rotate_right(1),
4371+ Direction::Forward => chunk.rotate_right(1),
4721− ReorderStrategy::RotateBackward => chunk.rotate_left(1),
4372+ Direction::Backward => chunk.rotate_left(1),
4722− ReorderStrategy::Reverse => chunk.reverse(),
4723 };
4373 };
4724 }
4374 }
4725
4375
@@ -4736,13 +4386,10 @@ fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
4736}
4386}
4737
4387
4738fn rotate_selection_contents_forward(cx: &mut Context) {
4388fn rotate_selection_contents_forward(cx: &mut Context) {
4739− reorder_selection_contents(cx, ReorderStrategy::RotateForward)
4389+ rotate_selection_contents(cx, Direction::Forward)
4740}
4390}
4741fn rotate_selection_contents_backward(cx: &mut Context) {
4391fn rotate_selection_contents_backward(cx: &mut Context) {
4742− reorder_selection_contents(cx, ReorderStrategy::RotateBackward)
4392+ rotate_selection_contents(cx, Direction::Backward)
4743−}
4744−fn reverse_selection_contents(cx: &mut Context) {
4745− reorder_selection_contents(cx, ReorderStrategy::Reverse)
4746}
4393}
4747
4394
4748// tree sitter node selection
4395// tree sitter node selection
@@ -4766,7 +4413,8 @@ fn expand_selection(cx: &mut Context) {
4766 }
4413 }
4767 }
4414 }
4768 };
4415 };
4416+ motion(cx.editor);
4769− cx.editor.apply_motion(motion);
4417+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
4770}
4418}
4771
4419
4772fn shrink_selection(cx: &mut Context) {
4420fn shrink_selection(cx: &mut Context) {
@@ -4790,7 +4438,8 @@ fn shrink_selection(cx: &mut Context) {
4790 doc.set_selection(view.id, selection);
4438 doc.set_selection(view.id, selection);
4791 }
4439 }
4792 };
4440 };
4441+ motion(cx.editor);
4793− cx.editor.apply_motion(motion);
4442+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
4794}
4443}
4795
4444
4796fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
4445fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
@@ -4808,7 +4457,8 @@ where
4808 doc.set_selection(view.id, selection);
4457 doc.set_selection(view.id, selection);
4809 }
4458 }
4810 };
4459 };
4460+ motion(cx.editor);
4811− cx.editor.apply_motion(motion);
4461+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
4812}
4462}
4813
4463
4814fn select_next_sibling(cx: &mut Context) {
4464fn select_next_sibling(cx: &mut Context) {
@@ -4819,68 +4469,22 @@ fn select_prev_sibling(cx: &mut Context) {
4819 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
4469 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
4820}
4470}
4821
4471
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) {
4472fn match_brackets(cx: &mut Context) {
4866 let (view, doc) = current!(cx.editor);
4473 let (view, doc) = current!(cx.editor);
4867− let is_select = cx.editor.mode == Mode::Select;
4868− let text = doc.text();
4869− let text_slice = text.slice(..);
4870−
4871− let selection = doc.selection(view.id).clone().transform(|range| {
4872− let pos = range.cursor(text_slice);
4873− if let Some(matched_pos) = doc.syntax().map_or_else(
4874− || match_brackets::find_matching_bracket_plaintext(text.slice(..), pos),
4875− |syntax| match_brackets::find_matching_bracket_fuzzy(syntax, text.slice(..), pos),
4876− ) {
4877− range.put_cursor(text_slice, matched_pos, is_select)
4878− } else {
4879− range
4880− }
4881− });
4882
4474
4475+ if let Some(syntax) = doc.syntax() {
4476+ let text = doc.text().slice(..);
4477+ let selection = doc.selection(view.id).clone().transform(|range| {
4478+ if let Some(pos) =
4479+ match_brackets::find_matching_bracket_fuzzy(syntax, doc.text(), range.cursor(text))
4480+ {
4481+ range.put_cursor(text, pos, cx.editor.mode == Mode::Select)
4482+ } else {
4483+ range
4484+ }
4485+ });
4883− doc.set_selection(view.id, selection);
4486+ doc.set_selection(view.id, selection);
4487+ }
4884}
4488}
4885
4489
4886//
4490//
@@ -4975,19 +4579,17 @@ fn transpose_view(cx: &mut Context) {
4975 cx.editor.transpose_view()
4579 cx.editor.transpose_view()
4976}
4580}
4977
4581
4978−/// Open a new split in the given direction specified by the action.
4582+// 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) {
4583+fn split(cx: &mut Context, action: Action) {
4982− let (view, doc) = current!(editor);
4584+ let (view, doc) = current!(cx.editor);
4983 let id = doc.id();
4585 let id = doc.id();
4984 let selection = doc.selection(view.id).clone();
4586 let selection = doc.selection(view.id).clone();
4985 let offset = view.offset;
4587 let offset = view.offset;
4986
4588
4987− editor.switch(id, action);
4589+ cx.editor.switch(id, action);
4988
4590
4989 // match the selection in the previous view
4591 // match the selection in the previous view
4990− let (view, doc) = current!(editor);
4592+ let (view, doc) = current!(cx.editor);
4991 doc.set_selection(view.id, selection);
4593 doc.set_selection(view.id, selection);
4992 // match the view scroll offset (switch doesn't handle this fully
4594 // match the view scroll offset (switch doesn't handle this fully
4993 // since the selection is only matched after the split)
4595 // since the selection is only matched after the split)
@@ -4995,7 +4597,7 @@ fn split(editor: &mut Editor, action: Action) {
4995}
4597}
4996
4598
4997fn hsplit(cx: &mut Context) {
4599fn hsplit(cx: &mut Context) {
4998− split(cx.editor, Action::HorizontalSplit);
4600+ split(cx, Action::HorizontalSplit);
4999}
4601}
5000
4602
5001fn hsplit_new(cx: &mut Context) {
4603fn hsplit_new(cx: &mut Context) {
@@ -5003,7 +4605,7 @@ fn hsplit_new(cx: &mut Context) {
5003}
4605}
5004
4606
5005fn vsplit(cx: &mut Context) {
4607fn vsplit(cx: &mut Context) {
5006− split(cx.editor, Action::VerticalSplit);
4608+ split(cx, Action::VerticalSplit);
5007}
4609}
5008
4610
5009fn vsplit_new(cx: &mut Context) {
4611fn vsplit_new(cx: &mut Context) {
@@ -5052,12 +4654,7 @@ fn insert_register(cx: &mut Context) {
5052 if let Some(ch) = event.char() {
4654 if let Some(ch) = event.char() {
5053 cx.editor.autoinfo = None;
4655 cx.editor.autoinfo = None;
5054 cx.register = Some(ch);
4656 cx.register = Some(ch);
5055− paste(
5056− cx.editor,
5057− cx.register.unwrap_or('"'),
5058− Paste::Cursor,
4657+ paste(cx, Paste::Cursor);
5059− cx.count(),
5060− );
5061 }
4658 }
5062 })
4659 })
5063}
4660}
@@ -5141,7 +4738,8 @@ fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direct
5141 editor.set_status("Syntax-tree is not available in current buffer");
4738 editor.set_status("Syntax-tree is not available in current buffer");
5142 }
4739 }
5143 };
4740 };
4741+ motion(cx.editor);
5144− cx.editor.apply_motion(motion);
4742+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
5145}
4743}
5146
4744
5147fn goto_next_function(cx: &mut Context) {
4745fn goto_next_function(cx: &mut Context) {
@@ -5262,7 +4860,8 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
5262 });
4860 });
5263 doc.set_selection(view.id, selection);
4861 doc.set_selection(view.id, selection);
5264 };
4862 };
4863+ textobject(cx.editor);
5265− cx.editor.apply_motion(textobject);
4864+ cx.editor.last_motion = Some(Motion(Box::new(textobject)));
5266 }
4865 }
5267 });
4866 });
5268
4867
@@ -5352,18 +4951,11 @@ fn surround_replace(cx: &mut Context) {
5352 }
4951 }
5353 };
4952 };
5354
4953
5355− let selection = selection.clone();
5356− let ranges: SmallVec<[Range; 1]> = change_pos.iter().map(|&p| Range::point(p)).collect();
5357− doc.set_selection(
5358− view.id,
5359− Selection::new(ranges, selection.primary_index() * 2),
5360− );
5361−
5362 cx.on_next_key(move |cx, event| {
4954 cx.on_next_key(move |cx, event| {
5363 let (view, doc) = current!(cx.editor);
4955 let (view, doc) = current!(cx.editor);
5364 let to = match event.char() {
4956 let to = match event.char() {
5365 Some(to) => to,
4957 Some(to) => to,
5366− None => return doc.set_selection(view.id, selection),
4958+ None => return,
5367 };
4959 };
5368 let (open, close) = surround::get_pair(to);
4960 let (open, close) = surround::get_pair(to);
5369 let transaction = Transaction::change(
4961 let transaction = Transaction::change(
@@ -5374,7 +4966,6 @@ fn surround_replace(cx: &mut Context) {
5374 (pos, pos + 1, Some(t))
4966 (pos, pos + 1, Some(t))
5375 }),
4967 }),
5376 );
4968 );
5377− doc.set_selection(view.id, selection);
5378 doc.apply(&transaction, view.id);
4969 doc.apply(&transaction, view.id);
5379 exit_select_mode(cx);
4970 exit_select_mode(cx);
5380 });
4971 });
@@ -5523,7 +5114,7 @@ async fn shell_impl_async(
5523 helix_view::document::to_writer(&mut stdin, (encoding::UTF_8, false), &input)
5114 helix_view::document::to_writer(&mut stdin, (encoding::UTF_8, false), &input)
5524 .await?;
5115 .await?;
5525 }
5116 }
5526− anyhow::Ok(())
5117+ Ok::<_, anyhow::Error>(())
5527 });
5118 });
5528 let (output, _) = tokio::join! {
5119 let (output, _) = tokio::join! {
5529 process.wait_with_output(),
5120 process.wait_with_output(),
@@ -5609,18 +5200,12 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
5609 };
5200 };
5610
5201
5611 // These `usize`s cannot underflow because selection ranges cannot overlap.
5202 // These `usize`s cannot underflow because selection ranges cannot overlap.
5612− let anchor = to
5203+ // Once the MSRV is 1.66.0 (mixed_integer_ops is stabilized), we can use checked
5613− .checked_add_signed(offset)
5204+ // arithmetic to assert this.
5614− .expect("Selection ranges cannot overlap")
5205+ 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());
5206 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
5618 ranges.push(new_range);
5207 ranges.push(new_range);
5619− offset = offset
5208+ 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
5209
5625 changes.push((from, to, Some(output)));
5210 changes.push((from, to, Some(output)));
5626 }
5211 }
@@ -5642,7 +5227,7 @@ fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBeha
5642 cx,
5227 cx,
5643 prompt,
5228 prompt,
5644 Some('|'),
5229 Some('|'),
5645− ui::completers::filename,
5230+ ui::completers::none,
5646 move |cx, input: &str, event: PromptEvent| {
5231 move |cx, input: &str, event: PromptEvent| {
5647 if event != PromptEvent::Validate {
5232 if event != PromptEvent::Validate {
5648 return;
5233 return;
@@ -5759,7 +5344,6 @@ fn increment_impl(cx: &mut Context, increment_direction: IncrementDirection) {
5759 let transaction = Transaction::change(doc.text(), changes.into_iter());
5344 let transaction = Transaction::change(doc.text(), changes.into_iter());
5760 let transaction = transaction.with_selection(new_selection);
5345 let transaction = transaction.with_selection(new_selection);
5761 doc.apply(&transaction, view.id);
5346 doc.apply(&transaction, view.id);
5762− exit_select_mode(cx);
5763 }
5347 }
5764}
5348}
5765
5349
@@ -5778,12 +5362,9 @@ fn record_macro(cx: &mut Context) {
5778 }
5362 }
5779 })
5363 })
5780 .collect::<String>();
5364 .collect::<String>();
5781− match cx.editor.registers.write(reg, vec![s]) {
5365+ cx.editor.registers.write(reg, vec![s]);
5782− Ok(_) => cx
5783− .editor
5366+ cx.editor
5784− .set_status(format!("Recorded to register [{}]", reg)),
5367+ .set_status(format!("Recorded to register [{}]", reg));
5785− Err(err) => cx.editor.set_error(err.to_string()),
5786− }
5787 } else {
5368 } else {
5788 let reg = cx.register.take().unwrap_or('@');
5369 let reg = cx.register.take().unwrap_or('@');
5789 cx.editor.macro_recording = Some((reg, Vec::new()));
5370 cx.editor.macro_recording = Some((reg, Vec::new()));
@@ -5803,14 +5384,8 @@ fn replay_macro(cx: &mut Context) {
5803 return;
5384 return;
5804 }
5385 }
5805
5386
5806− let keys: Vec<KeyEvent> = if let Some(keys) = cx
5387+ 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) {
5388+ match helix_view::input::parse_macro(keys_str) {
5814 Ok(keys) => keys,
5389 Ok(keys) => keys,
5815 Err(err) => {
5390 Err(err) => {
5816 cx.editor.set_error(format!("Invalid macro: {}", err));
5391 cx.editor.set_error(format!("Invalid macro: {}", err));
helix-term/src/commands/dap.rs+56−59
@@ -2,13 +2,13 @@ use super::{Context, Editor};
2use crate::{
2use crate::{
3 compositor::{self, Compositor},
3 compositor::{self, Compositor},
4 job::{Callback, Jobs},
4 job::{Callback, Jobs},
5− ui::{self, overlay::overlaid, Picker, Popup, Prompt, PromptEvent, Text},
5+ ui::{self, overlay::overlaid, FilePicker, Picker, Popup, Prompt, PromptEvent, Text},
6};
6};
7use dap::{StackFrame, Thread, ThreadStates};
7use 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;
@@ -73,19 +73,21 @@ fn thread_picker(
73 let debugger = debugger!(editor);
73 let debugger = debugger!(editor);
74
74
75 let thread_states = debugger.thread_states.clone();
75 let thread_states = debugger.thread_states.clone();
76− let picker = Picker::new(threads, thread_states, move |cx, thread, _action| {
76+ let picker = FilePicker::new(
77+ threads,
78+ thread_states,
77− callback_fn(cx.editor, thread)
79+ move |cx, thread, _action| callback_fn(cx.editor, thread),
78− })
79− .with_preview(move |editor, thread| {
80+ move |editor, thread| {
80− let frames = editor.debugger.as_ref()?.stack_frames.get(&thread.id)?;
81+ let frames = editor.debugger.as_ref()?.stack_frames.get(&thread.id)?;
81− let frame = frames.get(0)?;
82+ let frame = frames.get(0)?;
82− let path = frame.source.as_ref()?.path.clone()?;
83+ let path = frame.source.as_ref()?.path.clone()?;
83− let pos = Some((
84+ let pos = Some((
84− frame.line.saturating_sub(1),
85+ frame.line.saturating_sub(1),
85− frame.end_line.unwrap_or(frame.line).saturating_sub(1),
86+ frame.end_line.unwrap_or(frame.line).saturating_sub(1),
86− ));
87+ ));
87− Some((path.into(), pos))
88+ Some((path.into(), pos))
89+ },
88− });
90+ );
89 compositor.push(Box::new(picker));
91 compositor.push(Box::new(picker));
90 },
92 },
91 );
93 );
@@ -217,7 +219,7 @@ pub fn dap_start_impl(
217 }
219 }
218 }
220 }
219
221
220− args.insert("cwd", to_value(helix_loader::current_working_dir())?);
222+ args.insert("cwd", to_value(std::env::current_dir().unwrap())?);
221
223
222 let args = to_value(args).unwrap();
224 let args = to_value(args).unwrap();
223
225
@@ -339,12 +341,8 @@ fn debug_parameter_prompt(
339 .to_owned();
341 .to_owned();
340
342
341 let completer = match field_type {
343 let completer = match field_type {
342− "filename" => |editor: &Editor, input: &str| {
344+ "filename" => ui::completers::filename,
343− ui::completers::filename_with_git_ignore(editor, input, false)
345+ "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,
346 _ => ui::completers::none,
349 };
347 };
350
348
@@ -581,13 +579,8 @@ pub fn dap_variables(cx: &mut Context) {
581 }
579 }
582
580
583 let contents = Text::from(tui::text::Text::from(variables));
581 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);
582+ let popup = Popup::new("dap-variables", contents);
590− cx.replace_or_push_layer("dap-variables", popup);
583+ cx.push_layer(Box::new(popup));
591}
584}
592
585
593pub fn dap_terminate(cx: &mut Context) {
586pub fn dap_terminate(cx: &mut Context) {
@@ -735,35 +728,39 @@ pub fn dap_switch_stack_frame(cx: &mut Context) {
735
728
736 let frames = debugger.stack_frames[&thread_id].clone();
729 let frames = debugger.stack_frames[&thread_id].clone();
737
730
731+ let picker = FilePicker::new(
732+ frames,
733+ (),
738− let picker = Picker::new(frames, (), move |cx, frame, _action| {
734+ move |cx, frame, _action| {
739− let debugger = debugger!(cx.editor);
735+ let debugger = debugger!(cx.editor);
740− // TODO: this should be simpler to find
736+ // TODO: this should be simpler to find
741− let pos = debugger.stack_frames[&thread_id]
737+ let pos = debugger.stack_frames[&thread_id]
742− .iter()
738+ .iter()
743− .position(|f| f.id == frame.id);
739+ .position(|f| f.id == frame.id);
744− debugger.active_frame = pos;
740+ debugger.active_frame = pos;
745−
741+
746− let frame = debugger.stack_frames[&thread_id]
742+ let frame = debugger.stack_frames[&thread_id]
747− .get(pos.unwrap_or(0))
743+ .get(pos.unwrap_or(0))
748− .cloned();
744+ .cloned();
749− if let Some(frame) = &frame {
745+ if let Some(frame) = &frame {
750− jump_to_stack_frame(cx.editor, frame);
746+ jump_to_stack_frame(cx.editor, frame);
751− }
747+ }
752− })
748+ },
753− .with_preview(move |_editor, frame| {
749+ move |_editor, frame| {
754− frame
750+ frame
755− .source
751+ .source
756− .as_ref()
752+ .as_ref()
757− .and_then(|source| source.path.clone())
753+ .and_then(|source| source.path.clone())
758− .map(|path| {
754+ .map(|path| {
759− (
755+ (
760− path.into(),
756+ path.into(),
761− Some((
757+ Some((
762− frame.line.saturating_sub(1),
758+ frame.line.saturating_sub(1),
763− frame.end_line.unwrap_or(frame.line).saturating_sub(1),
759+ frame.end_line.unwrap_or(frame.line).saturating_sub(1),
764− )),
760+ )),
765− )
761+ )
766− })
762+ })
763+ },
767− });
764+ );
768 cx.push_layer(Box::new(picker))
765 cx.push_layer(Box::new(picker))
769}
766}
helix-term/src/commands/lsp.rs+492−505
@@ -1,4 +1,4 @@
1−use futures_util::{future::BoxFuture, stream::FuturesUnordered, FutureExt};
1+use futures_util::FutureExt;
2use helix_lsp::{
2use helix_lsp::{
3 block_on,
3 block_on,
4 lsp::{
4 lsp::{
@@ -6,10 +6,8 @@ use helix_lsp::{
6 NumberOrString,
6 NumberOrString,
7 },
7 },
8 util::{diagnostic_to_lsp_diagnostic, lsp_range_to_range, range_to_lsp_range},
8 util::{diagnostic_to_lsp_diagnostic, lsp_range_to_range, range_to_lsp_range},
9− Client, OffsetEncoding,
9+ OffsetEncoding,
10};
10};
11−use serde_json::Value;
12−use tokio_stream::StreamExt;
13use tui::{
11use tui::{
14 text::{Span, Spans},
12 text::{Span, Spans},
15 widgets::Row,
13 widgets::Row,
@@ -17,55 +15,42 @@ use tui::{
17
15
18use super::{align_view, push_jump, Align, Context, Editor, Open};
16use super::{align_view, push_jump, Align, Context, Editor, Open};
19
17
20−use helix_core::{
21− path, syntax::LanguageServerFeature, text_annotations::InlineAnnotation, Selection,
18+use helix_core::{path, text_annotations::InlineAnnotation, Selection};
22−};
23use helix_view::{
19use helix_view::{
24 document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
20 document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
25 editor::Action,
21 editor::Action,
26− graphics::Margin,
27 theme::Style,
22 theme::Style,
28 Document, View,
23 Document, View,
29};
24};
30
25
31use crate::{
26use crate::{
32 compositor::{self, Compositor},
27 compositor::{self, Compositor},
33− job::Callback,
34 ui::{
28 ui::{
35− self, lsp::SignatureHelp, overlay::overlaid, DynamicPicker, FileLocation, Picker, Popup,
29+ self, lsp::SignatureHelp, overlay::overlaid, DynamicPicker, FileLocation, FilePicker,
36− PromptEvent,
30+ Popup, PromptEvent,
37 },
31 },
38};
32};
39
33
40use std::{
34use std::{
41− cmp::Ordering,
42− collections::{BTreeMap, HashSet},
35+ cmp::Ordering, collections::BTreeMap, fmt::Write, future::Future, path::PathBuf, sync::Arc,
43− fmt::Write,
44− future::Future,
45− path::PathBuf,
46− sync::Arc,
47};
36};
48
37
49−/// Gets the first language server that is attached to a document which supports a specific feature.
38+/// Gets the language server that is attached to a document, and
50−/// If there is no configured language server that supports the feature, this displays a status message.
39+/// if it's not active displays a status message. Using this macro
51−/// Using this macro in a context where the editor automatically queries the LSP
40+/// in a context where the editor automatically queries the LSP
52−/// (instead of when the user explicitly does so via a keybind like `gd`)
41+/// (instead of when the user explicitly does so via a keybind like
53−/// will spam the "No configured language server supports \<feature>" status message confusingly.
42+/// `gd`) will spam the "LSP inactive" status message confusingly.
54#[macro_export]
43#[macro_export]
55−macro_rules! language_server_with_feature {
44+macro_rules! language_server {
56− ($editor:expr, $doc:expr, $feature:expr) => {{
45+ ($editor:expr, $doc:expr) => {
57− let language_server = $doc.language_servers_with_feature($feature).next();
58− match language_server {
46+ match $doc.language_server() {
59 Some(language_server) => language_server,
47 Some(language_server) => language_server,
60 None => {
48 None => {
61− $editor.set_status(format!(
49+ $editor.set_status("Language server not active for current buffer");
62− "No configured language server supports {}",
63− $feature
64− ));
65 return;
50 return;
66 }
51 }
67 }
52 }
68− }};
53+ };
69}
54}
70
55
71impl ui::menu::Item for lsp::Location {
56impl ui::menu::Item for lsp::Location {
@@ -102,30 +87,20 @@ impl ui::menu::Item for lsp::Location {
102 }
87 }
103}
88}
104
89
105−struct SymbolInformationItem {
106− symbol: lsp::SymbolInformation,
107− offset_encoding: OffsetEncoding,
108−}
109−
110−impl ui::menu::Item for SymbolInformationItem {
90+impl ui::menu::Item for lsp::SymbolInformation {
111 /// Path to currently focussed document
91 /// Path to currently focussed document
112 type Data = Option<lsp::Url>;
92 type Data = Option<lsp::Url>;
113
93
114 fn format(&self, current_doc_path: &Self::Data) -> Row {
94 fn format(&self, current_doc_path: &Self::Data) -> Row {
115− if current_doc_path.as_ref() == Some(&self.symbol.location.uri) {
95+ if current_doc_path.as_ref() == Some(&self.location.uri) {
116− self.symbol.name.as_str().into()
96+ self.name.as_str().into()
117 } else {
97 } else {
118− match self.symbol.location.uri.to_file_path() {
98+ match self.location.uri.to_file_path() {
119 Ok(path) => {
99 Ok(path) => {
120 let get_relative_path = path::get_relative_path(path.as_path());
100 let get_relative_path = path::get_relative_path(path.as_path());
121− format!(
122− "{} ({})",
123− &self.symbol.name,
124− get_relative_path.to_string_lossy()
101+ format!("{} ({})", &self.name, get_relative_path.to_string_lossy()).into()
125− )
126− .into()
127 }
102 }
128− Err(_) => format!("{} ({})", &self.symbol.name, &self.symbol.location.uri).into(),
103+ Err(_) => format!("{} ({})", &self.name, &self.location.uri).into(),
129 }
104 }
130 }
105 }
131 }
106 }
@@ -141,7 +116,6 @@ struct DiagnosticStyles {
141struct PickerDiagnostic {
116struct PickerDiagnostic {
142 url: lsp::Url,
117 url: lsp::Url,
143 diag: lsp::Diagnostic,
118 diag: lsp::Diagnostic,
144− offset_encoding: OffsetEncoding,
145}
119}
146
120
147impl ui::menu::Item for PickerDiagnostic {
121impl ui::menu::Item for PickerDiagnostic {
@@ -196,6 +170,7 @@ fn location_to_file_location(location: &lsp::Location) -> FileLocation {
196 (path.into(), line)
170 (path.into(), line)
197}
171}
198
172
173+// TODO: share with symbol picker(symbol.location)
199fn jump_to_location(
174fn jump_to_location(
200 editor: &mut Editor,
175 editor: &mut Editor,
201 location: &lsp::Location,
176 location: &lsp::Location,
@@ -213,16 +188,15 @@ fn jump_to_location(
213 return;
188 return;
214 }
189 }
215 };
190 };
216−
217− let doc = match editor.open(&path, action) {
191+ match editor.open(&path, action) {
218− Ok(id) => doc_mut!(editor, &id),
192+ Ok(_) => (),
219 Err(err) => {
193 Err(err) => {
220 let err = format!("failed to open path: {:?}: {:?}", location.uri, err);
194 let err = format!("failed to open path: {:?}: {:?}", location.uri, err);
221 editor.set_error(err);
195 editor.set_error(err);
222 return;
196 return;
223 }
197 }
224− };
198+ }
225− let view = view_mut!(editor);
199+ let (view, doc) = current!(editor);
226 // TODO: convert inside server
200 // TODO: convert inside server
227 let new_range =
201 let new_range =
228 if let Some(new_range) = lsp_range_to_range(doc.text(), location.range, offset_encoding) {
202 if let Some(new_range) = lsp_range_to_range(doc.text(), location.range, offset_encoding) {
@@ -234,24 +208,53 @@ fn jump_to_location(
234 // we flip the range so that the cursor sits on the start of the symbol
208 // we flip the range so that the cursor sits on the start of the symbol
235 // (for example start of the function).
209 // (for example start of the function).
236 doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
210 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);
211+ align_view(doc, view, Align::Center);
239− }
240}
212}
241
213
242−type SymbolPicker = Picker<SymbolInformationItem>;
214+fn sym_picker(
243−
215+ symbols: Vec<lsp::SymbolInformation>,
244−fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url>) -> SymbolPicker {
216+ current_path: Option<lsp::Url>,
217+ offset_encoding: OffsetEncoding,
218+) -> FilePicker<lsp::SymbolInformation> {
245 // TODO: drop current_path comparison and instead use workspace: bool flag?
219 // TODO: drop current_path comparison and instead use workspace: bool flag?
246− Picker::new(symbols, current_path, move |cx, item, action| {
220+ FilePicker::new(
247− jump_to_location(
221+ symbols,
222+ current_path.clone(),
223+ move |cx, symbol, action| {
248− cx.editor,
224+ let (view, doc) = current!(cx.editor);
225+ push_jump(view, doc);
226+
227+ if current_path.as_ref() != Some(&symbol.location.uri) {
249− &item.symbol.location,
228+ let uri = &symbol.location.uri;
250− item.offset_encoding,
229+ let path = match uri.to_file_path() {
251− action,
230+ Ok(path) => path,
252− );
231+ Err(_) => {
232+ let err = format!("unable to convert URI to filepath: {}", uri);
233+ cx.editor.set_error(err);
234+ return;
235+ }
236+ };
237+ if let Err(err) = cx.editor.open(&path, action) {
238+ let err = format!("failed to open document: {}: {}", uri, err);
239+ log::error!("{}", err);
240+ cx.editor.set_error(err);
241+ return;
242+ }
243+ }
244+
245+ let (view, doc) = current!(cx.editor);
246+
247+ if let Some(range) =
248+ lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding)
249+ {
250+ // we flip the range so that the cursor sits on the start of the symbol
251+ // (for example start of the function).
252+ doc.set_selection(view.id, Selection::single(range.head, range.anchor));
253+ align_view(doc, view, Align::Center);
253− })
254+ }
255+ },
254− .with_preview(move |_editor, item| Some(location_to_file_location(&item.symbol.location)))
256+ move |_editor, symbol| Some(location_to_file_location(&symbol.location)),
257+ )
255 .truncate_start(false)
258 .truncate_start(false)
256}
259}
257
260
@@ -263,25 +266,22 @@ enum DiagnosticsFormat {
263
266
264fn diag_picker(
267fn diag_picker(
265 cx: &Context,
268 cx: &Context,
266− diagnostics: BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize)>>,
269+ diagnostics: BTreeMap<lsp::Url, Vec<lsp::Diagnostic>>,
267− _current_path: Option<lsp::Url>,
270+ current_path: Option<lsp::Url>,
268 format: DiagnosticsFormat,
271 format: DiagnosticsFormat,
272+ offset_encoding: OffsetEncoding,
269−) -> Picker<PickerDiagnostic> {
273+) -> FilePicker<PickerDiagnostic> {
270 // TODO: drop current_path comparison and instead use workspace: bool flag?
274 // TODO: drop current_path comparison and instead use workspace: bool flag?
271
275
272 // flatten the map to a vec of (url, diag) pairs
276 // flatten the map to a vec of (url, diag) pairs
273 let mut flat_diag = Vec::new();
277 let mut flat_diag = Vec::new();
274 for (url, diags) in diagnostics {
278 for (url, diags) in diagnostics {
275 flat_diag.reserve(diags.len());
279 flat_diag.reserve(diags.len());
276−
277− for (diag, ls) in diags {
280+ for diag in diags {
278− if let Some(ls) = cx.editor.language_server_by_id(ls) {
279− flat_diag.push(PickerDiagnostic {
281+ flat_diag.push(PickerDiagnostic {
280− url: url.clone(),
282+ url: url.clone(),
281− diag,
283+ diag,
282− offset_encoding: ls.offset_encoding(),
283− });
284+ });
284− }
285 }
285 }
286 }
286 }
287
287
@@ -292,190 +292,157 @@ fn diag_picker(
292 error: cx.editor.theme.get("error"),
292 error: cx.editor.theme.get("error"),
293 };
293 };
294
294
295− Picker::new(
295+ FilePicker::new(
296 flat_diag,
296 flat_diag,
297 (styles, format),
297 (styles, format),
298− move |cx,
299− PickerDiagnostic {
298+ move |cx, PickerDiagnostic { url, diag }, action| {
300− url,
299+ if current_path.as_ref() == Some(url) {
301− diag,
300+ let (view, doc) = current!(cx.editor);
302− offset_encoding,
301+ push_jump(view, doc);
302+ } else {
303+ let path = url.to_file_path().unwrap();
304+ cx.editor.open(&path, action).expect("editor.open failed");
303− },
305+ }
304− action| {
306+
305− jump_to_location(
307+ let (view, doc) = current!(cx.editor);
306− cx.editor,
308+
309+ if let Some(range) = lsp_range_to_range(doc.text(), diag.range, offset_encoding) {
310+ // we flip the range so that the cursor sits on the start of the symbol
311+ // (for example start of the function).
312+ doc.set_selection(view.id, Selection::single(range.head, range.anchor));
313+ align_view(doc, view, Align::Center);
314+ }
315+ },
316+ move |_editor, PickerDiagnostic { url, diag }| {
307− &lsp::Location::new(url.clone(), diag.range),
317+ let location = lsp::Location::new(url.clone(), diag.range);
308− *offset_encoding,
318+ Some(location_to_file_location(&location))
309− action,
310− )
311 },
319 },
312 )
320 )
313− .with_preview(move |_editor, PickerDiagnostic { url, diag, .. }| {
314− let location = lsp::Location::new(url.clone(), diag.range);
315− Some(location_to_file_location(&location))
316− })
317 .truncate_start(false)
321 .truncate_start(false)
318}
322}
319
323
320pub fn symbol_picker(cx: &mut Context) {
324pub fn symbol_picker(cx: &mut Context) {
321 fn nested_to_flat(
325 fn nested_to_flat(
322− list: &mut Vec<SymbolInformationItem>,
326+ list: &mut Vec<lsp::SymbolInformation>,
323 file: &lsp::TextDocumentIdentifier,
327 file: &lsp::TextDocumentIdentifier,
324 symbol: lsp::DocumentSymbol,
328 symbol: lsp::DocumentSymbol,
325− offset_encoding: OffsetEncoding,
326 ) {
329 ) {
327 #[allow(deprecated)]
330 #[allow(deprecated)]
328− list.push(SymbolInformationItem {
329− symbol: lsp::SymbolInformation {
331+ list.push(lsp::SymbolInformation {
330− name: symbol.name,
332+ name: symbol.name,
331− kind: symbol.kind,
333+ kind: symbol.kind,
332− tags: symbol.tags,
334+ tags: symbol.tags,
333− deprecated: symbol.deprecated,
335+ deprecated: symbol.deprecated,
334− location: lsp::Location::new(file.uri.clone(), symbol.selection_range),
336+ location: lsp::Location::new(file.uri.clone(), symbol.selection_range),
335− container_name: None,
337+ container_name: None,
336− },
337− offset_encoding,
338 });
338 });
339 for child in symbol.children.into_iter().flatten() {
339 for child in symbol.children.into_iter().flatten() {
340− nested_to_flat(list, file, child, offset_encoding);
340+ nested_to_flat(list, file, child);
341 }
341 }
342 }
342 }
343 let doc = doc!(cx.editor);
343 let doc = doc!(cx.editor);
344
344
345− let mut seen_language_servers = HashSet::new();
345+ let language_server = language_server!(cx.editor, doc);
346+ let current_url = doc.url();
347+ let offset_encoding = language_server.offset_encoding();
346
348
347− let mut futures: FuturesUnordered<_> = doc
348− .language_servers_with_feature(LanguageServerFeature::DocumentSymbols)
349− .filter(|ls| seen_language_servers.insert(ls.id()))
350− .map(|language_server| {
351− let request = language_server.document_symbols(doc.identifier()).unwrap();
349+ let future = match language_server.document_symbols(doc.identifier()) {
352− let offset_encoding = language_server.offset_encoding();
350+ Some(future) => future,
353− let doc_id = doc.identifier();
351+ None => {
354−
352+ cx.editor
355− async move {
353+ .set_error("Language server does not support document symbols");
356− let json = request.await?;
354+ return;
355+ }
356+ };
357+
358+ cx.callback(
359+ future,
357− let response: Option<lsp::DocumentSymbolResponse> = serde_json::from_value(json)?;
360+ move |editor, compositor, response: Option<lsp::DocumentSymbolResponse>| {
358− let symbols = match response {
359− Some(symbols) => symbols,
361+ if let Some(symbols) = response {
360− None => return anyhow::Ok(vec![]),
361− };
362 // lsp has two ways to represent symbols (flat/nested)
362 // lsp has two ways to represent symbols (flat/nested)
363 // convert the nested variant to flat, so that we have a homogeneous list
363 // convert the nested variant to flat, so that we have a homogeneous list
364 let symbols = match symbols {
364 let symbols = match symbols {
365− lsp::DocumentSymbolResponse::Flat(symbols) => symbols
365+ lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
366− .into_iter()
367− .map(|symbol| SymbolInformationItem {
368− symbol,
369− offset_encoding,
370− })
371− .collect(),
372 lsp::DocumentSymbolResponse::Nested(symbols) => {
366 lsp::DocumentSymbolResponse::Nested(symbols) => {
367+ let doc = doc!(editor);
373 let mut flat_symbols = Vec::new();
368 let mut flat_symbols = Vec::new();
374 for symbol in symbols {
369 for symbol in symbols {
375− nested_to_flat(&mut flat_symbols, &doc_id, symbol, offset_encoding)
370+ nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
376 }
371 }
377 flat_symbols
372 flat_symbols
378 }
373 }
379 };
374 };
380− Ok(symbols)
381− }
382− })
383− .collect();
384− let current_url = doc.url();
385
375
386− if futures.is_empty() {
387− cx.editor
388− .set_error("No configured language server supports document symbols");
389− return;
390− }
391−
392− cx.jobs.callback(async move {
393− let mut symbols = Vec::new();
394− // TODO if one symbol request errors, all other requests are discarded (even if they're valid)
395− while let Some(mut lsp_items) = futures.try_next().await? {
396− symbols.append(&mut lsp_items);
397− }
398− let call = move |_editor: &mut Editor, compositor: &mut Compositor| {
399− let picker = sym_picker(symbols, current_url);
376+ let picker = sym_picker(symbols, current_url, offset_encoding);
400− compositor.push(Box::new(overlaid(picker)))
377+ compositor.push(Box::new(overlaid(picker)))
401− };
378+ }
402−
379+ },
403− Ok(Callback::EditorCompositor(Box::new(call)))
404− });
380+ )
405}
381}
406
382
407pub fn workspace_symbol_picker(cx: &mut Context) {
383pub fn workspace_symbol_picker(cx: &mut Context) {
408 let doc = doc!(cx.editor);
384 let doc = doc!(cx.editor);
409− if doc
385+ let current_url = doc.url();
410− .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
386+ let language_server = language_server!(cx.editor, doc);
411− .count()
387+ let offset_encoding = language_server.offset_encoding();
412− == 0
413− {
414− cx.editor
415− .set_error("No configured language server supports workspace symbols");
416− return;
417− }
418−
419− let get_symbols = move |pattern: String, editor: &mut Editor| {
420− let doc = doc!(editor);
421− let mut seen_language_servers = HashSet::new();
422− let mut futures: FuturesUnordered<_> = doc
423− .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
424− .filter(|ls| seen_language_servers.insert(ls.id()))
425− .map(|language_server| {
426− let request = language_server.workspace_symbols(pattern.clone()).unwrap();
388+ let future = match language_server.workspace_symbols("".to_string()) {
427− let offset_encoding = language_server.offset_encoding();
389+ Some(future) => future,
428− async move {
390+ None => {
429− let json = request.await?;
391+ cx.editor
430−
392+ .set_error("Language server does not support workspace symbols");
431− let response =
393+ return;
432− serde_json::from_value::<Option<Vec<lsp::SymbolInformation>>>(json)?
433− .unwrap_or_default()
434− .into_iter()
435− .map(|symbol| SymbolInformationItem {
436− symbol,
437− offset_encoding,
438− })
439− .collect();
440−
441− anyhow::Ok(response)
442− }
443− })
444− .collect();
445−
446− if futures.is_empty() {
447− editor.set_error("No configured language server supports workspace symbols");
448− }
449−
450− async move {
451− let mut symbols = Vec::new();
452− // TODO if one symbol request errors, all other requests are discarded (even if they're valid)
453− while let Some(mut lsp_items) = futures.try_next().await? {
454− symbols.append(&mut lsp_items);
455− }
456− anyhow::Ok(symbols)
457 }
394 }
458− .boxed()
459 };
395 };
460
396
461− let current_url = doc.url();
397+ cx.callback(
462− let initial_symbols = get_symbols("".to_owned(), cx.editor);
398+ future,
399+ move |_editor, compositor, response: Option<Vec<lsp::SymbolInformation>>| {
400+ let symbols = response.unwrap_or_default();
401+ let picker = sym_picker(symbols, current_url, offset_encoding);
402+ let get_symbols = |query: String, editor: &mut Editor| {
403+ let doc = doc!(editor);
404+ let language_server = match doc.language_server() {
405+ Some(s) => s,
406+ None => {
407+ // This should not generally happen since the picker will not
408+ // even open in the first place if there is no server.
409+ return async move { Err(anyhow::anyhow!("LSP not active")) }.boxed();
410+ }
411+ };
412+ let symbol_request = match language_server.workspace_symbols(query) {
413+ Some(future) => future,
414+ None => {
415+ // This should also not happen since the language server must have
416+ // supported workspace symbols before to reach this block.
417+ return async move {
418+ Err(anyhow::anyhow!(
419+ "Language server does not support workspace symbols"
420+ ))
421+ }
422+ .boxed();
423+ }
424+ };
463
425
464− cx.jobs.callback(async move {
426+ let future = async move {
465− let symbols = initial_symbols.await?;
427+ let json = symbol_request.await?;
466− let call = move |_editor: &mut Editor, compositor: &mut Compositor| {
428+ let response: Option<Vec<lsp::SymbolInformation>> =
467− let picker = sym_picker(symbols, current_url);
429+ serde_json::from_value(json)?;
430+
431+ Ok(response.unwrap_or_default())
432+ };
433+ future.boxed()
434+ };
468 let dyn_picker = DynamicPicker::new(picker, Box::new(get_symbols));
435 let dyn_picker = DynamicPicker::new(picker, Box::new(get_symbols));
469 compositor.push(Box::new(overlaid(dyn_picker)))
436 compositor.push(Box::new(overlaid(dyn_picker)))
470− };
437+ },
471−
472− Ok(Callback::EditorCompositor(Box::new(call)))
473− });
438+ )
474}
439}
475
440
476pub fn diagnostics_picker(cx: &mut Context) {
441pub fn diagnostics_picker(cx: &mut Context) {
477 let doc = doc!(cx.editor);
442 let doc = doc!(cx.editor);
443+ let language_server = language_server!(cx.editor, doc);
478 if let Some(current_url) = doc.url() {
444 if let Some(current_url) = doc.url() {
445+ let offset_encoding = language_server.offset_encoding();
479 let diagnostics = cx
446 let diagnostics = cx
480 .editor
447 .editor
481 .diagnostics
448 .diagnostics
@@ -487,6 +454,7 @@ pub fn diagnostics_picker(cx: &mut Context) {
487 [(current_url.clone(), diagnostics)].into(),
454 [(current_url.clone(), diagnostics)].into(),
488 Some(current_url),
455 Some(current_url),
489 DiagnosticsFormat::HideSourcePath,
456 DiagnosticsFormat::HideSourcePath,
457+ offset_encoding,
490 );
458 );
491 cx.push_layer(Box::new(overlaid(picker)));
459 cx.push_layer(Box::new(overlaid(picker)));
492 }
460 }
@@ -494,27 +462,24 @@ pub fn diagnostics_picker(cx: &mut Context) {
494
462
495pub fn workspace_diagnostics_picker(cx: &mut Context) {
463pub fn workspace_diagnostics_picker(cx: &mut Context) {
496 let doc = doc!(cx.editor);
464 let doc = doc!(cx.editor);
465+ let language_server = language_server!(cx.editor, doc);
497 let current_url = doc.url();
466 let current_url = doc.url();
498− // TODO not yet filtered by LanguageServerFeature, need to do something similar as Document::shown_diagnostics here for all open documents
467+ let offset_encoding = language_server.offset_encoding();
499 let diagnostics = cx.editor.diagnostics.clone();
468 let diagnostics = cx.editor.diagnostics.clone();
500 let picker = diag_picker(
469 let picker = diag_picker(
501 cx,
470 cx,
502 diagnostics,
471 diagnostics,
503 current_url,
472 current_url,
504 DiagnosticsFormat::ShowSourcePath,
473 DiagnosticsFormat::ShowSourcePath,
474+ offset_encoding,
505 );
475 );
506 cx.push_layer(Box::new(overlaid(picker)));
476 cx.push_layer(Box::new(overlaid(picker)));
507}
477}
508
478
509−struct CodeActionOrCommandItem {
510− lsp_item: lsp::CodeActionOrCommand,
511− language_server_id: usize,
512−}
513−
514−impl ui::menu::Item for CodeActionOrCommandItem {
479+impl ui::menu::Item for lsp::CodeActionOrCommand {
515 type Data = ();
480 type Data = ();
516 fn format(&self, _data: &Self::Data) -> Row {
481 fn format(&self, _data: &Self::Data) -> Row {
517− match &self.lsp_item {
482+ match self {
518 lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str().into(),
483 lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str().into(),
519 lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
484 lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
520 }
485 }
@@ -581,42 +546,45 @@ fn action_fixes_diagnostics(action: &CodeActionOrCommand) -> bool {
581pub fn code_action(cx: &mut Context) {
546pub fn code_action(cx: &mut Context) {
582 let (view, doc) = current!(cx.editor);
547 let (view, doc) = current!(cx.editor);
583
548
549+ let language_server = language_server!(cx.editor, doc);
550+
584 let selection_range = doc.selection(view.id).primary();
551 let selection_range = doc.selection(view.id).primary();
552+ let offset_encoding = language_server.offset_encoding();
585
553
586− let mut seen_language_servers = HashSet::new();
554+ let range = range_to_lsp_range(doc.text(), selection_range, offset_encoding);
587
555
588− let mut futures: FuturesUnordered<_> = doc
556+ let future = match language_server.code_actions(
589− .language_servers_with_feature(LanguageServerFeature::CodeAction)
557+ doc.identifier(),
590− .filter(|ls| seen_language_servers.insert(ls.id()))
558+ range,
591− // TODO this should probably already been filtered in something like "language_servers_with_feature"
592− .filter_map(|language_server| {
593− let offset_encoding = language_server.offset_encoding();
594− let language_server_id = language_server.id();
595− let range = range_to_lsp_range(doc.text(), selection_range, offset_encoding);
596− // Filter and convert overlapping diagnostics
559+ // Filter and convert overlapping diagnostics
597− let code_action_context = lsp::CodeActionContext {
560+ lsp::CodeActionContext {
598− diagnostics: doc
561+ diagnostics: doc
599− .diagnostics()
562+ .diagnostics()
600− .iter()
563+ .iter()
601− .filter(|&diag| {
564+ .filter(|&diag| {
602− selection_range
565+ selection_range
603− .overlaps(&helix_core::Range::new(diag.range.start, diag.range.end))
566+ .overlaps(&helix_core::Range::new(diag.range.start, diag.range.end))
604− })
567+ })
605− .map(|diag| diagnostic_to_lsp_diagnostic(doc.text(), diag, offset_encoding))
568+ .map(|diag| diagnostic_to_lsp_diagnostic(doc.text(), diag, offset_encoding))
606− .collect(),
569+ .collect(),
607− only: None,
570+ only: None,
608− trigger_kind: Some(CodeActionTriggerKind::INVOKED),
571+ trigger_kind: Some(CodeActionTriggerKind::INVOKED),
609− };
572+ },
610− let code_action_request =
573+ ) {
611− language_server.code_actions(doc.identifier(), range, code_action_context)?;
574+ Some(future) => future,
612− Some((code_action_request, language_server_id))
575+ None => {
576+ cx.editor
577+ .set_error("Language server does not support code actions");
578+ return;
613− })
579+ }
614− .map(|(request, ls_id)| async move {
580+ };
615− let json = request.await?;
581+
582+ cx.callback(
583+ future,
616− let response: Option<lsp::CodeActionResponse> = serde_json::from_value(json)?;
584+ move |editor, compositor, response: Option<lsp::CodeActionResponse>| {
617 let mut actions = match response {
585 let mut actions = match response {
618 Some(a) => a,
586 Some(a) => a,
619− None => return anyhow::Ok(Vec::new()),
587+ None => return,
620 };
588 };
621
589
622 // remove disabled code actions
590 // remove disabled code actions
@@ -628,6 +596,11 @@ pub fn code_action(cx: &mut Context) {
628 )
596 )
629 });
597 });
630
598
599+ if actions.is_empty() {
600+ editor.set_status("No code actions available");
601+ return;
602+ }
603+
631 // Sort codeactions into a useful order. This behaviour is only partially described in the LSP spec.
604 // Sort codeactions into a useful order. This behaviour is only partially described in the LSP spec.
632 // Many details are modeled after vscode because language servers are usually tested against it.
605 // Many details are modeled after vscode because language servers are usually tested against it.
633 // VScode sorts the codeaction two times:
606 // VScode sorts the codeaction two times:
@@ -663,74 +636,22 @@ pub fn code_action(cx: &mut Context) {
663 .reverse()
636 .reverse()
664 });
637 });
665
638
666− Ok(actions
667− .into_iter()
668− .map(|lsp_item| CodeActionOrCommandItem {
669− lsp_item,
670− language_server_id: ls_id,
671− })
672− .collect())
673− })
674− .collect();
675−
676− if futures.is_empty() {
677− cx.editor
678− .set_error("No configured language server supports code actions");
679− return;
680− }
681−
682− cx.jobs.callback(async move {
683− let mut actions = Vec::new();
684− // TODO if one code action request errors, all other requests are ignored (even if they're valid)
685− while let Some(mut lsp_items) = futures.try_next().await? {
686− actions.append(&mut lsp_items);
687− }
688−
689− let call = move |editor: &mut Editor, compositor: &mut Compositor| {
690− if actions.is_empty() {
691− editor.set_error("No code actions available");
692− return;
693− }
694− let mut picker = ui::Menu::new(actions, (), move |editor, action, event| {
639+ let mut picker = ui::Menu::new(actions, (), move |editor, code_action, event| {
695 if event != PromptEvent::Validate {
640 if event != PromptEvent::Validate {
696 return;
641 return;
697 }
642 }
698
643
699 // always present here
644 // always present here
700− let action = action.unwrap();
645+ let code_action = code_action.unwrap();
701− let Some(language_server) = editor.language_server_by_id(action.language_server_id)
702− else {
703− editor.set_error("Language Server disappeared");
704− return;
705− };
706− let offset_encoding = language_server.offset_encoding();
707
646
708− match &action.lsp_item {
647+ match code_action {
709 lsp::CodeActionOrCommand::Command(command) => {
648 lsp::CodeActionOrCommand::Command(command) => {
710 log::debug!("code action command: {:?}", command);
649 log::debug!("code action command: {:?}", command);
711− execute_lsp_command(editor, action.language_server_id, command.clone());
650+ execute_lsp_command(editor, command.clone());
712 }
651 }
713 lsp::CodeActionOrCommand::CodeAction(code_action) => {
652 lsp::CodeActionOrCommand::CodeAction(code_action) => {
714 log::debug!("code action: {:?}", code_action);
653 log::debug!("code action: {:?}", code_action);
715− // we support lsp "codeAction/resolve" for `edit` and `command` fields
716− let mut resolved_code_action = None;
717− if code_action.edit.is_none() || code_action.command.is_none() {
718− if let Some(future) =
719− language_server.resolve_code_action(code_action.clone())
720− {
721− if let Ok(response) = helix_lsp::block_on(future) {
722− if let Ok(code_action) =
723− serde_json::from_value::<CodeAction>(response)
724− {
725− resolved_code_action = Some(code_action);
726− }
727− }
728− }
729− }
730− let resolved_code_action =
731− resolved_code_action.as_ref().unwrap_or(code_action);
732−
733− if let Some(ref workspace_edit) = resolved_code_action.edit {
654+ if let Some(ref workspace_edit) = code_action.edit {
734 log::debug!("edit: {:?}", workspace_edit);
655 log::debug!("edit: {:?}", workspace_edit);
735 let _ = apply_workspace_edit(editor, offset_encoding, workspace_edit);
656 let _ = apply_workspace_edit(editor, offset_encoding, workspace_edit);
736 }
657 }
@@ -738,28 +659,17 @@ pub fn code_action(cx: &mut Context) {
738 // if code action provides both edit and command first the edit
659 // if code action provides both edit and command first the edit
739 // should be applied and then the command
660 // should be applied and then the command
740 if let Some(command) = &code_action.command {
661 if let Some(command) = &code_action.command {
741− execute_lsp_command(editor, action.language_server_id, command.clone());
662+ execute_lsp_command(editor, command.clone());
742 }
663 }
743 }
664 }
744 }
665 }
745 });
666 });
746 picker.move_down(); // pre-select the first item
667 picker.move_down(); // pre-select the first item
747
668
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)
669+ 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);
670 compositor.replace_or_push("code-action", popup);
759− };
671+ },
760−
761− Ok(Callback::EditorCompositor(Box::new(call)))
762− });
672+ )
763}
673}
764
674
765impl ui::menu::Item for lsp::Command {
675impl ui::menu::Item for lsp::Command {
@@ -769,13 +679,13 @@ impl ui::menu::Item for lsp::Command {
769 }
679 }
770}
680}
771
681
772−pub fn execute_lsp_command(editor: &mut Editor, language_server_id: usize, cmd: lsp::Command) {
682+pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
683+ let doc = doc!(editor);
684+ let language_server = language_server!(editor, doc);
685+
773 // the command is executed on the server and communicated back
686 // the command is executed on the server and communicated back
774 // to the client asynchronously using workspace edits
687 // to the client asynchronously using workspace edits
775− let future = match editor
776− .language_server_by_id(language_server_id)
777− .and_then(|language_server| language_server.command(cmd))
688+ let future = match language_server.command(cmd) {
778− {
779 Some(future) => future,
689 Some(future) => future,
780 None => {
690 None => {
781 editor.set_error("Language server does not support executing commands");
691 editor.set_error("Language server does not support executing commands");
@@ -1029,7 +939,7 @@ fn goto_impl(
1029 locations: Vec<lsp::Location>,
939 locations: Vec<lsp::Location>,
1030 offset_encoding: OffsetEncoding,
940 offset_encoding: OffsetEncoding,
1031) {
941) {
1032− let cwdir = helix_loader::current_working_dir();
942+ let cwdir = std::env::current_dir().unwrap_or_default();
1033
943
1034 match locations.as_slice() {
944 match locations.as_slice() {
1035 [location] => {
945 [location] => {
@@ -1039,10 +949,14 @@ fn goto_impl(
1039 editor.set_error("No definition found.");
949 editor.set_error("No definition found.");
1040 }
950 }
1041 _locations => {
951 _locations => {
952+ let picker = FilePicker::new(
953+ locations,
954+ cwdir,
1042− let picker = Picker::new(locations, cwdir, move |cx, location, action| {
955+ move |cx, location, action| {
1043− jump_to_location(cx.editor, location, offset_encoding, action)
956+ jump_to_location(cx.editor, location, offset_encoding, action)
1044− })
957+ },
1045− .with_preview(move |_editor, location| Some(location_to_file_location(location)));
958+ move |_editor, location| Some(location_to_file_location(location)),
959+ );
1046 compositor.push(Box::new(overlaid(picker)));
960 compositor.push(Box::new(overlaid(picker)));
1047 }
961 }
1048 }
962 }
@@ -1063,17 +977,21 @@ fn to_locations(definitions: Option<lsp::GotoDefinitionResponse>) -> Vec<lsp::Lo
1063 }
977 }
1064}
978}
1065
979
1066−fn goto_single_impl<P, F>(cx: &mut Context, feature: LanguageServerFeature, request_provider: P)
980+pub fn goto_declaration(cx: &mut Context) {
1067−where
1068− P: Fn(&Client, lsp::Position, lsp::TextDocumentIdentifier) -> Option<F>,
1069− F: Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send,
1070−{
1071 let (view, doc) = current!(cx.editor);
981 let (view, doc) = current!(cx.editor);
1072−
1073− let language_server = language_server_with_feature!(cx.editor, doc, feature);
982+ let language_server = language_server!(cx.editor, doc);
1074 let offset_encoding = language_server.offset_encoding();
983 let offset_encoding = language_server.offset_encoding();
984+
1075 let pos = doc.position(view.id, offset_encoding);
985 let pos = doc.position(view.id, offset_encoding);
986+
1076− let future = request_provider(language_server, pos, doc.identifier()).unwrap();
987+ let future = match language_server.goto_declaration(doc.identifier(), pos, None) {
988+ Some(future) => future,
989+ None => {
990+ cx.editor
991+ .set_error("Language server does not support goto-declaration");
992+ return;
993+ }
994+ };
1077
995
1078 cx.callback(
996 cx.callback(
1079 future,
997 future,
@@ -1084,56 +1002,102 @@ where
1084 );
1002 );
1085}
1003}
1086
1004
1087−pub fn goto_declaration(cx: &mut Context) {
1088− goto_single_impl(
1089− cx,
1090− LanguageServerFeature::GotoDeclaration,
1091− |ls, pos, doc_id| ls.goto_declaration(doc_id, pos, None),
1092− );
1093−}
1094−
1095pub fn goto_definition(cx: &mut Context) {
1005pub fn goto_definition(cx: &mut Context) {
1096− goto_single_impl(
1006+ let (view, doc) = current!(cx.editor);
1097− cx,
1007+ let language_server = language_server!(cx.editor, doc);
1098− LanguageServerFeature::GotoDefinition,
1008+ let offset_encoding = language_server.offset_encoding();
1099− |ls, pos, doc_id| ls.goto_definition(doc_id, pos, None),
1009+
1010+ let pos = doc.position(view.id, offset_encoding);
1011+
1012+ let future = match language_server.goto_definition(doc.identifier(), pos, None) {
1013+ Some(future) => future,
1014+ None => {
1015+ cx.editor
1016+ .set_error("Language server does not support goto-definition");
1017+ return;
1018+ }
1019+ };
1020+
1021+ cx.callback(
1022+ future,
1023+ move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
1024+ let items = to_locations(response);
1025+ goto_impl(editor, compositor, items, offset_encoding);
1026+ },
1100 );
1027 );
1101}
1028}
1102
1029
1103pub fn goto_type_definition(cx: &mut Context) {
1030pub fn goto_type_definition(cx: &mut Context) {
1104− goto_single_impl(
1031+ let (view, doc) = current!(cx.editor);
1032+ let language_server = language_server!(cx.editor, doc);
1033+ let offset_encoding = language_server.offset_encoding();
1034+
1035+ let pos = doc.position(view.id, offset_encoding);
1036+
1037+ let future = match language_server.goto_type_definition(doc.identifier(), pos, None) {
1038+ Some(future) => future,
1039+ None => {
1105− cx,
1040+ cx.editor
1106− LanguageServerFeature::GotoTypeDefinition,
1041+ .set_error("Language server does not support goto-type-definition");
1107− |ls, pos, doc_id| ls.goto_type_definition(doc_id, pos, None),
1042+ return;
1043+ }
1044+ };
1045+
1046+ cx.callback(
1047+ future,
1048+ move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
1049+ let items = to_locations(response);
1050+ goto_impl(editor, compositor, items, offset_encoding);
1051+ },
1108 );
1052 );
1109}
1053}
1110
1054
1111pub fn goto_implementation(cx: &mut Context) {
1055pub fn goto_implementation(cx: &mut Context) {
1112− goto_single_impl(
1056+ let (view, doc) = current!(cx.editor);
1113− cx,
1057+ let language_server = language_server!(cx.editor, doc);
1114− LanguageServerFeature::GotoImplementation,
1058+ let offset_encoding = language_server.offset_encoding();
1115− |ls, pos, doc_id| ls.goto_implementation(doc_id, pos, None),
1059+
1060+ let pos = doc.position(view.id, offset_encoding);
1061+
1062+ let future = match language_server.goto_implementation(doc.identifier(), pos, None) {
1063+ Some(future) => future,
1064+ None => {
1065+ cx.editor
1066+ .set_error("Language server does not support goto-implementation");
1067+ return;
1068+ }
1069+ };
1070+
1071+ cx.callback(
1072+ future,
1073+ move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
1074+ let items = to_locations(response);
1075+ goto_impl(editor, compositor, items, offset_encoding);
1076+ },
1116 );
1077 );
1117}
1078}
1118
1079
1119pub fn goto_reference(cx: &mut Context) {
1080pub fn goto_reference(cx: &mut Context) {
1120 let config = cx.editor.config();
1081 let config = cx.editor.config();
1121 let (view, doc) = current!(cx.editor);
1082 let (view, doc) = current!(cx.editor);
1122−
1123− // TODO could probably support multiple language servers,
1124− // not sure if there's a real practical use case for this though
1125− let language_server =
1083+ let language_server = language_server!(cx.editor, doc);
1126− language_server_with_feature!(cx.editor, doc, LanguageServerFeature::GotoReference);
1127 let offset_encoding = language_server.offset_encoding();
1084 let offset_encoding = language_server.offset_encoding();
1085+
1128 let pos = doc.position(view.id, offset_encoding);
1086 let pos = doc.position(view.id, offset_encoding);
1087+
1129− let future = language_server
1088+ let future = match language_server.goto_reference(
1130− .goto_reference(
1131− doc.identifier(),
1089+ doc.identifier(),
1132− pos,
1090+ pos,
1133− config.lsp.goto_reference_include_declaration,
1091+ config.lsp.goto_reference_include_declaration,
1134− None,
1092+ None,
1135− )
1093+ ) {
1136− .unwrap();
1094+ Some(future) => future,
1095+ None => {
1096+ cx.editor
1097+ .set_error("Language server does not support goto-reference");
1098+ return;
1099+ }
1100+ };
1137
1101
1138 cx.callback(
1102 cx.callback(
1139 future,
1103 future,
@@ -1144,7 +1108,7 @@ pub fn goto_reference(cx: &mut Context) {
1144 );
1108 );
1145}
1109}
1146
1110
1147−#[derive(PartialEq, Eq, Clone, Copy)]
1111+#[derive(PartialEq, Eq)]
1148pub enum SignatureHelpInvoked {
1112pub enum SignatureHelpInvoked {
1149 Manual,
1113 Manual,
1150 Automatic,
1114 Automatic,
@@ -1156,32 +1120,35 @@ pub fn signature_help(cx: &mut Context) {
1156
1120
1157pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
1121pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
1158 let (view, doc) = current!(cx.editor);
1122 let (view, doc) = current!(cx.editor);
1123+ let was_manually_invoked = invoked == SignatureHelpInvoked::Manual;
1159
1124
1160− // TODO merge multiple language server signature help into one instead of just taking the first language server that supports it
1125+ let language_server = match doc.language_server() {
1161− let future = doc
1126+ Some(language_server) => language_server,
1162− .language_servers_with_feature(LanguageServerFeature::SignatureHelp)
1127+ None => {
1163− .find_map(|language_server| {
1128+ // Do not show the message if signature help was invoked
1129+ // automatically on backspace, trigger characters, etc.
1130+ if was_manually_invoked {
1131+ cx.editor
1132+ .set_status("Language server not active for current buffer");
1133+ }
1134+ return;
1135+ }
1136+ };
1164− let pos = doc.position(view.id, language_server.offset_encoding());
1137+ let offset_encoding = language_server.offset_encoding();
1165− language_server.text_document_signature_help(doc.identifier(), pos, None)
1166− });
1167
1138
1168− let Some(future) = future else {
1139+ let pos = doc.position(view.id, offset_encoding);
1169− // Do not show the message if signature help was invoked
1140+
1170− // automatically on backspace, trigger characters, etc.
1141+ let future = match language_server.text_document_signature_help(doc.identifier(), pos, None) {
1171− if invoked == SignatureHelpInvoked::Manual {
1142+ Some(f) => f,
1143+ None => {
1144+ if was_manually_invoked {
1172− cx.editor
1145+ cx.editor
1173− .set_error("No configured language server supports signature-help");
1146+ .set_error("Language server does not support signature-help");
1147+ }
1148+ return;
1174 }
1149 }
1175− return;
1176 };
1150 };
1177− signature_help_impl_with_future(cx, future.boxed(), invoked);
1178−}
1179
1151
1180−pub fn signature_help_impl_with_future(
1181− cx: &mut Context,
1182− future: BoxFuture<'static, helix_lsp::Result<Value>>,
1183− invoked: SignatureHelpInvoked,
1184−) {
1185 cx.callback(
1152 cx.callback(
1186 future,
1153 future,
1187 move |editor, compositor, response: Option<lsp::SignatureHelp>| {
1154 move |editor, compositor, response: Option<lsp::SignatureHelp>| {
@@ -1189,7 +1156,7 @@ pub fn signature_help_impl_with_future(
1189
1156
1190 if !(config.lsp.auto_signature_help
1157 if !(config.lsp.auto_signature_help
1191 || SignatureHelp::visible_popup(compositor).is_some()
1158 || SignatureHelp::visible_popup(compositor).is_some()
1192− || invoked == SignatureHelpInvoked::Manual)
1159+ || was_manually_invoked)
1193 {
1160 {
1194 return;
1161 return;
1195 }
1162 }
@@ -1198,7 +1165,7 @@ pub fn signature_help_impl_with_future(
1198 // it very probably means the server was a little slow to respond and the user has
1165 // it very probably means the server was a little slow to respond and the user has
1199 // already moved on to something else, making a signature help popup will just be an
1166 // already moved on to something else, making a signature help popup will just be an
1200 // annoyance, see https://github.com/helix-editor/helix/issues/3112
1167 // annoyance, see https://github.com/helix-editor/helix/issues/3112
1201− if invoked == SignatureHelpInvoked::Automatic && editor.mode != Mode::Insert {
1168+ if !was_manually_invoked && editor.mode != Mode::Insert {
1202 return;
1169 return;
1203 }
1170 }
1204
1171
@@ -1288,15 +1255,21 @@ pub fn signature_help_impl_with_future(
1288
1255
1289pub fn hover(cx: &mut Context) {
1256pub fn hover(cx: &mut Context) {
1290 let (view, doc) = current!(cx.editor);
1257 let (view, doc) = current!(cx.editor);
1258+ let language_server = language_server!(cx.editor, doc);
1259+ let offset_encoding = language_server.offset_encoding();
1291
1260
1292− // TODO support multiple language servers (merge UI somehow)
1293− let language_server =
1294− language_server_with_feature!(cx.editor, doc, LanguageServerFeature::Hover);
1295 // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
1261 // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
1262+
1296− let pos = doc.position(view.id, language_server.offset_encoding());
1263+ let pos = doc.position(view.id, offset_encoding);
1297− let future = language_server
1264+
1298− .text_document_hover(doc.identifier(), pos, None)
1265+ let future = match language_server.text_document_hover(doc.identifier(), pos, None) {
1299− .unwrap();
1266+ Some(future) => future,
1267+ None => {
1268+ cx.editor
1269+ .set_error("Language server does not support hover");
1270+ return;
1271+ }
1272+ };
1300
1273
1301 cx.callback(
1274 cx.callback(
1302 future,
1275 future,
@@ -1376,11 +1349,7 @@ pub fn rename_symbol(cx: &mut Context) {
1376 }
1349 }
1377 }
1350 }
1378
1351
1379− fn create_rename_prompt(
1352+ fn create_rename_prompt(editor: &Editor, prefill: String) -> Box<ui::Prompt> {
1380− editor: &Editor,
1381− prefill: String,
1382− language_server_id: Option<usize>,
1383− ) -> Box<ui::Prompt> {
1384 let prompt = ui::Prompt::new(
1353 let prompt = ui::Prompt::new(
1385 "rename-to:".into(),
1354 "rename-to:".into(),
1386 None,
1355 None,
@@ -1389,23 +1358,22 @@ pub fn rename_symbol(cx: &mut Context) {
1389 if event != PromptEvent::Validate {
1358 if event != PromptEvent::Validate {
1390 return;
1359 return;
1391 }
1360 }
1392− let (view, doc) = current!(cx.editor);
1393−
1394− let Some(language_server) = doc
1395− .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1396− .find(|ls| language_server_id.map_or(true, |id| id == ls.id()))
1397− else {
1398− cx.editor
1399− .set_error("No configured language server supports symbol renaming");
1400− return;
1401− };
1402
1361
1362+ let (view, doc) = current!(cx.editor);
1363+ let language_server = language_server!(cx.editor, doc);
1403 let offset_encoding = language_server.offset_encoding();
1364 let offset_encoding = language_server.offset_encoding();
1365+
1404 let pos = doc.position(view.id, offset_encoding);
1366 let pos = doc.position(view.id, offset_encoding);
1405− let future = language_server
1406− .rename_symbol(doc.identifier(), pos, input.to_string())
1407− .unwrap();
1408
1367
1368+ let future =
1369+ match language_server.rename_symbol(doc.identifier(), pos, input.to_string()) {
1370+ Some(future) => future,
1371+ None => {
1372+ cx.editor
1373+ .set_error("Language server does not support symbol renaming");
1374+ return;
1375+ }
1376+ };
1409 match block_on(future) {
1377 match block_on(future) {
1410 Ok(edits) => {
1378 Ok(edits) => {
1411 let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits);
1379 let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits);
@@ -1419,28 +1387,21 @@ pub fn rename_symbol(cx: &mut Context) {
1419 Box::new(prompt)
1387 Box::new(prompt)
1420 }
1388 }
1421
1389
1422− let (view, doc) = current_ref!(cx.editor);
1390+ let (view, doc) = current!(cx.editor);
1423−
1391+ let language_server = language_server!(cx.editor, doc);
1424− let language_server_with_prepare_rename_support = doc
1392+ let offset_encoding = language_server.offset_encoding();
1425− .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1426− .find(|ls| {
1427− matches!(
1428− ls.capabilities().rename_provider,
1429− Some(lsp::OneOf::Right(lsp::RenameOptions {
1430− prepare_provider: Some(true),
1431− ..
1432− }))
1433− )
1434− });
1435
1393
1436− if let Some(language_server) = language_server_with_prepare_rename_support {
1394+ if !language_server.supports_rename() {
1437− let ls_id = language_server.id();
1395+ cx.editor
1438− let offset_encoding = language_server.offset_encoding();
1396+ .set_error("Language server does not support symbol renaming");
1397+ return;
1398+ }
1399+
1439− let pos = doc.position(view.id, offset_encoding);
1400+ let pos = doc.position(view.id, offset_encoding);
1440− let future = language_server
1401+
1441− .prepare_rename(doc.identifier(), pos)
1402+ match language_server.prepare_rename(doc.identifier(), pos) {
1442− .unwrap();
1403+ // Language server supports textDocument/prepareRename, use it.
1443− cx.callback(
1404+ Some(future) => cx.callback(
1444 future,
1405 future,
1445 move |editor, compositor, response: Option<lsp::PrepareRenameResponse>| {
1406 move |editor, compositor, response: Option<lsp::PrepareRenameResponse>| {
1446 let prefill = match get_prefill_from_lsp_response(editor, offset_encoding, response)
1407 let prefill = match get_prefill_from_lsp_response(editor, offset_encoding, response)
@@ -1452,27 +1413,39 @@ pub fn rename_symbol(cx: &mut Context) {
1452 }
1413 }
1453 };
1414 };
1454
1415
1455− let prompt = create_rename_prompt(editor, prefill, Some(ls_id));
1416+ let prompt = create_rename_prompt(editor, prefill);
1456
1417
1457 compositor.push(prompt);
1418 compositor.push(prompt);
1458 },
1419 },
1459− );
1420+ ),
1460− } else {
1421+ // Language server does not support textDocument/prepareRename, fall back
1422+ // to word boundary selection.
1423+ None => {
1461− let prefill = get_prefill_from_word_boundary(cx.editor);
1424+ let prefill = get_prefill_from_word_boundary(cx.editor);
1425+
1462− let prompt = create_rename_prompt(cx.editor, prefill, None);
1426+ let prompt = create_rename_prompt(cx.editor, prefill);
1427+
1463− cx.push_layer(prompt);
1428+ cx.push_layer(prompt);
1429+ }
1464− }
1430+ };
1465}
1431}
1466
1432
1467pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
1433pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
1468 let (view, doc) = current!(cx.editor);
1434 let (view, doc) = current!(cx.editor);
1469− let language_server =
1435+ let language_server = language_server!(cx.editor, doc);
1470− language_server_with_feature!(cx.editor, doc, LanguageServerFeature::DocumentHighlight);
1471 let offset_encoding = language_server.offset_encoding();
1436 let offset_encoding = language_server.offset_encoding();
1437+
1472 let pos = doc.position(view.id, offset_encoding);
1438 let pos = doc.position(view.id, offset_encoding);
1473− let future = language_server
1439+
1474− .text_document_document_highlight(doc.identifier(), pos, None)
1440+ let future = match language_server.text_document_document_highlight(doc.identifier(), pos, None)
1475− .unwrap();
1441+ {
1442+ Some(future) => future,
1443+ None => {
1444+ cx.editor
1445+ .set_error("Language server does not support document highlight");
1446+ return;
1447+ }
1448+ };
1476
1449
1477 cx.callback(
1450 cx.callback(
1478 future,
1451 future,
@@ -1482,8 +1455,10 @@ pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
1482 _ => return,
1455 _ => return,
1483 };
1456 };
1484 let (view, doc) = current!(editor);
1457 let (view, doc) = current!(editor);
1458+ let language_server = language_server!(editor, doc);
1459+ let offset_encoding = language_server.offset_encoding();
1485 let text = doc.text();
1460 let text = doc.text();
1486− let pos = doc.selection(view.id).primary().cursor(text.slice(..));
1461+ let pos = doc.selection(view.id).primary().head;
1487
1462
1488 // We must find the range that contains our primary cursor to prevent our primary cursor to move
1463 // We must find the range that contains our primary cursor to prevent our primary cursor to move
1489 let mut primary_index = 0;
1464 let mut primary_index = 0;
@@ -1527,51 +1502,63 @@ fn compute_inlay_hints_for_view(
1527 let view_id = view.id;
1502 let view_id = view.id;
1528 let doc_id = view.doc;
1503 let doc_id = view.doc;
1529
1504
1530− let language_server = doc
1505+ let language_server = doc.language_server()?;
1531− .language_servers_with_feature(LanguageServerFeature::InlayHints)
1532− .next()?;
1533−
1534− let doc_text = doc.text();
1535− let len_lines = doc_text.len_lines();
1536−
1537− // Compute ~3 times the current view height of inlay hints, that way some scrolling
1538− // will not show half the view with hints and half without while still being faster
1539− // than computing all the hints for the full file (which could be dozens of time
1540− // longer than the view is).
1541− let view_height = view.inner_height();
1542− let first_visible_line = doc_text.char_to_line(view.offset.anchor.min(doc_text.len_chars()));
1543− let first_line = first_visible_line.saturating_sub(view_height);
1544− let last_line = first_visible_line
1545− .saturating_add(view_height.saturating_mul(2))
1546− .min(len_lines);
1547−
1548− let new_doc_inlay_hints_id = DocumentInlayHintsId {
1549− first_line,
1550− last_line,
1551− };
1552− // Don't recompute the annotations in case nothing has changed about the view
1553− if !doc.inlay_hints_oudated
1554− && doc
1555− .inlay_hints(view_id)
1556− .map_or(false, |dih| dih.id == new_doc_inlay_hints_id)
1557− {
1558− return None;
1559− }
1560
1506
1561− let doc_slice = doc_text.slice(..);
1507+ let capabilities = language_server.capabilities();
1562− let first_char_in_range = doc_slice.line_to_char(first_line);
1563− let last_char_in_range = doc_slice.line_to_char(last_line);
1564
1508
1565− let range = helix_lsp::util::range_to_lsp_range(
1509+ let (future, new_doc_inlay_hints_id) = match capabilities.inlay_hint_provider {
1566− doc_text,
1510+ Some(
1567− helix_core::Range::new(first_char_in_range, last_char_in_range),
1511+ lsp::OneOf::Left(true)
1568− language_server.offset_encoding(),
1512+ | lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_)),
1569− );
1513+ ) => {
1514+ let doc_text = doc.text();
1515+ let len_lines = doc_text.len_lines();
1516+
1517+ // Compute ~3 times the current view height of inlay hints, that way some scrolling
1518+ // will not show half the view with hints and half without while still being faster
1519+ // than computing all the hints for the full file (which could be dozens of time
1520+ // longer than the view is).
1521+ let view_height = view.inner_height();
1522+ let first_visible_line =
1523+ doc_text.char_to_line(view.offset.anchor.min(doc_text.len_chars()));
1524+ let first_line = first_visible_line.saturating_sub(view_height);
1525+ let last_line = first_visible_line
1526+ .saturating_add(view_height.saturating_mul(2))
1527+ .min(len_lines);
1528+
1529+ let new_doc_inlay_hint_id = DocumentInlayHintsId {
1530+ first_line,
1531+ last_line,
1532+ };
1533+ // Don't recompute the annotations in case nothing has changed about the view
1534+ if !doc.inlay_hints_oudated
1535+ && doc
1536+ .inlay_hints(view_id)
1537+ .map_or(false, |dih| dih.id == new_doc_inlay_hint_id)
1538+ {
1539+ return None;
1540+ }
1570
1541
1571− let offset_encoding = language_server.offset_encoding();
1542+ let doc_slice = doc_text.slice(..);
1543+ let first_char_in_range = doc_slice.line_to_char(first_line);
1544+ let last_char_in_range = doc_slice.line_to_char(last_line);
1545+
1546+ let range = helix_lsp::util::range_to_lsp_range(
1547+ doc_text,
1548+ helix_core::Range::new(first_char_in_range, last_char_in_range),
1549+ language_server.offset_encoding(),
1550+ );
1551+
1552+ (
1553+ language_server.text_document_range_inlay_hints(doc.identifier(), range, None),
1554+ new_doc_inlay_hint_id,
1555+ )
1556+ }
1557+ _ => return None,
1558+ };
1572
1559
1573 let callback = super::make_job_callback(
1560 let callback = super::make_job_callback(
1574− language_server.text_document_range_inlay_hints(doc.identifier(), range, None)?,
1561+ future?,
1575 move |editor, _compositor, response: Option<Vec<lsp::InlayHint>>| {
1562 move |editor, _compositor, response: Option<Vec<lsp::InlayHint>>| {
1576 // The config was modified or the window was closed while the request was in flight
1563 // The config was modified or the window was closed while the request was in flight
1577 if !editor.config().lsp.display_inlay_hints || editor.tree.try_get(view_id).is_none() {
1564 if !editor.config().lsp.display_inlay_hints || editor.tree.try_get(view_id).is_none() {
@@ -1585,8 +1572,8 @@ fn compute_inlay_hints_for_view(
1585 };
1572 };
1586
1573
1587 // If we have neither hints nor an LSP, empty the inlay hints since they're now oudated
1574 // If we have neither hints nor an LSP, empty the inlay hints since they're now oudated
1588− let mut hints = match response {
1575+ let (mut hints, offset_encoding) = match (response, doc.language_server()) {
1589− Some(hints) if !hints.is_empty() => hints,
1576+ (Some(h), Some(ls)) if !h.is_empty() => (h, ls.offset_encoding()),
1590 _ => {
1577 _ => {
1591 doc.set_inlay_hints(
1578 doc.set_inlay_hints(
1592 view_id,
1579 view_id,
helix-term/src/commands/typed.rs+730−1047
@@ -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>],
@@ -399,36 +382,6 @@ fn force_write(
399 write_impl(cx, args.first(), true)
382 write_impl(cx, args.first(), true)
400}
383}
401
384
402−fn write_buffer_close(
403− cx: &mut compositor::Context,
404− args: &[Cow<str>],
405− event: PromptEvent,
406−) -> anyhow::Result<()> {
407− if event != PromptEvent::Validate {
408− return Ok(());
409− }
410−
411− write_impl(cx, args.first(), false)?;
412−
413− let document_ids = buffer_gather_paths_impl(cx.editor, args);
414− buffer_close_by_ids_impl(cx, &document_ids, false)
415−}
416−
417−fn force_write_buffer_close(
418− cx: &mut compositor::Context,
419− args: &[Cow<str>],
420− event: PromptEvent,
421−) -> anyhow::Result<()> {
422− if event != PromptEvent::Validate {
423− return Ok(());
424− }
425−
426− write_impl(cx, args.first(), true)?;
427−
428− let document_ids = buffer_gather_paths_impl(cx.editor, args);
429− buffer_close_by_ids_impl(cx, &document_ids, false)
430−}
431−
432fn new_file(
385fn new_file(
433 cx: &mut compositor::Context,
386 cx: &mut compositor::Context,
434 _args: &[Cow<str>],
387 _args: &[Cow<str>],
@@ -477,7 +430,8 @@ fn set_indent_style(
477 cx.editor.set_status(match style {
430 cx.editor.set_status(match style {
478 Tabs => "tabs".to_owned(),
431 Tabs => "tabs".to_owned(),
479 Spaces(1) => "1 space".to_owned(),
432 Spaces(1) => "1 space".to_owned(),
480− Spaces(n) => format!("{} spaces", n),
433+ Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
434+ _ => unreachable!(), // Shouldn't happen.
481 });
435 });
482 return Ok(());
436 return Ok(());
483 }
437 }
@@ -489,7 +443,7 @@ fn set_indent_style(
489 Some(arg) => arg
443 Some(arg) => arg
490 .parse::<u8>()
444 .parse::<u8>()
491 .ok()
445 .ok()
492− .filter(|n| (1..=MAX_INDENT).contains(n))
446+ .filter(|n| (1..=8).contains(n))
493 .map(Spaces),
447 .map(Spaces),
494 _ => None,
448 _ => None,
495 };
449 };
@@ -574,6 +528,7 @@ fn set_line_ending(
574
528
575 Ok(())
529 Ok(())
576}
530}
531+
577fn earlier(
532fn earlier(
578 cx: &mut compositor::Context,
533 cx: &mut compositor::Context,
579 args: &[Cow<str>],
534 args: &[Cow<str>],
@@ -672,10 +627,11 @@ pub fn write_all_impl(
672 write_scratch: bool,
627 write_scratch: bool,
673) -> anyhow::Result<()> {
628) -> anyhow::Result<()> {
674 let mut errors: Vec<&'static str> = Vec::new();
629 let mut errors: Vec<&'static str> = Vec::new();
675− let config = cx.editor.config();
630+ let auto_format = cx.editor.config().auto_format;
676 let jobs = &mut cx.jobs;
631 let jobs = &mut cx.jobs;
677 let current_view = view!(cx.editor);
632 let current_view = view!(cx.editor);
678
633
634+ // save all documents
679 let saves: Vec<_> = cx
635 let saves: Vec<_> = cx
680 .editor
636 .editor
681 .documents
637 .documents
@@ -686,7 +642,7 @@ pub fn write_all_impl(
686 }
642 }
687 if doc.path().is_none() {
643 if doc.path().is_none() {
688 if write_scratch {
644 if write_scratch {
689− errors.push("cannot write a buffer without a filename");
645+ errors.push("cannot write a buffer without a filename\n");
690 }
646 }
691 return None;
647 return None;
692 }
648 }
@@ -706,35 +662,32 @@ pub fn write_all_impl(
706 current_view.id
662 current_view.id
707 };
663 };
708
664
709− Some((doc.id(), target_view))
665+ let fmt = if auto_format {
666+ doc.auto_format().map(|fmt| {
667+ let callback = make_format_callback(
668+ doc.id(),
669+ doc.version(),
670+ target_view,
671+ fmt,
672+ Some((None, force)),
710− })
673+ );
711− .collect();
674+ jobs.add(Job::with_callback(callback).wait_before_exiting());
712−
675+ })
713− for (doc_id, target_view) in saves {
676+ } else {
714− let doc = doc_mut!(cx.editor, &doc_id);
677+ None
678+ };
715
679
716− if config.insert_final_newline {
680+ if fmt.is_none() {
717− insert_final_newline(doc, view_mut!(cx.editor, target_view));
681+ return Some(doc.id());
718− }
682+ }
719
683
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
684 None
733− };
685+ })
686+ .collect();
734
687
735− if fmt.is_none() {
688+ // manually call save for the rest of docs that don't have a formatter
689+ for id in saves {
736− cx.editor.save::<PathBuf>(doc_id, None, force)?;
690+ cx.editor.save::<PathBuf>(id, None, force)?;
737− }
738 }
691 }
739
692
740 if !errors.is_empty() && !force {
693 if !errors.is_empty() && !force {
@@ -756,18 +709,6 @@ fn write_all(
756 write_all_impl(cx, false, true)
709 write_all_impl(cx, false, true)
757}
710}
758
711
759−fn force_write_all(
760− cx: &mut compositor::Context,
761− _args: &[Cow<str>],
762− event: PromptEvent,
763−) -> anyhow::Result<()> {
764− if event != PromptEvent::Validate {
765− return Ok(());
766− }
767−
768− write_all_impl(cx, true, true)
769−}
770−
771fn write_all_quit(
712fn write_all_quit(
772 cx: &mut compositor::Context,
713 cx: &mut compositor::Context,
773 _args: &[Cow<str>],
714 _args: &[Cow<str>],
@@ -921,27 +862,7 @@ fn yank_main_selection_to_clipboard(
921 return Ok(());
862 return Ok(());
922 }
863 }
923
864
924− yank_primary_selection_impl(cx.editor, '+');
865+ yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard)
925− Ok(())
926−}
927−
928−fn yank_joined(
929− cx: &mut compositor::Context,
930− args: &[Cow<str>],
931− event: PromptEvent,
932−) -> anyhow::Result<()> {
933− if event != PromptEvent::Validate {
934− return Ok(());
935− }
936−
937− ensure!(args.len() <= 1, ":yank-join takes at most 1 argument");
938−
939− let doc = doc!(cx.editor);
940− let default_sep = Cow::Borrowed(doc.line_ending.as_str());
941− let separator = args.first().unwrap_or(&default_sep);
942− let register = cx.editor.selected_register.unwrap_or('"');
943− yank_joined_impl(cx.editor, separator, register);
944− Ok(())
945}
866}
946
867
947fn yank_joined_to_clipboard(
868fn yank_joined_to_clipboard(
@@ -956,8 +877,7 @@ fn yank_joined_to_clipboard(
956 let doc = doc!(cx.editor);
877 let doc = doc!(cx.editor);
957 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
878 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
958 let separator = args.first().unwrap_or(&default_sep);
879 let separator = args.first().unwrap_or(&default_sep);
959− yank_joined_impl(cx.editor, separator, '+');
880+ yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
960− Ok(())
961}
881}
962
882
963fn yank_main_selection_to_primary_clipboard(
883fn yank_main_selection_to_primary_clipboard(
@@ -969,8 +889,7 @@ fn yank_main_selection_to_primary_clipboard(
969 return Ok(());
889 return Ok(());
970 }
890 }
971
891
972− yank_primary_selection_impl(cx.editor, '*');
892+ yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection)
973− Ok(())
974}
893}
975
894
976fn yank_joined_to_primary_clipboard(
895fn yank_joined_to_primary_clipboard(
@@ -985,8 +904,7 @@ fn yank_joined_to_primary_clipboard(
985 let doc = doc!(cx.editor);
904 let doc = doc!(cx.editor);
986 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
905 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
987 let separator = args.first().unwrap_or(&default_sep);
906 let separator = args.first().unwrap_or(&default_sep);
988− yank_joined_impl(cx.editor, separator, '*');
907+ yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
989− Ok(())
990}
908}
991
909
992fn paste_clipboard_after(
910fn paste_clipboard_after(
@@ -998,8 +916,7 @@ fn paste_clipboard_after(
998 return Ok(());
916 return Ok(());
999 }
917 }
1000
918
1001− paste(cx.editor, '+', Paste::After, 1);
919+ paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard, 1)
1002− Ok(())
1003}
920}
1004
921
1005fn paste_clipboard_before(
922fn paste_clipboard_before(
@@ -1011,8 +928,7 @@ fn paste_clipboard_before(
1011 return Ok(());
928 return Ok(());
1012 }
929 }
1013
930
1014− paste(cx.editor, '+', Paste::Before, 1);
931+ paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Clipboard, 1)
1015− Ok(())
1016}
932}
1017
933
1018fn paste_primary_clipboard_after(
934fn paste_primary_clipboard_after(
@@ -1024,8 +940,7 @@ fn paste_primary_clipboard_after(
1024 return Ok(());
940 return Ok(());
1025 }
941 }
1026
942
1027− paste(cx.editor, '*', Paste::After, 1);
943+ paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection, 1)
1028− Ok(())
1029}
944}
1030
945
1031fn paste_primary_clipboard_before(
946fn paste_primary_clipboard_before(
@@ -1037,8 +952,30 @@ fn paste_primary_clipboard_before(
1037 return Ok(());
952 return Ok(());
1038 }
953 }
1039
954
1040− paste(cx.editor, '*', Paste::Before, 1);
955+ paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Selection, 1)
1041− Ok(())
956+}
957+
958+fn replace_selections_with_clipboard_impl(
959+ cx: &mut compositor::Context,
960+ clipboard_type: ClipboardType,
961+) -> anyhow::Result<()> {
962+ let scrolloff = cx.editor.config().scrolloff;
963+ let (view, doc) = current!(cx.editor);
964+
965+ match cx.editor.clipboard_provider.get_contents(clipboard_type) {
966+ Ok(contents) => {
967+ let selection = doc.selection(view.id);
968+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
969+ (range.from(), range.to(), Some(contents.as_str().into()))
970+ });
971+
972+ doc.apply(&transaction, view.id);
973+ doc.append_changes_to_history(view);
974+ view.ensure_cursor_in_view(doc, scrolloff);
975+ Ok(())
976+ }
977+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
978+ }
1042}
979}
1043
980
1044fn replace_selections_with_clipboard(
981fn replace_selections_with_clipboard(
@@ -1050,8 +987,7 @@ fn replace_selections_with_clipboard(
1050 return Ok(());
987 return Ok(());
1051 }
988 }
1052
989
1053− replace_with_yanked_impl(cx.editor, '+', 1);
990+ replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
1054− Ok(())
1055}
991}
1056
992
1057fn replace_selections_with_primary_clipboard(
993fn replace_selections_with_primary_clipboard(
@@ -1063,8 +999,7 @@ fn replace_selections_with_primary_clipboard(
1063 return Ok(());
999 return Ok(());
1064 }
1000 }
1065
1001
1066− replace_with_yanked_impl(cx.editor, '*', 1);
1002+ replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
1067− Ok(())
1068}
1003}
1069
1004
1070fn show_clipboard_provider(
1005fn show_clipboard_provider(
@@ -1077,7 +1012,7 @@ fn show_clipboard_provider(
1077 }
1012 }
1078
1013
1079 cx.editor
1014 cx.editor
1080− .set_status(cx.editor.registers.clipboard_provider_name().to_string());
1015+ .set_status(cx.editor.clipboard_provider.name().to_string());
1081 Ok(())
1016 Ok(())
1082}
1017}
1083
1018
@@ -1097,11 +1032,14 @@ fn change_current_directory(
1097 .as_ref(),
1032 .as_ref(),
1098 );
1033 );
1099
1034
1100− helix_loader::set_current_working_dir(dir)?;
1035+ if let Err(e) = std::env::set_current_dir(dir) {
1036+ bail!("Couldn't change the current working directory: {}", e);
1037+ }
1101
1038
1039+ let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1102 cx.editor.set_status(format!(
1040 cx.editor.set_status(format!(
1103 "Current working directory is now {}",
1041 "Current working directory is now {}",
1104− helix_loader::current_working_dir().display()
1042+ cwd.display()
1105 ));
1043 ));
1106 Ok(())
1044 Ok(())
1107}
1045}
@@ -1115,14 +1053,9 @@ fn show_current_directory(
1115 return Ok(());
1053 return Ok(());
1116 }
1054 }
1117
1055
1118− let cwd = helix_loader::current_working_dir();
1056+ let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1057+ cx.editor
1119− let message = format!("Current working directory is {}", cwd.display());
1058+ .set_status(format!("Current working directory is {}", cwd.display()));
1120−
1121− if cwd.exists() {
1122− cx.editor.set_status(message);
1123− } else {
1124− cx.editor.set_error(format!("{} (deleted)", message));
1125− }
1126 Ok(())
1059 Ok(())
1127}
1060}
1128
1061
@@ -1282,17 +1215,12 @@ fn reload(
1282 }
1215 }
1283
1216
1284 let scrolloff = cx.editor.config().scrolloff;
1217 let scrolloff = cx.editor.config().scrolloff;
1218+ let redraw_handle = cx.editor.redraw_handle.clone();
1285 let (view, doc) = current!(cx.editor);
1219 let (view, doc) = current!(cx.editor);
1286− doc.reload(view, &cx.editor.diff_providers).map(|_| {
1220+ doc.reload(view, &cx.editor.diff_providers, redraw_handle)
1221+ .map(|_| {
1287− view.ensure_cursor_in_view(doc, scrolloff);
1222+ view.ensure_cursor_in_view(doc, scrolloff);
1288− })?;
1289− if let Some(path) = doc.path() {
1290− cx.editor
1291− .language_servers
1292− .file_event_handler
1293− .file_changed(path.clone());
1294− }
1223+ })
1295− Ok(())
1296}
1224}
1297
1225
1298fn reload_all(
1226fn reload_all(
@@ -1331,13 +1259,8 @@ fn reload_all(
1331 // Ensure that the view is synced with the document's history.
1259 // Ensure that the view is synced with the document's history.
1332 view.sync_changes(doc);
1260 view.sync_changes(doc);
1333
1261
1262+ let redraw_handle = cx.editor.redraw_handle.clone();
1334− doc.reload(view, &cx.editor.diff_providers)?;
1263+ doc.reload(view, &cx.editor.diff_providers, redraw_handle)?;
1335− if let Some(path) = doc.path() {
1336− cx.editor
1337− .language_servers
1338− .file_event_handler
1339− .file_changed(path.clone());
1340− }
1341
1264
1342 for view_id in view_ids {
1265 for view_id in view_ids {
1343 let view = view_mut!(cx.editor, view_id);
1266 let view = view_mut!(cx.editor, view_id);
@@ -1376,21 +1299,26 @@ fn lsp_workspace_command(
1376 if event != PromptEvent::Validate {
1299 if event != PromptEvent::Validate {
1377 return Ok(());
1300 return Ok(());
1378 }
1301 }
1302+
1379− let doc = doc!(cx.editor);
1303+ let (_, doc) = current!(cx.editor);
1380− let Some((language_server_id, options)) = doc
1304+
1381− .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
1305+ let language_server = match doc.language_server() {
1382− .find_map(|ls| {
1306+ Some(language_server) => language_server,
1383− ls.capabilities()
1307+ None => {
1384− .execute_command_provider
1385− .as_ref()
1386− .map(|options| (ls.id(), options))
1387− })
1388− else {
1389− cx.editor
1308+ cx.editor
1390− .set_status("No active language servers for this document support workspace commands");
1309+ .set_status("Language server not active for current buffer");
1391− return Ok(());
1310+ return Ok(());
1311+ }
1392 };
1312 };
1393
1313
1314+ let options = match &language_server.capabilities().execute_command_provider {
1315+ Some(options) => options,
1316+ None => {
1317+ cx.editor
1318+ .set_status("Workspace commands are not supported for this language server");
1319+ return Ok(());
1320+ }
1321+ };
1394 if args.is_empty() {
1322 if args.is_empty() {
1395 let commands = options
1323 let commands = options
1396 .commands
1324 .commands
@@ -1404,8 +1332,8 @@ fn lsp_workspace_command(
1404 let callback = async move {
1332 let callback = async move {
1405 let call: job::Callback = Callback::EditorCompositor(Box::new(
1333 let call: job::Callback = Callback::EditorCompositor(Box::new(
1406 move |_editor: &mut Editor, compositor: &mut Compositor| {
1334 move |_editor: &mut Editor, compositor: &mut Compositor| {
1407− let picker = ui::Picker::new(commands, (), move |cx, command, _action| {
1335+ let picker = ui::Picker::new(commands, (), |cx, command, _action| {
1408− execute_lsp_command(cx.editor, language_server_id, command.clone());
1336+ execute_lsp_command(cx.editor, command.clone());
1409 });
1337 });
1410 compositor.push(Box::new(overlaid(picker)))
1338 compositor.push(Box::new(overlaid(picker)))
1411 },
1339 },
@@ -1418,7 +1346,6 @@ fn lsp_workspace_command(
1418 if options.commands.iter().any(|c| c == &command) {
1346 if options.commands.iter().any(|c| c == &command) {
1419 execute_lsp_command(
1347 execute_lsp_command(
1420 cx.editor,
1348 cx.editor,
1421− language_server_id,
1422 helix_lsp::lsp::Command {
1349 helix_lsp::lsp::Command {
1423 title: command.clone(),
1350 title: command.clone(),
1424 arguments: None,
1351 arguments: None,
@@ -1450,6 +1377,7 @@ fn lsp_restart(
1450 .language_config()
1377 .language_config()
1451 .context("LSP not defined for the current document")?;
1378 .context("LSP not defined for the current document")?;
1452
1379
1380+ let scope = config.scope.clone();
1453 cx.editor.language_servers.restart(
1381 cx.editor.language_servers.restart(
1454 config,
1382 config,
1455 doc.path(),
1383 doc.path(),
@@ -1462,22 +1390,13 @@ fn lsp_restart(
1462 .editor
1390 .editor
1463 .documents()
1391 .documents()
1464 .filter_map(|doc| match doc.language_config() {
1392 .filter_map(|doc| match doc.language_config() {
1465− Some(config)
1393+ Some(config) if config.scope.eq(&scope) => Some(doc.id()),
1466− if config.language_servers.iter().any(|ls| {
1467− config
1468− .language_servers
1469− .iter()
1470− .any(|restarted_ls| restarted_ls.name == ls.name)
1471− }) =>
1472− {
1473− Some(doc.id())
1474− }
1475 _ => None,
1394 _ => None,
1476 })
1395 })
1477 .collect();
1396 .collect();
1478
1397
1479 for document_id in document_ids_to_refresh {
1398 for document_id in document_ids_to_refresh {
1480− cx.editor.refresh_language_servers(document_id);
1399+ cx.editor.refresh_language_server(document_id);
1481 }
1400 }
1482
1401
1483 Ok(())
1402 Ok(())
@@ -1492,18 +1411,22 @@ fn lsp_stop(
1492 return Ok(());
1411 return Ok(());
1493 }
1412 }
1494
1413
1495− let ls_shutdown_names = doc!(cx.editor)
1414+ let doc = doc!(cx.editor);
1496− .language_servers()
1497− .map(|ls| ls.name().to_string())
1498− .collect::<Vec<_>>();
1499
1415
1500− for ls_name in &ls_shutdown_names {
1416+ let ls_id = doc
1501− cx.editor.language_servers.stop(ls_name);
1417+ .language_server()
1418+ .map(|ls| ls.id())
1419+ .context("LSP not running for the current document")?;
1502
1420
1421+ let config = doc
1422+ .language_config()
1423+ .context("LSP not defined for the current document")?;
1424+ cx.editor.language_servers.stop(config);
1425+
1503− for doc in cx.editor.documents_mut() {
1426+ for doc in cx.editor.documents_mut() {
1504− if let Some(client) = doc.remove_language_server_by_name(ls_name) {
1427+ if doc.language_server().map_or(false, |ls| ls.id() == ls_id) {
1505− doc.clear_diagnostics(client.id());
1428+ doc.set_language_server(None);
1506− }
1429+ doc.set_diagnostics(Default::default());
1507 }
1430 }
1508 }
1431 }
1509
1432
@@ -1543,84 +1466,6 @@ fn tree_sitter_scopes(
1543 Ok(())
1466 Ok(())
1544}
1467}
1545
1468
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(
1469fn vsplit(
1625 cx: &mut compositor::Context,
1470 cx: &mut compositor::Context,
1626 args: &[Cow<str>],
1471 args: &[Cow<str>],
@@ -1630,8 +1475,10 @@ fn vsplit(
1630 return Ok(());
1475 return Ok(());
1631 }
1476 }
1632
1477
1478+ let id = view!(cx.editor).doc;
1479+
1633 if args.is_empty() {
1480 if args.is_empty() {
1634− split(cx.editor, Action::VerticalSplit);
1481+ cx.editor.switch(id, Action::VerticalSplit);
1635 } else {
1482 } else {
1636 for arg in args {
1483 for arg in args {
1637 cx.editor
1484 cx.editor
@@ -1651,8 +1498,10 @@ fn hsplit(
1651 return Ok(());
1498 return Ok(());
1652 }
1499 }
1653
1500
1501+ let id = view!(cx.editor).doc;
1502+
1654 if args.is_empty() {
1503 if args.is_empty() {
1655− split(cx.editor, Action::HorizontalSplit);
1504+ cx.editor.switch(id, Action::HorizontalSplit);
1656 } else {
1505 } else {
1657 for arg in args {
1506 for arg in args {
1658 cx.editor
1507 cx.editor
@@ -1767,7 +1616,7 @@ fn tutor(
1767 let path = helix_loader::runtime_file(Path::new("tutor"));
1616 let path = helix_loader::runtime_file(Path::new("tutor"));
1768 cx.editor.open(&path, Action::Replace)?;
1617 cx.editor.open(&path, Action::Replace)?;
1769 // Unset path to prevent accidentally saving to the original tutor file.
1618 // Unset path to prevent accidentally saving to the original tutor file.
1770− doc_mut!(cx.editor).set_path(None);
1619+ doc_mut!(cx.editor).set_path(None)?;
1771 Ok(())
1620 Ok(())
1772}
1621}
1773
1622
@@ -1885,7 +1734,7 @@ fn set_option(
1885
1734
1886 *value = if value.is_string() {
1735 *value = if value.is_string() {
1887 // JSON strings require quotes, so we can't .parse() directly
1736 // JSON strings require quotes, so we can't .parse() directly
1888− Value::String(arg.to_string())
1737+ serde_json::Value::String(arg.to_string())
1889 } else {
1738 } else {
1890 arg.parse().map_err(field_error)?
1739 arg.parse().map_err(field_error)?
1891 };
1740 };
@@ -1910,8 +1759,8 @@ fn toggle_option(
1910 return Ok(());
1759 return Ok(());
1911 }
1760 }
1912
1761
1913− if args.is_empty() {
1762+ if args.len() != 1 {
1914− anyhow::bail!("Bad arguments. Usage: `:toggle key [values]?`");
1763+ anyhow::bail!("Bad arguments. Usage: `:toggle key`");
1915 }
1764 }
1916 let key = &args[0].to_lowercase();
1765 let key = &args[0].to_lowercase();
1917
1766
@@ -1921,58 +1770,22 @@ fn toggle_option(
1921 let pointer = format!("/{}", key.replace('.', "/"));
1770 let pointer = format!("/{}", key.replace('.', "/"));
1922 let value = config.pointer_mut(&pointer).ok_or_else(key_error)?;
1771 let value = config.pointer_mut(&pointer).ok_or_else(key_error)?;
1923
1772
1924− *value = match value {
1773+ let Value::Bool(old_value) = *value else {
1925− Value::Bool(ref value) => {
1926− ensure!(
1927− args.len() == 1,
1928− "Bad arguments. For boolean configurations use: `:toggle key`"
1929− );
1930− Value::Bool(!value)
1931− }
1932− Value::String(ref value) => {
1933− ensure!(
1934− args.len() > 2,
1935− "Bad arguments. For string configurations use: `:toggle key val1 val2 ...`",
1936− );
1937−
1938− Value::String(
1939− args[1..]
1940− .iter()
1941− .skip_while(|e| *e != value)
1942− .nth(1)
1943− .unwrap_or_else(|| &args[1])
1944− .to_string(),
1945− )
1946− }
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(_) => {
1963− anyhow::bail!("Configuration {key} does not support toggle yet")
1774+ anyhow::bail!("Key `{}` is not toggle-able", key)
1964− }
1965 };
1775 };
1966
1776
1967− let status = format!("'{key}' is now set to {value}");
1777+ let new_value = !old_value;
1778+ *value = Value::Bool(new_value);
1779+ // This unwrap should never fail because we only replace one boolean value
1780+ // with another, maintaining a valid json config
1968− let config = serde_json::from_value(config)
1781+ let config = serde_json::from_value(config).unwrap();
1969− .map_err(|err| anyhow::anyhow!("Cannot parse `{:?}`, {}", &args, err))?;
1970
1782
1971 cx.editor
1783 cx.editor
1972 .config_events
1784 .config_events
1973 .0
1785 .0
1974 .send(ConfigEvent::Update(config))?;
1786 .send(ConfigEvent::Update(config))?;
1975− cx.editor.set_status(status);
1787+ cx.editor
1788+ .set_status(format!("Option `{}` is now set to `{}`", key, new_value));
1976 Ok(())
1789 Ok(())
1977}
1790}
1978
1791
@@ -2007,7 +1820,7 @@ fn language(
2007 doc.detect_indent_and_line_ending();
1820 doc.detect_indent_and_line_ending();
2008
1821
2009 let id = doc.id();
1822 let id = doc.id();
2010− cx.editor.refresh_language_servers(id);
1823+ cx.editor.refresh_language_server(id);
2011 Ok(())
1824 Ok(())
2012}
1825}
2013
1826
@@ -2375,721 +2188,578 @@ fn clear_register(
2375 format!("Invalid register {}", args[0])
2188 format!("Invalid register {}", args[0])
2376 );
2189 );
2377 let register = args[0].chars().next().unwrap_or_default();
2190 let register = args[0].chars().next().unwrap_or_default();
2378− if cx.editor.registers.remove(register) {
2191+ match cx.editor.registers.remove(register) {
2192+ Some(_) => cx
2379− cx.editor
2193+ .editor
2380− .set_status(format!("Register {} cleared", register));
2194+ .set_status(format!("Register {} cleared", register)),
2381− } else {
2195+ None => cx
2382− cx.editor
2196+ .editor
2383− .set_error(format!("Register {} not found", register));
2197+ .set_error(format!("Register {} not found", register)),
2384 }
2198 }
2385 Ok(())
2199 Ok(())
2386}
2200}
2387
2201
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] = &[
2202pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2486− TypableCommand {
2203+ TypableCommand {
2487− name: "quit",
2204+ name: "quit",
2488− aliases: &["q"],
2205+ aliases: &["q"],
2489− doc: "Close the current view.",
2206+ doc: "Close the current view.",
2490− fun: quit,
2207+ fun: quit,
2491− signature: CommandSignature::none(),
2208+ signature: CommandSignature::none(),
2492− },
2209+ },
2493− TypableCommand {
2210+ TypableCommand {
2494− name: "quit!",
2211+ name: "quit!",
2495− aliases: &["q!"],
2212+ aliases: &["q!"],
2496− doc: "Force close the current view, ignoring unsaved changes.",
2213+ doc: "Force close the current view, ignoring unsaved changes.",
2497− fun: force_quit,
2214+ fun: force_quit,
2498− signature: CommandSignature::none(),
2215+ signature: CommandSignature::none(),
2499− },
2216+ },
2500− TypableCommand {
2217+ TypableCommand {
2501− name: "open",
2218+ name: "open",
2502− aliases: &["o"],
2219+ aliases: &["o"],
2503− doc: "Open a file from disk into the current view.",
2220+ doc: "Open a file from disk into the current view.",
2504− fun: open,
2221+ fun: open,
2505− signature: CommandSignature::all(completers::filename),
2222+ signature: CommandSignature::all(completers::filename),
2506− },
2223+ },
2507− TypableCommand {
2224+ TypableCommand {
2508− name: "buffer-close",
2225+ name: "buffer-close",
2509− aliases: &["bc", "bclose"],
2226+ aliases: &["bc", "bclose"],
2510− doc: "Close the current buffer.",
2227+ doc: "Close the current buffer.",
2511− fun: buffer_close,
2228+ fun: buffer_close,
2512− signature: CommandSignature::all(completers::buffer),
2229+ signature: CommandSignature::all(completers::buffer),
2513− },
2230+ },
2514− TypableCommand {
2231+ TypableCommand {
2515− name: "buffer-close!",
2232+ name: "buffer-close!",
2516− aliases: &["bc!", "bclose!"],
2233+ aliases: &["bc!", "bclose!"],
2517− doc: "Close the current buffer forcefully, ignoring unsaved changes.",
2234+ doc: "Close the current buffer forcefully, ignoring unsaved changes.",
2518− fun: force_buffer_close,
2235+ fun: force_buffer_close,
2519− signature: CommandSignature::all(completers::buffer)
2236+ signature: CommandSignature::all(completers::buffer)
2520− },
2237+ },
2521− TypableCommand {
2238+ TypableCommand {
2522− name: "buffer-close-others",
2239+ name: "buffer-close-others",
2523− aliases: &["bco", "bcloseother"],
2240+ aliases: &["bco", "bcloseother"],
2524− doc: "Close all buffers but the currently focused one.",
2241+ doc: "Close all buffers but the currently focused one.",
2525− fun: buffer_close_others,
2242+ fun: buffer_close_others,
2526− signature: CommandSignature::none(),
2243+ signature: CommandSignature::none(),
2527− },
2244+ },
2528− TypableCommand {
2245+ TypableCommand {
2529− name: "buffer-close-others!",
2246+ name: "buffer-close-others!",
2530− aliases: &["bco!", "bcloseother!"],
2247+ aliases: &["bco!", "bcloseother!"],
2531− doc: "Force close all buffers but the currently focused one.",
2248+ doc: "Force close all buffers but the currently focused one.",
2532− fun: force_buffer_close_others,
2249+ fun: force_buffer_close_others,
2533− signature: CommandSignature::none(),
2250+ signature: CommandSignature::none(),
2534− },
2251+ },
2535− TypableCommand {
2252+ TypableCommand {
2536− name: "buffer-close-all",
2253+ name: "buffer-close-all",
2537− aliases: &["bca", "bcloseall"],
2254+ aliases: &["bca", "bcloseall"],
2538− doc: "Close all buffers without quitting.",
2255+ doc: "Close all buffers without quitting.",
2539− fun: buffer_close_all,
2256+ fun: buffer_close_all,
2540− signature: CommandSignature::none(),
2257+ signature: CommandSignature::none(),
2541− },
2258+ },
2542− TypableCommand {
2259+ TypableCommand {
2543− name: "buffer-close-all!",
2260+ name: "buffer-close-all!",
2544− aliases: &["bca!", "bcloseall!"],
2261+ aliases: &["bca!", "bcloseall!"],
2545− doc: "Force close all buffers ignoring unsaved changes without quitting.",
2262+ doc: "Force close all buffers ignoring unsaved changes without quitting.",
2546− fun: force_buffer_close_all,
2263+ fun: force_buffer_close_all,
2547− signature: CommandSignature::none(),
2264+ signature: CommandSignature::none(),
2548− },
2265+ },
2549− TypableCommand {
2266+ TypableCommand {
2550− name: "buffer-next",
2267+ name: "buffer-next",
2551− aliases: &["bn", "bnext"],
2268+ aliases: &["bn", "bnext"],
2552− doc: "Goto next buffer.",
2269+ doc: "Goto next buffer.",
2553− fun: buffer_next,
2270+ fun: buffer_next,
2554− signature: CommandSignature::none(),
2271+ signature: CommandSignature::none(),
2555− },
2272+ },
2556− TypableCommand {
2273+ TypableCommand {
2557− name: "buffer-previous",
2274+ name: "buffer-previous",
2558− aliases: &["bp", "bprev"],
2275+ aliases: &["bp", "bprev"],
2559− doc: "Goto previous buffer.",
2276+ doc: "Goto previous buffer.",
2560− fun: buffer_previous,
2277+ fun: buffer_previous,
2561− signature: CommandSignature::none(),
2278+ signature: CommandSignature::none(),
2562− },
2279+ },
2563− TypableCommand {
2280+ TypableCommand {
2564− name: "write",
2281+ name: "write",
2565− aliases: &["w"],
2282+ aliases: &["w"],
2566− doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
2283+ doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
2567− fun: write,
2284+ fun: write,
2568− signature: CommandSignature::positional(&[completers::filename]),
2285+ signature: CommandSignature::positional(&[completers::filename]),
2569− },
2286+ },
2570− TypableCommand {
2287+ TypableCommand {
2571− name: "write!",
2288+ name: "write!",
2572− aliases: &["w!"],
2289+ aliases: &["w!"],
2573− doc: "Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt)",
2290+ doc: "Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write some/path.txt)",
2574− fun: force_write,
2291+ fun: force_write,
2575− signature: CommandSignature::positional(&[completers::filename]),
2292+ signature: CommandSignature::positional(&[completers::filename]),
2576− },
2293+ },
2577− TypableCommand {
2294+ TypableCommand {
2578− name: "write-buffer-close",
2295+ name: "new",
2579− aliases: &["wbc"],
2296+ aliases: &["n"],
2580− doc: "Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt)",
2297+ doc: "Create a new scratch buffer.",
2581− fun: write_buffer_close,
2298+ fun: new_file,
2582− signature: CommandSignature::positional(&[completers::filename]),
2299+ // TODO: This seems to complete with a filename, but doesn't use that filename to
2583− },
2300+ // set the path of the newly created buffer.
2584− TypableCommand {
2301+ signature: CommandSignature::positional(&[completers::filename]),
2585− name: "write-buffer-close!",
2302+ },
2586− aliases: &["wbc!"],
2303+ TypableCommand {
2587− doc: "Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt)",
2304+ name: "format",
2588− fun: force_write_buffer_close,
2305+ aliases: &["fmt"],
2589− signature: CommandSignature::positional(&[completers::filename]),
2306+ doc: "Format the file using the LSP formatter.",
2590− },
2307+ fun: format,
2591− TypableCommand {
2308+ signature: CommandSignature::none(),
2592− name: "new",
2309+ },
2593− aliases: &["n"],
2310+ TypableCommand {
2594− doc: "Create a new scratch buffer.",
2311+ name: "indent-style",
2595− fun: new_file,
2312+ aliases: &[],
2596− signature: CommandSignature::none(),
2313+ doc: "Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)",
2597− },
2314+ fun: set_indent_style,
2598− TypableCommand {
2315+ signature: CommandSignature::none(),
2599− name: "format",
2316+ },
2600− aliases: &["fmt"],
2317+ TypableCommand {
2601− doc: "Format the file using the LSP formatter.",
2318+ name: "line-ending",
2602− fun: format,
2319+ aliases: &[],
2603− signature: CommandSignature::none(),
2320+ #[cfg(not(feature = "unicode-lines"))]
2604− },
2321+ doc: "Set the document's default line ending. Options: crlf, lf.",
2605− TypableCommand {
2322+ #[cfg(feature = "unicode-lines")]
2606− name: "indent-style",
2323+ doc: "Set the document's default line ending. Options: crlf, lf, cr, ff, nel.",
2607− aliases: &[],
2324+ fun: set_line_ending,
2608− doc: "Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.)",
2325+ signature: CommandSignature::none(),
2609− fun: set_indent_style,
2326+ },
2610− signature: CommandSignature::none(),
2327+ TypableCommand {
2611− },
2328+ name: "earlier",
2612− TypableCommand {
2329+ aliases: &["ear"],
2613− name: "line-ending",
2330+ doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
2614− aliases: &[],
2331+ fun: earlier,
2615− #[cfg(not(feature = "unicode-lines"))]
2332+ signature: CommandSignature::none(),
2616− doc: "Set the document's default line ending. Options: crlf, lf.",
2333+ },
2617− #[cfg(feature = "unicode-lines")]
2334+ TypableCommand {
2618− doc: "Set the document's default line ending. Options: crlf, lf, cr, ff, nel.",
2335+ name: "later",
2619− fun: set_line_ending,
2336+ aliases: &["lat"],
2620− signature: CommandSignature::none(),
2337+ doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
2621− },
2338+ fun: later,
2622− TypableCommand {
2339+ signature: CommandSignature::none(),
2623− name: "earlier",
2340+ },
2624− aliases: &["ear"],
2341+ TypableCommand {
2625− doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
2342+ name: "write-quit",
2626− fun: earlier,
2343+ aliases: &["wq", "x"],
2627− signature: CommandSignature::none(),
2344+ doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
2628− },
2345+ fun: write_quit,
2629− TypableCommand {
2346+ signature: CommandSignature::positional(&[completers::filename]),
2630− name: "later",
2347+ },
2631− aliases: &["lat"],
2348+ TypableCommand {
2632− doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
2349+ name: "write-quit!",
2633− fun: later,
2350+ aliases: &["wq!", "x!"],
2634− signature: CommandSignature::none(),
2351+ doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
2635− },
2352+ fun: force_write_quit,
2636− TypableCommand {
2353+ signature: CommandSignature::positional(&[completers::filename]),
2637− name: "write-quit",
2354+ },
2638− aliases: &["wq", "x"],
2355+ TypableCommand {
2639− doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
2356+ name: "write-all",
2640− fun: write_quit,
2357+ aliases: &["wa"],
2641− signature: CommandSignature::positional(&[completers::filename]),
2358+ doc: "Write changes from all buffers to disk.",
2642− },
2359+ fun: write_all,
2643− TypableCommand {
2360+ signature: CommandSignature::none(),
2644− name: "write-quit!",
2361+ },
2645− aliases: &["wq!", "x!"],
2362+ TypableCommand {
2646− doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
2363+ name: "write-quit-all",
2647− fun: force_write_quit,
2364+ aliases: &["wqa", "xa"],
2648− signature: CommandSignature::positional(&[completers::filename]),
2365+ doc: "Write changes from all buffers to disk and close all views.",
2649− },
2366+ fun: write_all_quit,
2650− TypableCommand {
2367+ signature: CommandSignature::none(),
2651− name: "write-all",
2368+ },
2652− aliases: &["wa"],
2369+ TypableCommand {
2653− doc: "Write changes from all buffers to disk.",
2370+ name: "write-quit-all!",
2654− fun: write_all,
2371+ aliases: &["wqa!", "xa!"],
2655− signature: CommandSignature::none(),
2372+ doc: "Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes).",
2656− },
2373+ fun: force_write_all_quit,
2657− TypableCommand {
2374+ signature: CommandSignature::none(),
2658− name: "write-all!",
2375+ },
2659− aliases: &["wa!"],
2376+ TypableCommand {
2660− doc: "Forcefully write changes from all buffers to disk creating necessary subdirectories.",
2377+ name: "quit-all",
2661− fun: force_write_all,
2378+ aliases: &["qa"],
2662− signature: CommandSignature::none(),
2379+ doc: "Close all views.",
2663− },
2380+ fun: quit_all,
2664− TypableCommand {
2381+ signature: CommandSignature::none(),
2665− name: "write-quit-all",
2382+ },
2666− aliases: &["wqa", "xa"],
2383+ TypableCommand {
2667− doc: "Write changes from all buffers to disk and close all views.",
2384+ name: "quit-all!",
2668− fun: write_all_quit,
2385+ aliases: &["qa!"],
2669− signature: CommandSignature::none(),
2386+ doc: "Force close all views ignoring unsaved changes.",
2670− },
2387+ fun: force_quit_all,
2671− TypableCommand {
2388+ signature: CommandSignature::none(),
2672− name: "write-quit-all!",
2389+ },
2673− aliases: &["wqa!", "xa!"],
2390+ TypableCommand {
2674− doc: "Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes).",
2391+ name: "cquit",
2675− fun: force_write_all_quit,
2392+ aliases: &["cq"],
2676− signature: CommandSignature::none(),
2393+ doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).",
2677− },
2394+ fun: cquit,
2678− TypableCommand {
2395+ signature: CommandSignature::none(),
2679− name: "quit-all",
2396+ },
2680− aliases: &["qa"],
2397+ TypableCommand {
2681− doc: "Close all views.",
2398+ name: "cquit!",
2682− fun: quit_all,
2399+ aliases: &["cq!"],
2683− signature: CommandSignature::none(),
2400+ doc: "Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).",
2684− },
2401+ fun: force_cquit,
2685− TypableCommand {
2402+ signature: CommandSignature::none(),
2686− name: "quit-all!",
2403+ },
2687− aliases: &["qa!"],
2404+ TypableCommand {
2688− doc: "Force close all views ignoring unsaved changes.",
2405+ name: "theme",
2689− fun: force_quit_all,
2406+ aliases: &[],
2690− signature: CommandSignature::none(),
2407+ doc: "Change the editor theme (show current theme if no name specified).",
2691− },
2408+ fun: theme,
2692− TypableCommand {
2409+ signature: CommandSignature::positional(&[completers::theme]),
2693− name: "cquit",
2410+ },
2694− aliases: &["cq"],
2411+ TypableCommand {
2695− doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).",
2412+ name: "clipboard-yank",
2696− fun: cquit,
2413+ aliases: &[],
2697− signature: CommandSignature::none(),
2414+ doc: "Yank main selection into system clipboard.",
2698− },
2415+ fun: yank_main_selection_to_clipboard,
2699− TypableCommand {
2416+ signature: CommandSignature::none(),
2700− name: "cquit!",
2417+ },
2701− aliases: &["cq!"],
2418+ TypableCommand {
2702− doc: "Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).",
2419+ name: "clipboard-yank-join",
2703− fun: force_cquit,
2420+ aliases: &[],
2704− signature: CommandSignature::none(),
2421+ 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.
2705− },
2422+ fun: yank_joined_to_clipboard,
2706− TypableCommand {
2423+ signature: CommandSignature::none(),
2707− name: "theme",
2424+ },
2708− aliases: &[],
2425+ TypableCommand {
2709− doc: "Change the editor theme (show current theme if no name specified).",
2426+ name: "primary-clipboard-yank",
2710− fun: theme,
2427+ aliases: &[],
2711− signature: CommandSignature::positional(&[completers::theme]),
2428+ doc: "Yank main selection into system primary clipboard.",
2712− },
2429+ fun: yank_main_selection_to_primary_clipboard,
2713− TypableCommand {
2430+ signature: CommandSignature::none(),
2714− name: "yank-join",
2431+ },
2715− aliases: &[],
2432+ TypableCommand {
2716− doc: "Yank joined selections. A separator can be provided as first argument. Default value is newline.",
2433+ name: "primary-clipboard-yank-join",
2717− fun: yank_joined,
2434+ aliases: &[],
2718− signature: CommandSignature::none(),
2435+ 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.
2719− },
2436+ fun: yank_joined_to_primary_clipboard,
2720− TypableCommand {
2437+ signature: CommandSignature::none(),
2721− name: "clipboard-yank",
2438+ },
2722− aliases: &[],
2439+ TypableCommand {
2723− doc: "Yank main selection into system clipboard.",
2440+ name: "clipboard-paste-after",
2724− fun: yank_main_selection_to_clipboard,
2441+ aliases: &[],
2725− signature: CommandSignature::none(),
2442+ doc: "Paste system clipboard after selections.",
2726− },
2443+ fun: paste_clipboard_after,
2727− TypableCommand {
2444+ signature: CommandSignature::none(),
2728− name: "clipboard-yank-join",
2445+ },
2729− aliases: &[],
2446+ 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.
2447+ name: "clipboard-paste-before",
2731− fun: yank_joined_to_clipboard,
2448+ aliases: &[],
2732− signature: CommandSignature::none(),
2449+ doc: "Paste system clipboard before selections.",
2733− },
2450+ fun: paste_clipboard_before,
2734− TypableCommand {
2451+ signature: CommandSignature::none(),
2735− name: "primary-clipboard-yank",
2452+ },
2736− aliases: &[],
2453+ TypableCommand {
2737− doc: "Yank main selection into system primary clipboard.",
2454+ name: "clipboard-paste-replace",
2738− fun: yank_main_selection_to_primary_clipboard,
2455+ aliases: &[],
2739− signature: CommandSignature::none(),
2456+ doc: "Replace selections with content of system clipboard.",
2740− },
2457+ fun: replace_selections_with_clipboard,
2741− TypableCommand {
2458+ signature: CommandSignature::none(),
2742− name: "primary-clipboard-yank-join",
2459+ },
2743− aliases: &[],
2460+ 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.
2461+ name: "primary-clipboard-paste-after",
2745− fun: yank_joined_to_primary_clipboard,
2462+ aliases: &[],
2746− signature: CommandSignature::none(),
2463+ doc: "Paste primary clipboard after selections.",
2747− },
2464+ fun: paste_primary_clipboard_after,
2748− TypableCommand {
2465+ signature: CommandSignature::none(),
2749− name: "clipboard-paste-after",
2466+ },
2750− aliases: &[],
2467+ TypableCommand {
2751− doc: "Paste system clipboard after selections.",
2468+ name: "primary-clipboard-paste-before",
2752− fun: paste_clipboard_after,
2469+ aliases: &[],
2753− signature: CommandSignature::none(),
2470+ doc: "Paste primary clipboard before selections.",
2754− },
2471+ fun: paste_primary_clipboard_before,
2755− TypableCommand {
2472+ signature: CommandSignature::none(),
2756− name: "clipboard-paste-before",
2473+ },
2757− aliases: &[],
2474+ TypableCommand {
2758− doc: "Paste system clipboard before selections.",
2475+ name: "primary-clipboard-paste-replace",
2759− fun: paste_clipboard_before,
2476+ aliases: &[],
2760− signature: CommandSignature::none(),
2477+ doc: "Replace selections with content of system primary clipboard.",
2761− },
2478+ fun: replace_selections_with_primary_clipboard,
2762− TypableCommand {
2479+ signature: CommandSignature::none(),
2763− name: "clipboard-paste-replace",
2480+ },
2764− aliases: &[],
2481+ TypableCommand {
2765− doc: "Replace selections with content of system clipboard.",
2482+ name: "show-clipboard-provider",
2766− fun: replace_selections_with_clipboard,
2483+ aliases: &[],
2767− signature: CommandSignature::none(),
2484+ doc: "Show clipboard provider name in status bar.",
2768− },
2485+ fun: show_clipboard_provider,
2769− TypableCommand {
2486+ signature: CommandSignature::none(),
2770− name: "primary-clipboard-paste-after",
2487+ },
2771− aliases: &[],
2488+ TypableCommand {
2772− doc: "Paste primary clipboard after selections.",
2489+ name: "change-current-directory",
2773− fun: paste_primary_clipboard_after,
2490+ aliases: &["cd"],
2774− signature: CommandSignature::none(),
2491+ doc: "Change the current working directory.",
2775− },
2492+ fun: change_current_directory,
2776− TypableCommand {
2493+ signature: CommandSignature::positional(&[completers::directory]),
2777− name: "primary-clipboard-paste-before",
2494+ },
2778− aliases: &[],
2495+ TypableCommand {
2779− doc: "Paste primary clipboard before selections.",
2496+ name: "show-directory",
2780− fun: paste_primary_clipboard_before,
2497+ aliases: &["pwd"],
2781− signature: CommandSignature::none(),
2498+ doc: "Show the current working directory.",
2782− },
2499+ fun: show_current_directory,
2783− TypableCommand {
2500+ signature: CommandSignature::none(),
2784− name: "primary-clipboard-paste-replace",
2501+ },
2785− aliases: &[],
2502+ TypableCommand {
2786− doc: "Replace selections with content of system primary clipboard.",
2503+ name: "encoding",
2787− fun: replace_selections_with_primary_clipboard,
2504+ aliases: &[],
2788− signature: CommandSignature::none(),
2505+ doc: "Set encoding. Based on `https://encoding.spec.whatwg.org`.",
2789− },
2506+ fun: set_encoding,
2790− TypableCommand {
2507+ signature: CommandSignature::none(),
2791− name: "show-clipboard-provider",
2508+ },
2792− aliases: &[],
2509+ TypableCommand {
2793− doc: "Show clipboard provider name in status bar.",
2510+ name: "character-info",
2794− fun: show_clipboard_provider,
2511+ aliases: &["char"],
2795− signature: CommandSignature::none(),
2512+ doc: "Get info about the character under the primary cursor.",
2796− },
2513+ fun: get_character_info,
2797− TypableCommand {
2514+ signature: CommandSignature::none(),
2798− name: "change-current-directory",
2515+ },
2799− aliases: &["cd"],
2516+ TypableCommand {
2800− doc: "Change the current working directory.",
2517+ name: "reload",
2801− fun: change_current_directory,
2518+ aliases: &[],
2802− signature: CommandSignature::positional(&[completers::directory]),
2519+ doc: "Discard changes and reload from the source file.",
2803− },
2520+ fun: reload,
2804− TypableCommand {
2521+ signature: CommandSignature::none(),
2805− name: "show-directory",
2522+ },
2806− aliases: &["pwd"],
2523+ TypableCommand {
2807− doc: "Show the current working directory.",
2524+ name: "reload-all",
2808− fun: show_current_directory,
2525+ aliases: &[],
2809− signature: CommandSignature::none(),
2526+ doc: "Discard changes and reload all documents from the source files.",
2810− },
2527+ fun: reload_all,
2811− TypableCommand {
2528+ signature: CommandSignature::none(),
2812− name: "encoding",
2529+ },
2813− aliases: &[],
2530+ TypableCommand {
2814− doc: "Set encoding. Based on `https://encoding.spec.whatwg.org`.",
2531+ name: "update",
2815− fun: set_encoding,
2532+ aliases: &["u"],
2816− signature: CommandSignature::none(),
2533+ doc: "Write changes only if the file has been modified.",
2817− },
2534+ fun: update,
2818− TypableCommand {
2535+ signature: CommandSignature::none(),
2819− name: "character-info",
2536+ },
2820− aliases: &["char"],
2537+ TypableCommand {
2821− doc: "Get info about the character under the primary cursor.",
2538+ name: "lsp-workspace-command",
2822− fun: get_character_info,
2539+ aliases: &[],
2823− signature: CommandSignature::none(),
2540+ doc: "Open workspace command picker",
2824− },
2541+ fun: lsp_workspace_command,
2825− TypableCommand {
2542+ signature: CommandSignature::positional(&[completers::lsp_workspace_command]),
2826− name: "reload",
2543+ },
2827− aliases: &["rl"],
2544+ TypableCommand {
2828− doc: "Discard changes and reload from the source file.",
2545+ name: "lsp-restart",
2829− fun: reload,
2546+ aliases: &[],
2830− signature: CommandSignature::none(),
2547+ doc: "Restarts the Language Server that is in use by the current doc",
2831− },
2548+ fun: lsp_restart,
2832− TypableCommand {
2549+ signature: CommandSignature::none(),
2833− name: "reload-all",
2550+ },
2834− aliases: &["rla"],
2551+ TypableCommand {
2835− doc: "Discard changes and reload all documents from the source files.",
2552+ name: "lsp-stop",
2836− fun: reload_all,
2553+ aliases: &[],
2837− signature: CommandSignature::none(),
2554+ doc: "Stops the Language Server that is in use by the current doc",
2838− },
2555+ fun: lsp_stop,
2839− TypableCommand {
2556+ signature: CommandSignature::none(),
2840− name: "update",
2557+ },
2841− aliases: &["u"],
2558+ TypableCommand {
2842− doc: "Write changes only if the file has been modified.",
2559+ name: "tree-sitter-scopes",
2843− fun: update,
2560+ aliases: &[],
2844− signature: CommandSignature::none(),
2561+ doc: "Display tree sitter scopes, primarily for theming and development.",
2845− },
2562+ fun: tree_sitter_scopes,
2846− TypableCommand {
2563+ signature: CommandSignature::none(),
2847− name: "lsp-workspace-command",
2564+ },
2848− aliases: &[],
2565+ TypableCommand {
2849− doc: "Open workspace command picker",
2566+ name: "debug-start",
2850− fun: lsp_workspace_command,
2567+ aliases: &["dbg"],
2851− signature: CommandSignature::positional(&[completers::lsp_workspace_command]),
2568+ doc: "Start a debug session from a given template with given parameters.",
2852− },
2569+ fun: debug_start,
2853− TypableCommand {
2570+ signature: CommandSignature::none(),
2854− name: "lsp-restart",
2571+ },
2855− aliases: &[],
2572+ TypableCommand {
2856− doc: "Restarts the language servers used by the current doc",
2573+ name: "debug-remote",
2857− fun: lsp_restart,
2574+ aliases: &["dbg-tcp"],
2858− signature: CommandSignature::none(),
2575+ doc: "Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.",
2859− },
2576+ fun: debug_remote,
2860− TypableCommand {
2577+ signature: CommandSignature::none(),
2861− name: "lsp-stop",
2578+ },
2862− aliases: &[],
2579+ TypableCommand {
2863− doc: "Stops the language servers that are used by the current doc",
2580+ name: "debug-eval",
2864− fun: lsp_stop,
2581+ aliases: &[],
2865− signature: CommandSignature::none(),
2582+ doc: "Evaluate expression in current debug context.",
2866− },
2583+ fun: debug_eval,
2867− TypableCommand {
2584+ signature: CommandSignature::none(),
2868− name: "tree-sitter-scopes",
2585+ },
2869− aliases: &[],
2586+ TypableCommand {
2870− doc: "Display tree sitter scopes, primarily for theming and development.",
2587+ name: "vsplit",
2871− fun: tree_sitter_scopes,
2588+ aliases: &["vs"],
2872− signature: CommandSignature::none(),
2589+ doc: "Open the file in a vertical split.",
2873− },
2590+ fun: vsplit,
2874− TypableCommand {
2591+ signature: CommandSignature::all(completers::filename)
2875− name: "tree-sitter-highlight-name",
2592+ },
2876− aliases: &[],
2593+ TypableCommand {
2877− doc: "Display name of tree-sitter highlight scope under the cursor.",
2594+ name: "vsplit-new",
2878− fun: tree_sitter_highlight_name,
2595+ aliases: &["vnew"],
2879− signature: CommandSignature::none(),
2596+ doc: "Open a scratch buffer in a vertical split.",
2880− },
2597+ fun: vsplit_new,
2881− TypableCommand {
2598+ signature: CommandSignature::none(),
2882− name: "debug-start",
2599+ },
2883− aliases: &["dbg"],
2600+ TypableCommand {
2884− doc: "Start a debug session from a given template with given parameters.",
2601+ name: "hsplit",
2885− fun: debug_start,
2602+ aliases: &["hs", "sp"],
2886− signature: CommandSignature::none(),
2603+ doc: "Open the file in a horizontal split.",
2887− },
2604+ fun: hsplit,
2888− TypableCommand {
2605+ signature: CommandSignature::all(completers::filename)
2889− name: "debug-remote",
2606+ },
2890− aliases: &["dbg-tcp"],
2607+ TypableCommand {
2891− doc: "Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.",
2608+ name: "hsplit-new",
2892− fun: debug_remote,
2609+ aliases: &["hnew"],
2893− signature: CommandSignature::none(),
2610+ doc: "Open a scratch buffer in a horizontal split.",
2894− },
2611+ fun: hsplit_new,
2895− TypableCommand {
2612+ signature: CommandSignature::none(),
2896− name: "debug-eval",
2613+ },
2897− aliases: &[],
2614+ TypableCommand {
2898− doc: "Evaluate expression in current debug context.",
2615+ name: "tutor",
2899− fun: debug_eval,
2616+ aliases: &[],
2900− signature: CommandSignature::none(),
2617+ doc: "Open the tutorial.",
2901− },
2618+ fun: tutor,
2902− TypableCommand {
2619+ signature: CommandSignature::none(),
2903− name: "vsplit",
2620+ },
2904− aliases: &["vs"],
2621+ TypableCommand {
2905− doc: "Open the file in a vertical split.",
2622+ name: "goto",
2906− fun: vsplit,
2623+ aliases: &["g"],
2907− signature: CommandSignature::all(completers::filename)
2624+ doc: "Goto line number.",
2908− },
2625+ fun: goto_line_number,
2909− TypableCommand {
2626+ signature: CommandSignature::none(),
2910− name: "vsplit-new",
2627+ },
2911− aliases: &["vnew"],
2628+ TypableCommand {
2912− doc: "Open a scratch buffer in a vertical split.",
2629+ name: "set-language",
2913− fun: vsplit_new,
2630+ aliases: &["lang"],
2914− signature: CommandSignature::none(),
2631+ doc: "Set the language of current buffer (show current language if no value specified).",
2915− },
2632+ fun: language,
2916− TypableCommand {
2633+ signature: CommandSignature::positional(&[completers::language]),
2917− name: "hsplit",
2634+ },
2918− aliases: &["hs", "sp"],
2635+ TypableCommand {
2919− doc: "Open the file in a horizontal split.",
2636+ name: "set-option",
2920− fun: hsplit,
2637+ aliases: &["set"],
2921− signature: CommandSignature::all(completers::filename)
2638+ doc: "Set a config option at runtime.\nFor example to disable smart case search, use `:set search.smart-case false`.",
2922− },
2639+ fun: set_option,
2923− TypableCommand {
2640+ // TODO: Add support for completion of the options value(s), when appropriate.
2924− name: "hsplit-new",
2641+ signature: CommandSignature::positional(&[completers::setting]),
2925− aliases: &["hnew"],
2642+ },
2926− doc: "Open a scratch buffer in a horizontal split.",
2643+ TypableCommand {
2927− fun: hsplit_new,
2644+ name: "toggle-option",
2928− signature: CommandSignature::none(),
2645+ aliases: &["toggle"],
2929− },
2646+ doc: "Toggle a boolean config option at runtime.\nFor example to toggle smart case search, use `:toggle search.smart-case`.",
2930− TypableCommand {
2647+ fun: toggle_option,
2931− name: "tutor",
2648+ signature: CommandSignature::positional(&[completers::setting]),
2932− aliases: &[],
2649+ },
2933− doc: "Open the tutorial.",
2650+ TypableCommand {
2934− fun: tutor,
2651+ name: "get-option",
2935− signature: CommandSignature::none(),
2652+ aliases: &["get"],
2936− },
2653+ doc: "Get the current value of a config option.",
2937− TypableCommand {
2654+ fun: get_option,
2938− name: "goto",
2655+ signature: CommandSignature::positional(&[completers::setting]),
2939− aliases: &["g"],
2656+ },
2940− doc: "Goto line number.",
2657+ TypableCommand {
2941− fun: goto_line_number,
2658+ name: "sort",
2942− signature: CommandSignature::none(),
2659+ aliases: &[],
2943− },
2660+ doc: "Sort ranges in selection.",
2944− TypableCommand {
2661+ fun: sort,
2945− name: "set-language",
2662+ signature: CommandSignature::none(),
2946− aliases: &["lang"],
2663+ },
2947− doc: "Set the language of current buffer (show current language if no value specified).",
2664+ TypableCommand {
2948− fun: language,
2665+ name: "rsort",
2949− signature: CommandSignature::positional(&[completers::language]),
2666+ aliases: &[],
2950− },
2667+ doc: "Sort ranges in selection in reverse order.",
2951− TypableCommand {
2668+ fun: sort_reverse,
2952− name: "set-option",
2669+ signature: CommandSignature::none(),
2953− aliases: &["set"],
2670+ },
2954− doc: "Set a config option at runtime.\nFor example to disable smart case search, use `:set search.smart-case false`.",
2671+ TypableCommand {
2955− fun: set_option,
2672+ name: "reflow",
2956− // TODO: Add support for completion of the options value(s), when appropriate.
2673+ aliases: &[],
2957− signature: CommandSignature::positional(&[completers::setting]),
2674+ doc: "Hard-wrap the current selection of lines to a given width.",
2958− },
2675+ fun: reflow,
2959− TypableCommand {
2676+ signature: CommandSignature::none(),
2960− name: "toggle-option",
2677+ },
2961− aliases: &["toggle"],
2678+ TypableCommand {
2962− doc: "Toggle a boolean config option at runtime.\nFor example to toggle smart case search, use `:toggle search.smart-case`.",
2679+ name: "tree-sitter-subtree",
2963− fun: toggle_option,
2680+ aliases: &["ts-subtree"],
2964− signature: CommandSignature::positional(&[completers::setting]),
2681+ doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
2965− },
2682+ fun: tree_sitter_subtree,
2966− TypableCommand {
2683+ signature: CommandSignature::none(),
2967− name: "get-option",
2684+ },
2968− aliases: &["get"],
2685+ TypableCommand {
2969− doc: "Get the current value of a config option.",
2686+ name: "config-reload",
2970− fun: get_option,
2687+ aliases: &[],
2971− signature: CommandSignature::positional(&[completers::setting]),
2688+ doc: "Refresh user config.",
2972− },
2689+ fun: refresh_config,
2973− TypableCommand {
2690+ signature: CommandSignature::none(),
2974− name: "sort",
2691+ },
2975− aliases: &[],
2692+ TypableCommand {
2976− doc: "Sort ranges in selection.",
2693+ name: "config-open",
2977− fun: sort,
2694+ aliases: &[],
2978− signature: CommandSignature::none(),
2695+ doc: "Open the user config.toml file.",
2979− },
2696+ fun: open_config,
2980− TypableCommand {
2697+ signature: CommandSignature::none(),
2981− name: "rsort",
2698+ },
2982− aliases: &[],
2699+ TypableCommand {
2983− doc: "Sort ranges in selection in reverse order.",
2700+ name: "config-open-workspace",
2984− fun: sort_reverse,
2701+ aliases: &[],
2985− signature: CommandSignature::none(),
2702+ doc: "Open the workspace config.toml file.",
2986− },
2703+ fun: open_workspace_config,
2987− TypableCommand {
2704+ signature: CommandSignature::none(),
2988− name: "reflow",
2705+ },
2989− aliases: &[],
2706+ TypableCommand {
2990− doc: "Hard-wrap the current selection of lines to a given width.",
2707+ name: "log-open",
2991− fun: reflow,
2708+ aliases: &[],
2992− signature: CommandSignature::none(),
2709+ doc: "Open the helix log file.",
2993− },
2710+ fun: open_log,
2994− TypableCommand {
2711+ signature: CommandSignature::none(),
2995− name: "tree-sitter-subtree",
2712+ },
2996− aliases: &["ts-subtree"],
2713+ TypableCommand {
2997− doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
2714+ name: "insert-output",
2998− fun: tree_sitter_subtree,
2715+ aliases: &[],
2999− signature: CommandSignature::none(),
2716+ doc: "Run shell command, inserting output before each selection.",
3000− },
2717+ fun: insert_output,
3001− TypableCommand {
2718+ signature: CommandSignature::none(),
3002− name: "config-reload",
2719+ },
3003− aliases: &[],
2720+ TypableCommand {
3004− doc: "Refresh user config.",
2721+ name: "append-output",
3005− fun: refresh_config,
2722+ aliases: &[],
3006− signature: CommandSignature::none(),
2723+ doc: "Run shell command, appending output after each selection.",
3007− },
2724+ fun: append_output,
3008− TypableCommand {
2725+ signature: CommandSignature::none(),
3009− name: "config-open",
2726+ },
3010− aliases: &[],
2727+ TypableCommand {
3011− doc: "Open the user config.toml file.",
2728+ name: "pipe",
3012− fun: open_config,
2729+ aliases: &[],
3013− signature: CommandSignature::none(),
2730+ doc: "Pipe each selection to the shell command.",
3014− },
2731+ fun: pipe,
3015− TypableCommand {
2732+ signature: CommandSignature::none(),
3016− name: "config-open-workspace",
2733+ },
3017− aliases: &[],
2734+ TypableCommand {
3018− doc: "Open the workspace config.toml file.",
2735+ name: "pipe-to",
3019− fun: open_workspace_config,
2736+ aliases: &[],
3020− signature: CommandSignature::none(),
2737+ doc: "Pipe each selection to the shell command, ignoring output.",
3021− },
2738+ fun: pipe_to,
3022− TypableCommand {
2739+ signature: CommandSignature::none(),
3023− name: "log-open",
2740+ },
3024− aliases: &[],
2741+ TypableCommand {
3025− doc: "Open the helix log file.",
2742+ name: "run-shell-command",
3026− fun: open_log,
2743+ aliases: &["sh"],
3027− signature: CommandSignature::none(),
2744+ doc: "Run a shell command",
3028− },
2745+ fun: run_shell_command,
3029− TypableCommand {
2746+ signature: CommandSignature::all(completers::filename)
3030− name: "insert-output",
2747+ },
3031− aliases: &[],
2748+ TypableCommand {
3032− doc: "Run shell command, inserting output before each selection.",
2749+ name: "reset-diff-change",
3033− fun: insert_output,
2750+ aliases: &["diffget", "diffg"],
3034− signature: CommandSignature::none(),
2751+ doc: "Reset the diff change at the cursor position.",
3035− },
2752+ fun: reset_diff_change,
3036− TypableCommand {
2753+ signature: CommandSignature::none(),
3037− name: "append-output",
2754+ },
3038− aliases: &[],
2755+ TypableCommand {
3039− doc: "Run shell command, appending output after each selection.",
2756+ name: "clear-register",
3040− fun: append_output,
2757+ aliases: &[],
3041− signature: CommandSignature::none(),
2758+ doc: "Clear given register. If no argument is provided, clear all registers.",
3042− },
2759+ fun: clear_register,
3043− TypableCommand {
2760+ signature: CommandSignature::none(),
3044− name: "pipe",
2761+ },
3045− aliases: &[],
2762+ ];
3046− doc: "Pipe each selection to the shell command.",
3047− fun: pipe,
3048− signature: CommandSignature::none(),
3049− },
3050− TypableCommand {
3051− name: "pipe-to",
3052− aliases: &[],
3053− doc: "Pipe each selection to the shell command, ignoring output.",
3054− fun: pipe_to,
3055− signature: CommandSignature::none(),
3056− },
3057− TypableCommand {
3058− name: "run-shell-command",
3059− aliases: &["sh"],
3060− doc: "Run a shell command",
3061− fun: run_shell_command,
3062− signature: CommandSignature::all(completers::filename)
3063− },
3064− TypableCommand {
3065− name: "reset-diff-change",
3066− aliases: &["diffget", "diffg"],
3067− doc: "Reset the diff change at the cursor position.",
3068− fun: reset_diff_change,
3069− signature: CommandSignature::none(),
3070− },
3071− TypableCommand {
3072− name: "clear-register",
3073− aliases: &[],
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
2763
3094pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
2764pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
3095 Lazy::new(|| {
2765 Lazy::new(|| {
@@ -3108,25 +2778,38 @@ pub(super) fn command_mode(cx: &mut Context) {
3108 ":".into(),
2778 ":".into(),
3109 Some(':'),
2779 Some(':'),
3110 |editor: &Editor, input: &str| {
2780 |editor: &Editor, input: &str| {
2781+ static FUZZY_MATCHER: Lazy<fuzzy_matcher::skim::SkimMatcherV2> =
2782+ Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default);
2783+
3111 let shellwords = Shellwords::from(input);
2784 let shellwords = Shellwords::from(input);
3112 let words = shellwords.words();
2785 let words = shellwords.words();
3113
2786
3114 if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) {
2787 if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) {
2788+ // If the command has not been finished yet, complete commands.
2789+ let mut matches: Vec<_> = typed::TYPABLE_COMMAND_LIST
2790+ .iter()
2791+ .filter_map(|command| {
2792+ FUZZY_MATCHER
3115− fuzzy_match(
2793+ .fuzzy_match(command.name, input)
3116− input,
3117− TYPABLE_COMMAND_LIST.iter().map(|command| command.name),
2794+ .map(|score| (command.name, score))
3118− false,
2795+ })
3119− )
2796+ .collect();
2797+
2798+ matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score));
2799+ matches
3120− .into_iter()
2800+ .into_iter()
3121− .map(|(name, _)| (0.., name.into()))
2801+ .map(|(name, _)| (0.., name.into()))
3122− .collect()
2802+ .collect()
3123 } else {
2803 } else {
3124 // Otherwise, use the command's completer and the last shellword
2804 // Otherwise, use the command's completer and the last shellword
3125 // as completion input.
2805 // as completion input.
3126− let (word, word_len) = if words.len() == 1 || shellwords.ends_with_whitespace() {
2806+ let (part, part_len) = if words.len() == 1 || shellwords.ends_with_whitespace() {
3127 (&Cow::Borrowed(""), 0)
2807 (&Cow::Borrowed(""), 0)
3128 } else {
2808 } else {
2809+ (
3129− (words.last().unwrap(), words.last().unwrap().len())
2810+ words.last().unwrap(),
2811+ shellwords.parts().last().unwrap().len(),
2812+ )
3130 };
2813 };
3131
2814
3132 let argument_number = argument_number_of(&shellwords);
2815 let argument_number = argument_number_of(&shellwords);
@@ -3135,13 +2818,13 @@ pub(super) fn command_mode(cx: &mut Context) {
3135 .get(&words[0] as &str)
2818 .get(&words[0] as &str)
3136 .map(|tc| tc.completer_for_argument_number(argument_number))
2819 .map(|tc| tc.completer_for_argument_number(argument_number))
3137 {
2820 {
3138− completer(editor, word)
2821+ completer(editor, part)
3139 .into_iter()
2822 .into_iter()
3140 .map(|(range, file)| {
2823 .map(|(range, file)| {
3141 let file = shellwords::escape(file);
2824 let file = shellwords::escape(file);
3142
2825
3143 // offset ranges to input
2826 // offset ranges to input
3144− let offset = input.len() - word_len;
2827+ let offset = input.len() - part_len;
3145 let range = (range.start + offset)..;
2828 let range = (range.start + offset)..;
3146 (range, file)
2829 (range, file)
3147 })
2830 })
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+8−8

This diff is too big to show here.

helix-term/src/health.rs+12−48

This diff is too big to show here.

helix-term/src/keymap.rs+145−105

This diff is too big to show here.

helix-term/src/keymap/default.rs+7−14

This diff is too big to show here.

helix-term/src/keymap/macros.rs+4−3

This diff is too big to show here.

helix-term/src/main.rs+18−22

This diff is too big to show here.

helix-term/src/ui/completion.rs+75−135

This diff is too big to show here.

helix-term/src/ui/document.rs+24−60

This diff is too big to show here.

helix-term/src/ui/editor.rs+84−157

This diff is too big to show here.

helix-term/src/ui/fuzzy_match.rsnew file+239

This diff is too big to show here.

helix-term/src/ui/fuzzy_match/test.rsnew file+47

This diff is too big to show here.

helix-term/src/ui/lsp.rs+3−5

This diff is too big to show here.

helix-term/src/ui/markdown.rs+12−19

This diff is too big to show here.

helix-term/src/ui/menu.rs+40−73

This diff is too big to show here.

helix-term/src/ui/mod.rs+179−108

This diff is too big to show here.

helix-term/src/ui/picker.rs+594−629

This diff is too big to show here.

helix-term/src/ui/popup.rs+7−27

This diff is too big to show here.

helix-term/src/ui/prompt.rs+48−55

This diff is too big to show here.

helix-term/src/ui/statusline.rs+7−30

This diff is too big to show here.

helix-term/tests/integration.rs−1

This diff is too big to show here.

helix-term/tests/test/auto_pairs.rs+1−1

This diff is too big to show here.

helix-term/tests/test/commands.rs+48−189

This diff is too big to show here.

helix-term/tests/test/commands/movement.rsdeleted−452

This diff is too big to show here.

helix-term/tests/test/commands/write.rs+3−122

This diff is too big to show here.

helix-term/tests/test/helpers.rs+2−10

This diff is too big to show here.

helix-term/tests/test/languages/go.rsdeleted−41

This diff is too big to show here.

helix-term/tests/test/languages/mod.rsdeleted−4

This diff is too big to show here.

helix-term/tests/test/languages/yaml.rsdeleted−819

This diff is too big to show here.

helix-term/tests/test/movement.rs−39

This diff is too big to show here.

helix-term/tests/test/splits.rs+3−3

This diff is too big to show here.

helix-tui/Cargo.toml+16−16

This diff is too big to show here.

helix-tui/src/backend/crossterm.rs+33−51

This diff is too big to show here.

helix-tui/src/buffer.rs+3−3

This diff is too big to show here.

helix-tui/src/terminal.rs+14

This diff is too big to show here.

helix-tui/src/widgets/table.rs+3−3

This diff is too big to show here.

helix-vcs/Cargo.toml+10−12

This diff is too big to show here.

helix-vcs/src/diff.rs+19−6

This diff is too big to show here.

helix-vcs/src/diff/line_cache.rs+9−9

This diff is too big to show here.

helix-vcs/src/diff/worker.rs+9−3

This diff is too big to show here.

helix-vcs/src/diff/worker/test.rs+5−1

This diff is too big to show here.

helix-vcs/src/git.rs+5−5

This diff is too big to show here.

helix-view/Cargo.toml+18−22

This diff is too big to show here.

helix-view/src/clipboard.rs+3−8

This diff is too big to show here.

helix-view/src/document.rs+134−381

This diff is too big to show here.

helix-view/src/editor.rs+93−266

This diff is too big to show here.

helix-view/src/gutter.rs+24−21

This diff is too big to show here.

helix-view/src/handlers/dap.rs+3−1

This diff is too big to show here.

helix-view/src/info.rs+25−5

This diff is too big to show here.

helix-view/src/input.rs+3−67

This diff is too big to show here.

helix-view/src/lib.rs−1

This diff is too big to show here.

helix-view/src/register.rsdeleted−333

This diff is too big to show here.

helix-view/src/theme.rs+4−25

This diff is too big to show here.

helix-view/src/tree.rs+2−1

This diff is too big to show here.

helix-view/src/view.rs+2−8

This diff is too big to show here.

languages.toml+393−879

This diff is too big to show here.

logo.svg+1−1

This diff is too big to show here.

runtime/queries/_javascript/highlights.scmdeleted−36

This diff is too big to show here.

runtime/queries/_javascript/locals.scmdeleted−14

This diff is too big to show here.

runtime/queries/_javascript/tags.scmdeleted−88

This diff is too big to show here.

runtime/queries/_jsx/highlights.scmdeleted−43

This diff is too big to show here.

runtime/queries/_jsx/indents.scmdeleted−6

This diff is too big to show here.

runtime/queries/_typescript/highlights.scmdeleted−142

This diff is too big to show here.

runtime/queries/_typescript/indents.scmdeleted−5

This diff is too big to show here.

runtime/queries/_typescript/locals.scmdeleted−33

This diff is too big to show here.

runtime/queries/_typescript/tags.scmdeleted−23

This diff is too big to show here.

runtime/queries/_typescript/textobjects.scmdeleted−6

This diff is too big to show here.

runtime/queries/agda/highlights.scmdeleted−124

This diff is too big to show here.

runtime/queries/bash/indents.scm−1

This diff is too big to show here.

runtime/queries/bash/textobjects.scmdeleted−9

This diff is too big to show here.

runtime/queries/blueprint/highlights.scmdeleted−61

This diff is too big to show here.

runtime/queries/c/indents.scm+1−18

This diff is too big to show here.

runtime/queries/cabal/highlights.scmnew file+15

This diff is too big to show here.

runtime/queries/cairo/highlights.scm+94−1

This diff is too big to show here.

runtime/queries/cairo/indents.scmdeleted−1

This diff is too big to show here.

runtime/queries/cairo/injections.scm+4−2

This diff is too big to show here.

runtime/queries/cairo/locals.scmdeleted−1

This diff is too big to show here.

runtime/queries/cairo/textobjects.scmdeleted−1

This diff is too big to show here.

runtime/queries/comment/highlights.scm+8−8

This diff is too big to show here.

runtime/queries/common-lisp/indents.scmdeleted−1

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/dart/highlights.scm+1−3

This diff is too big to show here.

runtime/queries/dbml/highlights.scmdeleted−44

This diff is too big to show here.

runtime/queries/dockerfile/highlights.scm+1−4

This diff is too big to show here.

runtime/queries/ecma/README.mddeleted−14

This diff is too big to show here.

runtime/queries/ecma/highlights.scm+2−9

This diff is too big to show here.

runtime/queries/ecma/locals.scm+3−17

This diff is too big to show here.

runtime/queries/forth/highlights.scmdeleted−7

This diff is too big to show here.

runtime/queries/fortran/injections.scmdeleted−2

This diff is too big to show here.

runtime/queries/fsharp/highlights.scmdeleted−196

This diff is too big to show here.

runtime/queries/fsharp/locals.scmdeleted−25

This diff is too big to show here.

runtime/queries/gas/highlights.scmdeleted−21

This diff is too big to show here.

runtime/queries/gas/injections.scmdeleted−2

This diff is too big to show here.

runtime/queries/gas/textobjects.scmdeleted−2

This diff is too big to show here.

runtime/queries/gemini/highlights.scmdeleted−26

This diff is too big to show here.

runtime/queries/gleam/highlights.scm+5−16

This diff is too big to show here.

runtime/queries/gn/highlights.scmdeleted−42

This diff is too big to show here.

runtime/queries/gn/injections.scmdeleted−15

This diff is too big to show here.

runtime/queries/gn/locals.scmdeleted−16

This diff is too big to show here.

runtime/queries/go/highlights.scm+5−13

This diff is too big to show here.

runtime/queries/go/locals.scm+2−10

This diff is too big to show here.

runtime/queries/hare/highlights.scm+1−4

This diff is too big to show here.

runtime/queries/haskell-persistent/folds.scmdeleted−3

This diff is too big to show here.

runtime/queries/haskell-persistent/highlights.scmdeleted−37

This diff is too big to show here.

runtime/queries/haskell-persistent/locals.scmdeleted−1

This diff is too big to show here.

runtime/queries/haskell/highlights.scm+11−24

This diff is too big to show here.

runtime/queries/haskell/injections.scm−8

This diff is too big to show here.

runtime/queries/janet/highlights.scmdeleted−1

This diff is too big to show here.

runtime/queries/java/highlights.scm+2−81

This diff is too big to show here.

runtime/queries/java/indents.scmdeleted−35

This diff is too big to show here.

runtime/queries/javascript/highlights.scm+37−2

This diff is too big to show here.

runtime/queries/javascript/indents.scm+1−3

This diff is too big to show here.

runtime/queries/javascript/injections.scm+1−3

This diff is too big to show here.

runtime/queries/javascript/locals.scm+1−3

This diff is too big to show here.

runtime/queries/javascript/tags.scm+87−2

This diff is too big to show here.

runtime/queries/javascript/textobjects.scm+1−3

This diff is too big to show here.

runtime/queries/jinja/highlights.scmdeleted−7

This diff is too big to show here.

runtime/queries/jinja/injections.scmdeleted−4

This diff is too big to show here.

runtime/queries/json5/highlights.scmdeleted−11

This diff is too big to show here.

runtime/queries/jsx/highlights.scm+36−2

This diff is too big to show here.

runtime/queries/jsx/indents.scm+8−2

This diff is too big to show here.

runtime/queries/jsx/injections.scm+1−3

This diff is too big to show here.

runtime/queries/jsx/locals.scm+1−3

This diff is too big to show here.

runtime/queries/jsx/tags.scmdeleted−3

This diff is too big to show here.

runtime/queries/jsx/textobjects.scm+1−3

This diff is too big to show here.

runtime/queries/kdl/highlights.scm+4−4

This diff is too big to show here.

runtime/queries/kdl/indents.scmdeleted−3

This diff is too big to show here.

runtime/queries/kdl/textobjects.scmdeleted−27

This diff is too big to show here.

runtime/queries/kotlin/highlights.scm+1−4

This diff is too big to show here.

runtime/queries/kotlin/locals.scmdeleted−15

This diff is too big to show here.

runtime/queries/log/highlights.scmdeleted−10

This diff is too big to show here.

runtime/queries/lpf/highlights.scmdeleted−19

This diff is too big to show here.

runtime/queries/markdown.inline/injections.scm+1−4

This diff is too big to show here.

runtime/queries/matlab/context.scmdeleted−41

This diff is too big to show here.

runtime/queries/matlab/folds.scmdeleted−11

This diff is too big to show here.

runtime/queries/matlab/highlights.scm+77−108

This diff is too big to show here.

runtime/queries/matlab/indents.scmdeleted−23

This diff is too big to show here.

runtime/queries/matlab/injections.scmdeleted−2

This diff is too big to show here.

runtime/queries/matlab/locals.scmdeleted−19

This diff is too big to show here.

runtime/queries/matlab/textobjects.scmdeleted−9

This diff is too big to show here.

runtime/queries/nickel/highlights.scm+2−2

This diff is too big to show here.

runtime/queries/nickel/indents.scm+1−1

This diff is too big to show here.

runtime/queries/nickel/injections.scmdeleted−3

This diff is too big to show here.

runtime/queries/nix/injections.scm−14

This diff is too big to show here.

runtime/queries/nu/folds.scmnew file+4

This diff is too big to show here.

runtime/queries/nu/highlights.scm+44−276

This diff is too big to show here.

runtime/queries/nu/injections.scm+1−1

This diff is too big to show here.

runtime/queries/nu/locals.scmnew file+13

This diff is too big to show here.

runtime/queries/nunjucks/highlights.scmdeleted−1

This diff is too big to show here.

runtime/queries/nunjucks/injections.scmdeleted−1

This diff is too big to show here.

runtime/queries/ocaml/highlights.scm−2

This diff is too big to show here.

runtime/queries/odin/injections.scmdeleted−2

This diff is too big to show here.

runtime/queries/perl/fold.scmdeleted−19

This diff is too big to show here.

runtime/queries/perl/highlights.scm+155−72

This diff is too big to show here.

runtime/queries/perl/indents.scm+11−25

This diff is too big to show here.

runtime/queries/perl/injections.scm+1−3

This diff is too big to show here.

runtime/queries/perl/textobjects.scm+13−10

This diff is too big to show here.

runtime/queries/pod/highlights.scmdeleted−61

This diff is too big to show here.

runtime/queries/prql/highlights.scm+12−19

This diff is too big to show here.

runtime/queries/purescript/highlights.scm+1−164

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/purescript/locals.scm+1−4

This diff is too big to show here.

runtime/queries/purescript/textobjects.scmdeleted−13

This diff is too big to show here.

runtime/queries/python/highlights.scm−6

This diff is too big to show here.

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/regex/highlights.scm+1−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/scala/locals.scmdeleted−29

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+176−326

This diff is too big to show here.

runtime/queries/strace/highlights.scmdeleted−8

This diff is too big to show here.

runtime/queries/svelte/indents.scmdeleted−18

This diff is too big to show here.

runtime/queries/svelte/injections.scm−10

This diff is too big to show here.

runtime/queries/t32/highlights.scmdeleted−227

This diff is too big to show here.

runtime/queries/t32/injections.scmdeleted−2

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/toml/highlights.scm+2−15

This diff is too big to show here.

runtime/queries/tsx/highlights.scm+1−3

This diff is too big to show here.

runtime/queries/tsx/indents.scm+1−3

This diff is too big to show here.

runtime/queries/tsx/injections.scm+1−3

This diff is too big to show here.

runtime/queries/tsx/locals.scmdeleted−3

This diff is too big to show here.

runtime/queries/tsx/tags.scmdeleted−3

This diff is too big to show here.

runtime/queries/tsx/textobjects.scm+1−3

This diff is too big to show here.

runtime/queries/typescript/highlights.scm+76−2

This diff is too big to show here.

runtime/queries/typescript/indents.scm+6−2

This diff is too big to show here.

runtime/queries/typescript/injections.scm+1−3

This diff is too big to show here.

runtime/queries/typescript/locals.scm+2−3

This diff is too big to show here.

runtime/queries/typescript/tags.scm+22−2

This diff is too big to show here.

runtime/queries/typescript/textobjects.scm+7−2

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/v/highlights.scm+51−85

This diff is too big to show here.

runtime/queries/v/indents.scm+3−2

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/webc/highlights.scmdeleted−1

This diff is too big to show here.

runtime/queries/webc/injections.scmdeleted−1

This diff is too big to show here.

runtime/queries/wren/highlights.scmdeleted−82

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.scmdeleted−16

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−67

This diff is too big to show here.

runtime/themes/autumn_night.toml+8−9

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/dark_plus.toml+1−2

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/everforest_dark.toml−4

This diff is too big to show here.

runtime/themes/ferra.toml+2−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/gruvbox.toml−1

This diff is too big to show here.

runtime/themes/gruvbox_dark_hard.toml+2

This diff is too big to show here.

runtime/themes/gruvbox_dark_soft.tomldeleted−7

This diff is too big to show here.

runtime/themes/gruvbox_light.toml+2

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/kaolin-dark.tomldeleted−108

This diff is too big to show here.

runtime/themes/kaolin-light.tomldeleted−60

This diff is too big to show here.

runtime/themes/kaolin-valley-dark.tomldeleted−73

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/merionette.tomldeleted−98

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.tomldeleted−91

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+4−8

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+105−162

This diff is too big to show here.

runtime/themes/onedark.toml−1

This diff is too big to show here.

runtime/themes/onedarker.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/pop-dark.toml+4−4

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/spacebones_light.toml−1

This diff is too big to show here.

runtime/themes/starlight.tomldeleted−113

This diff is too big to show here.

runtime/themes/vim_dark_high_contrast.tomldeleted−72

This diff is too big to show here.

runtime/themes/yellowed.tomldeleted−135

This diff is too big to show here.

runtime/themes/zed_onedark.tomldeleted−110

This diff is too big to show here.

runtime/themes/zed_onelight.tomldeleted−55

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.

xtask/src/docgen.rs+5−6

This diff is too big to show here.