on master help sign in
.envrc+1
since splitsnapshots
unifiedsplit
@@ -1,5 +1,6 @@
1watch_file shell.nix
1watch_file shell.nix
2watch_file flake.lock
2watch_file flake.lock
3+watch_file rust-toolchain.toml
3
4
4# try to use flakes, if it fails use normal nix (ie. shell.nix)
5# try to use flakes, if it fails use normal nix (ie. shell.nix)
5use flake || use nix
6use flake || use nix
.github/ISSUE_TEMPLATE/bug_report.yaml+10
@@ -55,6 +55,16 @@ 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
58 - type: input
68 - type: input
59 id: helix-version
69 id: helix-version
60 attributes:
70 attributes:
.github/workflows/build.yml+15−7
@@ -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@v3
17+ uses: actions/checkout@v4
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,6 +22,8 @@ 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"
25
27
26 - name: Run cargo check
28 - name: Run cargo check
27 run: cargo check
29 run: cargo check
@@ -34,12 +36,14 @@ jobs:
34 HELIX_LOG_LEVEL: info
36 HELIX_LOG_LEVEL: info
35 steps:
37 steps:
36 - name: Checkout sources
38 - name: Checkout sources
37− uses: actions/checkout@v3
39+ uses: actions/checkout@v4
38
40
39 - name: Install stable toolchain
41 - name: Install stable toolchain
40− uses: dtolnay/rust-toolchain@1.63
42+ uses: dtolnay/rust-toolchain@1.70
41
43
42 - uses: Swatinem/rust-cache@v2
44 - uses: Swatinem/rust-cache@v2
45+ with:
46+ shared-key: "build"
43
47
44 - name: Cache test tree-sitter grammar
48 - name: Cache test tree-sitter grammar
45 uses: actions/cache@v3
49 uses: actions/cache@v3
@@ -63,14 +67,16 @@ jobs:
63 runs-on: ubuntu-latest
67 runs-on: ubuntu-latest
64 steps:
68 steps:
65 - name: Checkout sources
69 - name: Checkout sources
66− uses: actions/checkout@v3
70+ uses: actions/checkout@v4
67
71
68 - name: Install stable toolchain
72 - name: Install stable toolchain
69− uses: dtolnay/rust-toolchain@1.63
73+ uses: dtolnay/rust-toolchain@1.70
70 with:
74 with:
71 components: rustfmt, clippy
75 components: rustfmt, clippy
72
76
73 - uses: Swatinem/rust-cache@v2
77 - uses: Swatinem/rust-cache@v2
78+ with:
79+ shared-key: "build"
74
80
75 - name: Run cargo fmt
81 - name: Run cargo fmt
76 run: cargo fmt --all --check
82 run: cargo fmt --all --check
@@ -88,12 +94,14 @@ jobs:
88 runs-on: ubuntu-latest
94 runs-on: ubuntu-latest
89 steps:
95 steps:
90 - name: Checkout sources
96 - name: Checkout sources
91− uses: actions/checkout@v3
97+ uses: actions/checkout@v4
92
98
93 - name: Install stable toolchain
99 - name: Install stable toolchain
94− uses: dtolnay/rust-toolchain@1.63
100+ uses: dtolnay/rust-toolchain@1.70
95
101
96 - uses: Swatinem/rust-cache@v2
102 - uses: Swatinem/rust-cache@v2
103+ with:
104+ shared-key: "build"
97
105
98 - name: Validate queries
106 - name: Validate queries
99 run: cargo xtask query-check
107 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@v3
14+ uses: actions/checkout@v4
15
15
16 - name: Install nix
16 - name: Install nix
17− uses: cachix/install-nix-action@v19
17+ uses: cachix/install-nix-action@v24
18
18
19 - name: Authenticate with Cachix
19 - name: Authenticate with Cachix
20− uses: cachix/cachix-action@v12
20+ uses: cachix/cachix-action@v13
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@v3
14+ - uses: actions/checkout@v4
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
29+ - name: Deploy stable
30 uses: peaceiris/actions-gh-pages@v3
30 uses: peaceiris/actions-gh-pages@v3
31+ if: startswith(github.ref, 'refs/tags/')
31 with:
32 with:
32 github_token: ${{ secrets.GITHUB_TOKEN }}
33 github_token: ${{ secrets.GITHUB_TOKEN }}
33 publish_dir: ./book/book
34 publish_dir: ./book/book
34− destination_dir: ./${{ env.OUTDIR }}
35
35
36− - name: Deploy stable
36+ - name: Deploy
37 uses: peaceiris/actions-gh-pages@v3
37 uses: peaceiris/actions-gh-pages@v3
38− if: startswith(github.ref, 'refs/tags/')
39 with:
38 with:
40 github_token: ${{ secrets.GITHUB_TOKEN }}
39 github_token: ${{ secrets.GITHUB_TOKEN }}
41 publish_dir: ./book/book
40 publish_dir: ./book/book
41+ destination_dir: ./${{ env.OUTDIR }}
.github/workflows/release.yml+27−16
@@ -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@v3
26+ uses: actions/checkout@v4
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@v3
39+ - uses: actions/upload-artifact@v4
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
@@ -85,6 +85,7 @@ jobs:
85 rust: stable
85 rust: stable
86 target: x86_64-pc-windows-msvc
86 target: x86_64-pc-windows-msvc
87 cross: false
87 cross: false
88+ # 23.03: build issues
88 - build: aarch64-macos
89 - build: aarch64-macos
89 os: macos-latest
90 os: macos-latest
90 rust: stable
91 rust: stable
@@ -102,10 +103,10 @@ jobs:
102
103
103 steps:
104 steps:
104 - name: Checkout sources
105 - name: Checkout sources
105− uses: actions/checkout@v3
106+ uses: actions/checkout@v4
106
107
107 - name: Download grammars
108 - name: Download grammars
108− uses: actions/download-artifact@v3
109+ uses: actions/download-artifact@v4
109
110
110 - name: Move grammars under runtime
111 - name: Move grammars under runtime
111 if: "!startsWith(matrix.os, 'windows')"
112 if: "!startsWith(matrix.os, 'windows')"
@@ -113,6 +114,12 @@ jobs:
113 mkdir -p runtime/grammars/sources
114 mkdir -p runtime/grammars/sources
114 tar xJf grammars/grammars.tar.xz -C runtime/grammars/sources
115 tar xJf grammars/grammars.tar.xz -C runtime/grammars/sources
115
116
117+ # The rust-toolchain action ignores rust-toolchain.toml files.
118+ # Removing this before building with cargo ensures that the rust-toolchain
119+ # is considered the same between installation and usage.
120+ - name: Remove the rust-toolchain.toml file
121+ run: rm rust-toolchain.toml
122+
116 - name: Install ${{ matrix.rust }} toolchain
123 - name: Install ${{ matrix.rust }} toolchain
117 uses: dtolnay/rust-toolchain@master
124 uses: dtolnay/rust-toolchain@master
118 with:
125 with:
@@ -153,8 +160,12 @@ jobs:
153
160
154 - name: Build AppImage
161 - name: Build AppImage
155 shell: bash
162 shell: bash
156− if: matrix.build == 'aarch64-linux' || matrix.build == 'x86_64-linux'
163+ if: matrix.build == 'x86_64-linux'
157 run: |
164 run: |
165+ # Required as of 22.x https://github.com/AppImage/AppImageKit/wiki/FUSE
166+ sudo add-apt-repository universe
167+ sudo apt install libfuse2
168+
158 mkdir dist
169 mkdir dist
159
170
160 name=dev
171 name=dev
@@ -209,7 +220,7 @@ jobs:
209 fi
220 fi
210 cp -r runtime dist
221 cp -r runtime dist
211
222
212− - uses: actions/upload-artifact@v3
223+ - uses: actions/upload-artifact@v4
213 with:
224 with:
214 name: bins-${{ matrix.build }}
225 name: bins-${{ matrix.build }}
215 path: dist
226 path: dist
@@ -220,9 +231,9 @@ jobs:
220 runs-on: ubuntu-latest
231 runs-on: ubuntu-latest
221 steps:
232 steps:
222 - name: Checkout sources
233 - name: Checkout sources
223− uses: actions/checkout@v3
234+ uses: actions/checkout@v4
224
235
225− - uses: actions/download-artifact@v3
236+ - uses: actions/download-artifact@v4
226
237
227 - name: Build archive
238 - name: Build archive
228 shell: bash
239 shell: bash
@@ -244,7 +255,7 @@ jobs:
244 exe=".exe"
255 exe=".exe"
245 fi
256 fi
246 pkgname=helix-$GITHUB_REF_NAME-$platform
257 pkgname=helix-$GITHUB_REF_NAME-$platform
247− mkdir $pkgname
258+ mkdir -p $pkgname
248 cp $source/LICENSE $source/README.md $pkgname
259 cp $source/LICENSE $source/README.md $pkgname
249 mkdir $pkgname/contrib
260 mkdir $pkgname/contrib
250 cp -r $source/contrib/completion $pkgname/contrib
261 cp -r $source/contrib/completion $pkgname/contrib
@@ -252,7 +263,7 @@ jobs:
252 mv bins-$platform/hx$exe $pkgname
263 mv bins-$platform/hx$exe $pkgname
253 chmod +x $pkgname/hx$exe
264 chmod +x $pkgname/hx$exe
254
265
255− if [[ "$platform" = "aarch64-linux" || "$platform" = "x86_64-linux" ]]; then
266+ if [[ "$platform" = "x86_64-linux" ]]; then
256 mv bins-$platform/helix-*.AppImage* dist/
267 mv bins-$platform/helix-*.AppImage* dist/
257 fi
268 fi
258
269
@@ -277,7 +288,7 @@ jobs:
277 overwrite: true
288 overwrite: true
278
289
279 - name: Upload binaries as artifact
290 - name: Upload binaries as artifact
280− uses: actions/upload-artifact@v3
291+ uses: actions/upload-artifact@v4
281 if: env.preview == 'true'
292 if: env.preview == 'true'
282 with:
293 with:
283 name: release
294 name: release
.ignore−3
@@ -1,5 +1,2 @@
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+651−1
@@ -1,3 +1,653 @@
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)
379+
380+23.03 brings some long-awaited and exciting features. Thank you to everyone involved! This release saw changes from 102 contributors.
381+
382+For the full log, check out the [git log](https://github.com/helix-editor/helix/compare/22.12..23.03).
383+Also check out the [release notes](https://helix-editor.com/news/release-23-03-highlights/) for more commentary on larger features.
384+
385+Breaking changes:
386+
387+- Select diagnostic range in `goto_*_diag` commands ([#4713](https://github.com/helix-editor/helix/pull/4713), [#5164](https://github.com/helix-editor/helix/pull/5164), [#6193](https://github.com/helix-editor/helix/pull/6193))
388+- Remove jump behavior from `increment`/`decrement` ([#4123](https://github.com/helix-editor/helix/pull/4123), [#5929](https://github.com/helix-editor/helix/pull/5929))
389+- Select change range in `goto_*_change` commands ([#5206](https://github.com/helix-editor/helix/pull/5206))
390+- Split file modification indicator from filename statusline elements ([#4731](https://github.com/helix-editor/helix/pull/4731), [#6036](https://github.com/helix-editor/helix/pull/6036))
391+- Jump to symbol ranges in LSP goto commands ([#5986](https://github.com/helix-editor/helix/pull/5986))
392+- Workspace detection now stops at the first `.helix/` directory (merging multiple `.helix/languages.toml` configurations is no longer supported) ([#5748](https://github.com/helix-editor/helix/pull/5748))
393+
394+Features:
395+
396+- Dynamic workspace symbol picker ([#5055](https://github.com/helix-editor/helix/pull/5055))
397+- Soft-wrap ([#5420](https://github.com/helix-editor/helix/pull/5420), [#5786](https://github.com/helix-editor/helix/pull/5786), [#5893](https://github.com/helix-editor/helix/pull/5893), [#6142](https://github.com/helix-editor/helix/pull/6142), [#6440](https://github.com/helix-editor/helix/pull/6440))
398+- Initial support for LSP snippet completions ([#5864](https://github.com/helix-editor/helix/pull/5864), [b1f7528](https://github.com/helix-editor/helix/commit/b1f7528), [#6263](https://github.com/helix-editor/helix/pull/6263), [bbf4800](https://github.com/helix-editor/helix/commit/bbf4800), [90348b8](https://github.com/helix-editor/helix/commit/90348b8), [f87299f](https://github.com/helix-editor/helix/commit/f87299f), [#6371](https://github.com/helix-editor/helix/pull/6371), [9fe3adc](https://github.com/helix-editor/helix/commit/9fe3adc))
399+- Add a statusline element for showing the current version control HEAD ([#5682](https://github.com/helix-editor/helix/pull/5682))
400+- Display LSP type hints ([#5420](https://github.com/helix-editor/helix/pull/5420), [#5934](https://github.com/helix-editor/helix/pull/5934), [#6312](https://github.com/helix-editor/helix/pull/6312))
401+- Enable the Kitty keyboard protocol on terminals with support ([#4939](https://github.com/helix-editor/helix/pull/4939), [#6170](https://github.com/helix-editor/helix/pull/6170), [#6194](https://github.com/helix-editor/helix/pull/6194), [#6438](https://github.com/helix-editor/helix/pull/6438))
402+- Add a statusline element for the basename of the current file ([#5318](https://github.com/helix-editor/helix/pull/5318))
403+- Add substring matching syntax for the picker ([#5658](https://github.com/helix-editor/helix/pull/5658))
404+- Support LSP `textDocument/prepareRename` ([#6103](https://github.com/helix-editor/helix/pull/6103))
405+- Allow multiple runtime directories with priorities ([#5411](https://github.com/helix-editor/helix/pull/5411))
406+- Allow configuring whether to insert or replace completions ([#5728](https://github.com/helix-editor/helix/pull/5728))
407+- Allow per-workspace config file `.helix/config.toml` ([#5748](https://github.com/helix-editor/helix/pull/5748))
408+- Add `workspace-lsp-roots` config option to support multiple LSP roots for use with monorepos ([#5748](https://github.com/helix-editor/helix/pull/5748))
409+
410+Commands:
411+
412+- `:pipe-to` which pipes selections into a shell command and ignores output ([#4931](https://github.com/helix-editor/helix/pull/4931))
413+- `merge_consecutive_selections` (`A-_`) combines all consecutive selections ([#5047](https://github.com/helix-editor/helix/pull/5047))
414+- `rotate_view_reverse` which focuses the previous view ([#5356](https://github.com/helix-editor/helix/pull/5356))
415+- `goto_declaration` (`gD`, requires LSP) which jumps to a symbol's declaration ([#5646](https://github.com/helix-editor/helix/pull/5646))
416+- `file_picker_in_current_buffer_directory` ([#4666](https://github.com/helix-editor/helix/pull/4666))
417+- `:character-info` which shows information about the character under the cursor ([#4000](https://github.com/helix-editor/helix/pull/4000))
418+- `:toggle-option` for toggling config options at runtime ([#4085](https://github.com/helix-editor/helix/pull/4085))
419+- `dap_restart` for restarting a debug session in DAP ([#5651](https://github.com/helix-editor/helix/pull/5651))
420+- `:lsp-stop` to stop the language server of the current buffer ([#5964](https://github.com/helix-editor/helix/pull/5964))
421+- `:reset-diff-change` for resetting a diff hunk to its original text ([#4974](https://github.com/helix-editor/helix/pull/4974))
422+- `:config-open-workspace` for opening the config file local to the current workspace ([#5748](https://github.com/helix-editor/helix/pull/5748))
423+
424+Usability improvements:
425+
426+- Remove empty detail section in completion menu when LSP doesn't send details ([#4902](https://github.com/helix-editor/helix/pull/4902))
427+- Pass client information on LSP initialization ([#4904](https://github.com/helix-editor/helix/pull/4904))
428+- Allow specifying environment variables for language servers in language config ([#4004](https://github.com/helix-editor/helix/pull/4004))
429+- Allow detached git worktrees to be recognized as root paths ([#5097](https://github.com/helix-editor/helix/pull/5097))
430+- Improve error message handling for theme loading failures ([#5073](https://github.com/helix-editor/helix/pull/5073))
431+- Print the names of binaries required for LSP/DAP in health-check ([#5195](https://github.com/helix-editor/helix/pull/5195))
432+- Improve sorting in the picker in cases of ties ([#5169](https://github.com/helix-editor/helix/pull/5169))
433+- Add theming for prompt suggestions ([#5104](https://github.com/helix-editor/helix/pull/5104))
434+- Open a file picker when using `:open` on directories ([#2707](https://github.com/helix-editor/helix/pull/2707), [#5278](https://github.com/helix-editor/helix/pull/5278))
435+- Reload language config with `:config-reload` ([#5239](https://github.com/helix-editor/helix/pull/5239), [#5381](https://github.com/helix-editor/helix/pull/5381), [#5431](https://github.com/helix-editor/helix/pull/5431))
436+- Improve indent queries for python when the tree is errored ([#5332](https://github.com/helix-editor/helix/pull/5332))
437+- Picker: Open files without closing the picker with `A-ret` ([#4435](https://github.com/helix-editor/helix/pull/4435))
438+- Allow theming cursors by primary/secondary and by mode ([#5130](https://github.com/helix-editor/helix/pull/5130))
439+- Allow configuration of the minimum width for the line-numbers gutter ([#4724](https://github.com/helix-editor/helix/pull/4724), [#5696](https://github.com/helix-editor/helix/pull/5696))
440+- Use filename completer for `:run-shell-command` command ([#5729](https://github.com/helix-editor/helix/pull/5729))
441+- Surround with line-endings with `ms<ret>` ([#4571](https://github.com/helix-editor/helix/pull/4571))
442+- Hide duplicate symlinks in file pickers ([#5658](https://github.com/helix-editor/helix/pull/5658))
443+- Tabulate buffer picker contents ([#5777](https://github.com/helix-editor/helix/pull/5777))
444+- Add an option to disable LSP ([#4425](https://github.com/helix-editor/helix/pull/4425))
445+- Short-circuit tree-sitter and word object motions ([#5851](https://github.com/helix-editor/helix/pull/5851))
446+- Add exit code to failed command message ([#5898](https://github.com/helix-editor/helix/pull/5898))
447+- Make `m` textobject look for pairs enclosing selections ([#3344](https://github.com/helix-editor/helix/pull/3344))
448+- Negotiate LSP position encoding ([#5894](https://github.com/helix-editor/helix/pull/5894), [a48d1a4](https://github.com/helix-editor/helix/commit/a48d1a4))
449+- Display deprecated LSP completions with strikethrough ([#5932](https://github.com/helix-editor/helix/pull/5932))
450+- Add JSONRPC request ID to failed LSP/DAP request log messages ([#6010](https://github.com/helix-editor/helix/pull/6010), [#6018](https://github.com/helix-editor/helix/pull/6018))
451+- Ignore case when filtering LSP completions ([#6008](https://github.com/helix-editor/helix/pull/6008))
452+- Show current language when no arguments are passed to `:set-language` ([#5895](https://github.com/helix-editor/helix/pull/5895))
453+- Refactor and rewrite all book documentation ([#5534](https://github.com/helix-editor/helix/pull/5534))
454+- Separate diagnostic picker message and code ([#6095](https://github.com/helix-editor/helix/pull/6095))
455+- Add a config option to bypass undercurl detection ([#6253](https://github.com/helix-editor/helix/pull/6253))
456+- Only complete appropriate arguments for typed commands ([#5966](https://github.com/helix-editor/helix/pull/5966))
457+- Discard outdated LSP diagnostics ([3c9d5d0](https://github.com/helix-editor/helix/commit/3c9d5d0))
458+- Discard outdated LSP workspace edits ([b6a4927](https://github.com/helix-editor/helix/commit/b6a4927))
459+- Run shell commands asynchronously ([#6373](https://github.com/helix-editor/helix/pull/6373))
460+- Show diagnostic codes in LSP diagnostic messages ([#6378](https://github.com/helix-editor/helix/pull/6378))
461+- Highlight the current line in a DAP debug session ([#5957](https://github.com/helix-editor/helix/pull/5957))
462+- Hide signature help if it overlaps with the completion menu ([#5523](https://github.com/helix-editor/helix/pull/5523), [7a69c40](https://github.com/helix-editor/helix/commit/7a69c40))
463+
464+Fixes:
465+
466+- Fix behavior of `auto-completion` flag for completion-on-trigger ([#5042](https://github.com/helix-editor/helix/pull/5042))
467+- Reset editor mode when changing buffers ([#5072](https://github.com/helix-editor/helix/pull/5072))
468+- Respect scrolloff settings in mouse movements ([#5255](https://github.com/helix-editor/helix/pull/5255))
469+- Avoid trailing `s` when only one file is opened ([#5189](https://github.com/helix-editor/helix/pull/5189))
470+- Fix erroneous indent between closers of auto-pairs ([#5330](https://github.com/helix-editor/helix/pull/5330))
471+- Expand `~` when parsing file paths in `:open` ([#5329](https://github.com/helix-editor/helix/pull/5329))
472+- Fix theme inheritance for default themes ([#5218](https://github.com/helix-editor/helix/pull/5218))
473+- Fix `extend_line` with a count when the current line(s) are selected ([#5288](https://github.com/helix-editor/helix/pull/5288))
474+- Prompt: Fix autocompletion for paths containing periods ([#5175](https://github.com/helix-editor/helix/pull/5175))
475+- Skip serializing JSONRPC params if params is null ([#5471](https://github.com/helix-editor/helix/pull/5471))
476+- Fix interaction with the `xclip` clipboard provider ([#5426](https://github.com/helix-editor/helix/pull/5426))
477+- Fix undo/redo execution from the command palette ([#5294](https://github.com/helix-editor/helix/pull/5294))
478+- Fix highlighting of non-block cursors ([#5575](https://github.com/helix-editor/helix/pull/5575))
479+- Fix panic when nooping in `join_selections` and `join_selections_space` ([#5423](https://github.com/helix-editor/helix/pull/5423))
480+- Fix selecting a changed file in global search ([#5639](https://github.com/helix-editor/helix/pull/5639))
481+- Fix initial syntax highlight layer sort order ([#5196](https://github.com/helix-editor/helix/pull/5196))
482+- Fix UTF-8 length handling for shellwords ([#5738](https://github.com/helix-editor/helix/pull/5738))
483+- Remove C-j and C-k bindings from the completion menu ([#5070](https://github.com/helix-editor/helix/pull/5070))
484+- Always commit to history when pasting ([#5790](https://github.com/helix-editor/helix/pull/5790))
485+- Properly handle LSP position encoding ([#5711](https://github.com/helix-editor/helix/pull/5711))
486+- Fix infinite loop in `copy_selection_on_prev_line` ([#5888](https://github.com/helix-editor/helix/pull/5888))
487+- Fix completion popup positioning ([#5842](https://github.com/helix-editor/helix/pull/5842))
488+- Fix a panic when uncommenting a line with only a comment token ([#5933](https://github.com/helix-editor/helix/pull/5933))
489+- Fix panic in `goto_window_center` at EOF ([#5987](https://github.com/helix-editor/helix/pull/5987))
490+- Ignore invalid file URIs sent by a language server ([#6000](https://github.com/helix-editor/helix/pull/6000))
491+- Decode LSP URIs for the workspace diagnostics picker ([#6016](https://github.com/helix-editor/helix/pull/6016))
492+- Fix incorrect usages of `tab_width` with `indent_width` ([#5918](https://github.com/helix-editor/helix/pull/5918))
493+- DAP: Send Disconnect if the Terminated event is received ([#5532](https://github.com/helix-editor/helix/pull/5532))
494+- DAP: Validate key and index exist when requesting variables ([#5628](https://github.com/helix-editor/helix/pull/5628))
495+- Check LSP renaming support before prompting for rename text ([#6257](https://github.com/helix-editor/helix/pull/6257))
496+- Fix indent guide rendering ([#6136](https://github.com/helix-editor/helix/pull/6136))
497+- Fix division by zero panic ([#6155](https://github.com/helix-editor/helix/pull/6155))
498+- Fix lacking space panic ([#6109](https://github.com/helix-editor/helix/pull/6109))
499+- Send error replies for malformed and unhandled LSP requests ([#6058](https://github.com/helix-editor/helix/pull/6058))
500+- Fix table column calculations for dynamic pickers ([#5920](https://github.com/helix-editor/helix/pull/5920))
501+- Skip adding jumplist entries for `:<n>` line number previews ([#5751](https://github.com/helix-editor/helix/pull/5751))
502+- Fix completion race conditions ([#6173](https://github.com/helix-editor/helix/pull/6173))
503+- Fix `shrink_selection` with multiple cursors ([#6093](https://github.com/helix-editor/helix/pull/6093))
504+- Fix indentation calculation for lines with mixed tabs/spaces ([#6278](https://github.com/helix-editor/helix/pull/6278))
505+- No-op `client/registerCapability` LSP requests ([#6258](https://github.com/helix-editor/helix/pull/6258))
506+- Send the STOP signal to all processes in the process group ([#3546](https://github.com/helix-editor/helix/pull/3546))
507+- Fix workspace edit client capabilities declaration ([7bf168d](https://github.com/helix-editor/helix/commit/7bf168d))
508+- Fix highlighting in picker results with multiple columns ([#6333](https://github.com/helix-editor/helix/pull/6333))
509+- Canonicalize paths before stripping the current dir as a prefix ([#6290](https://github.com/helix-editor/helix/pull/6290))
510+- Fix truncation behavior for long path names in the file picker ([#6410](https://github.com/helix-editor/helix/pull/6410), [67783dd](https://github.com/helix-editor/helix/commit/67783dd))
511+- Fix theme reloading behavior in `:config-reload` ([ab819d8](https://github.com/helix-editor/helix/commit/ab819d8))
512+
513+Themes:
514+
515+- Update `serika` ([#5038](https://github.com/helix-editor/helix/pull/5038), [#6344](https://github.com/helix-editor/helix/pull/6344))
516+- Update `flatwhite` ([#5036](https://github.com/helix-editor/helix/pull/5036), [#6323](https://github.com/helix-editor/helix/pull/6323))
517+- Update `autumn` ([#5051](https://github.com/helix-editor/helix/pull/5051), [#5397](https://github.com/helix-editor/helix/pull/5397), [#6280](https://github.com/helix-editor/helix/pull/6280), [#6316](https://github.com/helix-editor/helix/pull/6316))
518+- Update `acme` ([#5019](https://github.com/helix-editor/helix/pull/5019), [#5486](https://github.com/helix-editor/helix/pull/5486), [#5488](https://github.com/helix-editor/helix/pull/5488))
519+- Update `gruvbox` themes ([#5066](https://github.com/helix-editor/helix/pull/5066), [#5333](https://github.com/helix-editor/helix/pull/5333), [#5540](https://github.com/helix-editor/helix/pull/5540), [#6285](https://github.com/helix-editor/helix/pull/6285), [#6295](https://github.com/helix-editor/helix/pull/6295))
520+- Update `base16_transparent` ([#5105](https://github.com/helix-editor/helix/pull/5105))
521+- Update `dark_high_contrast` ([#5105](https://github.com/helix-editor/helix/pull/5105))
522+- Update `dracula` ([#5236](https://github.com/helix-editor/helix/pull/5236), [#5627](https://github.com/helix-editor/helix/pull/5627), [#6414](https://github.com/helix-editor/helix/pull/6414))
523+- Update `monokai_pro_spectrum` ([#5250](https://github.com/helix-editor/helix/pull/5250), [#5602](https://github.com/helix-editor/helix/pull/5602))
524+- Update `rose_pine` ([#5267](https://github.com/helix-editor/helix/pull/5267), [#5489](https://github.com/helix-editor/helix/pull/5489), [#6384](https://github.com/helix-editor/helix/pull/6384))
525+- Update `kanagawa` ([#5273](https://github.com/helix-editor/helix/pull/5273), [#5571](https://github.com/helix-editor/helix/pull/5571), [#6085](https://github.com/helix-editor/helix/pull/6085))
526+- Update `emacs` ([#5334](https://github.com/helix-editor/helix/pull/5334))
527+- Add `github` themes ([#5353](https://github.com/helix-editor/helix/pull/5353), [efeec12](https://github.com/helix-editor/helix/commit/efeec12))
528+ - Dark themes: `github_dark`, `github_dark_colorblind`, `github_dark_dimmed`, `github_dark_high_contrast`, `github_dark_tritanopia`
529+ - Light themes: `github_light`, `github_light_colorblind`, `github_light_dimmed`, `github_light_high_contrast`, `github_light_tritanopia`
530+- Update `solarized` variants ([#5445](https://github.com/helix-editor/helix/pull/5445), [#6327](https://github.com/helix-editor/helix/pull/6327))
531+- Update `catppuccin` variants ([#5404](https://github.com/helix-editor/helix/pull/5404), [#6107](https://github.com/helix-editor/helix/pull/6107), [#6269](https://github.com/helix-editor/helix/pull/6269), [#6464](https://github.com/helix-editor/helix/pull/6464))
532+- Use curly underlines in built-in themes ([#5419](https://github.com/helix-editor/helix/pull/5419))
533+- Update `zenburn` ([#5573](https://github.com/helix-editor/helix/pull/5573))
534+- Rewrite `snazzy` ([#3971](https://github.com/helix-editor/helix/pull/3971))
535+- Add `monokai_aqua` ([#5578](https://github.com/helix-editor/helix/pull/5578))
536+- Add `markup.strikethrough` to existing themes ([#5619](https://github.com/helix-editor/helix/pull/5619))
537+- Update `sonokai` ([#5440](https://github.com/helix-editor/helix/pull/5440))
538+- Update `onedark` ([#5755](https://github.com/helix-editor/helix/pull/5755))
539+- Add `ayu_evolve` ([#5638](https://github.com/helix-editor/helix/pull/5638), [#6028](https://github.com/helix-editor/helix/pull/6028), [#6225](https://github.com/helix-editor/helix/pull/6225))
540+- Add `jellybeans` ([#5719](https://github.com/helix-editor/helix/pull/5719))
541+- Update `fleet_dark` ([#5605](https://github.com/helix-editor/helix/pull/5605), [#6266](https://github.com/helix-editor/helix/pull/6266), [#6324](https://github.com/helix-editor/helix/pull/6324), [#6375](https://github.com/helix-editor/helix/pull/6375))
542+- Add `darcula-solid` ([#5778](https://github.com/helix-editor/helix/pull/5778))
543+- Remove text background from monokai themes ([#6009](https://github.com/helix-editor/helix/pull/6009))
544+- Update `pop_dark` ([#5992](https://github.com/helix-editor/helix/pull/5992), [#6208](https://github.com/helix-editor/helix/pull/6208), [#6227](https://github.com/helix-editor/helix/pull/6227), [#6292](https://github.com/helix-editor/helix/pull/6292))
545+- Add `everblush` ([#6086](https://github.com/helix-editor/helix/pull/6086))
546+- Add `adwaita-dark` ([#6042](https://github.com/helix-editor/helix/pull/6042), [#6342](https://github.com/helix-editor/helix/pull/6342))
547+- Update `papercolor` ([#6162](https://github.com/helix-editor/helix/pull/6162))
548+- Update `onelight` ([#6192](https://github.com/helix-editor/helix/pull/6192), [#6276](https://github.com/helix-editor/helix/pull/6276))
549+- Add `molokai` ([#6260](https://github.com/helix-editor/helix/pull/6260))
550+- Update `ayu` variants ([#6329](https://github.com/helix-editor/helix/pull/6329))
551+- Update `tokyonight` variants ([#6349](https://github.com/helix-editor/helix/pull/6349))
552+- Update `nord` variants ([#6376](https://github.com/helix-editor/helix/pull/6376))
553+
554+New languages:
555+
556+- BibTeX ([#5064](https://github.com/helix-editor/helix/pull/5064))
557+- Mermaid.js ([#5147](https://github.com/helix-editor/helix/pull/5147))
558+- Crystal ([#4993](https://github.com/helix-editor/helix/pull/4993), [#5205](https://github.com/helix-editor/helix/pull/5205))
559+- MATLAB/Octave ([#5192](https://github.com/helix-editor/helix/pull/5192))
560+- `tfvars` (uses HCL) ([#5396](https://github.com/helix-editor/helix/pull/5396))
561+- Ponylang ([#5416](https://github.com/helix-editor/helix/pull/5416))
562+- DHall ([1f6809c](https://github.com/helix-editor/helix/commit/1f6809c))
563+- Sagemath ([#5649](https://github.com/helix-editor/helix/pull/5649))
564+- MSBuild ([#5793](https://github.com/helix-editor/helix/pull/5793))
565+- pem ([#5797](https://github.com/helix-editor/helix/pull/5797))
566+- passwd ([#4959](https://github.com/helix-editor/helix/pull/4959))
567+- hosts ([#4950](https://github.com/helix-editor/helix/pull/4950), [#5914](https://github.com/helix-editor/helix/pull/5914))
568+- uxntal ([#6047](https://github.com/helix-editor/helix/pull/6047))
569+- Yuck ([#6064](https://github.com/helix-editor/helix/pull/6064), [#6242](https://github.com/helix-editor/helix/pull/6242))
570+- GNU gettext PO ([#5996](https://github.com/helix-editor/helix/pull/5996))
571+- Sway ([#6023](https://github.com/helix-editor/helix/pull/6023))
572+- NASM ([#6068](https://github.com/helix-editor/helix/pull/6068))
573+- PRQL ([#6126](https://github.com/helix-editor/helix/pull/6126))
574+- reStructuredText ([#6180](https://github.com/helix-editor/helix/pull/6180))
575+- Smithy ([#6370](https://github.com/helix-editor/helix/pull/6370))
576+- VHDL ([#5826](https://github.com/helix-editor/helix/pull/5826))
577+- Rego (OpenPolicy Agent) ([#6415](https://github.com/helix-editor/helix/pull/6415))
578+- Nim ([#6123](https://github.com/helix-editor/helix/pull/6123))
579+
580+Updated languages and queries:
581+
582+- Use diff syntax for patch files ([#5085](https://github.com/helix-editor/helix/pull/5085))
583+- Add Haskell textobjects ([#5061](https://github.com/helix-editor/helix/pull/5061))
584+- Fix commonlisp configuration ([#5091](https://github.com/helix-editor/helix/pull/5091))
585+- Update Scheme ([bae890d](https://github.com/helix-editor/helix/commit/bae890d))
586+- Add indent queries for Bash ([#5149](https://github.com/helix-editor/helix/pull/5149))
587+- Recognize `c++` as a C++ extension ([#5183](https://github.com/helix-editor/helix/pull/5183))
588+- Enable HTTP server in `metals` (Scala) config ([#5551](https://github.com/helix-editor/helix/pull/5551))
589+- Change V-lang language server to `v ls` from `vls` ([#5677](https://github.com/helix-editor/helix/pull/5677))
590+- Inject comment grammar into Nix ([#5208](https://github.com/helix-editor/helix/pull/5208))
591+- Update Rust highlights ([#5238](https://github.com/helix-editor/helix/pull/5238), [#5349](https://github.com/helix-editor/helix/pull/5349))
592+- Fix HTML injection within Markdown ([#5265](https://github.com/helix-editor/helix/pull/5265))
593+- Fix comment token for godot ([#5276](https://github.com/helix-editor/helix/pull/5276))
594+- Expand injections for Vue ([#5268](https://github.com/helix-editor/helix/pull/5268))
595+- Add `.bash_aliases` as a Bash file-type ([#5347](https://github.com/helix-editor/helix/pull/5347))
596+- Fix comment token for sshclientconfig ([#5351](https://github.com/helix-editor/helix/pull/5351))
597+- Update Prisma ([#5417](https://github.com/helix-editor/helix/pull/5417))
598+- Update C++ ([#5457](https://github.com/helix-editor/helix/pull/5457))
599+- Add more file-types for Python ([#5593](https://github.com/helix-editor/helix/pull/5593))
600+- Update tree-sitter-scala ([#5576](https://github.com/helix-editor/helix/pull/5576))
601+- Add an injection regex for Lua ([#5606](https://github.com/helix-editor/helix/pull/5606))
602+- Add `build.gradle` to java roots configuration ([#5641](https://github.com/helix-editor/helix/pull/5641))
603+- Add Hub PR files to markdown file-types ([#5634](https://github.com/helix-editor/helix/pull/5634))
604+- Add an external formatter configuration for Cue ([#5679](https://github.com/helix-editor/helix/pull/5679))
605+- Add injections for builders and writers to Nix ([#5629](https://github.com/helix-editor/helix/pull/5629))
606+- Update tree-sitter-xml to fix whitespace parsing ([#5685](https://github.com/helix-editor/helix/pull/5685))
607+- Add `Justfile` to the make file-types configuration ([#5687](https://github.com/helix-editor/helix/pull/5687))
608+- Update tree-sitter-sql and highlight queries ([#5683](https://github.com/helix-editor/helix/pull/5683), [#5772](https://github.com/helix-editor/helix/pull/5772))
609+- Use the bash grammar and queries for env language ([#5720](https://github.com/helix-editor/helix/pull/5720))
610+- Add podspec files to ruby file-types ([#5811](https://github.com/helix-editor/helix/pull/5811))
611+- Recognize `.C` and `.H` file-types as C++ ([#5808](https://github.com/helix-editor/helix/pull/5808))
612+- Recognize plist and mobileconfig files as XML ([#5863](https://github.com/helix-editor/helix/pull/5863))
613+- Fix `select` indentation in Go ([#5713](https://github.com/helix-editor/helix/pull/5713))
614+- Check for external file modifications when writing ([#5805](https://github.com/helix-editor/helix/pull/5805))
615+- Recognize containerfiles as dockerfile syntax ([#5873](https://github.com/helix-editor/helix/pull/5873))
616+- Update godot grammar and queries ([#5944](https://github.com/helix-editor/helix/pull/5944), [#6186](https://github.com/helix-editor/helix/pull/6186))
617+- Improve DHall highlights ([#5959](https://github.com/helix-editor/helix/pull/5959))
618+- Recognize `.env.dist` and `source.env` as env language ([#6003](https://github.com/helix-editor/helix/pull/6003))
619+- Update tree-sitter-git-rebase ([#6030](https://github.com/helix-editor/helix/pull/6030), [#6094](https://github.com/helix-editor/helix/pull/6094))
620+- Improve SQL highlights ([#6041](https://github.com/helix-editor/helix/pull/6041))
621+- Improve markdown highlights and inject LaTeX ([#6100](https://github.com/helix-editor/helix/pull/6100))
622+- Add textobject queries for Elm ([#6084](https://github.com/helix-editor/helix/pull/6084))
623+- Recognize graphql schema file type ([#6159](https://github.com/helix-editor/helix/pull/6159))
624+- Improve highlighting in comments ([#6143](https://github.com/helix-editor/helix/pull/6143))
625+- Improve highlighting for JavaScript/TypeScript/ECMAScript languages ([#6205](https://github.com/helix-editor/helix/pull/6205))
626+- Improve PHP highlights ([#6203](https://github.com/helix-editor/helix/pull/6203), [#6250](https://github.com/helix-editor/helix/pull/6250), [#6299](https://github.com/helix-editor/helix/pull/6299))
627+- Improve Go highlights ([#6204](https://github.com/helix-editor/helix/pull/6204))
628+- Highlight unchecked sqlx functions as SQL in Rust ([#6256](https://github.com/helix-editor/helix/pull/6256))
629+- Improve Erlang highlights ([cdd6c8d](https://github.com/helix-editor/helix/commit/cdd6c8d))
630+- Improve Nix highlights ([fb4d703](https://github.com/helix-editor/helix/commit/fb4d703))
631+- Improve gdscript highlights ([#6311](https://github.com/helix-editor/helix/pull/6311))
632+- Improve Vlang highlights ([#6279](https://github.com/helix-editor/helix/pull/6279))
633+- Improve Makefile highlights ([#6339](https://github.com/helix-editor/helix/pull/6339))
634+- Remove auto-pair for `'` in OCaml ([#6381](https://github.com/helix-editor/helix/pull/6381))
635+- Fix indents in switch statements in ECMA languages ([#6369](https://github.com/helix-editor/helix/pull/6369))
636+- Recognize xlb and storyboard file-types as XML ([#6407](https://github.com/helix-editor/helix/pull/6407))
637+- Recognize cts and mts file-types as TypeScript ([#6424](https://github.com/helix-editor/helix/pull/6424))
638+- Recognize SVG file-type as XML ([#6431](https://github.com/helix-editor/helix/pull/6431))
639+- Add theme scopes for (un)checked list item markup scopes ([#6434](https://github.com/helix-editor/helix/pull/6434))
640+- Update git commit grammar and add the comment textobject ([#6439](https://github.com/helix-editor/helix/pull/6439), [#6493](https://github.com/helix-editor/helix/pull/6493))
641+- Recognize ARB file-type as JSON ([#6452](https://github.com/helix-editor/helix/pull/6452))
642+- Inject markdown into markdown strings in Julia ([#6489](https://github.com/helix-editor/helix/pull/6489))
643+
644+Packaging:
645+
646+- Fix Nix flake devShell for darwin hosts ([#5368](https://github.com/helix-editor/helix/pull/5368))
647+- Add Appstream metadata file to `contrib/` ([#5643](https://github.com/helix-editor/helix/pull/5643))
648+- Increase the MSRV to 1.65 ([#5570](https://github.com/helix-editor/helix/pull/5570), [#6185](https://github.com/helix-editor/helix/pull/6185))
649+- Expose the Nix flake's `wrapper` ([#5994](https://github.com/helix-editor/helix/pull/5994))
650+
1# 22.12 (2022-12-06)
651# 22.12 (2022-12-06)
2
652
3This is a great big release filled with changes from a 99 contributors. A big _thank you_ to you all!
653This is a great big release filled with changes from a 99 contributors. A big _thank you_ to you all!
@@ -1206,7 +1856,7 @@ to distinguish it in bug reports..
1206
1856
1207- The `runtime/` directory is now properly detected on binary releases and
1857- The `runtime/` directory is now properly detected on binary releases and
1208 on cargo run. `~/.config/helix/runtime` can also be used.
1858 on cargo run. `~/.config/helix/runtime` can also be used.
1209−- Registers can now be selected via " (for example `"ay`)
1859+- Registers can now be selected via " (for example, `"ay`)
1210- Support for Nix files was added
1860- Support for Nix files was added
1211- Movement is now fully tested and matches Kakoune implementation
1861- Movement is now fully tested and matches Kakoune implementation
1212- A per-file LSP symbol picker was added to space+s
1862- A per-file LSP symbol picker was added to space+s
Cargo.lock+1006−910
@@ -3,43 +3,54 @@
3version = 3
3version = 3
4
4
5[[package]]
5[[package]]
6−name = "adler"
6+name = "addr2line"
7−version = "1.0.2"
7+version = "0.20.0"
8source = "registry+https://github.com/rust-lang/crates.io-index"
8source = "registry+https://github.com/rust-lang/crates.io-index"
9−checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
9+checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
10+dependencies = [
11+ "gimli",
12+]
10
13
11[[package]]
14[[package]]
12−name = "ahash"
15+name = "adler"
13−version = "0.7.6"
16+version = "1.0.2"
14source = "registry+https://github.com/rust-lang/crates.io-index"
17source = "registry+https://github.com/rust-lang/crates.io-index"
15−checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
18+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
16−dependencies = [
17− "getrandom",
18− "once_cell",
19− "version_check",
20−]
21
19
22[[package]]
20[[package]]
23name = "ahash"
21name = "ahash"
24−version = "0.8.3"
22+version = "0.8.6"
25source = "registry+https://github.com/rust-lang/crates.io-index"
23source = "registry+https://github.com/rust-lang/crates.io-index"
26−checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
24+checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
27dependencies = [
25dependencies = [
28 "cfg-if",
26 "cfg-if",
29 "getrandom",
27 "getrandom",
30 "once_cell",
28 "once_cell",
31 "version_check",
29 "version_check",
30+ "zerocopy",
32]
31]
33
32
34[[package]]
33[[package]]
35name = "aho-corasick"
34name = "aho-corasick"
36−version = "0.7.20"
35+version = "1.1.2"
37source = "registry+https://github.com/rust-lang/crates.io-index"
36source = "registry+https://github.com/rust-lang/crates.io-index"
38−checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
37+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
39dependencies = [
38dependencies = [
40 "memchr",
39 "memchr",
41]
40]
42
41
42+[[package]]
43+name = "allocator-api2"
44+version = "0.2.14"
45+source = "registry+https://github.com/rust-lang/crates.io-index"
46+checksum = "c4f263788a35611fba42eb41ff811c5d0360c58b97402570312a350736e2542e"
47+
48+[[package]]
49+name = "android-tzdata"
50+version = "0.1.1"
51+source = "registry+https://github.com/rust-lang/crates.io-index"
52+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
53+
43[[package]]
54[[package]]
44name = "android_system_properties"
55name = "android_system_properties"
45version = "0.1.5"
56version = "0.1.5"
@@ -51,9 +62,9 @@ dependencies = [
51
62
52[[package]]
63[[package]]
53name = "anyhow"
64name = "anyhow"
54−version = "1.0.69"
65+version = "1.0.76"
55source = "registry+https://github.com/rust-lang/crates.io-index"
66source = "registry+https://github.com/rust-lang/crates.io-index"
56−checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
67+checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
57
68
58[[package]]
69[[package]]
59name = "arc-swap"
70name = "arc-swap"
@@ -61,21 +72,27 @@ version = "1.6.0"
61source = "registry+https://github.com/rust-lang/crates.io-index"
72source = "registry+https://github.com/rust-lang/crates.io-index"
62checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
73checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
63
74
64−[[package]]
65−name = "atoi"
66−version = "2.0.0"
67−source = "registry+https://github.com/rust-lang/crates.io-index"
68−checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
69−dependencies = [
70− "num-traits",
71−]
72−
73[[package]]
75[[package]]
74name = "autocfg"
76name = "autocfg"
75version = "1.1.0"
77version = "1.1.0"
76source = "registry+https://github.com/rust-lang/crates.io-index"
78source = "registry+https://github.com/rust-lang/crates.io-index"
77checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
79checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
78
80
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+
79[[package]]
96[[package]]
80name = "bitflags"
97name = "bitflags"
81version = "1.3.2"
98version = "1.3.2"
@@ -83,60 +100,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
83checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
100checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
84
101
85[[package]]
102[[package]]
86−name = "bstr"
103+name = "bitflags"
87−version = "0.2.17"
104+version = "2.4.1"
88source = "registry+https://github.com/rust-lang/crates.io-index"
105source = "registry+https://github.com/rust-lang/crates.io-index"
89−checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
106+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
90−dependencies = [
91− "lazy_static",
92− "memchr",
93− "regex-automata",
94−]
95
107
96[[package]]
108[[package]]
97name = "bstr"
109name = "bstr"
98−version = "1.0.1"
110+version = "1.8.0"
99source = "registry+https://github.com/rust-lang/crates.io-index"
111source = "registry+https://github.com/rust-lang/crates.io-index"
100−checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd"
112+checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
101dependencies = [
113dependencies = [
102 "memchr",
114 "memchr",
103− "once_cell",
104 "regex-automata",
115 "regex-automata",
105 "serde",
116 "serde",
106]
117]
107
118
108[[package]]
119[[package]]
109name = "btoi"
120name = "btoi"
110−version = "0.4.2"
121+version = "0.4.3"
111source = "registry+https://github.com/rust-lang/crates.io-index"
122source = "registry+https://github.com/rust-lang/crates.io-index"
112−checksum = "97c0869a9faa81f8bbf8102371105d6d0a7b79167a04c340b04ab16892246a11"
123+checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad"
113dependencies = [
124dependencies = [
114 "num-traits",
125 "num-traits",
115]
126]
116
127
117[[package]]
128[[package]]
118name = "bumpalo"
129name = "bumpalo"
119−version = "3.11.1"
130+version = "3.12.0"
120−source = "registry+https://github.com/rust-lang/crates.io-index"
121−checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
122−
123−[[package]]
124−name = "bytecount"
125−version = "0.6.3"
126source = "registry+https://github.com/rust-lang/crates.io-index"
131source = "registry+https://github.com/rust-lang/crates.io-index"
127−checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
132+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
128
133
129[[package]]
134[[package]]
130name = "bytes"
135name = "bytes"
131−version = "1.3.0"
136+version = "1.4.0"
132−source = "registry+https://github.com/rust-lang/crates.io-index"
133−checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
134−
135−[[package]]
136−name = "bytesize"
137−version = "1.1.0"
138source = "registry+https://github.com/rust-lang/crates.io-index"
137source = "registry+https://github.com/rust-lang/crates.io-index"
139−checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70"
138+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
140
139
141[[package]]
140[[package]]
142name = "cassowary"
141name = "cassowary"
@@ -145,20 +144,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
145checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
144checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
146
145
147[[package]]
146[[package]]
148−name = "castaway"
147+name = "cc"
149−version = "0.2.2"
148+version = "1.0.84"
150source = "registry+https://github.com/rust-lang/crates.io-index"
149source = "registry+https://github.com/rust-lang/crates.io-index"
151−checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
150+checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
152dependencies = [
151dependencies = [
153− "rustversion",
152+ "libc",
154]
153]
155
154
156−[[package]]
157−name = "cc"
158−version = "1.0.79"
159−source = "registry+https://github.com/rust-lang/crates.io-index"
160−checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
161−
162[[package]]
155[[package]]
163name = "cfg-if"
156name = "cfg-if"
164version = "1.0.0"
157version = "1.0.0"
@@ -178,25 +171,23 @@ dependencies = [
178
171
179[[package]]
172[[package]]
180name = "chrono"
173name = "chrono"
181−version = "0.4.23"
174+version = "0.4.31"
182source = "registry+https://github.com/rust-lang/crates.io-index"
175source = "registry+https://github.com/rust-lang/crates.io-index"
183−checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
176+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
184dependencies = [
177dependencies = [
178+ "android-tzdata",
185 "iana-time-zone",
179 "iana-time-zone",
186− "num-integer",
187 "num-traits",
180 "num-traits",
188− "winapi",
181+ "windows-targets 0.48.0",
189]
182]
190
183
191[[package]]
184[[package]]
192name = "clipboard-win"
185name = "clipboard-win"
193−version = "4.5.0"
186+version = "5.0.0"
194source = "registry+https://github.com/rust-lang/crates.io-index"
187source = "registry+https://github.com/rust-lang/crates.io-index"
195−checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
188+checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
196dependencies = [
189dependencies = [
197 "error-code",
190 "error-code",
198− "str-buf",
199− "winapi",
200]
191]
201
192
202[[package]]
193[[package]]
@@ -215,17 +206,6 @@ dependencies = [
215 "unicode-width",
206 "unicode-width",
216]
207]
217
208
218−[[package]]
219−name = "compact_str"
220−version = "0.6.1"
221−source = "registry+https://github.com/rust-lang/crates.io-index"
222−checksum = "5138945395949e7dfba09646dc9e766b548ff48e23deb5246890e6b64ae9e1b9"
223−dependencies = [
224− "castaway",
225− "itoa",
226− "ryu",
227−]
228−
229[[package]]
209[[package]]
230name = "content_inspector"
210name = "content_inspector"
231version = "0.2.4"
211version = "0.2.4"
@@ -237,9 +217,15 @@ dependencies = [
237
217
238[[package]]
218[[package]]
239name = "core-foundation-sys"
219name = "core-foundation-sys"
240−version = "0.8.3"
220+version = "0.8.4"
241source = "registry+https://github.com/rust-lang/crates.io-index"
221source = "registry+https://github.com/rust-lang/crates.io-index"
242−checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
222+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
223+
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"
243
229
244[[package]]
230[[package]]
245name = "crc32fast"
231name = "crc32fast"
@@ -250,27 +236,62 @@ dependencies = [
250 "cfg-if",
236 "cfg-if",
251]
237]
252
238
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+
253[[package]]
273[[package]]
254name = "crossbeam-utils"
274name = "crossbeam-utils"
255−version = "0.8.14"
275+version = "0.8.16"
256source = "registry+https://github.com/rust-lang/crates.io-index"
276source = "registry+https://github.com/rust-lang/crates.io-index"
257−checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
277+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
258dependencies = [
278dependencies = [
259 "cfg-if",
279 "cfg-if",
260]
280]
261
281
262[[package]]
282[[package]]
263name = "crossterm"
283name = "crossterm"
264−version = "0.25.0"
284+version = "0.27.0"
265source = "registry+https://github.com/rust-lang/crates.io-index"
285source = "registry+https://github.com/rust-lang/crates.io-index"
266−checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
286+checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
267dependencies = [
287dependencies = [
268− "bitflags",
288+ "bitflags 2.4.1",
269 "crossterm_winapi",
289 "crossterm_winapi",
290+ "filedescriptor",
270 "futures-core",
291 "futures-core",
271 "libc",
292 "libc",
272 "mio",
293 "mio",
273− "parking_lot 0.12.1",
294+ "parking_lot",
274 "signal-hook",
295 "signal-hook",
275 "signal-hook-mio",
296 "signal-hook-mio",
276 "winapi",
297 "winapi",
@@ -278,18 +299,18 @@ dependencies = [
278
299
279[[package]]
300[[package]]
280name = "crossterm_winapi"
301name = "crossterm_winapi"
281−version = "0.9.0"
302+version = "0.9.1"
282source = "registry+https://github.com/rust-lang/crates.io-index"
303source = "registry+https://github.com/rust-lang/crates.io-index"
283−checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
304+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
284dependencies = [
305dependencies = [
285 "winapi",
306 "winapi",
286]
307]
287
308
288[[package]]
309[[package]]
289name = "cxx"
310name = "cxx"
290−version = "1.0.82"
311+version = "1.0.94"
291source = "registry+https://github.com/rust-lang/crates.io-index"
312source = "registry+https://github.com/rust-lang/crates.io-index"
292−checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453"
313+checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
293dependencies = [
314dependencies = [
294 "cc",
315 "cc",
295 "cxxbridge-flags",
316 "cxxbridge-flags",
@@ -299,9 +320,9 @@ dependencies = [
299
320
300[[package]]
321[[package]]
301name = "cxx-build"
322name = "cxx-build"
302−version = "1.0.82"
323+version = "1.0.94"
303source = "registry+https://github.com/rust-lang/crates.io-index"
324source = "registry+https://github.com/rust-lang/crates.io-index"
304−checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0"
325+checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
305dependencies = [
326dependencies = [
306 "cc",
327 "cc",
307 "codespan-reporting",
328 "codespan-reporting",
@@ -309,191 +330,154 @@ dependencies = [
309 "proc-macro2",
330 "proc-macro2",
310 "quote",
331 "quote",
311 "scratch",
332 "scratch",
312− "syn",
333+ "syn 2.0.38",
313]
334]
314
335
315[[package]]
336[[package]]
316name = "cxxbridge-flags"
337name = "cxxbridge-flags"
317−version = "1.0.82"
338+version = "1.0.94"
318source = "registry+https://github.com/rust-lang/crates.io-index"
339source = "registry+https://github.com/rust-lang/crates.io-index"
319−checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71"
340+checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
320
341
321[[package]]
342[[package]]
322name = "cxxbridge-macro"
343name = "cxxbridge-macro"
323−version = "1.0.82"
344+version = "1.0.94"
324source = "registry+https://github.com/rust-lang/crates.io-index"
345source = "registry+https://github.com/rust-lang/crates.io-index"
325−checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470"
346+checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
326dependencies = [
347dependencies = [
327 "proc-macro2",
348 "proc-macro2",
328 "quote",
349 "quote",
329− "syn",
350+ "syn 2.0.38",
330]
351]
331
352
332[[package]]
353[[package]]
333−name = "dashmap"
354+name = "dunce"
334−version = "5.4.0"
355+version = "1.0.4"
335source = "registry+https://github.com/rust-lang/crates.io-index"
356source = "registry+https://github.com/rust-lang/crates.io-index"
336−checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
357+checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
337−dependencies = [
338− "cfg-if",
339− "hashbrown 0.12.3",
340− "lock_api",
341− "once_cell",
342− "parking_lot_core 0.9.4",
343−]
344
358
345[[package]]
359[[package]]
346−name = "dirs"
360+name = "either"
347−version = "4.0.0"
361+version = "1.8.1"
348source = "registry+https://github.com/rust-lang/crates.io-index"
362source = "registry+https://github.com/rust-lang/crates.io-index"
349−checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
363+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
350−dependencies = [
351− "dirs-sys",
352−]
353
364
354[[package]]
365[[package]]
355−name = "dirs-next"
366+name = "encoding_rs"
356−version = "2.0.0"
367+version = "0.8.33"
357source = "registry+https://github.com/rust-lang/crates.io-index"
368source = "registry+https://github.com/rust-lang/crates.io-index"
358−checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
369+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
359dependencies = [
370dependencies = [
360 "cfg-if",
371 "cfg-if",
361− "dirs-sys-next",
362]
372]
363
373
364[[package]]
374[[package]]
365−name = "dirs-sys"
375+name = "encoding_rs_io"
366−version = "0.3.7"
367−source = "registry+https://github.com/rust-lang/crates.io-index"
368−checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
369−dependencies = [
370− "libc",
371− "redox_users",
372− "winapi",
373−]
374−
375−[[package]]
376−name = "dirs-sys-next"
377−version = "0.1.2"
376+version = "0.1.7"
378source = "registry+https://github.com/rust-lang/crates.io-index"
377source = "registry+https://github.com/rust-lang/crates.io-index"
379−checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
378+checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83"
380dependencies = [
379dependencies = [
381− "libc",
380+ "encoding_rs",
382− "redox_users",
383− "winapi",
384]
381]
385
382
386[[package]]
383[[package]]
387−name = "either"
384+name = "equivalent"
388−version = "1.8.0"
385+version = "1.0.0"
389source = "registry+https://github.com/rust-lang/crates.io-index"
386source = "registry+https://github.com/rust-lang/crates.io-index"
390−checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
387+checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
391
388
392[[package]]
389[[package]]
393−name = "encoding_rs"
390+name = "errno"
394−version = "0.8.32"
391+version = "0.3.8"
395source = "registry+https://github.com/rust-lang/crates.io-index"
392source = "registry+https://github.com/rust-lang/crates.io-index"
396−checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
393+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
397dependencies = [
394dependencies = [
398− "cfg-if",
395+ "libc",
396+ "windows-sys 0.52.0",
399]
397]
400
398
401[[package]]
399[[package]]
402−name = "encoding_rs_io"
400+name = "error-code"
403−version = "0.1.7"
401+version = "3.0.0"
404source = "registry+https://github.com/rust-lang/crates.io-index"
402source = "registry+https://github.com/rust-lang/crates.io-index"
405−checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83"
403+checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
406−dependencies = [
407− "encoding_rs",
408−]
409
404
410[[package]]
405[[package]]
411−name = "error-code"
406+name = "etcetera"
412−version = "2.3.1"
407+version = "0.8.0"
413source = "registry+https://github.com/rust-lang/crates.io-index"
408source = "registry+https://github.com/rust-lang/crates.io-index"
414−checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
409+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
415dependencies = [
410dependencies = [
416− "libc",
411+ "cfg-if",
417− "str-buf",
412+ "home",
413+ "windows-sys 0.48.0",
418]
414]
419
415
420[[package]]
416[[package]]
421−name = "etcetera"
417+name = "faster-hex"
422−version = "0.4.0"
418+version = "0.9.0"
423source = "registry+https://github.com/rust-lang/crates.io-index"
419source = "registry+https://github.com/rust-lang/crates.io-index"
424−checksum = "d017fce18e4e9bfa75e1db51f49f4487bd3f8a7df509b24a46474a956ee962fd"
420+checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
425dependencies = [
421dependencies = [
426− "cfg-if",
422+ "serde",
427− "dirs-next",
428− "thiserror",
429]
423]
430
424
431[[package]]
425[[package]]
432name = "fastrand"
426name = "fastrand"
433−version = "1.8.0"
427+version = "2.0.0"
434source = "registry+https://github.com/rust-lang/crates.io-index"
428source = "registry+https://github.com/rust-lang/crates.io-index"
435−checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
429+checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
436−dependencies = [
437− "instant",
438−]
439
430
440[[package]]
431[[package]]
441name = "fern"
432name = "fern"
442−version = "0.6.1"
433+version = "0.6.2"
443source = "registry+https://github.com/rust-lang/crates.io-index"
434source = "registry+https://github.com/rust-lang/crates.io-index"
444−checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a"
435+checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee"
445dependencies = [
436dependencies = [
446 "log",
437 "log",
447]
438]
448
439
449[[package]]
440[[package]]
450−name = "filetime"
441+name = "filedescriptor"
451−version = "0.2.18"
442+version = "0.8.2"
452source = "registry+https://github.com/rust-lang/crates.io-index"
443source = "registry+https://github.com/rust-lang/crates.io-index"
453−checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3"
444+checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
454dependencies = [
445dependencies = [
455− "cfg-if",
456 "libc",
446 "libc",
457− "redox_syscall",
447+ "thiserror",
458− "windows-sys",
448+ "winapi",
459]
449]
460
450
461[[package]]
451[[package]]
462name = "flate2"
452name = "flate2"
463−version = "1.0.25"
453+version = "1.0.27"
464source = "registry+https://github.com/rust-lang/crates.io-index"
454source = "registry+https://github.com/rust-lang/crates.io-index"
465−checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
455+checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
466dependencies = [
456dependencies = [
467 "crc32fast",
457 "crc32fast",
468 "miniz_oxide",
458 "miniz_oxide",
469]
459]
470
460
471−[[package]]
472−name = "fnv"
473−version = "1.0.7"
474−source = "registry+https://github.com/rust-lang/crates.io-index"
475−checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
476−
477[[package]]
461[[package]]
478name = "form_urlencoded"
462name = "form_urlencoded"
479−version = "1.1.0"
463+version = "1.2.1"
480source = "registry+https://github.com/rust-lang/crates.io-index"
464source = "registry+https://github.com/rust-lang/crates.io-index"
481−checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
465+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
482dependencies = [
466dependencies = [
483 "percent-encoding",
467 "percent-encoding",
484]
468]
485
469
486[[package]]
470[[package]]
487name = "futures-core"
471name = "futures-core"
488−version = "0.3.26"
472+version = "0.3.30"
489source = "registry+https://github.com/rust-lang/crates.io-index"
473source = "registry+https://github.com/rust-lang/crates.io-index"
490−checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
474+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
491
475
492[[package]]
476[[package]]
493name = "futures-executor"
477name = "futures-executor"
494−version = "0.3.26"
478+version = "0.3.29"
495source = "registry+https://github.com/rust-lang/crates.io-index"
479source = "registry+https://github.com/rust-lang/crates.io-index"
496−checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
480+checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
497dependencies = [
481dependencies = [
498 "futures-core",
482 "futures-core",
499 "futures-task",
483 "futures-task",
@@ -502,15 +486,15 @@ dependencies = [
502
486
503[[package]]
487[[package]]
504name = "futures-task"
488name = "futures-task"
505−version = "0.3.26"
489+version = "0.3.30"
506source = "registry+https://github.com/rust-lang/crates.io-index"
490source = "registry+https://github.com/rust-lang/crates.io-index"
507−checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
491+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
508
492
509[[package]]
493[[package]]
510name = "futures-util"
494name = "futures-util"
511−version = "0.3.26"
495+version = "0.3.30"
512source = "registry+https://github.com/rust-lang/crates.io-index"
496source = "registry+https://github.com/rust-lang/crates.io-index"
513−checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
497+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
514dependencies = [
498dependencies = [
515 "futures-core",
499 "futures-core",
516 "futures-task",
500 "futures-task",
@@ -519,20 +503,11 @@ dependencies = [
519 "slab",
503 "slab",
520]
504]
521
505
522−[[package]]
523−name = "fuzzy-matcher"
524−version = "0.3.7"
525−source = "registry+https://github.com/rust-lang/crates.io-index"
526−checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
527−dependencies = [
528− "thread_local",
529−]
530−
531[[package]]
506[[package]]
532name = "getrandom"
507name = "getrandom"
533−version = "0.2.8"
508+version = "0.2.9"
534source = "registry+https://github.com/rust-lang/crates.io-index"
509source = "registry+https://github.com/rust-lang/crates.io-index"
535−checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
510+checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
536dependencies = [
511dependencies = [
537 "cfg-if",
512 "cfg-if",
538 "libc",
513 "libc",
@@ -540,556 +515,519 @@ dependencies = [
540]
515]
541
516
542[[package]]
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]]
524+name = "gix"
525+version = "0.57.0"
526+source = "registry+https://github.com/rust-lang/crates.io-index"
527+checksum = "721c7497ab24b665ed5a1eb2b3526936aa60068e61ba260651f7e77c52feec69"
528+dependencies = [
543−name = "git-actor"
529+ "gix-actor",
544−version = "0.17.0"
530+ "gix-commitgraph",
545−source = "registry+https://github.com/rust-lang/crates.io-index"
531+ "gix-config",
546−checksum = "d9e5fd7bc63ad527d64584f8d01f99b89c051f5fbb8144b58ae5f812775065cf"
547−dependencies = [
548− "bstr 1.0.1",
549− "btoi",
550− "git-date",
532+ "gix-date",
551− "itoa",
533+ "gix-diff",
552− "nom",
534+ "gix-discover",
553− "quick-error",
554−]
555−
556−[[package]]
557−name = "git-attributes"
558−version = "0.8.0"
559−source = "registry+https://github.com/rust-lang/crates.io-index"
560−checksum = "f8013dfce47c1e29236d732308933e2c77af5355ec5105755d26faf7764d3f7b"
561−dependencies = [
562− "bstr 1.0.1",
563− "compact_str",
564− "git-features",
535+ "gix-features",
536+ "gix-fs",
565− "git-glob",
537+ "gix-glob",
538+ "gix-hash",
539+ "gix-hashtable",
540+ "gix-lock",
541+ "gix-macros",
542+ "gix-object",
543+ "gix-odb",
544+ "gix-pack",
566− "git-path",
545+ "gix-path",
567− "git-quote",
546+ "gix-ref",
547+ "gix-refspec",
548+ "gix-revision",
549+ "gix-revwalk",
550+ "gix-sec",
551+ "gix-tempfile",
552+ "gix-trace",
553+ "gix-traverse",
554+ "gix-url",
555+ "gix-utils",
556+ "gix-validate",
557+ "once_cell",
558+ "parking_lot",
559+ "smallvec",
568 "thiserror",
560 "thiserror",
569− "unicode-bom",
561+ "unicode-normalization",
570]
562]
571
563
572[[package]]
564[[package]]
573−name = "git-bitmap"
565+name = "gix-actor"
574−version = "0.2.0"
566+version = "0.29.0"
575source = "registry+https://github.com/rust-lang/crates.io-index"
567source = "registry+https://github.com/rust-lang/crates.io-index"
576−checksum = "44304093ac66a0ada1b243c15c3a503a165a1d0f50bec748f4e5a9b84a0d0722"
568+checksum = "886014c4865b93ce268f1d3eddd4fd3261242c3f3ee61eb36009f913016a9059"
577dependencies = [
569dependencies = [
578− "quick-error",
570+ "bstr",
571+ "btoi",
572+ "gix-date",
573+ "itoa",
574+ "thiserror",
575+ "winnow 0.5.28",
579]
576]
580
577
581[[package]]
578[[package]]
582−name = "git-chunk"
579+name = "gix-chunk"
583−version = "0.4.0"
580+version = "0.4.6"
584source = "registry+https://github.com/rust-lang/crates.io-index"
581source = "registry+https://github.com/rust-lang/crates.io-index"
585−checksum = "3090baa2f4a3fe488a9b3e31090b83259aaf930bf0634af34c18117274f8f1a8"
582+checksum = "2ade37ef69870de0ed966b97c57a3a947a22ff3482a52c3b99b205f77bcb08fb"
586dependencies = [
583dependencies = [
587 "thiserror",
584 "thiserror",
588]
585]
589
586
590[[package]]
587[[package]]
591−name = "git-command"
588+name = "gix-commitgraph"
592−version = "0.2.1"
589+version = "0.23.0"
593source = "registry+https://github.com/rust-lang/crates.io-index"
590source = "registry+https://github.com/rust-lang/crates.io-index"
594−checksum = "215145cc1686a45bc6f9872b153a0d3f3c40a1b94173a928325e1b53dfa5e2af"
591+checksum = "e7559ea9cefee188cd88b05afcc8e3ef7a3cb4a5c647bccf06b981e591b02b77"
595dependencies = [
592dependencies = [
596− "bstr 1.0.1",
593+ "bstr",
594+ "gix-chunk",
595+ "gix-features",
596+ "gix-hash",
597+ "memmap2",
598+ "thiserror",
597]
599]
598
600
599[[package]]
601[[package]]
600−name = "git-config"
602+name = "gix-config"
601−version = "0.15.0"
603+version = "0.33.0"
602source = "registry+https://github.com/rust-lang/crates.io-index"
604source = "registry+https://github.com/rust-lang/crates.io-index"
603−checksum = "c9da662fd64ac69772158dcf04777da6266f0f36bc9a310b3eb2d805bb696315"
605+checksum = "9c35dc7f9c00a42bbc9cfa1ca2ec0a78ad1b76ff0736d3d35dfd612962244467"
604dependencies = [
606dependencies = [
605− "bstr 1.0.1",
607+ "bstr",
606− "git-config-value",
608+ "gix-config-value",
607− "git-features",
609+ "gix-features",
608− "git-glob",
610+ "gix-glob",
609− "git-path",
611+ "gix-path",
610− "git-ref",
612+ "gix-ref",
611− "git-sec",
613+ "gix-sec",
612 "memchr",
614 "memchr",
613− "nom",
614 "once_cell",
615 "once_cell",
615 "smallvec",
616 "smallvec",
616 "thiserror",
617 "thiserror",
617 "unicode-bom",
618 "unicode-bom",
619+ "winnow 0.5.28",
618]
620]
619
621
620[[package]]
622[[package]]
621−name = "git-config-value"
623+name = "gix-config-value"
622−version = "0.10.0"
624+version = "0.14.2"
623source = "registry+https://github.com/rust-lang/crates.io-index"
625source = "registry+https://github.com/rust-lang/crates.io-index"
624−checksum = "989a90c1c630513a153c685b4249b96fdf938afc75bf7ef2ae1ccbd3d799f5db"
626+checksum = "39f388cb2396aee82d6f460a2e7770659bdf8854e9e5478f7d2b1324a9698284"
625dependencies = [
627dependencies = [
626− "bitflags",
628+ "bitflags 2.4.1",
627− "bstr 1.0.1",
629+ "bstr",
628− "git-path",
630+ "gix-path",
629 "libc",
631 "libc",
630 "thiserror",
632 "thiserror",
631]
633]
632
634
633[[package]]
635[[package]]
634−name = "git-credentials"
636+name = "gix-date"
635−version = "0.9.0"
636−source = "registry+https://github.com/rust-lang/crates.io-index"
637−checksum = "97cd6bbe001afd6356b35ef13f2a6b0f0abc0133d1b2ecaec1033bdd769616d6"
638−dependencies = [
639− "bstr 1.0.1",
640− "git-command",
641− "git-config-value",
642− "git-path",
643− "git-prompt",
644− "git-sec",
645− "git-url",
646− "thiserror",
647−]
648−
649−[[package]]
650−name = "git-date"
651−version = "0.4.0"
637+version = "0.8.2"
652source = "registry+https://github.com/rust-lang/crates.io-index"
638source = "registry+https://github.com/rust-lang/crates.io-index"
653−checksum = "412c9b89026505bd24d5f8acafa578de6eea3b271ece307a73b8e646e671302a"
639+checksum = "9d85f4a01e0d05c3de585e28bae514d4baf01655e3fc3f14ce6f30bf62405345"
654dependencies = [
640dependencies = [
655− "bstr 1.0.1",
641+ "bstr",
656 "itoa",
642 "itoa",
657 "thiserror",
643 "thiserror",
658 "time",
644 "time",
659]
645]
660
646
661[[package]]
647[[package]]
662−name = "git-diff"
648+name = "gix-diff"
663−version = "0.26.0"
649+version = "0.39.0"
664source = "registry+https://github.com/rust-lang/crates.io-index"
650source = "registry+https://github.com/rust-lang/crates.io-index"
665−checksum = "ca87474422d26d606d04cec6bedfabcd92a0a74102cd7936785358ced6a4a25a"
651+checksum = "ac0e75f5afd2f6c47c800b6b0a000a08045739d0450d20482e8faa42543f62d1"
666dependencies = [
652dependencies = [
653+ "bstr",
667− "git-hash",
654+ "gix-hash",
668− "git-object",
655+ "gix-object",
669− "imara-diff",
670 "thiserror",
656 "thiserror",
671]
657]
672
658
673[[package]]
659[[package]]
674−name = "git-discover"
660+name = "gix-discover"
675−version = "0.12.0"
661+version = "0.28.0"
676source = "registry+https://github.com/rust-lang/crates.io-index"
662source = "registry+https://github.com/rust-lang/crates.io-index"
677−checksum = "1e9e26e0bc434643228cd418185bd28ca5c7cf831bde1da434807391c27ac40e"
663+checksum = "f0b63ce2ad81632ac1a84ac370f85a5e580c3261580bd85ea17ff35d3a0bba18"
678dependencies = [
664dependencies = [
679− "bstr 1.0.1",
665+ "bstr",
666+ "dunce",
680− "git-hash",
667+ "gix-hash",
681− "git-path",
668+ "gix-path",
682− "git-ref",
669+ "gix-ref",
683− "git-sec",
670+ "gix-sec",
684 "thiserror",
671 "thiserror",
685]
672]
686
673
687[[package]]
674[[package]]
688−name = "git-features"
675+name = "gix-features"
689−version = "0.26.0"
676+version = "0.37.0"
690source = "registry+https://github.com/rust-lang/crates.io-index"
677source = "registry+https://github.com/rust-lang/crates.io-index"
691−checksum = "5ff74064fa007c5beefa89a64bb72834f32b3c497750a56c79c6802bbdb311f9"
678+checksum = "befe7edea299a824504b5acc96d7a3a538125b38c42f3a8379f6912a29c90c81"
692dependencies = [
679dependencies = [
693 "crc32fast",
680 "crc32fast",
694 "flate2",
681 "flate2",
695− "git-hash",
682+ "gix-hash",
683+ "gix-trace",
696 "libc",
684 "libc",
697 "once_cell",
685 "once_cell",
698 "prodash",
686 "prodash",
699− "quick-error",
700 "sha1_smol",
687 "sha1_smol",
688+ "thiserror",
701 "walkdir",
689 "walkdir",
702]
690]
703
691
704[[package]]
692[[package]]
705−name = "git-glob"
693+name = "gix-fs"
706−version = "0.5.1"
694+version = "0.9.0"
707source = "registry+https://github.com/rust-lang/crates.io-index"
695source = "registry+https://github.com/rust-lang/crates.io-index"
708−checksum = "3908404c9b76ac7b3f636a104142378d3eaa78623cbc6eb7c7f0651979d48e8a"
696+checksum = "007017ce93b819ea52c0ec68306f7d72212a2d307c3d70f1548c7141c015d0a1"
709dependencies = [
697dependencies = [
710− "bitflags",
698+ "gix-features",
711− "bstr 1.0.1",
712]
699]
713
700
714[[package]]
701[[package]]
715−name = "git-hash"
702+name = "gix-glob"
716−version = "0.10.1"
703+version = "0.15.0"
717source = "registry+https://github.com/rust-lang/crates.io-index"
704source = "registry+https://github.com/rust-lang/crates.io-index"
718−checksum = "1532d82bf830532f8d545c5b7b568e311e3593f16cf7ee9dd0ce03c74b12b99d"
705+checksum = "61fb116c2516d3a1170e010118f639944a6389872c875a008960cdab2a44ac72"
719dependencies = [
706dependencies = [
720− "hex",
707+ "bitflags 2.4.1",
721− "thiserror",
708+ "bstr",
709+ "gix-features",
710+ "gix-path",
722]
711]
723
712
724[[package]]
713[[package]]
725−name = "git-hashtable"
714+name = "gix-hash"
726−version = "0.1.0"
715+version = "0.14.0"
727source = "registry+https://github.com/rust-lang/crates.io-index"
716source = "registry+https://github.com/rust-lang/crates.io-index"
728−checksum = "c52b625ad8cc360a0b7f426266f21fb07bd49b8f4ccf1b3ca7bc89424db1dec4"
717+checksum = "c52c3170a17b6031833cd2eb4ad7bc23f2755d06e6e70f78dec21d42e8fe1b30"
729dependencies = [
718dependencies = [
730− "git-hash",
719+ "faster-hex",
731− "hashbrown 0.13.2",
720+ "thiserror",
732]
721]
733
722
734[[package]]
723[[package]]
735−name = "git-index"
724+name = "gix-hashtable"
736−version = "0.12.1"
725+version = "0.5.0"
737source = "registry+https://github.com/rust-lang/crates.io-index"
726source = "registry+https://github.com/rust-lang/crates.io-index"
738−checksum = "485da97dd4f69c7d9a8dc238cd6f4a726387ffc34573489e8e0d2bee266e3454"
727+checksum = "40a838e6366a5e5b84668b6997ce0981b833136468e8ba949f424c0ef2927eba"
739−dependencies = [
728+dependencies = [
740− "atoi",
741− "bitflags",
742− "bstr 1.0.1",
743− "filetime",
744− "git-bitmap",
745− "git-features",
746− "git-hash",
729+ "gix-hash",
747− "git-lock",
730+ "hashbrown 0.14.3",
748− "git-object",
731+ "parking_lot",
749− "git-traverse",
750− "itoa",
751− "memmap2",
752− "smallvec",
753− "thiserror",
754]
732]
755
733
756[[package]]
734[[package]]
757−name = "git-lock"
735+name = "gix-lock"
758−version = "3.0.0"
736+version = "12.0.0"
759source = "registry+https://github.com/rust-lang/crates.io-index"
737source = "registry+https://github.com/rust-lang/crates.io-index"
760−checksum = "89e4f05b8a68c3a5dd83a6651c76be384e910fe283072184fdab9d77f87ccec2"
738+checksum = "6cf112ddee94223c119a8534dad027740dc3aba3365ac5edeef8a7f6660c74db"
761dependencies = [
739dependencies = [
762− "fastrand",
763− "git-tempfile",
740+ "gix-tempfile",
764− "quick-error",
741+ "gix-utils",
742+ "thiserror",
765]
743]
766
744
767[[package]]
745[[package]]
768−name = "git-mailmap"
746+name = "gix-macros"
769−version = "0.9.0"
747+version = "0.1.2"
770source = "registry+https://github.com/rust-lang/crates.io-index"
748source = "registry+https://github.com/rust-lang/crates.io-index"
771−checksum = "0316b4346f3e162ade368209efb8a609b587793c74aa3b8de0ec01a4f3580120"
749+checksum = "fc207b64189cf71bcb17fc841ab99a5d63d0845546b611e2703ce467f659323a"
772dependencies = [
750dependencies = [
773− "bstr 1.0.1",
751+ "proc-macro2",
774− "git-actor",
752+ "quote",
775− "quick-error",
753+ "syn 2.0.38",
776]
754]
777
755
778[[package]]
756[[package]]
779−name = "git-object"
757+name = "gix-object"
780−version = "0.26.0"
758+version = "0.40.0"
781source = "registry+https://github.com/rust-lang/crates.io-index"
759source = "registry+https://github.com/rust-lang/crates.io-index"
782−checksum = "3f8563e2d6f524d7053f3106714f99ecdc3adbba2cb7108c09d71a02579f2e19"
760+checksum = "e4c77e47ffba92127faf632a841fce23d19547e269bd8b88e68961a70eab4e93"
783dependencies = [
761dependencies = [
784− "bstr 1.0.1",
762+ "bstr",
785 "btoi",
763 "btoi",
786− "git-actor",
764+ "gix-actor",
765+ "gix-date",
787− "git-features",
766+ "gix-features",
788− "git-hash",
767+ "gix-hash",
789− "git-validate",
768+ "gix-validate",
790− "hex",
791 "itoa",
769 "itoa",
792− "nom",
793 "smallvec",
770 "smallvec",
794 "thiserror",
771 "thiserror",
772+ "winnow 0.5.28",
795]
773]
796
774
797[[package]]
775[[package]]
798−name = "git-odb"
776+name = "gix-odb"
799−version = "0.40.0"
777+version = "0.56.0"
800source = "registry+https://github.com/rust-lang/crates.io-index"
778source = "registry+https://github.com/rust-lang/crates.io-index"
801−checksum = "616115a0e3daff6e08842758d24547b37a6eb6d0e2eedd95a740c3aaa2750333"
779+checksum = "a3254f2005cc7553ea78e85e816a09150c6f7a64e6b7627b8d1fdc56721bea73"
802dependencies = [
780dependencies = [
803 "arc-swap",
781 "arc-swap",
782+ "gix-date",
804− "git-features",
783+ "gix-features",
805− "git-hash",
784+ "gix-hash",
806− "git-object",
785+ "gix-object",
807− "git-pack",
786+ "gix-pack",
808− "git-path",
787+ "gix-path",
809− "git-quote",
788+ "gix-quote",
810− "parking_lot 0.12.1",
789+ "parking_lot",
811 "tempfile",
790 "tempfile",
812 "thiserror",
791 "thiserror",
813]
792]
814
793
815[[package]]
794[[package]]
816−name = "git-pack"
795+name = "gix-pack"
817−version = "0.30.0"
796+version = "0.46.0"
818source = "registry+https://github.com/rust-lang/crates.io-index"
797source = "registry+https://github.com/rust-lang/crates.io-index"
819−checksum = "7cd16b88f4b66041f41ca510c28bd81c4ee7363c5a544b3d62b4170432965871"
798+checksum = "02ebc8cd657eec207d82d8f876ca402361308ecd3c87a47935b0299506257b4f"
820dependencies = [
799dependencies = [
821− "bytesize",
822 "clru",
800 "clru",
823− "dashmap",
824− "git-chunk",
801+ "gix-chunk",
825− "git-diff",
826− "git-features",
802+ "gix-features",
827− "git-hash",
803+ "gix-hash",
828− "git-hashtable",
804+ "gix-hashtable",
829− "git-object",
805+ "gix-object",
830− "git-path",
806+ "gix-path",
831− "git-tempfile",
807+ "gix-tempfile",
832− "git-traverse",
833 "memmap2",
808 "memmap2",
834− "parking_lot 0.12.1",
809+ "parking_lot",
835 "smallvec",
810 "smallvec",
836 "thiserror",
811 "thiserror",
837]
812]
838
813
839[[package]]
814[[package]]
840−name = "git-path"
815+name = "gix-path"
841−version = "0.7.0"
816+version = "0.10.2"
842−source = "registry+https://github.com/rust-lang/crates.io-index"
843−checksum = "e40e68481a06da243d3f4dfd86a4be39c24eefb535017a862e845140dcdb878a"
844−dependencies = [
845− "bstr 1.0.1",
846− "thiserror",
847−]
848−
849−[[package]]
850−name = "git-prompt"
851−version = "0.3.0"
852source = "registry+https://github.com/rust-lang/crates.io-index"
817source = "registry+https://github.com/rust-lang/crates.io-index"
853−checksum = "3612a486e507dd431ef0f7108eeaafc8fd1ed7bd0f205a88554f6f91fe5dccbf"
818+checksum = "10931652a3da126990ac93bce1b1c600cc99d7c268d712b6360ed52174ce1b68"
854dependencies = [
819dependencies = [
855− "git-command",
820+ "bstr",
856− "git-config-value",
821+ "gix-trace",
857− "nix",
822+ "home",
858− "parking_lot 0.12.1",
823+ "once_cell",
859 "thiserror",
824 "thiserror",
860]
825]
861
826
862[[package]]
827[[package]]
863−name = "git-quote"
828+name = "gix-quote"
864−version = "0.4.0"
829+version = "0.4.9"
865source = "registry+https://github.com/rust-lang/crates.io-index"
830source = "registry+https://github.com/rust-lang/crates.io-index"
866−checksum = "1dd11f4e7f251ab297545faa4c5a4517f4985a43b9c16bf96fa49107f58e837f"
831+checksum = "6c43530cb94a7759807e6f8d180e17ac9c65673b891645c6c433831dc0cf4342"
867dependencies = [
832dependencies = [
868− "bstr 1.0.1",
833+ "bstr",
869 "btoi",
834 "btoi",
870− "quick-error",
835+ "thiserror",
871]
836]
872
837
873[[package]]
838[[package]]
874−name = "git-ref"
839+name = "gix-ref"
875−version = "0.23.0"
840+version = "0.40.0"
876source = "registry+https://github.com/rust-lang/crates.io-index"
841source = "registry+https://github.com/rust-lang/crates.io-index"
877−checksum = "5e6767925a6fc4af5c5a81e348d1d851c1b3ab2b512bd7f562ac11be37c14468"
842+checksum = "baa951b3b850d6d1be4f900768e49af20b76bf9505beac22af723d57249a2f1d"
878−dependencies = [
843+dependencies = [
879− "git-actor",
844+ "gix-actor",
845+ "gix-date",
880− "git-features",
846+ "gix-features",
847+ "gix-fs",
881− "git-hash",
848+ "gix-hash",
882− "git-lock",
849+ "gix-lock",
883− "git-object",
850+ "gix-object",
884− "git-path",
851+ "gix-path",
885− "git-tempfile",
852+ "gix-tempfile",
886− "git-validate",
853+ "gix-validate",
887 "memmap2",
854 "memmap2",
888− "nom",
889 "thiserror",
855 "thiserror",
856+ "winnow 0.5.28",
890]
857]
891
858
892[[package]]
859[[package]]
893−name = "git-refspec"
860+name = "gix-refspec"
894−version = "0.7.0"
861+version = "0.21.0"
895source = "registry+https://github.com/rust-lang/crates.io-index"
862source = "registry+https://github.com/rust-lang/crates.io-index"
896−checksum = "ddf310ed5f2829ac0af96e7d4aebd4ae4b89f0718a7ae3666d09b02b2c5a1dfd"
863+checksum = "88c61f849d58c06e3068a0b601cf10127d2a07cdad00a725ed66cf303f76f6b3"
897dependencies = [
864dependencies = [
898− "bstr 1.0.1",
865+ "bstr",
899− "git-hash",
866+ "gix-hash",
900− "git-revision",
867+ "gix-revision",
901− "git-validate",
868+ "gix-validate",
902 "smallvec",
869 "smallvec",
903 "thiserror",
870 "thiserror",
904]
871]
905
872
906[[package]]
873[[package]]
907−name = "git-repository"
874+name = "gix-revision"
908−version = "0.32.0"
875+version = "0.25.0"
909−source = "registry+https://github.com/rust-lang/crates.io-index"
876+source = "registry+https://github.com/rust-lang/crates.io-index"
910−checksum = "993277960cb7e2d3991a11c1ec6951c1d142de052c26a18d2db64304e52d3741"
877+checksum = "bcbbf91f4200c5c76802ef5f057b96f5a336827508881fe55a780be71a794d22"
911−dependencies = [
878+dependencies = [
912− "git-actor",
879+ "bstr",
913− "git-attributes",
914− "git-config",
915− "git-credentials",
916− "git-date",
880+ "gix-date",
917− "git-diff",
918− "git-discover",
919− "git-features",
920− "git-glob",
921− "git-hash",
881+ "gix-hash",
922− "git-hashtable",
882+ "gix-hashtable",
923− "git-index",
924− "git-lock",
925− "git-mailmap",
926− "git-object",
883+ "gix-object",
927− "git-odb",
884+ "gix-revwalk",
928− "git-pack",
885+ "gix-trace",
929− "git-path",
930− "git-prompt",
931− "git-ref",
932− "git-refspec",
933− "git-revision",
934− "git-sec",
935− "git-tempfile",
936− "git-traverse",
937− "git-url",
938− "git-validate",
939− "git-worktree",
940− "log",
941− "once_cell",
942− "prodash",
943− "signal-hook",
944− "smallvec",
945 "thiserror",
886 "thiserror",
946− "unicode-normalization",
947]
887]
948
888
949[[package]]
889[[package]]
950−name = "git-revision"
890+name = "gix-revwalk"
951−version = "0.10.0"
891+version = "0.11.0"
952source = "registry+https://github.com/rust-lang/crates.io-index"
892source = "registry+https://github.com/rust-lang/crates.io-index"
953−checksum = "4f9a6bd28c9d1676bb96f428cd09614ae18a0087d7cea1cebfd177e25f99b2af"
893+checksum = "33ab66354d83f70f2730391747d0c75f94ef3c3dd40ebde2e206db9faaf7a0a7"
954dependencies = [
894dependencies = [
955− "bstr 1.0.1",
895+ "gix-commitgraph",
956− "git-date",
896+ "gix-date",
957− "git-hash",
897+ "gix-hash",
958− "git-hashtable",
898+ "gix-hashtable",
959− "git-object",
899+ "gix-object",
900+ "smallvec",
960 "thiserror",
901 "thiserror",
961]
902]
962
903
963[[package]]
904[[package]]
964−name = "git-sec"
905+name = "gix-sec"
965−version = "0.6.0"
906+version = "0.10.2"
966source = "registry+https://github.com/rust-lang/crates.io-index"
907source = "registry+https://github.com/rust-lang/crates.io-index"
967−checksum = "9e1802e8252fa223b0ad89a393aed461132174ced1e6842a41f56dc92a3fc14f"
908+checksum = "a9963f38a42144253ed4d571882d7db5ef644c12e6726e4b75135597cc9d0e1a"
968dependencies = [
909dependencies = [
969− "bitflags",
910+ "bitflags 2.4.1",
970− "dirs",
971− "git-path",
911+ "gix-path",
972 "libc",
912 "libc",
973− "windows",
913+ "windows 0.52.0",
974]
914]
975
915
976[[package]]
916[[package]]
977−name = "git-tempfile"
917+name = "gix-tempfile"
978−version = "3.0.0"
918+version = "12.0.0"
979source = "registry+https://github.com/rust-lang/crates.io-index"
919source = "registry+https://github.com/rust-lang/crates.io-index"
980−checksum = "a6bb4dee86c8cae5a078cfaac3b004ef99c31548ed86218f23a7ff9b4b74f3be"
920+checksum = "e76a494bd530e1a1309188ff971825a24f159c76c2db0bf71fa5dfb469a2c915"
981dependencies = [
921dependencies = [
982− "dashmap",
922+ "gix-fs",
983 "libc",
923 "libc",
984 "once_cell",
924 "once_cell",
985− "signal-hook",
925+ "parking_lot",
986− "signal-hook-registry",
987 "tempfile",
926 "tempfile",
988]
927]
989
928
990[[package]]
929[[package]]
991−name = "git-traverse"
930+name = "gix-trace"
992−version = "0.22.0"
931+version = "0.1.5"
993source = "registry+https://github.com/rust-lang/crates.io-index"
932source = "registry+https://github.com/rust-lang/crates.io-index"
994−checksum = "bd356da21ec00f69b9d4f105df4cb85543c746b18f4b7fc81529ce77713cdb29"
933+checksum = "bda62acb44dd86a40c7c3762a5403cfc1ac789ea559df54085cedf79864f809e"
934+
935+[[package]]
936+name = "gix-traverse"
937+version = "0.36.0"
938+source = "registry+https://github.com/rust-lang/crates.io-index"
939+checksum = "8661fab39985c9214e56d81a63ceb5886ac948cec2fba76c39494d1e0e307ea8"
995dependencies = [
940dependencies = [
941+ "gix-commitgraph",
942+ "gix-date",
996− "git-hash",
943+ "gix-hash",
997− "git-hashtable",
944+ "gix-hashtable",
998− "git-object",
945+ "gix-object",
946+ "gix-revwalk",
947+ "smallvec",
999 "thiserror",
948 "thiserror",
1000]
949]
1001
950
1002[[package]]
951[[package]]
1003−name = "git-url"
952+name = "gix-url"
1004−version = "0.13.0"
953+version = "0.26.0"
1005source = "registry+https://github.com/rust-lang/crates.io-index"
954source = "registry+https://github.com/rust-lang/crates.io-index"
1006−checksum = "c85af407ed0dbb8d8da2a7241827d2fd5681186d9dab3570fc8dd8d6152ec48f"
955+checksum = "10a0129c1e8b52736d7c5128300a4485dbc85863001371e2771ac1754bd89fd7"
1007dependencies = [
956dependencies = [
1008− "bstr 1.0.1",
957+ "bstr",
1009− "git-features",
958+ "gix-features",
1010− "git-path",
959+ "gix-path",
1011 "home",
960 "home",
1012 "thiserror",
961 "thiserror",
1013 "url",
962 "url",
1014]
963]
1015
964
1016[[package]]
965[[package]]
1017−name = "git-validate"
966+name = "gix-utils"
1018−version = "0.7.1"
967+version = "0.1.7"
1019source = "registry+https://github.com/rust-lang/crates.io-index"
968source = "registry+https://github.com/rust-lang/crates.io-index"
1020−checksum = "0431cf9352c596dc7c8ec9066ee551ce54e63c86c3c767e5baf763f6019ff3c2"
969+checksum = "ab9277a5e32e85d53f738096d872a4a9b76067ad471894ad31bd99c8fa2da1dc"
1021dependencies = [
970dependencies = [
1022− "bstr 1.0.1",
971+ "fastrand",
1023− "thiserror",
1024]
972]
1025
973
1026[[package]]
974[[package]]
1027−name = "git-worktree"
975+name = "gix-validate"
1028−version = "0.12.0"
976+version = "0.8.2"
1029source = "registry+https://github.com/rust-lang/crates.io-index"
977source = "registry+https://github.com/rust-lang/crates.io-index"
1030−checksum = "ca3bc63878f134e08ed52dba5d82422798c01a3f2e48c38ae9a2f7ff9194f362"
978+checksum = "f805ebbdbaa4bfd98e2ee43e6d14099b8a4d9141f5d7b8202fea4d48e44263e7"
1031dependencies = [
979dependencies = [
1032− "bstr 1.0.1",
980+ "bstr",
1033− "git-attributes",
1034− "git-features",
1035− "git-glob",
1036− "git-hash",
1037− "git-index",
1038− "git-object",
1039− "git-path",
1040− "io-close",
1041 "thiserror",
981 "thiserror",
1042]
982]
1043
983
1044[[package]]
984[[package]]
1045name = "globset"
985name = "globset"
1046−version = "0.4.9"
986+version = "0.4.14"
1047source = "registry+https://github.com/rust-lang/crates.io-index"
987source = "registry+https://github.com/rust-lang/crates.io-index"
1048−checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
988+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
1049dependencies = [
989dependencies = [
1050 "aho-corasick",
990 "aho-corasick",
1051− "bstr 0.2.17",
991+ "bstr",
1052− "fnv",
1053 "log",
992 "log",
1054− "regex",
993+ "regex-automata",
994+ "regex-syntax",
1055]
995]
1056
996
1057[[package]]
997[[package]]
1058name = "grep-matcher"
998name = "grep-matcher"
1059−version = "0.1.5"
999+version = "0.1.7"
1060source = "registry+https://github.com/rust-lang/crates.io-index"
1000source = "registry+https://github.com/rust-lang/crates.io-index"
1061−checksum = "6d27563c33062cd33003b166ade2bb4fd82db1fd6a86db764dfdad132d46c1cc"
1001+checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02"
1062dependencies = [
1002dependencies = [
1063 "memchr",
1003 "memchr",
1064]
1004]
1065
1005
1066[[package]]
1006[[package]]
1067name = "grep-regex"
1007name = "grep-regex"
1068−version = "0.1.10"
1008+version = "0.1.12"
1069source = "registry+https://github.com/rust-lang/crates.io-index"
1009source = "registry+https://github.com/rust-lang/crates.io-index"
1070−checksum = "1345f8d33c89f2d5b081f2f2a41175adef9fd0bed2fea6a26c96c2deb027e58e"
1010+checksum = "f748bb135ca835da5cbc67ca0e6955f968db9c5df74ca4f56b18e1ddbc68230d"
1071dependencies = [
1011dependencies = [
1072− "aho-corasick",
1073− "bstr 0.2.17",
1012+ "bstr",
1074 "grep-matcher",
1013 "grep-matcher",
1075 "log",
1014 "log",
1076− "regex",
1015+ "regex-automata",
1077 "regex-syntax",
1016 "regex-syntax",
1078− "thread_local",
1079]
1017]
1080
1018
1081[[package]]
1019[[package]]
1082name = "grep-searcher"
1020name = "grep-searcher"
1083−version = "0.1.10"
1021+version = "0.1.13"
1084source = "registry+https://github.com/rust-lang/crates.io-index"
1022source = "registry+https://github.com/rust-lang/crates.io-index"
1085−checksum = "48852bd08f9b4eb3040ecb6d2f4ade224afe880a9a0909c5563cc59fa67932cc"
1023+checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54"
1086dependencies = [
1024dependencies = [
1087− "bstr 0.2.17",
1025+ "bstr",
1088− "bytecount",
1089 "encoding_rs",
1026 "encoding_rs",
1090 "encoding_rs_io",
1027 "encoding_rs_io",
1091 "grep-matcher",
1028 "grep-matcher",
1092 "log",
1029 "log",
1030+ "memchr",
1093 "memmap2",
1031 "memmap2",
1094]
1032]
1095
1033
@@ -1098,34 +1036,36 @@ name = "hashbrown"
1098version = "0.12.3"
1036version = "0.12.3"
1099source = "registry+https://github.com/rust-lang/crates.io-index"
1037source = "registry+https://github.com/rust-lang/crates.io-index"
1100checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1038checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1101−dependencies = [
1102− "ahash 0.7.6",
1103−]
1104
1039
1105[[package]]
1040[[package]]
1106name = "hashbrown"
1041name = "hashbrown"
1107−version = "0.13.2"
1042+version = "0.14.3"
1108source = "registry+https://github.com/rust-lang/crates.io-index"
1043source = "registry+https://github.com/rust-lang/crates.io-index"
1109−checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
1044+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
1110dependencies = [
1045dependencies = [
1111− "ahash 0.8.3",
1046+ "ahash",
1047+ "allocator-api2",
1112]
1048]
1113
1049
1114[[package]]
1050[[package]]
1115name = "helix-core"
1051name = "helix-core"
1116−version = "0.6.0"
1052+version = "23.10.0"
1117dependencies = [
1053dependencies = [
1118− "ahash 0.8.3",
1054+ "ahash",
1119 "arc-swap",
1055 "arc-swap",
1120− "bitflags",
1056+ "bitflags 2.4.1",
1121 "chrono",
1057 "chrono",
1058+ "dunce",
1122 "encoding_rs",
1059 "encoding_rs",
1123 "etcetera",
1060 "etcetera",
1124− "hashbrown 0.13.2",
1061+ "hashbrown 0.14.3",
1125 "helix-loader",
1062 "helix-loader",
1126 "imara-diff",
1063 "imara-diff",
1064+ "indoc",
1127 "log",
1065 "log",
1066+ "nucleo",
1128 "once_cell",
1067 "once_cell",
1068+ "parking_lot",
1129 "quickcheck",
1069 "quickcheck",
1130 "regex",
1070 "regex",
1131 "ropey",
1071 "ropey",
@@ -1144,7 +1084,7 @@ dependencies = [
1144
1084
1145[[package]]
1085[[package]]
1146name = "helix-dap"
1086name = "helix-dap"
1147−version = "0.6.0"
1087+version = "23.10.0"
1148dependencies = [
1088dependencies = [
1149 "anyhow",
1089 "anyhow",
1150 "fern",
1090 "fern",
@@ -1157,33 +1097,47 @@ dependencies = [
1157 "which",
1097 "which",
1158]
1098]
1159
1099
1100+[[package]]
1101+name = "helix-event"
1102+version = "23.10.0"
1103+dependencies = [
1104+ "parking_lot",
1105+ "tokio",
1106+]
1107+
1160[[package]]
1108[[package]]
1161name = "helix-loader"
1109name = "helix-loader"
1162−version = "0.6.0"
1110+version = "23.10.0"
1163dependencies = [
1111dependencies = [
1164 "anyhow",
1112 "anyhow",
1165 "cc",
1113 "cc",
1114+ "dunce",
1166 "etcetera",
1115 "etcetera",
1167 "libloading",
1116 "libloading",
1168 "log",
1117 "log",
1169 "once_cell",
1118 "once_cell",
1170 "serde",
1119 "serde",
1120+ "tempfile",
1171 "threadpool",
1121 "threadpool",
1172 "toml",
1122 "toml",
1173 "tree-sitter",
1123 "tree-sitter",
1124+ "which",
1174]
1125]
1175
1126
1176[[package]]
1127[[package]]
1177name = "helix-lsp"
1128name = "helix-lsp"
1178−version = "0.6.0"
1129+version = "23.10.0"
1179dependencies = [
1130dependencies = [
1180 "anyhow",
1131 "anyhow",
1181 "futures-executor",
1132 "futures-executor",
1182 "futures-util",
1133 "futures-util",
1134+ "globset",
1183 "helix-core",
1135 "helix-core",
1184 "helix-loader",
1136 "helix-loader",
1137+ "helix-parsec",
1185 "log",
1138 "log",
1186 "lsp-types",
1139 "lsp-types",
1140+ "parking_lot",
1187 "serde",
1141 "serde",
1188 "serde_json",
1142 "serde_json",
1189 "thiserror",
1143 "thiserror",
@@ -1192,9 +1146,13 @@ dependencies = [
1192 "which",
1146 "which",
1193]
1147]
1194
1148
1149+[[package]]
1150+name = "helix-parsec"
1151+version = "23.10.0"
1152+
1195[[package]]
1153[[package]]
1196name = "helix-term"
1154name = "helix-term"
1197−version = "0.6.0"
1155+version = "23.10.0"
1198dependencies = [
1156dependencies = [
1199 "anyhow",
1157 "anyhow",
1200 "arc-swap",
1158 "arc-swap",
@@ -1203,11 +1161,11 @@ dependencies = [
1203 "crossterm",
1161 "crossterm",
1204 "fern",
1162 "fern",
1205 "futures-util",
1163 "futures-util",
1206− "fuzzy-matcher",
1207 "grep-regex",
1164 "grep-regex",
1208 "grep-searcher",
1165 "grep-searcher",
1209 "helix-core",
1166 "helix-core",
1210 "helix-dap",
1167 "helix-dap",
1168+ "helix-event",
1211 "helix-loader",
1169 "helix-loader",
1212 "helix-lsp",
1170 "helix-lsp",
1213 "helix-tui",
1171 "helix-tui",
@@ -1215,8 +1173,11 @@ dependencies = [
1215 "helix-view",
1173 "helix-view",
1216 "ignore",
1174 "ignore",
1217 "indoc",
1175 "indoc",
1176+ "libc",
1218 "log",
1177 "log",
1178+ "nucleo",
1219 "once_cell",
1179 "once_cell",
1180+ "open",
1220 "pulldown-cmark",
1181 "pulldown-cmark",
1221 "serde",
1182 "serde",
1222 "serde_json",
1183 "serde_json",
@@ -1227,18 +1188,21 @@ dependencies = [
1227 "tokio",
1188 "tokio",
1228 "tokio-stream",
1189 "tokio-stream",
1229 "toml",
1190 "toml",
1191+ "url",
1230 "which",
1192 "which",
1231]
1193]
1232
1194
1233[[package]]
1195[[package]]
1234name = "helix-tui"
1196name = "helix-tui"
1235−version = "0.6.0"
1197+version = "23.10.0"
1236dependencies = [
1198dependencies = [
1237− "bitflags",
1199+ "bitflags 2.4.1",
1238 "cassowary",
1200 "cassowary",
1239 "crossterm",
1201 "crossterm",
1240 "helix-core",
1202 "helix-core",
1241 "helix-view",
1203 "helix-view",
1204+ "log",
1205+ "once_cell",
1242 "serde",
1206 "serde",
1243 "termini",
1207 "termini",
1244 "unicode-segmentation",
1208 "unicode-segmentation",
@@ -1246,30 +1210,34 @@ dependencies = [
1246
1210
1247[[package]]
1211[[package]]
1248name = "helix-vcs"
1212name = "helix-vcs"
1249−version = "0.6.0"
1213+version = "23.10.0"
1250dependencies = [
1214dependencies = [
1251− "git-repository",
1215+ "anyhow",
1216+ "arc-swap",
1217+ "gix",
1252 "helix-core",
1218 "helix-core",
1219+ "helix-event",
1253 "imara-diff",
1220 "imara-diff",
1254 "log",
1221 "log",
1255− "parking_lot 0.12.1",
1222+ "parking_lot",
1256 "tempfile",
1223 "tempfile",
1257 "tokio",
1224 "tokio",
1258]
1225]
1259
1226
1260[[package]]
1227[[package]]
1261name = "helix-view"
1228name = "helix-view"
1262−version = "0.6.0"
1229+version = "23.10.0"
1263dependencies = [
1230dependencies = [
1264 "anyhow",
1231 "anyhow",
1265 "arc-swap",
1232 "arc-swap",
1266− "bitflags",
1233+ "bitflags 2.4.1",
1267 "chardetng",
1234 "chardetng",
1268 "clipboard-win",
1235 "clipboard-win",
1269 "crossterm",
1236 "crossterm",
1270 "futures-util",
1237 "futures-util",
1271 "helix-core",
1238 "helix-core",
1272 "helix-dap",
1239 "helix-dap",
1240+ "helix-event",
1273 "helix-loader",
1241 "helix-loader",
1274 "helix-lsp",
1242 "helix-lsp",
1275 "helix-tui",
1243 "helix-tui",
@@ -1277,6 +1245,8 @@ dependencies = [
1277 "libc",
1245 "libc",
1278 "log",
1246 "log",
1279 "once_cell",
1247 "once_cell",
1248+ "parking_lot",
1249+ "rustix",
1280 "serde",
1250 "serde",
1281 "serde_json",
1251 "serde_json",
1282 "slotmap",
1252 "slotmap",
@@ -1289,46 +1259,34 @@ dependencies = [
1289
1259
1290[[package]]
1260[[package]]
1291name = "hermit-abi"
1261name = "hermit-abi"
1292−version = "0.1.19"
1262+version = "0.2.6"
1293source = "registry+https://github.com/rust-lang/crates.io-index"
1263source = "registry+https://github.com/rust-lang/crates.io-index"
1294−checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
1264+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
1295dependencies = [
1265dependencies = [
1296 "libc",
1266 "libc",
1297]
1267]
1298
1268
1299−[[package]]
1300−name = "hex"
1301−version = "0.4.3"
1302−source = "registry+https://github.com/rust-lang/crates.io-index"
1303−checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1304−
1305[[package]]
1269[[package]]
1306name = "home"
1270name = "home"
1307−version = "0.5.4"
1271+version = "0.5.5"
1308source = "registry+https://github.com/rust-lang/crates.io-index"
1272source = "registry+https://github.com/rust-lang/crates.io-index"
1309−checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
1273+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
1310dependencies = [
1274dependencies = [
1311− "winapi",
1275+ "windows-sys 0.48.0",
1312]
1276]
1313
1277
1314−[[package]]
1315−name = "human_format"
1316−version = "1.0.3"
1317−source = "registry+https://github.com/rust-lang/crates.io-index"
1318−checksum = "86cce260d758a9aa3d7c4b99d55c815a540f8a37514ba6046ab6be402a157cb0"
1319−
1320[[package]]
1278[[package]]
1321name = "iana-time-zone"
1279name = "iana-time-zone"
1322−version = "0.1.53"
1280+version = "0.1.56"
1323source = "registry+https://github.com/rust-lang/crates.io-index"
1281source = "registry+https://github.com/rust-lang/crates.io-index"
1324−checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
1282+checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
1325dependencies = [
1283dependencies = [
1326 "android_system_properties",
1284 "android_system_properties",
1327 "core-foundation-sys",
1285 "core-foundation-sys",
1328 "iana-time-zone-haiku",
1286 "iana-time-zone-haiku",
1329 "js-sys",
1287 "js-sys",
1330 "wasm-bindgen",
1288 "wasm-bindgen",
1331− "winapi",
1289+ "windows 0.48.0",
1332]
1290]
1333
1291
1334[[package]]
1292[[package]]
@@ -1343,9 +1301,9 @@ dependencies = [
1343
1301
1344[[package]]
1302[[package]]
1345name = "idna"
1303name = "idna"
1346−version = "0.3.0"
1304+version = "0.5.0"
1347source = "registry+https://github.com/rust-lang/crates.io-index"
1305source = "registry+https://github.com/rust-lang/crates.io-index"
1348−checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
1306+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
1349dependencies = [
1307dependencies = [
1350 "unicode-bidi",
1308 "unicode-bidi",
1351 "unicode-normalization",
1309 "unicode-normalization",
@@ -1353,18 +1311,16 @@ dependencies = [
1353
1311
1354[[package]]
1312[[package]]
1355name = "ignore"
1313name = "ignore"
1356−version = "0.4.18"
1314+version = "0.4.21"
1357source = "registry+https://github.com/rust-lang/crates.io-index"
1315source = "registry+https://github.com/rust-lang/crates.io-index"
1358−checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
1316+checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
1359dependencies = [
1317dependencies = [
1360− "crossbeam-utils",
1318+ "crossbeam-deque",
1361 "globset",
1319 "globset",
1362− "lazy_static",
1363 "log",
1320 "log",
1364 "memchr",
1321 "memchr",
1365− "regex",
1322+ "regex-automata",
1366 "same-file",
1323 "same-file",
1367− "thread_local",
1368 "walkdir",
1324 "walkdir",
1369 "winapi-util",
1325 "winapi-util",
1370]
1326]
@@ -1375,91 +1331,91 @@ version = "0.1.5"
1375source = "registry+https://github.com/rust-lang/crates.io-index"
1331source = "registry+https://github.com/rust-lang/crates.io-index"
1376checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
1332checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
1377dependencies = [
1333dependencies = [
1378− "ahash 0.8.3",
1334+ "ahash",
1379 "hashbrown 0.12.3",
1335 "hashbrown 0.12.3",
1380]
1336]
1381
1337
1382[[package]]
1338[[package]]
1383name = "indexmap"
1339name = "indexmap"
1384−version = "1.9.2"
1340+version = "2.0.0"
1385source = "registry+https://github.com/rust-lang/crates.io-index"
1341source = "registry+https://github.com/rust-lang/crates.io-index"
1386−checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
1342+checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
1387dependencies = [
1343dependencies = [
1388− "autocfg",
1344+ "equivalent",
1389− "hashbrown 0.12.3",
1345+ "hashbrown 0.14.3",
1390]
1346]
1391
1347
1392[[package]]
1348[[package]]
1393name = "indoc"
1349name = "indoc"
1394−version = "2.0.0"
1350+version = "2.0.4"
1395source = "registry+https://github.com/rust-lang/crates.io-index"
1351source = "registry+https://github.com/rust-lang/crates.io-index"
1396−checksum = "6fe2b9d82064e8a0226fddb3547f37f28eaa46d0fc210e275d835f08cf3b76a7"
1352+checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
1397
1353
1398[[package]]
1354[[package]]
1399−name = "instant"
1355+name = "is-docker"
1400−version = "0.1.12"
1356+version = "0.2.0"
1401source = "registry+https://github.com/rust-lang/crates.io-index"
1357source = "registry+https://github.com/rust-lang/crates.io-index"
1402−checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
1358+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
1403dependencies = [
1359dependencies = [
1404− "cfg-if",
1360+ "once_cell",
1405]
1361]
1406
1362
1407[[package]]
1363[[package]]
1408−name = "io-close"
1364+name = "is-wsl"
1409−version = "0.3.7"
1365+version = "0.4.0"
1410source = "registry+https://github.com/rust-lang/crates.io-index"
1366source = "registry+https://github.com/rust-lang/crates.io-index"
1411−checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc"
1367+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
1412dependencies = [
1368dependencies = [
1413− "libc",
1369+ "is-docker",
1414− "winapi",
1370+ "once_cell",
1415]
1371]
1416
1372
1417[[package]]
1373[[package]]
1418name = "itoa"
1374name = "itoa"
1419−version = "1.0.4"
1375+version = "1.0.6"
1420source = "registry+https://github.com/rust-lang/crates.io-index"
1376source = "registry+https://github.com/rust-lang/crates.io-index"
1421−checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
1377+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
1422
1378
1423[[package]]
1379[[package]]
1424name = "js-sys"
1380name = "js-sys"
1425−version = "0.3.60"
1381+version = "0.3.61"
1426source = "registry+https://github.com/rust-lang/crates.io-index"
1382source = "registry+https://github.com/rust-lang/crates.io-index"
1427−checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
1383+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
1428dependencies = [
1384dependencies = [
1429 "wasm-bindgen",
1385 "wasm-bindgen",
1430]
1386]
1431
1387
1432−[[package]]
1433−name = "lazy_static"
1434−version = "1.4.0"
1435−source = "registry+https://github.com/rust-lang/crates.io-index"
1436−checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
1437−
1438[[package]]
1388[[package]]
1439name = "libc"
1389name = "libc"
1440−version = "0.2.139"
1390+version = "0.2.151"
1441source = "registry+https://github.com/rust-lang/crates.io-index"
1391source = "registry+https://github.com/rust-lang/crates.io-index"
1442−checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
1392+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
1443
1393
1444[[package]]
1394[[package]]
1445name = "libloading"
1395name = "libloading"
1446−version = "0.7.4"
1396+version = "0.8.1"
1447source = "registry+https://github.com/rust-lang/crates.io-index"
1397source = "registry+https://github.com/rust-lang/crates.io-index"
1448−checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
1398+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
1449dependencies = [
1399dependencies = [
1450 "cfg-if",
1400 "cfg-if",
1451− "winapi",
1401+ "windows-sys 0.48.0",
1452]
1402]
1453
1403
1454[[package]]
1404[[package]]
1455name = "link-cplusplus"
1405name = "link-cplusplus"
1456−version = "1.0.7"
1406+version = "1.0.8"
1457source = "registry+https://github.com/rust-lang/crates.io-index"
1407source = "registry+https://github.com/rust-lang/crates.io-index"
1458−checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
1408+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
1459dependencies = [
1409dependencies = [
1460 "cc",
1410 "cc",
1461]
1411]
1462
1412
1413+[[package]]
1414+name = "linux-raw-sys"
1415+version = "0.4.11"
1416+source = "registry+https://github.com/rust-lang/crates.io-index"
1417+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
1418+
1463[[package]]
1419[[package]]
1464name = "lock_api"
1420name = "lock_api"
1465version = "0.4.9"
1421version = "0.4.9"
@@ -1472,20 +1428,17 @@ dependencies = [
1472
1428
1473[[package]]
1429[[package]]
1474name = "log"
1430name = "log"
1475−version = "0.4.17"
1431+version = "0.4.20"
1476source = "registry+https://github.com/rust-lang/crates.io-index"
1432source = "registry+https://github.com/rust-lang/crates.io-index"
1477−checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
1433+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
1478−dependencies = [
1479− "cfg-if",
1480−]
1481
1434
1482[[package]]
1435[[package]]
1483name = "lsp-types"
1436name = "lsp-types"
1484−version = "0.94.0"
1437+version = "0.95.0"
1485source = "registry+https://github.com/rust-lang/crates.io-index"
1438source = "registry+https://github.com/rust-lang/crates.io-index"
1486−checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
1439+checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
1487dependencies = [
1440dependencies = [
1488− "bitflags",
1441+ "bitflags 1.3.2",
1489 "serde",
1442 "serde",
1490 "serde_json",
1443 "serde_json",
1491 "serde_repr",
1444 "serde_repr",
@@ -1494,85 +1447,69 @@ dependencies = [
1494
1447
1495[[package]]
1448[[package]]
1496name = "memchr"
1449name = "memchr"
1497−version = "2.5.0"
1450+version = "2.6.3"
1498source = "registry+https://github.com/rust-lang/crates.io-index"
1451source = "registry+https://github.com/rust-lang/crates.io-index"
1499−checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
1452+checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
1500
1453
1501[[package]]
1454[[package]]
1502name = "memmap2"
1455name = "memmap2"
1503−version = "0.5.8"
1456+version = "0.9.0"
1504source = "registry+https://github.com/rust-lang/crates.io-index"
1457source = "registry+https://github.com/rust-lang/crates.io-index"
1505−checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc"
1458+checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
1506dependencies = [
1459dependencies = [
1507 "libc",
1460 "libc",
1508]
1461]
1509
1462
1510[[package]]
1463[[package]]
1511−name = "minimal-lexical"
1464+name = "memoffset"
1512−version = "0.2.1"
1465+version = "0.9.0"
1513source = "registry+https://github.com/rust-lang/crates.io-index"
1466source = "registry+https://github.com/rust-lang/crates.io-index"
1514−checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
1467+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
1468+dependencies = [
1469+ "autocfg",
1470+]
1515
1471
1516[[package]]
1472[[package]]
1517name = "miniz_oxide"
1473name = "miniz_oxide"
1518−version = "0.6.2"
1474+version = "0.7.1"
1519source = "registry+https://github.com/rust-lang/crates.io-index"
1475source = "registry+https://github.com/rust-lang/crates.io-index"
1520−checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
1476+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
1521dependencies = [
1477dependencies = [
1522 "adler",
1478 "adler",
1523]
1479]
1524
1480
1525[[package]]
1481[[package]]
1526name = "mio"
1482name = "mio"
1527−version = "0.8.5"
1483+version = "0.8.9"
1528source = "registry+https://github.com/rust-lang/crates.io-index"
1484source = "registry+https://github.com/rust-lang/crates.io-index"
1529−checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
1485+checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
1530dependencies = [
1486dependencies = [
1531 "libc",
1487 "libc",
1532 "log",
1488 "log",
1533 "wasi",
1489 "wasi",
1534− "windows-sys",
1490+ "windows-sys 0.48.0",
1535−]
1536−
1537−[[package]]
1538−name = "nix"
1539−version = "0.26.1"
1540−source = "registry+https://github.com/rust-lang/crates.io-index"
1541−checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694"
1542−dependencies = [
1543− "bitflags",
1544− "cfg-if",
1545− "libc",
1546− "static_assertions",
1547]
1491]
1548
1492
1549[[package]]
1493[[package]]
1550−name = "nom"
1494+name = "nucleo"
1551−version = "7.1.1"
1495+version = "0.2.1"
1552source = "registry+https://github.com/rust-lang/crates.io-index"
1496source = "registry+https://github.com/rust-lang/crates.io-index"
1553−checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
1497+checksum = "ae5331f4bcce475cf28cb29c95366c3091af4b0aa7703f1a6bc858f29718fdf3"
1554dependencies = [
1498dependencies = [
1555− "memchr",
1499+ "nucleo-matcher",
1556− "minimal-lexical",
1500+ "parking_lot",
1501+ "rayon",
1557]
1502]
1558
1503
1559[[package]]
1504[[package]]
1560−name = "nom8"
1505+name = "nucleo-matcher"
1561version = "0.2.0"
1506version = "0.2.0"
1562source = "registry+https://github.com/rust-lang/crates.io-index"
1507source = "registry+https://github.com/rust-lang/crates.io-index"
1563−checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
1508+checksum = "1b702b402fe286162d1f00b552a046ce74365d2ac473a2607ff36ba650f9bd57"
1564dependencies = [
1509dependencies = [
1510+ "cov-mark",
1565 "memchr",
1511 "memchr",
1566−]
1512+ "unicode-segmentation",
1567−
1568−[[package]]
1569−name = "num-integer"
1570−version = "0.1.45"
1571−source = "registry+https://github.com/rust-lang/crates.io-index"
1572−checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
1573−dependencies = [
1574− "autocfg",
1575− "num-traits",
1576]
1513]
1577
1514
1578[[package]]
1515[[package]]
@@ -1586,9 +1523,9 @@ dependencies = [
1586
1523
1587[[package]]
1524[[package]]
1588name = "num_cpus"
1525name = "num_cpus"
1589−version = "1.14.0"
1526+version = "1.15.0"
1590source = "registry+https://github.com/rust-lang/crates.io-index"
1527source = "registry+https://github.com/rust-lang/crates.io-index"
1591−checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
1528+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
1592dependencies = [
1529dependencies = [
1593 "hermit-abi",
1530 "hermit-abi",
1594 "libc",
1531 "libc",
@@ -1603,21 +1540,30 @@ dependencies = [
1603 "libc",
1540 "libc",
1604]
1541]
1605
1542
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+
1606[[package]]
1552[[package]]
1607name = "once_cell"
1553name = "once_cell"
1608−version = "1.17.1"
1554+version = "1.19.0"
1609source = "registry+https://github.com/rust-lang/crates.io-index"
1555source = "registry+https://github.com/rust-lang/crates.io-index"
1610−checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
1556+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
1611
1557
1612[[package]]
1558[[package]]
1613−name = "parking_lot"
1559+name = "open"
1614−version = "0.11.2"
1560+version = "5.0.1"
1615source = "registry+https://github.com/rust-lang/crates.io-index"
1561source = "registry+https://github.com/rust-lang/crates.io-index"
1616−checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
1562+checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349"
1617dependencies = [
1563dependencies = [
1618− "instant",
1564+ "is-wsl",
1619− "lock_api",
1565+ "libc",
1620− "parking_lot_core 0.8.6",
1566+ "pathdiff",
1621]
1567]
1622
1568
1623[[package]]
1569[[package]]
@@ -1627,47 +1573,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1627checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
1573checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
1628dependencies = [
1574dependencies = [
1629 "lock_api",
1575 "lock_api",
1630− "parking_lot_core 0.9.4",
1576+ "parking_lot_core",
1631]
1577]
1632
1578
1633[[package]]
1579[[package]]
1634name = "parking_lot_core"
1580name = "parking_lot_core"
1635−version = "0.8.6"
1581+version = "0.9.7"
1636source = "registry+https://github.com/rust-lang/crates.io-index"
1582source = "registry+https://github.com/rust-lang/crates.io-index"
1637−checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
1583+checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
1638dependencies = [
1584dependencies = [
1639 "cfg-if",
1585 "cfg-if",
1640− "instant",
1641 "libc",
1586 "libc",
1642− "redox_syscall",
1587+ "redox_syscall 0.2.16",
1643 "smallvec",
1588 "smallvec",
1644− "winapi",
1589+ "windows-sys 0.45.0",
1645]
1590]
1646
1591
1647[[package]]
1592[[package]]
1648−name = "parking_lot_core"
1593+name = "pathdiff"
1649−version = "0.9.4"
1594+version = "0.2.1"
1650source = "registry+https://github.com/rust-lang/crates.io-index"
1595source = "registry+https://github.com/rust-lang/crates.io-index"
1651−checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
1596+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
1652−dependencies = [
1653− "cfg-if",
1654− "libc",
1655− "redox_syscall",
1656− "smallvec",
1657− "windows-sys",
1658−]
1659
1597
1660[[package]]
1598[[package]]
1661name = "percent-encoding"
1599name = "percent-encoding"
1662−version = "2.2.0"
1600+version = "2.3.1"
1663source = "registry+https://github.com/rust-lang/crates.io-index"
1601source = "registry+https://github.com/rust-lang/crates.io-index"
1664−checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
1602+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
1665
1603
1666[[package]]
1604[[package]]
1667name = "pin-project-lite"
1605name = "pin-project-lite"
1668−version = "0.2.9"
1606+version = "0.2.12"
1669source = "registry+https://github.com/rust-lang/crates.io-index"
1607source = "registry+https://github.com/rust-lang/crates.io-index"
1670−checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
1608+checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
1671
1609
1672[[package]]
1610[[package]]
1673name = "pin-utils"
1611name = "pin-utils"
@@ -1677,42 +1615,30 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1677
1615
1678[[package]]
1616[[package]]
1679name = "proc-macro2"
1617name = "proc-macro2"
1680−version = "1.0.47"
1618+version = "1.0.69"
1681source = "registry+https://github.com/rust-lang/crates.io-index"
1619source = "registry+https://github.com/rust-lang/crates.io-index"
1682−checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
1620+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
1683dependencies = [
1621dependencies = [
1684 "unicode-ident",
1622 "unicode-ident",
1685]
1623]
1686
1624
1687[[package]]
1625[[package]]
1688name = "prodash"
1626name = "prodash"
1689−version = "23.0.0"
1627+version = "28.0.0"
1690source = "registry+https://github.com/rust-lang/crates.io-index"
1628source = "registry+https://github.com/rust-lang/crates.io-index"
1691−checksum = "5d8c414345b4a98cbcd0e8d8829c8f54b47a7ed4fb771c45b7c5c6c0ae23dc4c"
1629+checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
1692−dependencies = [
1693− "bytesize",
1694− "dashmap",
1695− "human_format",
1696− "parking_lot 0.11.2",
1697−]
1698
1630
1699[[package]]
1631[[package]]
1700name = "pulldown-cmark"
1632name = "pulldown-cmark"
1701−version = "0.9.2"
1633+version = "0.9.3"
1702source = "registry+https://github.com/rust-lang/crates.io-index"
1634source = "registry+https://github.com/rust-lang/crates.io-index"
1703−checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
1635+checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
1704dependencies = [
1636dependencies = [
1705− "bitflags",
1637+ "bitflags 1.3.2",
1706 "memchr",
1638 "memchr",
1707 "unicase",
1639 "unicase",
1708]
1640]
1709
1641
1710−[[package]]
1711−name = "quick-error"
1712−version = "2.0.1"
1713−source = "registry+https://github.com/rust-lang/crates.io-index"
1714−checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
1715−
1716[[package]]
1642[[package]]
1717name = "quickcheck"
1643name = "quickcheck"
1718version = "1.0.3"
1644version = "1.0.3"
@@ -1724,9 +1650,9 @@ dependencies = [
1724
1650
1725[[package]]
1651[[package]]
1726name = "quote"
1652name = "quote"
1727−version = "1.0.21"
1653+version = "1.0.29"
1728source = "registry+https://github.com/rust-lang/crates.io-index"
1654source = "registry+https://github.com/rust-lang/crates.io-index"
1729−checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
1655+checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
1730dependencies = [
1656dependencies = [
1731 "proc-macro2",
1657 "proc-macro2",
1732]
1658]
@@ -1749,79 +1675,109 @@ dependencies = [
1749 "getrandom",
1675 "getrandom",
1750]
1676]
1751
1677
1678+[[package]]
1679+name = "rayon"
1680+version = "1.7.0"
1681+source = "registry+https://github.com/rust-lang/crates.io-index"
1682+checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
1683+dependencies = [
1684+ "either",
1685+ "rayon-core",
1686+]
1687+
1688+[[package]]
1689+name = "rayon-core"
1690+version = "1.11.0"
1691+source = "registry+https://github.com/rust-lang/crates.io-index"
1692+checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
1693+dependencies = [
1694+ "crossbeam-channel",
1695+ "crossbeam-deque",
1696+ "crossbeam-utils",
1697+ "num_cpus",
1698+]
1699+
1752[[package]]
1700[[package]]
1753name = "redox_syscall"
1701name = "redox_syscall"
1754version = "0.2.16"
1702version = "0.2.16"
1755source = "registry+https://github.com/rust-lang/crates.io-index"
1703source = "registry+https://github.com/rust-lang/crates.io-index"
1756checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
1704checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
1757dependencies = [
1705dependencies = [
1758− "bitflags",
1706+ "bitflags 1.3.2",
1759]
1707]
1760
1708
1761[[package]]
1709[[package]]
1762−name = "redox_users"
1710+name = "redox_syscall"
1763−version = "0.4.3"
1711+version = "0.4.1"
1764source = "registry+https://github.com/rust-lang/crates.io-index"
1712source = "registry+https://github.com/rust-lang/crates.io-index"
1765−checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
1713+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
1766dependencies = [
1714dependencies = [
1767− "getrandom",
1715+ "bitflags 1.3.2",
1768− "redox_syscall",
1769− "thiserror",
1770]
1716]
1771
1717
1772[[package]]
1718[[package]]
1773name = "regex"
1719name = "regex"
1774−version = "1.7.1"
1720+version = "1.10.2"
1775source = "registry+https://github.com/rust-lang/crates.io-index"
1721source = "registry+https://github.com/rust-lang/crates.io-index"
1776−checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
1722+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
1777dependencies = [
1723dependencies = [
1778 "aho-corasick",
1724 "aho-corasick",
1779 "memchr",
1725 "memchr",
1726+ "regex-automata",
1780 "regex-syntax",
1727 "regex-syntax",
1781]
1728]
1782
1729
1783[[package]]
1730[[package]]
1784name = "regex-automata"
1731name = "regex-automata"
1785−version = "0.1.10"
1732+version = "0.4.3"
1786source = "registry+https://github.com/rust-lang/crates.io-index"
1733source = "registry+https://github.com/rust-lang/crates.io-index"
1787−checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
1734+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
1735+dependencies = [
1736+ "aho-corasick",
1737+ "memchr",
1738+ "regex-syntax",
1739+]
1788
1740
1789[[package]]
1741[[package]]
1790name = "regex-syntax"
1742name = "regex-syntax"
1791−version = "0.6.28"
1743+version = "0.8.2"
1792−source = "registry+https://github.com/rust-lang/crates.io-index"
1793−checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
1794−
1795−[[package]]
1796−name = "remove_dir_all"
1797−version = "0.5.3"
1798source = "registry+https://github.com/rust-lang/crates.io-index"
1744source = "registry+https://github.com/rust-lang/crates.io-index"
1799−checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
1745+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
1800−dependencies = [
1801− "winapi",
1802−]
1803
1746
1804[[package]]
1747[[package]]
1805name = "ropey"
1748name = "ropey"
1806−version = "1.6.0"
1749+version = "1.6.1"
1807source = "registry+https://github.com/rust-lang/crates.io-index"
1750source = "registry+https://github.com/rust-lang/crates.io-index"
1808−checksum = "53ce7a2c43a32e50d666e33c5a80251b31147bb4b49024bcab11fb6f20c671ed"
1751+checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
1809dependencies = [
1752dependencies = [
1810 "smallvec",
1753 "smallvec",
1811 "str_indices",
1754 "str_indices",
1812]
1755]
1813
1756
1814[[package]]
1757[[package]]
1815−name = "rustversion"
1758+name = "rustc-demangle"
1816−version = "1.0.9"
1759+version = "0.1.23"
1760+source = "registry+https://github.com/rust-lang/crates.io-index"
1761+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
1762+
1763+[[package]]
1764+name = "rustix"
1765+version = "0.38.28"
1817source = "registry+https://github.com/rust-lang/crates.io-index"
1766source = "registry+https://github.com/rust-lang/crates.io-index"
1818−checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
1767+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
1768+dependencies = [
1769+ "bitflags 2.4.1",
1770+ "errno",
1771+ "libc",
1772+ "linux-raw-sys",
1773+ "windows-sys 0.52.0",
1774+]
1819
1775
1820[[package]]
1776[[package]]
1821name = "ryu"
1777name = "ryu"
1822−version = "1.0.11"
1778+version = "1.0.13"
1823source = "registry+https://github.com/rust-lang/crates.io-index"
1779source = "registry+https://github.com/rust-lang/crates.io-index"
1824−checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
1780+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
1825
1781
1826[[package]]
1782[[package]]
1827name = "same-file"
1783name = "same-file"
@@ -1840,35 +1796,35 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1840
1796
1841[[package]]
1797[[package]]
1842name = "scratch"
1798name = "scratch"
1843−version = "1.0.2"
1799+version = "1.0.5"
1844source = "registry+https://github.com/rust-lang/crates.io-index"
1800source = "registry+https://github.com/rust-lang/crates.io-index"
1845−checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
1801+checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
1846
1802
1847[[package]]
1803[[package]]
1848name = "serde"
1804name = "serde"
1849−version = "1.0.152"
1805+version = "1.0.193"
1850source = "registry+https://github.com/rust-lang/crates.io-index"
1806source = "registry+https://github.com/rust-lang/crates.io-index"
1851−checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
1807+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
1852dependencies = [
1808dependencies = [
1853 "serde_derive",
1809 "serde_derive",
1854]
1810]
1855
1811
1856[[package]]
1812[[package]]
1857name = "serde_derive"
1813name = "serde_derive"
1858−version = "1.0.152"
1814+version = "1.0.193"
1859source = "registry+https://github.com/rust-lang/crates.io-index"
1815source = "registry+https://github.com/rust-lang/crates.io-index"
1860−checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
1816+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
1861dependencies = [
1817dependencies = [
1862 "proc-macro2",
1818 "proc-macro2",
1863 "quote",
1819 "quote",
1864− "syn",
1820+ "syn 2.0.38",
1865]
1821]
1866
1822
1867[[package]]
1823[[package]]
1868name = "serde_json"
1824name = "serde_json"
1869−version = "1.0.93"
1825+version = "1.0.108"
1870source = "registry+https://github.com/rust-lang/crates.io-index"
1826source = "registry+https://github.com/rust-lang/crates.io-index"
1871−checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
1827+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
1872dependencies = [
1828dependencies = [
1873 "itoa",
1829 "itoa",
1874 "ryu",
1830 "ryu",
@@ -1877,20 +1833,20 @@ dependencies = [
1877
1833
1878[[package]]
1834[[package]]
1879name = "serde_repr"
1835name = "serde_repr"
1880−version = "0.1.9"
1836+version = "0.1.12"
1881source = "registry+https://github.com/rust-lang/crates.io-index"
1837source = "registry+https://github.com/rust-lang/crates.io-index"
1882−checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca"
1838+checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
1883dependencies = [
1839dependencies = [
1884 "proc-macro2",
1840 "proc-macro2",
1885 "quote",
1841 "quote",
1886− "syn",
1842+ "syn 2.0.38",
1887]
1843]
1888
1844
1889[[package]]
1845[[package]]
1890name = "serde_spanned"
1846name = "serde_spanned"
1891−version = "0.6.1"
1847+version = "0.6.3"
1892source = "registry+https://github.com/rust-lang/crates.io-index"
1848source = "registry+https://github.com/rust-lang/crates.io-index"
1893−checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
1849+checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
1894dependencies = [
1850dependencies = [
1895 "serde",
1851 "serde",
1896]
1852]
@@ -1903,9 +1859,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
1903
1859
1904[[package]]
1860[[package]]
1905name = "signal-hook"
1861name = "signal-hook"
1906−version = "0.3.15"
1862+version = "0.3.17"
1907source = "registry+https://github.com/rust-lang/crates.io-index"
1863source = "registry+https://github.com/rust-lang/crates.io-index"
1908−checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
1864+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
1909dependencies = [
1865dependencies = [
1910 "libc",
1866 "libc",
1911 "signal-hook-registry",
1867 "signal-hook-registry",
@@ -1924,9 +1880,9 @@ dependencies = [
1924
1880
1925[[package]]
1881[[package]]
1926name = "signal-hook-registry"
1882name = "signal-hook-registry"
1927−version = "1.4.0"
1883+version = "1.4.1"
1928source = "registry+https://github.com/rust-lang/crates.io-index"
1884source = "registry+https://github.com/rust-lang/crates.io-index"
1929−checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
1885+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
1930dependencies = [
1886dependencies = [
1931 "libc",
1887 "libc",
1932]
1888]
@@ -1945,27 +1901,27 @@ dependencies = [
1945
1901
1946[[package]]
1902[[package]]
1947name = "slab"
1903name = "slab"
1948−version = "0.4.7"
1904+version = "0.4.8"
1949source = "registry+https://github.com/rust-lang/crates.io-index"
1905source = "registry+https://github.com/rust-lang/crates.io-index"
1950−checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
1906+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
1951dependencies = [
1907dependencies = [
1952 "autocfg",
1908 "autocfg",
1953]
1909]
1954
1910
1955[[package]]
1911[[package]]
1956name = "slotmap"
1912name = "slotmap"
1957−version = "1.0.6"
1913+version = "1.0.7"
1958source = "registry+https://github.com/rust-lang/crates.io-index"
1914source = "registry+https://github.com/rust-lang/crates.io-index"
1959−checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
1915+checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
1960dependencies = [
1916dependencies = [
1961 "version_check",
1917 "version_check",
1962]
1918]
1963
1919
1964[[package]]
1920[[package]]
1965name = "smallvec"
1921name = "smallvec"
1966−version = "1.10.0"
1922+version = "1.11.2"
1967source = "registry+https://github.com/rust-lang/crates.io-index"
1923source = "registry+https://github.com/rust-lang/crates.io-index"
1968−checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
1924+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
1969
1925
1970[[package]]
1926[[package]]
1971name = "smartstring"
1927name = "smartstring"
@@ -1986,12 +1942,12 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
1986
1942
1987[[package]]
1943[[package]]
1988name = "socket2"
1944name = "socket2"
1989−version = "0.4.7"
1945+version = "0.5.5"
1990source = "registry+https://github.com/rust-lang/crates.io-index"
1946source = "registry+https://github.com/rust-lang/crates.io-index"
1991−checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
1947+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
1992dependencies = [
1948dependencies = [
1993 "libc",
1949 "libc",
1994− "winapi",
1950+ "windows-sys 0.48.0",
1995]
1951]
1996
1952
1997[[package]]
1953[[package]]
@@ -2001,22 +1957,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
2001checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1957checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
2002
1958
2003[[package]]
1959[[package]]
2004−name = "str-buf"
1960+name = "str_indices"
2005−version = "1.0.6"
1961+version = "0.4.1"
2006source = "registry+https://github.com/rust-lang/crates.io-index"
1962source = "registry+https://github.com/rust-lang/crates.io-index"
2007−checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
1963+checksum = "5f026164926842ec52deb1938fae44f83dfdb82d0a5b0270c5bd5935ab74d6dd"
2008
1964
2009[[package]]
1965[[package]]
2010−name = "str_indices"
1966+name = "syn"
2011−version = "0.4.0"
1967+version = "1.0.109"
2012source = "registry+https://github.com/rust-lang/crates.io-index"
1968source = "registry+https://github.com/rust-lang/crates.io-index"
2013−checksum = "9d9199fa80c817e074620be84374a520062ebac833f358d74b37060ce4a0f2c0"
1969+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
1970+dependencies = [
1971+ "proc-macro2",
1972+ "quote",
1973+ "unicode-ident",
1974+]
2014
1975
2015[[package]]
1976[[package]]
2016name = "syn"
1977name = "syn"
2017−version = "1.0.104"
1978+version = "2.0.38"
2018source = "registry+https://github.com/rust-lang/crates.io-index"
1979source = "registry+https://github.com/rust-lang/crates.io-index"
2019−checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce"
1980+checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
2020dependencies = [
1981dependencies = [
2021 "proc-macro2",
1982 "proc-macro2",
2022 "quote",
1983 "quote",
@@ -2025,34 +1986,33 @@ dependencies = [
2025
1986
2026[[package]]
1987[[package]]
2027name = "tempfile"
1988name = "tempfile"
2028−version = "3.3.0"
1989+version = "3.8.1"
2029source = "registry+https://github.com/rust-lang/crates.io-index"
1990source = "registry+https://github.com/rust-lang/crates.io-index"
2030−checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
1991+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
2031dependencies = [
1992dependencies = [
2032 "cfg-if",
1993 "cfg-if",
2033 "fastrand",
1994 "fastrand",
2034− "libc",
2035− "redox_syscall",
1995+ "redox_syscall 0.4.1",
2036− "remove_dir_all",
1996+ "rustix",
2037− "winapi",
1997+ "windows-sys 0.48.0",
2038]
1998]
2039
1999
2040[[package]]
2000[[package]]
2041name = "termcolor"
2001name = "termcolor"
2042−version = "1.1.3"
2002+version = "1.2.0"
2043source = "registry+https://github.com/rust-lang/crates.io-index"
2003source = "registry+https://github.com/rust-lang/crates.io-index"
2044−checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
2004+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
2045dependencies = [
2005dependencies = [
2046 "winapi-util",
2006 "winapi-util",
2047]
2007]
2048
2008
2049[[package]]
2009[[package]]
2050name = "termini"
2010name = "termini"
2051−version = "0.1.4"
2011+version = "1.0.0"
2052source = "registry+https://github.com/rust-lang/crates.io-index"
2012source = "registry+https://github.com/rust-lang/crates.io-index"
2053−checksum = "8c0f7ecb9c2a380d2686a747e4fc574043712326e8d39fbd220ab3bd29768a12"
2013+checksum = "2ad441d87dd98bc5eeb31cf2fb7e4839968763006b478efb38668a3bf9da0d59"
2054dependencies = [
2014dependencies = [
2055− "dirs-next",
2015+ "home",
2056]
2016]
2057
2017
2058[[package]]
2018[[package]]
@@ -2068,31 +2028,22 @@ dependencies = [
2068
2028
2069[[package]]
2029[[package]]
2070name = "thiserror"
2030name = "thiserror"
2071−version = "1.0.38"
2031+version = "1.0.52"
2072source = "registry+https://github.com/rust-lang/crates.io-index"
2032source = "registry+https://github.com/rust-lang/crates.io-index"
2073−checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
2033+checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
2074dependencies = [
2034dependencies = [
2075 "thiserror-impl",
2035 "thiserror-impl",
2076]
2036]
2077
2037
2078[[package]]
2038[[package]]
2079name = "thiserror-impl"
2039name = "thiserror-impl"
2080−version = "1.0.38"
2040+version = "1.0.52"
2081source = "registry+https://github.com/rust-lang/crates.io-index"
2041source = "registry+https://github.com/rust-lang/crates.io-index"
2082−checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
2042+checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
2083dependencies = [
2043dependencies = [
2084 "proc-macro2",
2044 "proc-macro2",
2085 "quote",
2045 "quote",
2086− "syn",
2046+ "syn 2.0.38",
2087−]
2088−
2089−[[package]]
2090−name = "thread_local"
2091−version = "1.1.4"
2092−source = "registry+https://github.com/rust-lang/crates.io-index"
2093−checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
2094−dependencies = [
2095− "once_cell",
2096]
2047]
2097
2048
2098[[package]]
2049[[package]]
@@ -2106,9 +2057,9 @@ dependencies = [
2106
2057
2107[[package]]
2058[[package]]
2108name = "time"
2059name = "time"
2109−version = "0.3.17"
2060+version = "0.3.23"
2110source = "registry+https://github.com/rust-lang/crates.io-index"
2061source = "registry+https://github.com/rust-lang/crates.io-index"
2111−checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
2062+checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
2112dependencies = [
2063dependencies = [
2113 "itoa",
2064 "itoa",
2114 "libc",
2065 "libc",
@@ -2120,15 +2071,15 @@ dependencies = [
2120
2071
2121[[package]]
2072[[package]]
2122name = "time-core"
2073name = "time-core"
2123−version = "0.1.0"
2074+version = "0.1.1"
2124source = "registry+https://github.com/rust-lang/crates.io-index"
2075source = "registry+https://github.com/rust-lang/crates.io-index"
2125−checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
2076+checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
2126
2077
2127[[package]]
2078[[package]]
2128name = "time-macros"
2079name = "time-macros"
2129−version = "0.2.6"
2080+version = "0.2.10"
2130source = "registry+https://github.com/rust-lang/crates.io-index"
2081source = "registry+https://github.com/rust-lang/crates.io-index"
2131−checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
2082+checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
2132dependencies = [
2083dependencies = [
2133 "time-core",
2084 "time-core",
2134]
2085]
@@ -2144,46 +2095,45 @@ dependencies = [
2144
2095
2145[[package]]
2096[[package]]
2146name = "tinyvec_macros"
2097name = "tinyvec_macros"
2147−version = "0.1.0"
2098+version = "0.1.1"
2148source = "registry+https://github.com/rust-lang/crates.io-index"
2099source = "registry+https://github.com/rust-lang/crates.io-index"
2149−checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
2100+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
2150
2101
2151[[package]]
2102[[package]]
2152name = "tokio"
2103name = "tokio"
2153−version = "1.25.0"
2104+version = "1.35.1"
2154source = "registry+https://github.com/rust-lang/crates.io-index"
2105source = "registry+https://github.com/rust-lang/crates.io-index"
2155−checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
2106+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
2156dependencies = [
2107dependencies = [
2157− "autocfg",
2108+ "backtrace",
2158 "bytes",
2109 "bytes",
2159 "libc",
2110 "libc",
2160− "memchr",
2161 "mio",
2111 "mio",
2162 "num_cpus",
2112 "num_cpus",
2163− "parking_lot 0.12.1",
2113+ "parking_lot",
2164 "pin-project-lite",
2114 "pin-project-lite",
2165 "signal-hook-registry",
2115 "signal-hook-registry",
2166 "socket2",
2116 "socket2",
2167 "tokio-macros",
2117 "tokio-macros",
2168− "windows-sys",
2118+ "windows-sys 0.48.0",
2169]
2119]
2170
2120
2171[[package]]
2121[[package]]
2172name = "tokio-macros"
2122name = "tokio-macros"
2173−version = "1.8.0"
2123+version = "2.2.0"
2174source = "registry+https://github.com/rust-lang/crates.io-index"
2124source = "registry+https://github.com/rust-lang/crates.io-index"
2175−checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
2125+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
2176dependencies = [
2126dependencies = [
2177 "proc-macro2",
2127 "proc-macro2",
2178 "quote",
2128 "quote",
2179− "syn",
2129+ "syn 2.0.38",
2180]
2130]
2181
2131
2182[[package]]
2132[[package]]
2183name = "tokio-stream"
2133name = "tokio-stream"
2184−version = "0.1.12"
2134+version = "0.1.14"
2185source = "registry+https://github.com/rust-lang/crates.io-index"
2135source = "registry+https://github.com/rust-lang/crates.io-index"
2186−checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313"
2136+checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
2187dependencies = [
2137dependencies = [
2188 "futures-core",
2138 "futures-core",
2189 "pin-project-lite",
2139 "pin-project-lite",
@@ -2192,9 +2142,9 @@ dependencies = [
2192
2142
2193[[package]]
2143[[package]]
2194name = "toml"
2144name = "toml"
2195−version = "0.7.2"
2145+version = "0.7.6"
2196source = "registry+https://github.com/rust-lang/crates.io-index"
2146source = "registry+https://github.com/rust-lang/crates.io-index"
2197−checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
2147+checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
2198dependencies = [
2148dependencies = [
2199 "serde",
2149 "serde",
2200 "serde_spanned",
2150 "serde_spanned",
@@ -2204,31 +2154,30 @@ dependencies = [
2204
2154
2205[[package]]
2155[[package]]
2206name = "toml_datetime"
2156name = "toml_datetime"
2207−version = "0.6.1"
2157+version = "0.6.3"
2208source = "registry+https://github.com/rust-lang/crates.io-index"
2158source = "registry+https://github.com/rust-lang/crates.io-index"
2209−checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
2159+checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
2210dependencies = [
2160dependencies = [
2211 "serde",
2161 "serde",
2212]
2162]
2213
2163
2214[[package]]
2164[[package]]
2215name = "toml_edit"
2165name = "toml_edit"
2216−version = "0.19.3"
2166+version = "0.19.12"
2217source = "registry+https://github.com/rust-lang/crates.io-index"
2167source = "registry+https://github.com/rust-lang/crates.io-index"
2218−checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5"
2168+checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
2219dependencies = [
2169dependencies = [
2220 "indexmap",
2170 "indexmap",
2221− "nom8",
2222 "serde",
2171 "serde",
2223 "serde_spanned",
2172 "serde_spanned",
2224 "toml_datetime",
2173 "toml_datetime",
2174+ "winnow 0.4.6",
2225]
2175]
2226
2176
2227[[package]]
2177[[package]]
2228name = "tree-sitter"
2178name = "tree-sitter"
2229−version = "0.20.9"
2179+version = "0.20.10"
2230−source = "registry+https://github.com/rust-lang/crates.io-index"
2180+source = "git+https://github.com/tree-sitter/tree-sitter?rev=ab09ae20d640711174b8da8a654f6b3dec93da1a#ab09ae20d640711174b8da8a654f6b3dec93da1a"
2231−checksum = "d4423c784fe11398ca91e505cdc71356b07b1a924fc8735cfab5333afe3e18bc"
2232dependencies = [
2181dependencies = [
2233 "cc",
2182 "cc",
2234 "regex",
2183 "regex",
@@ -2245,15 +2194,15 @@ dependencies = [
2245
2194
2246[[package]]
2195[[package]]
2247name = "unicode-bidi"
2196name = "unicode-bidi"
2248−version = "0.3.8"
2197+version = "0.3.13"
2249source = "registry+https://github.com/rust-lang/crates.io-index"
2198source = "registry+https://github.com/rust-lang/crates.io-index"
2250−checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
2199+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
2251
2200
2252[[package]]
2201[[package]]
2253name = "unicode-bom"
2202name = "unicode-bom"
2254−version = "1.1.4"
2203+version = "2.0.2"
2255source = "registry+https://github.com/rust-lang/crates.io-index"
2204source = "registry+https://github.com/rust-lang/crates.io-index"
2256−checksum = "63ec69f541d875b783ca40184d655f2927c95f0bffd486faa83cd3ac3529ec32"
2205+checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552"
2257
2206
2258[[package]]
2207[[package]]
2259name = "unicode-general-category"
2208name = "unicode-general-category"
@@ -2263,19 +2212,15 @@ checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7"
2263
2212
2264[[package]]
2213[[package]]
2265name = "unicode-ident"
2214name = "unicode-ident"
2266−version = "1.0.5"
2215+version = "1.0.8"
2267source = "registry+https://github.com/rust-lang/crates.io-index"
2216source = "registry+https://github.com/rust-lang/crates.io-index"
2268−checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
2217+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
2269
2218
2270[[package]]
2219[[package]]
2271name = "unicode-linebreak"
2220name = "unicode-linebreak"
2272−version = "0.1.4"
2221+version = "0.1.5"
2273source = "registry+https://github.com/rust-lang/crates.io-index"
2222source = "registry+https://github.com/rust-lang/crates.io-index"
2274−checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
2223+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
2275−dependencies = [
2276− "hashbrown 0.12.3",
2277− "regex",
2278−]
2279
2224
2280[[package]]
2225[[package]]
2281name = "unicode-normalization"
2226name = "unicode-normalization"
@@ -2294,15 +2239,15 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
2294
2239
2295[[package]]
2240[[package]]
2296name = "unicode-width"
2241name = "unicode-width"
2297−version = "0.1.10"
2242+version = "0.1.11"
2298source = "registry+https://github.com/rust-lang/crates.io-index"
2243source = "registry+https://github.com/rust-lang/crates.io-index"
2299−checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
2244+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
2300
2245
2301[[package]]
2246[[package]]
2302name = "url"
2247name = "url"
2303−version = "2.3.1"
2248+version = "2.5.0"
2304source = "registry+https://github.com/rust-lang/crates.io-index"
2249source = "registry+https://github.com/rust-lang/crates.io-index"
2305−checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
2250+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
2306dependencies = [
2251dependencies = [
2307 "form_urlencoded",
2252 "form_urlencoded",
2308 "idna",
2253 "idna",
@@ -2318,12 +2263,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
2318
2263
2319[[package]]
2264[[package]]
2320name = "walkdir"
2265name = "walkdir"
2321−version = "2.3.2"
2266+version = "2.4.0"
2322source = "registry+https://github.com/rust-lang/crates.io-index"
2267source = "registry+https://github.com/rust-lang/crates.io-index"
2323−checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
2268+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
2324dependencies = [
2269dependencies = [
2325 "same-file",
2270 "same-file",
2326− "winapi",
2327 "winapi-util",
2271 "winapi-util",
2328]
2272]
2329
2273
@@ -2335,9 +2279,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
2335
2279
2336[[package]]
2280[[package]]
2337name = "wasm-bindgen"
2281name = "wasm-bindgen"
2338−version = "0.2.83"
2282+version = "0.2.84"
2339source = "registry+https://github.com/rust-lang/crates.io-index"
2283source = "registry+https://github.com/rust-lang/crates.io-index"
2340−checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
2284+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
2341dependencies = [
2285dependencies = [
2342 "cfg-if",
2286 "cfg-if",
2343 "wasm-bindgen-macro",
2287 "wasm-bindgen-macro",
@@ -2345,24 +2289,24 @@ dependencies = [
2345
2289
2346[[package]]
2290[[package]]
2347name = "wasm-bindgen-backend"
2291name = "wasm-bindgen-backend"
2348−version = "0.2.83"
2292+version = "0.2.84"
2349source = "registry+https://github.com/rust-lang/crates.io-index"
2293source = "registry+https://github.com/rust-lang/crates.io-index"
2350−checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
2294+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
2351dependencies = [
2295dependencies = [
2352 "bumpalo",
2296 "bumpalo",
2353 "log",
2297 "log",
2354 "once_cell",
2298 "once_cell",
2355 "proc-macro2",
2299 "proc-macro2",
2356 "quote",
2300 "quote",
2357− "syn",
2301+ "syn 1.0.109",
2358 "wasm-bindgen-shared",
2302 "wasm-bindgen-shared",
2359]
2303]
2360
2304
2361[[package]]
2305[[package]]
2362name = "wasm-bindgen-macro"
2306name = "wasm-bindgen-macro"
2363−version = "0.2.83"
2307+version = "0.2.84"
2364source = "registry+https://github.com/rust-lang/crates.io-index"
2308source = "registry+https://github.com/rust-lang/crates.io-index"
2365−checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
2309+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
2366dependencies = [
2310dependencies = [
2367 "quote",
2311 "quote",
2368 "wasm-bindgen-macro-support",
2312 "wasm-bindgen-macro-support",
@@ -2370,32 +2314,34 @@ dependencies = [
2370
2314
2371[[package]]
2315[[package]]
2372name = "wasm-bindgen-macro-support"
2316name = "wasm-bindgen-macro-support"
2373−version = "0.2.83"
2317+version = "0.2.84"
2374source = "registry+https://github.com/rust-lang/crates.io-index"
2318source = "registry+https://github.com/rust-lang/crates.io-index"
2375−checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
2319+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
2376dependencies = [
2320dependencies = [
2377 "proc-macro2",
2321 "proc-macro2",
2378 "quote",
2322 "quote",
2379− "syn",
2323+ "syn 1.0.109",
2380 "wasm-bindgen-backend",
2324 "wasm-bindgen-backend",
2381 "wasm-bindgen-shared",
2325 "wasm-bindgen-shared",
2382]
2326]
2383
2327
2384[[package]]
2328[[package]]
2385name = "wasm-bindgen-shared"
2329name = "wasm-bindgen-shared"
2386−version = "0.2.83"
2330+version = "0.2.84"
2387source = "registry+https://github.com/rust-lang/crates.io-index"
2331source = "registry+https://github.com/rust-lang/crates.io-index"
2388−checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
2332+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
2389
2333
2390[[package]]
2334[[package]]
2391name = "which"
2335name = "which"
2392−version = "4.4.0"
2336+version = "5.0.0"
2393source = "registry+https://github.com/rust-lang/crates.io-index"
2337source = "registry+https://github.com/rust-lang/crates.io-index"
2394−checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
2338+checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14"
2395dependencies = [
2339dependencies = [
2396 "either",
2340 "either",
2397− "libc",
2341+ "home",
2398 "once_cell",
2342 "once_cell",
2343+ "rustix",
2344+ "windows-sys 0.48.0",
2399]
2345]
2400
2346
2401[[package]]
2347[[package]]
@@ -2431,121 +2377,251 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
2431
2377
2432[[package]]
2378[[package]]
2433name = "windows"
2379name = "windows"
2434−version = "0.40.0"
2380+version = "0.48.0"
2381+source = "registry+https://github.com/rust-lang/crates.io-index"
2382+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
2383+dependencies = [
2384+ "windows-targets 0.48.0",
2385+]
2386+
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]]
2407+name = "windows-sys"
2408+version = "0.45.0"
2409+source = "registry+https://github.com/rust-lang/crates.io-index"
2410+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
2411+dependencies = [
2412+ "windows-targets 0.42.2",
2413+]
2414+
2415+[[package]]
2416+name = "windows-sys"
2417+version = "0.48.0"
2435source = "registry+https://github.com/rust-lang/crates.io-index"
2418source = "registry+https://github.com/rust-lang/crates.io-index"
2436−checksum = "e30acc718a52fb130fec72b1cb5f55ffeeec9253e1b785e94db222178a6acaa1"
2419+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
2437dependencies = [
2420dependencies = [
2438− "windows_aarch64_gnullvm 0.40.0",
2421+ "windows-targets 0.48.0",
2439− "windows_aarch64_msvc 0.40.0",
2440− "windows_i686_gnu 0.40.0",
2441− "windows_i686_msvc 0.40.0",
2442− "windows_x86_64_gnu 0.40.0",
2443− "windows_x86_64_gnullvm 0.40.0",
2444− "windows_x86_64_msvc 0.40.0",
2445]
2422]
2446
2423
2447[[package]]
2424[[package]]
2448name = "windows-sys"
2425name = "windows-sys"
2449−version = "0.42.0"
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]]
2434+name = "windows-targets"
2435+version = "0.42.2"
2436+source = "registry+https://github.com/rust-lang/crates.io-index"
2437+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
2438+dependencies = [
2439+ "windows_aarch64_gnullvm 0.42.2",
2440+ "windows_aarch64_msvc 0.42.2",
2441+ "windows_i686_gnu 0.42.2",
2442+ "windows_i686_msvc 0.42.2",
2443+ "windows_x86_64_gnu 0.42.2",
2444+ "windows_x86_64_gnullvm 0.42.2",
2445+ "windows_x86_64_msvc 0.42.2",
2446+]
2447+
2448+[[package]]
2449+name = "windows-targets"
2450+version = "0.48.0"
2451+source = "registry+https://github.com/rust-lang/crates.io-index"
2452+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
2453+dependencies = [
2454+ "windows_aarch64_gnullvm 0.48.0",
2455+ "windows_aarch64_msvc 0.48.0",
2456+ "windows_i686_gnu 0.48.0",
2457+ "windows_i686_msvc 0.48.0",
2458+ "windows_x86_64_gnu 0.48.0",
2459+ "windows_x86_64_gnullvm 0.48.0",
2460+ "windows_x86_64_msvc 0.48.0",
2461+]
2462+
2463+[[package]]
2464+name = "windows-targets"
2465+version = "0.52.0"
2450source = "registry+https://github.com/rust-lang/crates.io-index"
2466source = "registry+https://github.com/rust-lang/crates.io-index"
2451−checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
2467+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
2452dependencies = [
2468dependencies = [
2453− "windows_aarch64_gnullvm 0.42.0",
2469+ "windows_aarch64_gnullvm 0.52.0",
2454− "windows_aarch64_msvc 0.42.0",
2470+ "windows_aarch64_msvc 0.52.0",
2455− "windows_i686_gnu 0.42.0",
2471+ "windows_i686_gnu 0.52.0",
2456− "windows_i686_msvc 0.42.0",
2472+ "windows_i686_msvc 0.52.0",
2457− "windows_x86_64_gnu 0.42.0",
2473+ "windows_x86_64_gnu 0.52.0",
2458− "windows_x86_64_gnullvm 0.42.0",
2474+ "windows_x86_64_gnullvm 0.52.0",
2459− "windows_x86_64_msvc 0.42.0",
2475+ "windows_x86_64_msvc 0.52.0",
2460]
2476]
2461
2477
2462[[package]]
2478[[package]]
2463name = "windows_aarch64_gnullvm"
2479name = "windows_aarch64_gnullvm"
2464−version = "0.40.0"
2480+version = "0.42.2"
2481+source = "registry+https://github.com/rust-lang/crates.io-index"
2482+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
2483+
2484+[[package]]
2485+name = "windows_aarch64_gnullvm"
2486+version = "0.48.0"
2465source = "registry+https://github.com/rust-lang/crates.io-index"
2487source = "registry+https://github.com/rust-lang/crates.io-index"
2466−checksum = "f3caa4a1a16561b714323ca6b0817403738583033a6a92e04c5d10d4ba37ca10"
2488+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
2467
2489
2468[[package]]
2490[[package]]
2469name = "windows_aarch64_gnullvm"
2491name = "windows_aarch64_gnullvm"
2470−version = "0.42.0"
2492+version = "0.52.0"
2471source = "registry+https://github.com/rust-lang/crates.io-index"
2493source = "registry+https://github.com/rust-lang/crates.io-index"
2472−checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
2494+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
2473
2495
2474[[package]]
2496[[package]]
2475name = "windows_aarch64_msvc"
2497name = "windows_aarch64_msvc"
2476−version = "0.40.0"
2498+version = "0.42.2"
2499+source = "registry+https://github.com/rust-lang/crates.io-index"
2500+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
2501+
2502+[[package]]
2503+name = "windows_aarch64_msvc"
2504+version = "0.48.0"
2477source = "registry+https://github.com/rust-lang/crates.io-index"
2505source = "registry+https://github.com/rust-lang/crates.io-index"
2478−checksum = "328973c62dfcc50fb1aaa8e7100676e0b642fe56bac6bafff3327902db843ab4"
2506+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
2479
2507
2480[[package]]
2508[[package]]
2481name = "windows_aarch64_msvc"
2509name = "windows_aarch64_msvc"
2482−version = "0.42.0"
2510+version = "0.52.0"
2483source = "registry+https://github.com/rust-lang/crates.io-index"
2511source = "registry+https://github.com/rust-lang/crates.io-index"
2484−checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
2512+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
2485
2513
2486[[package]]
2514[[package]]
2487name = "windows_i686_gnu"
2515name = "windows_i686_gnu"
2488−version = "0.40.0"
2516+version = "0.42.2"
2517+source = "registry+https://github.com/rust-lang/crates.io-index"
2518+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
2519+
2520+[[package]]
2521+name = "windows_i686_gnu"
2522+version = "0.48.0"
2489source = "registry+https://github.com/rust-lang/crates.io-index"
2523source = "registry+https://github.com/rust-lang/crates.io-index"
2490−checksum = "aa5b09fad70f0df85dea2ac2a525537e415e2bf63ee31cf9b8e263645ee9f3c1"
2524+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
2491
2525
2492[[package]]
2526[[package]]
2493name = "windows_i686_gnu"
2527name = "windows_i686_gnu"
2494−version = "0.42.0"
2528+version = "0.52.0"
2495source = "registry+https://github.com/rust-lang/crates.io-index"
2529source = "registry+https://github.com/rust-lang/crates.io-index"
2496−checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
2530+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
2497
2531
2498[[package]]
2532[[package]]
2499name = "windows_i686_msvc"
2533name = "windows_i686_msvc"
2500−version = "0.40.0"
2534+version = "0.42.2"
2535+source = "registry+https://github.com/rust-lang/crates.io-index"
2536+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
2537+
2538+[[package]]
2539+name = "windows_i686_msvc"
2540+version = "0.48.0"
2501source = "registry+https://github.com/rust-lang/crates.io-index"
2541source = "registry+https://github.com/rust-lang/crates.io-index"
2502−checksum = "2a1ad4031c1a98491fa195d8d43d7489cb749f135f2e5c4eed58da094bd0d876"
2542+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
2503
2543
2504[[package]]
2544[[package]]
2505name = "windows_i686_msvc"
2545name = "windows_i686_msvc"
2506−version = "0.42.0"
2546+version = "0.52.0"
2507source = "registry+https://github.com/rust-lang/crates.io-index"
2547source = "registry+https://github.com/rust-lang/crates.io-index"
2508−checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
2548+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
2509
2549
2510[[package]]
2550[[package]]
2511name = "windows_x86_64_gnu"
2551name = "windows_x86_64_gnu"
2512−version = "0.40.0"
2552+version = "0.42.2"
2553+source = "registry+https://github.com/rust-lang/crates.io-index"
2554+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
2555+
2556+[[package]]
2557+name = "windows_x86_64_gnu"
2558+version = "0.48.0"
2513source = "registry+https://github.com/rust-lang/crates.io-index"
2559source = "registry+https://github.com/rust-lang/crates.io-index"
2514−checksum = "520ff37edd72da8064b49d2281182898e17f0688ae9f4070bca27e4b5c162ac7"
2560+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
2515
2561
2516[[package]]
2562[[package]]
2517name = "windows_x86_64_gnu"
2563name = "windows_x86_64_gnu"
2518−version = "0.42.0"
2564+version = "0.52.0"
2519source = "registry+https://github.com/rust-lang/crates.io-index"
2565source = "registry+https://github.com/rust-lang/crates.io-index"
2520−checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
2566+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
2521
2567
2522[[package]]
2568[[package]]
2523name = "windows_x86_64_gnullvm"
2569name = "windows_x86_64_gnullvm"
2524−version = "0.40.0"
2570+version = "0.42.2"
2571+source = "registry+https://github.com/rust-lang/crates.io-index"
2572+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
2573+
2574+[[package]]
2575+name = "windows_x86_64_gnullvm"
2576+version = "0.48.0"
2525source = "registry+https://github.com/rust-lang/crates.io-index"
2577source = "registry+https://github.com/rust-lang/crates.io-index"
2526−checksum = "046e5b82215102c44fd75f488f1b9158973d02aa34d06ed85c23d6f5520a2853"
2578+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
2527
2579
2528[[package]]
2580[[package]]
2529name = "windows_x86_64_gnullvm"
2581name = "windows_x86_64_gnullvm"
2530−version = "0.42.0"
2582+version = "0.52.0"
2531source = "registry+https://github.com/rust-lang/crates.io-index"
2583source = "registry+https://github.com/rust-lang/crates.io-index"
2532−checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
2584+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
2533
2585
2534[[package]]
2586[[package]]
2535name = "windows_x86_64_msvc"
2587name = "windows_x86_64_msvc"
2536−version = "0.40.0"
2588+version = "0.42.2"
2589+source = "registry+https://github.com/rust-lang/crates.io-index"
2590+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
2591+
2592+[[package]]
2593+name = "windows_x86_64_msvc"
2594+version = "0.48.0"
2537source = "registry+https://github.com/rust-lang/crates.io-index"
2595source = "registry+https://github.com/rust-lang/crates.io-index"
2538−checksum = "2a0c9c6df55dd1bfa76e131cef44bdd8ec9c819ef3611f04dfe453fd5bfeda28"
2596+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
2539
2597
2540[[package]]
2598[[package]]
2541name = "windows_x86_64_msvc"
2599name = "windows_x86_64_msvc"
2542−version = "0.42.0"
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]]
2614+name = "winnow"
2615+version = "0.5.28"
2543source = "registry+https://github.com/rust-lang/crates.io-index"
2616source = "registry+https://github.com/rust-lang/crates.io-index"
2544−checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
2617+checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
2618+dependencies = [
2619+ "memchr",
2620+]
2545
2621
2546[[package]]
2622[[package]]
2547name = "xtask"
2623name = "xtask"
2548−version = "0.6.0"
2624+version = "23.10.0"
2549dependencies = [
2625dependencies = [
2550 "helix-core",
2626 "helix-core",
2551 "helix-loader",
2627 "helix-loader",
@@ -2553,3 +2629,23 @@ dependencies = [
2553 "helix-view",
2629 "helix-view",
2554 "toml",
2630 "toml",
2555]
2631]
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+17
@@ -1,13 +1,16 @@
1[workspace]
1[workspace]
2+resolver = "2"
2members = [
3members = [
3 "helix-core",
4 "helix-core",
4 "helix-view",
5 "helix-view",
5 "helix-term",
6 "helix-term",
6 "helix-tui",
7 "helix-tui",
7 "helix-lsp",
8 "helix-lsp",
9+ "helix-event",
8 "helix-dap",
10 "helix-dap",
9 "helix-loader",
11 "helix-loader",
10 "helix-vcs",
12 "helix-vcs",
13+ "helix-parsec",
11 "xtask",
14 "xtask",
12]
15]
13
16
@@ -31,3 +34,17 @@ inherits = "test"
31package.helix-core.opt-level = 2
34package.helix-core.opt-level = 2
32package.helix-tui.opt-level = 2
35package.helix-tui.opt-level = 2
33package.helix-term.opt-level = 2
36package.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+3−85
@@ -18,7 +18,7 @@
18
18
19![Screenshot](./screenshot.png)
19![Screenshot](./screenshot.png)
20
20
21−A Kakoune / Neovim inspired editor, written in Rust.
21+A [Kakoune](https://github.com/mawww/kakoune) / [Neovim](https://github.com/neovim/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.
@@ -45,92 +45,10 @@ Note: Only certain languages have indentation definitions at the moment. Check
45
45
46# Installation
46# Installation
47
47
48−Packages are available for various distributions (see [Installation docs](https://docs.helix-editor.com/install.html)).
48+[Installation documentation](https://docs.helix-editor.com/install.html).
49−
50−If you would like to build from source:
51−
52−```shell
53−git clone https://github.com/helix-editor/helix
54−cd helix
55−cargo install --locked --path helix-term
56−```
57−
58−This will install the `hx` binary to `$HOME/.cargo/bin` and build tree-sitter grammars in `./runtime/grammars`.
59−
60−Helix needs its runtime files so make sure to copy/symlink the `runtime/` directory into the
61−config directory (for example `~/.config/helix/runtime` on Linux/macOS, or `%AppData%/helix/runtime` on Windows).
62−
63−| OS | Command |
64−| -------------------- | ------------------------------------------------ |
65−| Windows (Cmd) | `xcopy /e /i runtime %AppData%\helix\runtime` |
66−| Windows (PowerShell) | `xcopy /e /i runtime $Env:AppData\helix\runtime` |
67−| Linux / macOS | `ln -s $PWD/runtime ~/.config/helix/runtime` |
68−
69−Starting with Windows Vista you can also create symbolic links on Windows. Note that this requires
70−elevated privileges - i.e. PowerShell or Cmd must be run as administrator.
71−
72−**PowerShell:**
73−
74−```powershell
75−New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime"
76−```
77−Note: "runtime" must be absolute path to the runtime directory.
78−
79−**Cmd:**
80−
81−```cmd
82−cd %appdata%\helix
83−mklink /D runtime "<helix-repo>\runtime"
84−```
85−
86−The runtime location can be overridden via the `HELIX_RUNTIME` environment variable.
87−
88−> NOTE: if `HELIX_RUNTIME` is set prior to calling `cargo install --locked --path helix-term`,
89−> tree-sitter grammars will be built in `$HELIX_RUNTIME/grammars`.
90−
91−If you plan on keeping the repo locally, an alternative to copying/symlinking
92−runtime files is to set `HELIX_RUNTIME=/path/to/helix/runtime`
93−(`HELIX_RUNTIME=$PWD/runtime` if you're in the helix repo directory).
94−
95−Packages already solve this for you by wrapping the `hx` binary with a wrapper
96−that sets the variable to the install dir.
97−
98−> NOTE: running via cargo also doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically
99−> detect the `runtime` directory in the project root.
100−
101−If you want to customize your `languages.toml` config,
102−tree-sitter grammars may be manually fetched and built with `hx --grammar fetch` and `hx --grammar build`.
103−
104−In order to use LSP features like auto-complete, you will need to
105−[install the appropriate Language Server](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
106−for a language.
107
49
108[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
50[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
109
51
110−## Adding Helix to your desktop environment
111−
112−If installing from source, to use Helix in desktop environments that supports [XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html), including Gnome and KDE, copy the provided `.desktop` file to the correct folder:
113−
114−```bash
115−cp contrib/Helix.desktop ~/.local/share/applications
116−cp contrib/helix.png ~/.local/share/icons
117−```
118−
119−To use another terminal than the default, you will need to modify the `.desktop` file. For example, to use `kitty`:
120−
121−```bash
122−sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
123−sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
124−```
125−
126−## macOS
127−
128−Helix can be installed on macOS through homebrew:
129−
130−```
131−brew install helix
132−```
133−
134# Contributing
52# Contributing
135
53
136Contributing guidelines can be found [here](./docs/CONTRIBUTING.md).
54Contributing guidelines can be found [here](./docs/CONTRIBUTING.md).
@@ -143,4 +61,4 @@ Discuss the project on the community [Matrix Space](https://matrix.to/#/#helix-c
143
61
144# Credits
62# Credits
145
63
146−Thanks to [@JakeHL](https://github.com/JakeHL) for designing the logo!
64+Thanks to [@jakenvac](https://github.com/jakenvac) for designing the logo!
VERSIONdeleted−1
@@ -1 +0,0 @@
1−22.12
book/book.toml+5−1
@@ -3,10 +3,14 @@ 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"
7
6
8[output.html]
7[output.html]
9cname = "docs.helix-editor.com"
8cname = "docs.helix-editor.com"
10default-theme = "colibri"
9default-theme = "colibri"
11preferred-dark-theme = "colibri"
10preferred-dark-theme = "colibri"
12git-repository-url = "https://github.com/helix-editor/helix"
11git-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.cssnew file+231
@@ -0,0 +1,231 @@
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/SUMMARY.md+6−5
@@ -6,13 +6,14 @@
6- [Usage](./usage.md)
6- [Usage](./usage.md)
7 - [Keymap](./keymap.md)
7 - [Keymap](./keymap.md)
8 - [Commands](./commands.md)
8 - [Commands](./commands.md)
9− - [Language Support](./lang-support.md)
9+ - [Language support](./lang-support.md)
10- [Migrating from Vim](./from-vim.md)
10- [Migrating from Vim](./from-vim.md)
11- [Configuration](./configuration.md)
11- [Configuration](./configuration.md)
12 - [Themes](./themes.md)
12 - [Themes](./themes.md)
13− - [Key Remapping](./remapping.md)
13+ - [Key remapping](./remapping.md)
14 - [Languages](./languages.md)
14 - [Languages](./languages.md)
15- [Guides](./guides/README.md)
15- [Guides](./guides/README.md)
16− - [Adding Languages](./guides/adding_languages.md)
16+ - [Adding languages](./guides/adding_languages.md)
17− - [Adding Textobject Queries](./guides/textobject.md)
17+ - [Adding textobject queries](./guides/textobject.md)
18− - [Adding Indent Queries](./guides/indent.md)
18+ - [Adding indent queries](./guides/indent.md)
19+ - [Adding injection queries](./guides/injection.md)
book/src/commands.md+1−1
@@ -1,5 +1,5 @@
1# Commands
1# Commands
2
2
3−Command mode can be activated by pressing `:`, similar to Vim. Built-in commands:
3+Command mode can be activated by pressing `:`. The built-in commands are:
4
4
5{{#include ./generated/typable-cmd.md}}
5{{#include ./generated/typable-cmd.md}}
book/src/configuration.md+95−53
@@ -2,10 +2,10 @@
2
2
3To override global configuration parameters, create a `config.toml` file located in your config directory:
3To override global configuration parameters, create a `config.toml` file located in your config directory:
4
4
5−* Linux and Mac: `~/.config/helix/config.toml`
5+- Linux and Mac: `~/.config/helix/config.toml`
6−* Windows: `%AppData%\helix\config.toml`
6+- Windows: `%AppData%\helix\config.toml`
7
7
8−> Hint: You can easily open the config file by typing `:config-open` within Helix normal mode.
8+> 💡 You can easily open the config file by typing `:config-open` within Helix normal mode.
9
9
10Example config:
10Example config:
11
11
@@ -25,12 +25,13 @@ select = "underline"
25hidden = false
25hidden = false
26```
26```
27
27
28−You may also specify a file to use for configuration with the `-c` or
28+You can use a custom configuration file by specifying it with the `-c` or
29−`--config` CLI argument: `hx -c path/to/custom-config.toml`.
29+`--config` command line argument, for example `hx -c path/to/custom-config.toml`.
30+Additionally, you can reload the configuration file by sending the USR1
31+signal to the Helix process on Unix operating systems, such as by using the command `pkill -USR1 hx`.
30
32
31−It is also possible to trigger configuration file reloading by sending the `USR1`
33+Finally, you can have a `config.toml` local to a project by putting it under a `.helix` directory in your repository.
32−signal to the helix process, e.g. via `pkill -USR1 hx`. This is only supported
34+Its settings will be merged with the configuration directory `config.toml` and the built-in configuration.
33−on unix operating systems.
34
35
35## Editor
36## Editor
36
37
@@ -38,25 +39,34 @@ on unix operating systems.
38
39
39| Key | Description | Default |
40| Key | Description | Default |
40|--|--|---------|
41|--|--|---------|
41−| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling. | `5` |
42+| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling | `5` |
42−| `mouse` | Enable mouse mode. | `true` |
43+| `mouse` | Enable mouse mode | `true` |
43−| `middle-click-paste` | Middle click paste support. | `true` |
44+| `middle-click-paste` | Middle click paste support | `true` |
44−| `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` |
45+| `scroll-lines` | Number of lines to scroll per scroll wheel step | `3` |
45−| `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
46+| `shell` | Shell to use when running external commands | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
46−| `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers. | `absolute` |
47+| `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers | `absolute` |
47−| `cursorline` | Highlight all lines with a cursor. | `false` |
48+| `cursorline` | Highlight all lines with a cursor | `false` |
48−| `cursorcolumn` | Highlight all columns with a cursor. | `false` |
49+| `cursorcolumn` | Highlight all columns with a cursor | `false` |
49| `gutters` | Gutters to display: Available are `diagnostics` and `diff` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
50| `gutters` | Gutters to display: Available are `diagnostics` and `diff` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
50−| `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
51+| `auto-completion` | Enable automatic pop up of auto-completion | `true` |
51−| `auto-format` | Enable automatic formatting on save. | `true` |
52+| `auto-format` | Enable automatic formatting on save | `true` |
52−| `auto-save` | Enable automatic saving on 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` |
53−| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
54+| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant | `250` |
55+| `preview-completion-insert` | Whether to apply completion item instantly when selected | `true` |
54| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
56| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
57+| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
55−| `auto-info` | Whether to display infoboxes | `true` |
58+| `auto-info` | Whether to display info boxes | `true` |
56−| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. | `false` |
59+| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative | `false` |
60+| `undercurl` | Set to `true` to override automatic detection of terminal undercurl support in the event of a false negative | `false` |
57−| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file. | `[]` |
61+| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file | `[]` |
58| `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` |
62| `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` |
59| `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` |
63| `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` |
65+| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
66+| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
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`
60
70
61### `[editor.statusline]` Section
71### `[editor.statusline]` Section
62
72
@@ -82,9 +92,9 @@ The `[editor.statusline]` key takes the following sub-keys:
82
92
83| Key | Description | Default |
93| Key | Description | Default |
84| --- | --- | --- |
94| --- | --- | --- |
85−| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name"]` |
95+| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "read-only-indicator", "file-modification-indicator"]` |
86| `center` | A list of elements aligned to the middle of the statusline | `[]` |
96| `center` | A list of elements aligned to the middle of the statusline | `[]` |
87−| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "position", "file-encoding"]` |
97+| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
88| `separator` | The character used to separate elements in the statusline | `"│"` |
98| `separator` | The character used to separate elements in the statusline | `"│"` |
89| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
99| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
90| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
100| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
@@ -98,8 +108,10 @@ The following statusline elements can be configured:
98| `spinner` | A progress spinner indicating LSP activity |
108| `spinner` | A progress spinner indicating LSP activity |
99| `file-name` | The path/name of the opened file |
109| `file-name` | The path/name of the opened file |
100| `file-base-name` | The basename of the opened file |
110| `file-base-name` | The basename of the opened file |
111+| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
101| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
112| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
102| `file-line-ending` | The file line endings (CRLF or LF) |
113| `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 |
103| `total-line-numbers` | The total line numbers of the opened file |
115| `total-line-numbers` | The total line numbers of the opened file |
104| `file-type` | The type of the opened file |
116| `file-type` | The type of the opened file |
105| `diagnostics` | The number of warnings and/or errors |
117| `diagnostics` | The number of warnings and/or errors |
@@ -110,6 +122,8 @@ The following statusline elements can be configured:
110| `position-percentage` | The cursor position as a percentage of the total number of lines |
122| `position-percentage` | The cursor position as a percentage of the total number of lines |
111| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
123| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
112| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
124| `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 |
126+| `register` | The current selected register |
113
127
114### `[editor.lsp]` Section
128### `[editor.lsp]` Section
115
129
@@ -118,16 +132,23 @@ The following statusline elements can be configured:
118| `enable` | Enables LSP integration. Setting to false will completely disable language servers regardless of language settings.| `true` |
132| `enable` | Enables LSP integration. Setting to false will completely disable language servers regardless of language settings.| `true` |
119| `display-messages` | Display LSP progress messages below statusline[^1] | `false` |
133| `display-messages` | Display LSP progress messages below statusline[^1] | `false` |
120| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
134| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
135+| `display-inlay-hints` | Display inlay hints[^2] | `false` |
121| `display-signature-help-docs` | Display docs under signature help popup | `true` |
136| `display-signature-help-docs` | Display docs under signature help popup | `true` |
137+| `snippets` | Enables snippet completions. Requires a server restart (`:lsp-restart`) to take effect after `:config-reload`/`:set`. | `true` |
138+| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
122
139
123[^1]: By default, a progress spinner is shown in the statusline beside the file path.
140[^1]: By default, a progress spinner is shown in the statusline beside the file path.
124
141
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!
143+
125### `[editor.cursor-shape]` Section
144### `[editor.cursor-shape]` Section
126
145
127−Defines the shape of cursor in each mode. Note that due to limitations
146+Defines the shape of cursor in each mode.
128−of the terminal environment, only the primary cursor can change shape.
129Valid values for these options are `block`, `bar`, `underline`, or `hidden`.
147Valid values for these options are `block`, `bar`, `underline`, or `hidden`.
130
148
149+> 💡 Due to limitations of the terminal environment, only the primary cursor can
150+> change shape.
151+
131| Key | Description | Default |
152| Key | Description | Default |
132| --- | ----------- | ------- |
153| --- | ----------- | ------- |
133| `normal` | Cursor shape in [normal mode][normal mode] | `block` |
154| `normal` | Cursor shape in [normal mode][normal mode] | `block` |
@@ -140,25 +161,37 @@ Valid values for these options are `block`, `bar`, `underline`, or `hidden`.
140
161
141### `[editor.file-picker]` Section
162### `[editor.file-picker]` Section
142
163
143−Sets options for file picker and global search. All but the last key listed in
164+Set options for file picker and global search. Ignoring a file means it is
144−the default file-picker configuration below are IgnoreOptions: whether hidden
145−files and files listed within ignore files are ignored by (not visible in) the
146−helix file picker and global search. There is also one other key, `max-depth`
165+not visible in the Helix file picker and global search.
147−available, which is not defined by default.
148
166
149All git related options are only enabled in a git repository.
167All git related options are only enabled in a git repository.
150
168
151| Key | Description | Default |
169| Key | Description | Default |
152|--|--|---------|
170|--|--|---------|
153−|`hidden` | Enables ignoring hidden files. | true
171+|`hidden` | Enables ignoring hidden files | `true`
154−|`follow-links` | Follow symlinks instead of ignoring them | true
172+|`follow-symlinks` | Follow symlinks instead of ignoring them | `true`
155−|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | true
173+|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | `true`
156−|`parents` | Enables reading ignore files from parent directories. | true
174+|`parents` | Enables reading ignore files from parent directories | `true`
157−|`ignore` | Enables reading `.ignore` files. | true
175+|`ignore` | Enables reading `.ignore` files | `true`
158−|`git-ignore` | Enables reading `.gitignore` files. | true
176+|`git-ignore` | Enables reading `.gitignore` files | `true`
159−|`git-global` | Enables reading global .gitignore, whose path is specified in git's config: `core.excludefile` option. | true
177+|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludesfile` option | `true`
160−|`git-exclude` | Enables reading `.git/info/exclude` files. | true
178+|`git-exclude` | Enables reading `.git/info/exclude` files | `true`
161−|`max-depth` | Set with an integer value for maximum depth to recurse. | Defaults to `None`.
179+|`max-depth` | Set with an integer value for maximum depth to recurse | Defaults to `None`.
180+
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+```
162
195
163### `[editor.auto-pairs]` Section
196### `[editor.auto-pairs]` Section
164
197
@@ -210,7 +243,7 @@ Search specific options.
210
243
211| Key | Description | Default |
244| Key | Description | Default |
212|--|--|---------|
245|--|--|---------|
213−| `smart-case` | Enable smart case regex searching (case insensitive unless pattern contains upper case characters) | `true` |
246+| `smart-case` | Enable smart case regex searching (case-insensitive unless pattern contains upper case characters) | `true` |
214| `wrap-around`| Whether the search should wrap after depleting the matches | `true` |
247| `wrap-around`| Whether the search should wrap after depleting the matches | `true` |
215
248
216### `[editor.whitespace]` Section
249### `[editor.whitespace]` Section
@@ -219,7 +252,7 @@ Options for rendering whitespace with visible characters. Use `:set whitespace.r
219
252
220| Key | Description | Default |
253| Key | Description | Default |
221|-----|-------------|---------|
254|-----|-------------|---------|
222−| `render` | Whether to render whitespace. May either be `"all"` or `"none"`, or a table with sub-keys `space`, `nbsp`, `tab`, and `newline`. | `"none"` |
255+| `render` | Whether to render whitespace. May either be `"all"` or `"none"`, or a table with sub-keys `space`, `nbsp`, `tab`, and `newline` | `"none"` |
223| `characters` | Literal characters to use when rendering whitespace. Sub-keys may be any of `tab`, `space`, `nbsp`, `newline` or `tabpad` | See example below |
256| `characters` | Literal characters to use when rendering whitespace. Sub-keys may be any of `tab`, `space`, `nbsp`, `newline` or `tabpad` | See example below |
224
257
225Example
258Example
@@ -247,7 +280,7 @@ Options for rendering vertical indent guides.
247
280
248| Key | Description | Default |
281| Key | Description | Default |
249| --- | --- | --- |
282| --- | --- | --- |
250−| `render` | Whether to render indent guides. | `false` |
283+| `render` | Whether to render indent guides | `false` |
251| `character` | Literal character to use for rendering the indent guide | `│` |
284| `character` | Literal character to use for rendering the indent guide | `│` |
252| `skip-levels` | Number of indent levels to skip | `0` |
285| `skip-levels` | Number of indent levels to skip | `0` |
253
286
@@ -272,7 +305,7 @@ gutters = ["diff", "diagnostics", "line-numbers", "spacer"]
272
305
273To customize the behavior of gutters, the `[editor.gutters]` section must
306To customize the behavior of gutters, the `[editor.gutters]` section must
274be used. This section contains top level settings, as well as settings for
307be used. This section contains top level settings, as well as settings for
275−specific gutter components as sub-sections.
308+specific gutter components as subsections.
276
309
277| Key | Description | Default |
310| Key | Description | Default |
278| --- | --- | --- |
311| --- | --- | --- |
@@ -300,7 +333,7 @@ Example:
300min-width = 1
333min-width = 1
301```
334```
302
335
303−#### `[editor.gutters.diagnotics]` Section
336+#### `[editor.gutters.diagnostics]` Section
304
337
305Currently unused
338Currently unused
306
339
@@ -314,14 +347,15 @@ Currently unused
314
347
315### `[editor.soft-wrap]` Section
348### `[editor.soft-wrap]` Section
316
349
317−Options for soft wrapping lines that exceed the view width
350+Options for soft wrapping lines that exceed the view width:
318
351
319−| Key | Description | Default |
352+| Key | Description | Default |
320−| --- | --- | --- |
353+| --- | --- | --- |
321−| `enable` | Whether soft wrapping is enabled. | `false` |
354+| `enable` | Whether soft wrapping is enabled. | `false` |
322−| `max-wrap` | Maximum free space left at the end of the line. | `20` |
355+| `max-wrap` | Maximum free space left at the end of the line. | `20` |
323−| `max-indent-retain` | Maximum indentation to carry over when soft wrapping a line. | `40` |
356+| `max-indent-retain` | Maximum indentation to carry over when soft wrapping a line. | `40` |
324−| `wrap-indicator` | Text inserted before soft wrapped lines, highlighted with `ui.virtual.wrap` | `↪ ` |
357+| `wrap-indicator` | Text inserted before soft wrapped lines, highlighted with `ui.virtual.wrap` | `↪ ` |
358+| `wrap-at-text-width` | Soft wrap at `text-width` instead of using the full viewport size. | `false` |
325
359
326Example:
360Example:
327
361
@@ -332,3 +366,11 @@ max-wrap = 25 # increase value to reduce forced mid-word wrapping
332max-indent-retain = 0
366max-indent-retain = 0
333wrap-indicator = "" # set wrap-indicator to "" to hide it
367wrap-indicator = "" # set wrap-indicator to "" to hide it
334```
368```
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/from-vim.md+3−3
@@ -2,9 +2,9 @@
2
2
3Helix's editing model is strongly inspired from Vim and Kakoune, and a notable
3Helix's editing model is strongly inspired from Vim and Kakoune, and a notable
4difference from Vim (and the most striking similarity to Kakoune) is that Helix
4difference from Vim (and the most striking similarity to Kakoune) is that Helix
5−follows the `selection → action` model. This means that the whatever you are
5+follows the `selection → action` model. This means that whatever you are
6−going to act on (a word, a paragraph, a line, etc) is selected first and the
6+going to act on (a word, a paragraph, a line, etc.) is selected first and the
7−action itself (delete, change, yank, etc) comes second. A cursor is simply a
7+action itself (delete, change, yank, etc.) comes second. A cursor is simply a
8single width selection.
8single width selection.
9
9
10See also Kakoune's [Migrating from Vim](https://github.com/mawww/kakoune/wiki/Migrating-from-Vim) and Helix's [Migrating from Vim](https://github.com/helix-editor/helix/wiki/Migrating-from-Vim).
10See also Kakoune's [Migrating from Vim](https://github.com/mawww/kakoune/wiki/Migrating-from-Vim) and Helix's [Migrating from Vim](https://github.com/helix-editor/helix/wiki/Migrating-from-Vim).
book/src/generated/lang-support.md+64−23
@@ -1,74 +1,91 @@
1| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
1| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
2| --- | --- | --- | --- | --- |
2| --- | --- | --- | --- | --- |
3+| agda | ✓ | | | |
3| astro | ✓ | | | |
4| astro | ✓ | | | |
4| awk | ✓ | ✓ | | `awk-language-server` |
5| awk | ✓ | ✓ | | `awk-language-server` |
5−| bash | ✓ | | ✓ | `bash-language-server` |
6+| bash | ✓ | ✓ | ✓ | `bash-language-server` |
6| bass | ✓ | | | `bass` |
7| bass | ✓ | | | `bass` |
7| beancount | ✓ | | | |
8| beancount | ✓ | | | |
8| bibtex | ✓ | | | `texlab` |
9| bibtex | ✓ | | | `texlab` |
9| bicep | ✓ | | | `bicep-langserver` |
10| bicep | ✓ | | | `bicep-langserver` |
11+| blueprint | ✓ | | | `blueprint-compiler` |
10| c | ✓ | ✓ | ✓ | `clangd` |
12| c | ✓ | ✓ | ✓ | `clangd` |
11| c-sharp | ✓ | ✓ | | `OmniSharp` |
13| c-sharp | ✓ | ✓ | | `OmniSharp` |
12−| cairo | ✓ | | | |
14+| cabal | | | | `haskell-language-server-wrapper` |
15+| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
16+| capnp | ✓ | | ✓ | |
13| clojure | ✓ | | | `clojure-lsp` |
17| clojure | ✓ | | | `clojure-lsp` |
14| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
18| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
15| comment | ✓ | | | |
19| comment | ✓ | | | |
16−| common-lisp | ✓ | | | `cl-lsp` |
20+| common-lisp | ✓ | | ✓ | `cl-lsp` |
17| cpon | ✓ | | ✓ | |
21| cpon | ✓ | | ✓ | |
18| cpp | ✓ | ✓ | ✓ | `clangd` |
22| cpp | ✓ | ✓ | ✓ | `clangd` |
19−| crystal | ✓ | ✓ | | |
23+| crystal | ✓ | ✓ | | `crystalline` |
20| css | ✓ | | | `vscode-css-language-server` |
24| css | ✓ | | | `vscode-css-language-server` |
21| cue | ✓ | | | `cuelsp` |
25| cue | ✓ | | | `cuelsp` |
22| d | ✓ | ✓ | ✓ | `serve-d` |
26| d | ✓ | ✓ | ✓ | `serve-d` |
23| dart | ✓ | | ✓ | `dart` |
27| dart | ✓ | | ✓ | `dart` |
28+| dbml | ✓ | | | |
24| devicetree | ✓ | | | |
29| devicetree | ✓ | | | |
25| dhall | ✓ | ✓ | | `dhall-lsp-server` |
30| dhall | ✓ | ✓ | | `dhall-lsp-server` |
26| diff | ✓ | | | |
31| diff | ✓ | | | |
27| dockerfile | ✓ | | | `docker-langserver` |
32| dockerfile | ✓ | | | `docker-langserver` |
28| dot | ✓ | | | `dot-language-server` |
33| dot | ✓ | | | `dot-language-server` |
34+| dtd | ✓ | | | |
29| edoc | ✓ | | | |
35| edoc | ✓ | | | |
30| eex | ✓ | | | |
36| eex | ✓ | | | |
31| ejs | ✓ | | | |
37| ejs | ✓ | | | |
32| elixir | ✓ | ✓ | ✓ | `elixir-ls` |
38| elixir | ✓ | ✓ | ✓ | `elixir-ls` |
33−| elm | ✓ | | | `elm-language-server` |
39+| elm | ✓ | ✓ | | `elm-language-server` |
34| elvish | ✓ | | | `elvish` |
40| elvish | ✓ | | | `elvish` |
35| env | ✓ | | | |
41| env | ✓ | | | |
36| erb | ✓ | | | |
42| erb | ✓ | | | |
37| erlang | ✓ | ✓ | | `erlang_ls` |
43| erlang | ✓ | ✓ | | `erlang_ls` |
38| esdl | ✓ | | | |
44| esdl | ✓ | | | |
39| fish | ✓ | ✓ | ✓ | |
45| fish | ✓ | ✓ | ✓ | |
46+| forth | ✓ | | | `forth-lsp` |
40| fortran | ✓ | | ✓ | `fortls` |
47| fortran | ✓ | | ✓ | `fortls` |
48+| fsharp | ✓ | | | `fsautocomplete` |
49+| gas | ✓ | ✓ | | |
41| gdscript | ✓ | ✓ | ✓ | |
50| gdscript | ✓ | ✓ | ✓ | |
51+| gemini | ✓ | | | |
42| git-attributes | ✓ | | | |
52| git-attributes | ✓ | | | |
43−| git-commit | ✓ | | | |
53+| git-commit | ✓ | ✓ | | |
44| git-config | ✓ | | | |
54| git-config | ✓ | | | |
45| git-ignore | ✓ | | | |
55| git-ignore | ✓ | | | |
46| git-rebase | ✓ | | | |
56| git-rebase | ✓ | | | |
47| gleam | ✓ | ✓ | | `gleam` |
57| gleam | ✓ | ✓ | | `gleam` |
48| glsl | ✓ | ✓ | ✓ | |
58| glsl | ✓ | ✓ | ✓ | |
49−| go | ✓ | ✓ | ✓ | `gopls` |
59+| gn | ✓ | | | |
60+| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
50| godot-resource | ✓ | | | |
61| godot-resource | ✓ | | | |
51| gomod | ✓ | | | `gopls` |
62| gomod | ✓ | | | `gopls` |
52| gotmpl | ✓ | | | `gopls` |
63| gotmpl | ✓ | | | `gopls` |
53| gowork | ✓ | | | `gopls` |
64| gowork | ✓ | | | `gopls` |
54−| graphql | ✓ | | | |
65+| graphql | ✓ | | | `graphql-lsp` |
55| hare | ✓ | | | |
66| hare | ✓ | | | |
56| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
67| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
68+| haskell-persistent | ✓ | | | |
57| hcl | ✓ | | ✓ | `terraform-ls` |
69| hcl | ✓ | | ✓ | `terraform-ls` |
58| heex | ✓ | ✓ | | `elixir-ls` |
70| heex | ✓ | ✓ | | `elixir-ls` |
59| hosts | ✓ | | | |
71| hosts | ✓ | | | |
60| html | ✓ | | | `vscode-html-language-server` |
72| html | ✓ | | | `vscode-html-language-server` |
73+| hurl | ✓ | | ✓ | |
61| idris | | | | `idris2-lsp` |
74| idris | | | | `idris2-lsp` |
62| iex | ✓ | | | |
75| iex | ✓ | | | |
63| ini | ✓ | | | |
76| ini | ✓ | | | |
77+| janet | ✓ | | | |
64−| java | ✓ | ✓ | | `jdtls` |
78+| java | ✓ | ✓ | ✓ | `jdtls` |
65| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
79| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
80+| jinja | ✓ | | | |
66| jsdoc | ✓ | | | |
81| jsdoc | ✓ | | | |
67| json | ✓ | | ✓ | `vscode-json-language-server` |
82| json | ✓ | | ✓ | `vscode-json-language-server` |
83+| json5 | ✓ | | | |
68| jsonnet | ✓ | | | `jsonnet-language-server` |
84| jsonnet | ✓ | | | `jsonnet-language-server` |
69| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
85| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
70−| julia | ✓ | | | `julia` |
86+| julia | ✓ | ✓ | ✓ | `julia` |
87+| just | ✓ | ✓ | ✓ | |
71−| kdl | ✓ | | | |
88+| kdl | ✓ | ✓ | ✓ | |
72| kotlin | ✓ | | | `kotlin-language-server` |
89| kotlin | ✓ | | | `kotlin-language-server` |
73| latex | ✓ | ✓ | | `texlab` |
90| latex | ✓ | ✓ | | `texlab` |
74| lean | ✓ | | | `lean` |
91| lean | ✓ | | | `lean` |
@@ -76,77 +93,101 @@
76| llvm | ✓ | ✓ | ✓ | |
93| llvm | ✓ | ✓ | ✓ | |
77| llvm-mir | ✓ | ✓ | ✓ | |
94| llvm-mir | ✓ | ✓ | ✓ | |
78| llvm-mir-yaml | ✓ | | ✓ | |
95| llvm-mir-yaml | ✓ | | ✓ | |
96+| log | ✓ | | | |
97+| lpf | ✓ | | | |
79| lua | ✓ | ✓ | ✓ | `lua-language-server` |
98| lua | ✓ | ✓ | ✓ | `lua-language-server` |
80| make | ✓ | | | |
99| make | ✓ | | | |
100+| markdoc | ✓ | | | `markdoc-ls` |
81| markdown | ✓ | | | `marksman` |
101| markdown | ✓ | | | `marksman` |
82| markdown.inline | ✓ | | | |
102| markdown.inline | ✓ | | | |
83−| matlab | ✓ | | | |
103+| matlab | ✓ | ✓ | ✓ | |
84| mermaid | ✓ | | | |
104| mermaid | ✓ | | | |
85| meson | ✓ | | ✓ | |
105| meson | ✓ | | ✓ | |
86| mint | | | | `mint` |
106| mint | | | | `mint` |
87| msbuild | ✓ | | ✓ | |
107| msbuild | ✓ | | ✓ | |
108+| nasm | ✓ | ✓ | | |
88| nickel | ✓ | | ✓ | `nls` |
109| nickel | ✓ | | ✓ | `nls` |
110+| nim | ✓ | ✓ | ✓ | `nimlangserver` |
89| nix | ✓ | | | `nil` |
111| nix | ✓ | | | `nil` |
90−| nu | ✓ | | | |
112+| nu | ✓ | | | `nu` |
113+| nunjucks | ✓ | | | |
91| ocaml | ✓ | | ✓ | `ocamllsp` |
114| ocaml | ✓ | | ✓ | `ocamllsp` |
92| ocaml-interface | ✓ | | | `ocamllsp` |
115| ocaml-interface | ✓ | | | `ocamllsp` |
93−| odin | ✓ | | | `ols` |
116+| odin | ✓ | | ✓ | `ols` |
117+| opencl | ✓ | ✓ | ✓ | `clangd` |
94| openscad | ✓ | | | `openscad-lsp` |
118| openscad | ✓ | | | `openscad-lsp` |
95| org | ✓ | | | |
119| org | ✓ | | | |
96| pascal | ✓ | ✓ | | `pasls` |
120| pascal | ✓ | ✓ | | `pasls` |
97| passwd | ✓ | | | |
121| passwd | ✓ | | | |
98| pem | ✓ | | | |
122| pem | ✓ | | | |
99−| perl | ✓ | ✓ | ✓ | |
123+| perl | ✓ | ✓ | ✓ | `perlnavigator` |
100| php | ✓ | ✓ | ✓ | `intelephense` |
124| php | ✓ | ✓ | ✓ | `intelephense` |
101| po | ✓ | ✓ | | |
125| po | ✓ | ✓ | | |
126+| pod | ✓ | | | |
102| ponylang | ✓ | ✓ | ✓ | |
127| ponylang | ✓ | ✓ | ✓ | |
103| prisma | ✓ | | | `prisma-language-server` |
128| prisma | ✓ | | | `prisma-language-server` |
104| prolog | | | | `swipl` |
129| prolog | | | | `swipl` |
105−| protobuf | ✓ | | ✓ | |
130+| protobuf | ✓ | | ✓ | `bufls`, `pb` |
131+| prql | ✓ | | | |
106−| purescript | ✓ | | | `purescript-language-server` |
132+| purescript | ✓ | ✓ | | `purescript-language-server` |
107| python | ✓ | ✓ | ✓ | `pylsp` |
133| python | ✓ | ✓ | ✓ | `pylsp` |
108| qml | ✓ | | ✓ | `qmlls` |
134| qml | ✓ | | ✓ | `qmlls` |
109| r | ✓ | | | `R` |
135| r | ✓ | | | `R` |
110−| racket | ✓ | | | `racket` |
136+| racket | ✓ | | ✓ | `racket` |
111| regex | ✓ | | | |
137| regex | ✓ | | | |
138+| rego | ✓ | | | `regols` |
112| rescript | ✓ | ✓ | | `rescript-language-server` |
139| rescript | ✓ | ✓ | | `rescript-language-server` |
113| rmarkdown | ✓ | | ✓ | `R` |
140| rmarkdown | ✓ | | ✓ | `R` |
141+| robot | ✓ | | | `robotframework_ls` |
114| ron | ✓ | | ✓ | |
142| ron | ✓ | | ✓ | |
143+| rst | ✓ | | | |
115| ruby | ✓ | ✓ | ✓ | `solargraph` |
144| ruby | ✓ | ✓ | ✓ | `solargraph` |
116| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
145| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
117| sage | ✓ | ✓ | | |
146| sage | ✓ | ✓ | | |
118| scala | ✓ | | ✓ | `metals` |
147| scala | ✓ | | ✓ | `metals` |
119−| scheme | ✓ | | | |
148+| scheme | ✓ | | ✓ | |
120| scss | ✓ | | | `vscode-css-language-server` |
149| scss | ✓ | | | `vscode-css-language-server` |
121| slint | ✓ | | ✓ | `slint-lsp` |
150| slint | ✓ | | ✓ | `slint-lsp` |
151+| smali | ✓ | | ✓ | |
152+| smithy | ✓ | | | `cs` |
122| sml | ✓ | | | |
153| sml | ✓ | | | |
123| solidity | ✓ | | | `solc` |
154| solidity | ✓ | | | `solc` |
124| sql | ✓ | | | |
155| sql | ✓ | | | |
125| sshclientconfig | ✓ | | | |
156| sshclientconfig | ✓ | | | |
126| starlark | ✓ | ✓ | | |
157| starlark | ✓ | ✓ | | |
158+| strace | ✓ | | | |
127−| svelte | ✓ | | | `svelteserver` |
159+| svelte | ✓ | | ✓ | `svelteserver` |
160+| sway | ✓ | ✓ | ✓ | `forc` |
128| swift | ✓ | | | `sourcekit-lsp` |
161| swift | ✓ | | | `sourcekit-lsp` |
162+| t32 | ✓ | | | |
129| tablegen | ✓ | ✓ | ✓ | |
163| tablegen | ✓ | ✓ | ✓ | |
130| task | ✓ | | | |
164| task | ✓ | | | |
165+| templ | ✓ | | | `templ` |
131| tfvars | ✓ | | ✓ | `terraform-ls` |
166| tfvars | ✓ | | ✓ | `terraform-ls` |
167+| todotxt | ✓ | | | |
132| toml | ✓ | | | `taplo` |
168| toml | ✓ | | | `taplo` |
133| tsq | ✓ | | | |
169| tsq | ✓ | | | |
134| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
170| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
135| twig | ✓ | | | |
171| twig | ✓ | | | |
136| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
172| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
173+| typst | ✓ | | | `typst-lsp` |
137| ungrammar | ✓ | | | |
174| ungrammar | ✓ | | | |
175+| unison | ✓ | | | |
138| uxntal | ✓ | | | |
176| uxntal | ✓ | | | |
139−| v | ✓ | | | `v` |
177+| v | ✓ | ✓ | ✓ | `v-analyzer` |
140| vala | ✓ | | | `vala-language-server` |
178| vala | ✓ | | | `vala-language-server` |
141| verilog | ✓ | ✓ | | `svlangserver` |
179| verilog | ✓ | ✓ | | `svlangserver` |
180+| vhdl | ✓ | | | `vhdl_ls` |
142| vhs | ✓ | | | |
181| vhs | ✓ | | | |
143−| vue | ✓ | | | `vls` |
182+| vue | ✓ | | | `vue-language-server` |
144| wast | ✓ | | | |
183| wast | ✓ | | | |
145| wat | ✓ | | | |
184| wat | ✓ | | | |
185+| webc | ✓ | | | |
146| wgsl | ✓ | | | `wgsl_analyzer` |
186| wgsl | ✓ | | | `wgsl_analyzer` |
147| wit | ✓ | | ✓ | |
187| wit | ✓ | | ✓ | |
188+| wren | ✓ | ✓ | ✓ | |
148| xit | ✓ | | | |
189| xit | ✓ | | | |
149| xml | ✓ | | ✓ | |
190| xml | ✓ | | ✓ | |
150−| yaml | ✓ | | ✓ | `yaml-language-server` |
191+| yaml | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
151| yuck | ✓ | | | |
192| yuck | ✓ | | | |
152| zig | ✓ | ✓ | ✓ | `zls` |
193| zig | ✓ | ✓ | ✓ | `zls` |
book/src/generated/typable-cmd.md+17−6
@@ -12,16 +12,19 @@
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) |
16| `:new`, `:n` | Create a new scratch buffer. |
18| `:new`, `:n` | Create a new scratch buffer. |
17| `:format`, `:fmt` | Format the file using the LSP formatter. |
19| `:format`, `:fmt` | Format the file using the LSP formatter. |
18−| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
20+| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.) |
19| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
21| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
20| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
22| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
21| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
23| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
22| `:write-quit`, `:wq`, `:x` | Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) |
24| `:write-quit`, `:wq`, `:x` | Write changes to disk and close the current view. 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) |
25| `:write-quit!`, `:wq!`, `:x!` | Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) |
24| `:write-all`, `:wa` | Write changes from all buffers to disk. |
26| `: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. |
25| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. |
28| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. |
26| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes). |
29| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes). |
27| `:quit-all`, `:qa` | Close all views. |
30| `:quit-all`, `:qa` | Close all views. |
@@ -29,6 +32,7 @@
29| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
32| `:cquit`, `:cq` | Quit with exit code (default 1). 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). |
33| `:cquit!`, `:cq!` | Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2). |
31| `:theme` | Change the editor theme (show current theme if no name specified). |
34| `: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. |
32| `:clipboard-yank` | Yank main selection into system clipboard. |
36| `:clipboard-yank` | Yank main selection into system clipboard. |
33| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
37| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
34| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
38| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
@@ -44,12 +48,14 @@
44| `:show-directory`, `:pwd` | Show the current working directory. |
48| `:show-directory`, `:pwd` | Show the current working directory. |
45| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
49| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
46| `:character-info`, `:char` | Get info about the character under the primary cursor. |
50| `:character-info`, `:char` | Get info about the character under the primary cursor. |
47−| `:reload` | Discard changes and reload from the source file. |
51+| `:reload`, `:rl` | Discard changes and reload from the source file. |
48−| `:reload-all` | Discard changes and reload all documents from the source files. |
52+| `:reload-all`, `:rla` | Discard changes and reload all documents from the source files. |
49−| `:update` | Write changes only if the file has been modified. |
53+| `:update`, `:u` | Write changes only if the file has been modified. |
50| `:lsp-workspace-command` | Open workspace command picker |
54| `:lsp-workspace-command` | Open workspace command picker |
51−| `:lsp-restart` | Restarts the Language Server that is in use by the current doc |
55+| `:lsp-restart` | Restarts the language servers used by the current doc |
56+| `:lsp-stop` | Stops the language servers that are used by the current doc |
52| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
57| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
58+| `:tree-sitter-highlight-name` | Display name of tree-sitter highlight scope under the cursor. |
53| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
59| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
54| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging 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-eval` | Evaluate expression in current debug context. |
61| `:debug-eval` | Evaluate expression in current debug context. |
@@ -69,9 +75,14 @@
69| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
75| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
70| `:config-reload` | Refresh user config. |
76| `:config-reload` | Refresh user config. |
71| `:config-open` | Open the user config.toml file. |
77| `:config-open` | Open the user config.toml file. |
78+| `:config-open-workspace` | Open the workspace config.toml file. |
72| `:log-open` | Open the helix log file. |
79| `:log-open` | Open the helix log file. |
73| `:insert-output` | Run shell command, inserting output before each selection. |
80| `:insert-output` | Run shell command, inserting output before each selection. |
74| `:append-output` | Run shell command, appending output after each selection. |
81| `:append-output` | Run shell command, appending output after each selection. |
75| `:pipe` | Pipe each selection to the shell command. |
82| `:pipe` | Pipe each selection to the shell command. |
76| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
83| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
77| `:run-shell-command`, `:sh` | Run a shell command |
84| `:run-shell-command`, `:sh` | Run a shell command |
85+| `: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. |
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/README.md+1−1
@@ -1,4 +1,4 @@
1# Guides
1# Guides
2
2
3This section contains guides for adding new language server configurations,
3This section contains guides for adding new language server configurations,
4−tree-sitter grammars, textobject queries, etc.
4+tree-sitter grammars, textobject queries, and other similar items.
book/src/guides/adding_languages.md+45−35
@@ -1,45 +1,55 @@
1−# Adding languages
1+# Adding new languages to Helix
2+
3+In order to add a new language to Helix, you will need to follow the steps
4+below.
2
5
3## Language configuration
6## Language configuration
4
7
5−To add a new language, you need to add a `[[language]]` entry to the
8+1. Add a new `[[language]]` entry in the `languages.toml` file and provide the
9+ necessary configuration for the new language. For more information on
10+ language configuration, refer to the
6−`languages.toml` (see the [language configuration section]).
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.
13+2. If you are adding a new language or updating an existing language server
14+ configuration, run the command `cargo xtask docgen` to update the
15+ [Language Support](../lang-support.md) documentation.
7
16
8−When adding a new language or Language Server configuration for an existing
9−language, run `cargo xtask docgen` to add the new configuration to the
10−[Language Support][lang-support] docs before creating a pull request.
11−When adding a Language Server configuration, be sure to update the
17+> 💡 If you are adding a new Language Server configuration, make sure to update
12−[Language Server Wiki][install-lsp-wiki] with installation notes.
18+> the
19+> [Language Server Wiki](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
20+> with the installation instructions.
13
21
14## Grammar configuration
22## Grammar configuration
15
23
16−If a tree-sitter grammar is available for the language, add a new `[[grammar]]`
24+1. If a tree-sitter grammar is available for the new language, add a new
17−entry to `languages.toml`.
25+ `[[grammar]]` entry to the `languages.toml` file.
18−
26+2. If you are testing the grammar locally, you can use the `source.path` key
19−You may use the `source.path` key rather than `source.git` with an absolute path
27+ with an absolute path to the grammar. However, before submitting a pull
20−to a locally available grammar for testing, but switch to `source.git` before
28+ request, make sure to switch to using `source.git`.
21−submitting a pull request.
22
29
23## Queries
30## Queries
24
31
25−For a language to have syntax-highlighting and indentation among
32+1. In order to provide syntax highlighting and indentation for the new language,
26−other things, you have to add queries. Add a directory for your
33+ you will need to add queries.
34+2. Create a new directory for the language with the path
27−language with the path `runtime/queries/<name>/`. The tree-sitter
35+ `runtime/queries/<name>/`.
36+3. Refer to the
28−[website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
37+ [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
29−gives more info on how to write queries.
38+ for more information on writing queries.
30−
39+4. A list of highlight captures can be found [on the themes page](https://docs.helix-editor.com/themes.html#scopes).
40+
31−> NOTE: When evaluating queries, the first matching query takes
41+> 💡 In Helix, the first matching query takes precedence when evaluating
32−precedence, which is different from other editors like Neovim where
42+> queries, which is different from other editors such as Neovim where the last
33−the last matching query supersedes the ones before it. See
43+> matching query supersedes the ones before it. See
44+> [this issue](https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090)
34−[this issue][neovim-query-precedence] for an example.
45+> for an example.
35−
46+
36−## Common Issues
47+## Common issues
37−
48+
49+- If you encounter errors when running Helix after switching branches, you may
50+ need to update the tree-sitter grammars. Run the command `hx --grammar fetch`
38−- If you get errors when running after switching branches, you may have to update the tree-sitter grammars. Run `hx --grammar fetch` to fetch the grammars and `hx --grammar build` to build any out-of-date grammars.
51+ to fetch the grammars and `hx --grammar build` to build any out-of-date
39−
52+ grammars.
40−- If a parser is segfaulting or you want to remove the parser, make sure to remove the compiled parser in `runtime/grammar/<name>.so`
53+- If a parser is causing a segfault, or you want to remove it, make sure to
41−
54+ remove the compiled parser located at `runtime/grammars/<name>.so`.
42−[language configuration section]: ../languages.md
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.
43−[neovim-query-precedence]: https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090
44−[install-lsp-wiki]: https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers
45−[lang-support]: ../lang-support.md
book/src/guides/indent.md+320−44
@@ -1,76 +1,308 @@
1−# Adding Indent Queries
1+# Adding indent queries
2
2
3−Helix uses tree-sitter to correctly indent new lines. This requires
3+Helix uses tree-sitter to correctly indent new lines. This requires a tree-
4−a tree-sitter grammar and an `indent.scm` query file placed in
4+sitter grammar and an `indent.scm` query file placed in `runtime/queries/
5−`runtime/queries/{language}/indents.scm`. The indentation for a line
5+{language}/indents.scm`. The indentation for a line is calculated by traversing
6−is calculated by traversing the syntax tree from the lowest node at the
6+the syntax tree from the lowest node at the beginning of the new line (see
7−beginning of the new line. Each of these nodes contributes to the total
7+[Indent queries](#indent-queries)). Each of these nodes contributes to the total
8−indent when it is captured by the query (in what way depends on the name
8+indent when it is captured by the query (in what way depends on the name of
9−of the capture).
9+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.
13+the total indent level by 1. See [Capture types](#capture-types).
14
14
15−## Scopes
15+By default, Helix uses the `hybrid` indentation heuristic. This means that
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.
16
23
17−Added indents don't always apply to the whole node. For example, in most
24+## Indent queries
18−cases when a node should be indented, we actually only want everything
19−except for its first line to be indented. For this, there are several
20−scopes (more scopes may be added in the future if required):
21
25
22−- `all`:
26+When Helix is inserting a new line through `o`, `O`, or `<ret>`, to determine
23−This scope applies to the whole captured node. This is only different from
27+the indent level for the new line, the query in `indents.scm` is run on the
24−`tail` when the captured node is the first node on its line.
28+document. The starting position of the query is the end of the line above where
29+a new line will be inserted.
25
30
26−- `tail`:
31+For `o`, the inserted line is the line below the cursor, so that starting
27−This scope applies to everything except for the first line of the
32+position of the query is the end of the current line.
28−captured node.
29
33
30−Every capture type has a default scope which should do the right thing
34+```rust
31−in most situations. When a different scope is required, this can be
35+fn need_hero(some_hero: Hero, life: Life) -> {
32−changed by using a `#set!` declaration anywhere in the pattern:
36+ matches!(some_hero, Hero { // ←─────────────────╮
33−```scm
37+ strong: true,//←╮ ↑ ↑ │
34−(assignment_expression
38+ fast: true, // │ │ ╰── query start │
35− right: (_) @indent
39+ sure: true, // │ ╰───── cursor ├─ traversal
36− (#set! "scope" "all"))
40+ soon: true, // ╰──────── new line inserted │ start node
41+ }) && // │
42+// ↑ │
43+// ╰───────────────────────────────────────────────╯
44+ some_hero > life
45+}
37```
46```
38
47
39−## Capture Types
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.
40
50
41−- `@indent` (default scope `tail`):
51+```rust
42−Increase the indent level by 1. Multiple occurrences in the same line
52+fn need_hero(some_hero: Hero, life: Life) -> { // ←─╮
43−don't stack. If there is at least one `@indent` and one `@outdent`
53+ matches!(some_hero, Hero { // ←╮ ↑ │
44−capture on the same line, the indent level isn't changed at all.
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+```
45
62
46−- `@outdent` (default scope `all`):
63+From this starting node, the syntax tree is traversed up until the root node.
47−Decrease the indent level by 1. The same rules as for `@indent` apply.
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.
48
67
49−- `@extend`:
68+### Capture types
50−Extend the range of this node to the end of the line and to lines that
51−are indented more than the line that this node starts on. This is useful
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.
54
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.
55- `@extend.prevent-once`:
94- `@extend.prevent-once`:
56−Prevents the first extension of an ancestor of this node. For example, in Python
95+ Prevents the first extension of an ancestor of this node. For example, in Python
57−a return expression always ends the block that it is in. Note that this only stops the
96+ a return expression always ends the block that it is in. Note that this only
58−extension of the next `@extend` capture. If multiple ancestors are captured,
97+ stops the extension of the next `@extend` capture. If multiple ancestors are
59−only the extension of the innermost one is prevented. All other ancestors are unaffected
98+ captured, only the extension of the innermost one is prevented. All other
60−(regardless of whether the innermost ancestor would actually have been extended).
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+```
61
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+
232+In Python, the are a few tokens that will always end a scope, such as a return
233+statement. Since the scope ends, so should the indent level. But because the
234+function span is extended to every line with a greater indent level, a new 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+
239+To help, we need to signal an end to the extension. We can do this with
240+`@extend.prevent-once`.
241+
242+```scm
243+(parenthesized_expression) @indent
244+
245+]
246+ (function_definition)
247+ (class_definition)
248+] @indent @extend
249+
250+(return_statement) @extend.prevent-once
251+```
252+
253+#### `@indent.always` / `@outdent.always`
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+
258+Sometimes, there are cases when you may want to ensure that every indent capture
259+is additive, regardless of how many occur on the same line. Consider this
260+example in YAML.
261+
262+```yaml
263+ - foo: bar
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+
276+In YAML, you often have lists of maps. In these cases, the syntax is such that
277+the list element and the map both start on the same line. But we really do want
278+to start an indentation for each of these so that subsequent keys in the map
279+hang over the list and align properly. This is where `@indent.always` helps.
280+
281+```scm
282+((block_sequence_item) @item @indent.always @extend
283+ (#not-one-line? @item))
284+
285+((block_mapping_pair
286+ key: (_) @key
287+ value: (_) @val
288+ (#not-same-line? @key @val)
289+ ) @indent.always @extend
290+)
291+```
62
292
63## Predicates
293## Predicates
64
294
65In some cases, an S-expression cannot express exactly what pattern should be matched.
295In some cases, an S-expression cannot express exactly what pattern should be matched.
66For that, tree-sitter allows for predicates to appear anywhere within a pattern,
296For that, tree-sitter allows for predicates to appear anywhere within a pattern,
67similar to how `#set!` declarations work:
297similar to how `#set!` declarations work:
298+
68```scm
299```scm
69(some_kind
300(some_kind
70 (child_kind) @indent
301 (child_kind) @indent
71 (#predicate? arg1 arg2 ...)
302 (#predicate? arg1 arg2 ...)
72)
303)
73```
304```
305+
74The number of arguments depends on the predicate that's used.
306The number of arguments depends on the predicate that's used.
75Each argument is either a capture (`@name`) or a string (`"some string"`).
307Each argument is either a capture (`@name`) or a string (`"some string"`).
76The following predicates are supported by tree-sitter:
308The following predicates are supported by tree-sitter:
@@ -91,3 +323,47 @@ argument (a string).
91
323
92- `#same-line?`/`#not-same-line?`:
324- `#same-line?`/`#not-same-line?`:
93The captures given by the 2 arguments must/must not start on the same line.
325The 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.mdnew file+57
@@ -0,0 +1,57 @@
1+# Adding Injection Queries
2+
3+Writing 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
5+are a few Helix specific extensions that allow for more control.
6+
7+And example of a simple query that would highlight all strings as bash in Nix:
8+```scm
9+((string_expression (string_fragment) @injection.content)
10+ (#set! injection.language "bash"))
11+```
12+
13+## Capture Types
14+
15+- `@injection.language` (standard):
16+The captured node may contain the language name used to highlight the node captured by
17+`@injection.content`.
18+
19+- `@injection.content` (standard):
20+Marks the content to be highlighted as the language captured with `@injection.language` _et al_.
21+
22+- `@injection.filename` (extension):
23+The captured node may contain a filename with a file-extension known to Helix,
24+highlighting `@injection.content` as that language. This uses the language extensions defined in
25+both the default languages.toml distributed with Helix, as well as user defined languages.
26+
27+- `@injection.shebang` (extension):
28+The captured node may contain a shebang used to choose a language to highlight as. This also uses
29+the shebangs defined in the default and user `languages.toml`.
30+
31+## Settings
32+
33+- `injection.combined` (standard):
34+Indicates that all the matching nodes in the tree should have their content parsed as one
35+nested document.
36+
37+- `injection.language` (standard):
38+Forces the captured content to be highlighted as the given language
39+
40+- `injection.include-children` (standard):
41+Indicates that the content node’s entire text should be re-parsed, including the text of its child
42+nodes. By default, child nodes’ text will be excluded from the injected document.
43+
44+- `injection.include-unnamed-children` (extension):
45+Same as `injection.include-children` but only for unnamed child nodes.
46+
47+## Predicates
48+
49+- `#eq?` (standard):
50+The first argument (a capture) must be equal to the second argument
51+(a capture or a string).
52+
53+- `#match?` (standard):
54+The first argument (a capture) must match the regex given in the
55+second argument (a string).
56+
57+[upstream-docs]: http://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
book/src/guides/textobject.md+7−9
@@ -1,14 +1,14 @@
1−# Adding Textobject Queries
1+# Adding textobject queries
2
2
3−Textobjects that are language specific ([like functions, classes, etc][textobjects])
3+Helix supports textobjects that are language specific, such as functions, classes, etc.
4−require an accompanying tree-sitter grammar and a `textobjects.scm` query file
4+These textobjects require an accompanying tree-sitter grammar and a `textobjects.scm` query file
5to work properly. Tree-sitter allows us to query the source code syntax tree
5to work properly. Tree-sitter allows us to query the source code syntax tree
6and capture specific parts of it. The queries are written in a lisp dialect.
6and capture specific parts of it. The queries are written in a lisp dialect.
7More information on how to write queries can be found in the [official tree-sitter
7More information on how to write queries can be found in the [official tree-sitter
8documentation][tree-sitter-queries].
8documentation][tree-sitter-queries].
9
9
10Query files should be placed in `runtime/queries/{language}/textobjects.scm`
10Query files should be placed in `runtime/queries/{language}/textobjects.scm`
11−when contributing. Note that to test the query files locally you should put
11+when contributing to Helix. Note that to test the query files locally you should put
12them under your local runtime directory (`~/.config/helix/runtime` on Linux
12them under your local runtime directory (`~/.config/helix/runtime` on Linux
13for example).
13for example).
14
14
@@ -28,9 +28,9 @@ The following [captures][tree-sitter-captures] are recognized:
28
28
29[Example query files][textobject-examples] can be found in the helix GitHub repository.
29[Example query files][textobject-examples] can be found in the helix GitHub repository.
30
30
31−## Queries for Textobject Based Navigation
31+## Queries for textobject based navigation
32
32
33−[Tree-sitter based navigation][textobjects-nav] is done using captures in the
33+Tree-sitter based navigation in Helix is done using captures in the
34following order:
34following order:
35
35
36- `object.movement`
36- `object.movement`
@@ -38,12 +38,10 @@ following order:
38- `object.inside`
38- `object.inside`
39
39
40For example if a `function.around` capture has been already defined for a language
40For example if a `function.around` capture has been already defined for a language
41−in it's `textobjects.scm` file, function navigation should also work automatically.
41+in its `textobjects.scm` file, function navigation should also work automatically.
42`function.movement` should be defined only if the node captured by `function.around`
42`function.movement` should be defined only if the node captured by `function.around`
43doesn't make sense in a navigation context.
43doesn't make sense in a navigation context.
44
44
45−[textobjects]: ../usage.md#textobjects
46−[textobjects-nav]: ../usage.md#tree-sitter-textobject-based-navigation
47[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
45[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
48[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes
46[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes
49[textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=
47[textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=
book/src/install.md+240−100
@@ -1,180 +1,320 @@
1−# Installation
1+# Installing Helix
2−
2+
3+<!--toc:start-->
4+- [Pre-built binaries](#pre-built-binaries)
5+- [Linux, macOS, Windows and OpenBSD packaging status](#linux-macos-windows-and-openbsd-packaging-status)
6+- [Linux](#linux)
7+ - [Ubuntu](#ubuntu)
8+ - [Fedora/RHEL](#fedorarhel)
9+ - [Arch Linux extra](#arch-linux-extra)
10+ - [NixOS](#nixos)
11+ - [Flatpak](#flatpak)
12+ - [Snap](#snap)
13+ - [AppImage](#appimage)
14+- [macOS](#macos)
15+ - [Homebrew Core](#homebrew-core)
16+ - [MacPorts](#macports)
17+- [Windows](#windows)
18+ - [Winget](#winget)
19+ - [Scoop](#scoop)
20+ - [Chocolatey](#chocolatey)
21+ - [MSYS2](#msys2)
22+- [Building from source](#building-from-source)
23+ - [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)
28+ - [Configure the desktop shortcut](#configure-the-desktop-shortcut)
29+<!--toc:end-->
30+
31+To install Helix, follow the instructions specific to your operating system.
32+Note that:
33+
34+- To get the latest nightly version of Helix, you need to
35+ [build from source](#building-from-source).
36+
37+- To take full advantage of Helix, install the language servers for your
38+ preferred programming languages. See the
39+ [wiki](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
40+ for instructions.
41+
42+## Pre-built binaries
43+
44+Download pre-built binaries from the
3−We provide pre-built binaries on the [GitHub Releases page](https://github.com/helix-editor/helix/releases).
45+[GitHub Releases page](https://github.com/helix-editor/helix/releases). Add the binary to your system's `$PATH` to use it from the command
46+line.
47+
48+## Linux, macOS, Windows and OpenBSD packaging status
4
49
5[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
50[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
6
51
7−## OSX
52+## Linux
53+
54+The following third party repositories are available:
55+
56+### Ubuntu
8
57
9−Helix is available in homebrew-core:
58+Add the `PPA` for Helix:
10
59
60+```sh
61+sudo add-apt-repository ppa:maveonair/helix-editor
62+sudo apt update
63+sudo apt install helix
11```
64```
65+
66+### Fedora/RHEL
67+
68+```sh
12−brew install helix
69+sudo dnf install helix
13```
70```
14
71
15−## Linux
72+### Arch Linux extra
73+
74+Releases are available in the `extra` repository:
75+
76+```sh
77+sudo pacman -S helix
78+```
79+Additionally, a [helix-git](https://aur.archlinux.org/packages/helix-git/) package is available
80+in the AUR, which builds the master branch.
16
81
17### NixOS
82### NixOS
18
83
19−A [flake](https://nixos.wiki/wiki/Flakes) containing the package is available in
84+Helix is available in [nixpkgs](https://github.com/nixos/nixpkgs) through the `helix` attribute,
20−the project root. The flake can also be used to spin up a reproducible development
85+the unstable channel usually carries the latest release.
21−shell for working on Helix with `nix develop`.
22
86
87+Helix is also available as a [flake](https://nixos.wiki/wiki/Flakes) in the project
88+root. Use `nix develop` to spin up a reproducible development shell. Outputs are
23−Flake outputs are cached for each push to master using
89+cached for each push to master using [Cachix](https://www.cachix.org/). The
24−[Cachix](https://www.cachix.org/). The flake is configured to
25−automatically make use of this cache assuming the user accepts
90+flake is configured to automatically make use of this cache assuming the user
26−the new settings on first use.
91+accepts the new settings on first use.
27
92
28−If you are using a version of Nix without flakes enabled you can
93+If you are using a version of Nix without flakes enabled,
29−[install Cachix cli](https://docs.cachix.org/installation); `cachix use helix` will
94+[install Cachix CLI](https://docs.cachix.org/installation) and use
30−configure Nix to use cached outputs when possible.
95+`cachix use helix` to configure Nix to use cached outputs when possible.
31
96
32−### Arch Linux
97+### Flatpak
33
98
34−Releases are available in the `community` repository.
99+Helix is available on [Flathub](https://flathub.org/en-GB/apps/com.helix_editor.Helix):
35
100
36−A [helix-git](https://aur.archlinux.org/packages/helix-git/) package is also available on the AUR, which builds the master branch.
101+```sh
102+flatpak install flathub com.helix_editor.Helix
103+flatpak run com.helix_editor.Helix
104+```
37
105
38−### Fedora Linux
106+### Snap
39
107
40−You can install the COPR package for Helix via
108+Helix is available on [Snapcraft](https://snapcraft.io/helix) and can be installed with:
41
109
110+```sh
111+snap install --classic helix
42```
112```
43−sudo dnf copr enable varlad/helix
113+
44−sudo dnf install helix
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
117+
118+Install Helix using the Linux [AppImage](https://appimage.org/) format.
119+Download the official Helix AppImage from the [latest releases](https://github.com/helix-editor/helix/releases/latest) page.
120+
121+```sh
122+chmod +x helix-*.AppImage # change permission for executable mode
123+./helix-*.AppImage # run helix
45```
124```
125+
126+## macOS
46
127
47−### Void Linux
128+### Homebrew Core
48
129
130+```sh
131+brew install helix
49```
132```
133+
134+### MacPorts
135+
136+```sh
50−sudo xbps-install helix
137+port install helix
51```
138```
52
139
53## Windows
140## Windows
54
141
55−Helix can be installed using [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
142+Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
56or [MSYS2](https://msys2.org/).
143or [MSYS2](https://msys2.org/).
57
144
58−**Scoop:**
145+### Winget
146+Windows Package Manager winget command-line tool is by default available on Windows 11 and modern versions of Windows 10 as a part of the App Installer.
147+You can get [App Installer from the Microsoft Store](https://www.microsoft.com/p/app-installer/9nblggh4nns1#activetab=pivot:overviewtab). If it's already installed, make sure it is updated with the latest version.
59
148
149+```sh
150+winget install Helix.Helix
60```
151```
152+
153+### Scoop
154+
155+```sh
61scoop install helix
156scoop install helix
62```
157```
63
158
64−**Chocolatey:**
159+### Chocolatey
65
160
66−```
161+```sh
67choco install helix
162choco install helix
68```
163```
69
164
70−**MSYS2:**
165+### MSYS2
71−
72−Choose the [proper command](https://www.msys2.org/docs/package-naming/) for your system from below:
73
166
74− - For 32 bit Windows 7 or above:
167+For 64-bit Windows 8.1 or above:
75
168
76−```
169+```sh
77−pacman -S mingw-w64-i686-helix
170+pacman -S mingw-w64-ucrt-x86_64-helix
78```
171```
79
172
80− - For 64 bit Windows 7 or above:
173+## Building from source
81
174
82−```
175+Requirements:
83−pacman -S mingw-w64-x86_64-helix
84−```
85
176
86− - For 64 bit Windows 8.1 or above:
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.
87
180
88−```
181+- The [Rust toolchain](https://www.rust-lang.org/tools/install)
89−pacman -S mingw-w64-ucrt-x86_64-helix
182+- The [Git version control system](https://git-scm.com/)
90−```
183+- A C++14 compatible compiler to build the tree-sitter grammars, for example GCC or Clang
91
184
92−## Build from source
185+If 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:
93
186
94−```
187+```sh
95−git clone https://github.com/helix-editor/helix
188+RUSTFLAGS="-C target-feature=-crt-static"
96−cd helix
97−cargo install --path helix-term --locked
98```
189```
99
190
100−This will install the `hx` binary to `$HOME/.cargo/bin` and build tree-sitter grammars in `./runtime/grammars`.
191+1. Clone the repository:
101
192
102−If you are using the musl-libc instead of glibc the following environment variable must be set during the build
193+ ```sh
103−to ensure tree sitter grammars can be loaded correctly:
194+ git clone https://github.com/helix-editor/helix
195+ cd helix
196+ ```
104
197
198+2. Compile from source:
199+
105−```
200+ ```sh
106−RUSTFLAGS="-C target-feature=-crt-static"
201+ cargo install --path helix-term --locked
107−```
202+ ```
108
203
204+ This command will create the `hx` executable and construct the tree-sitter
205+ grammars in the local `runtime` folder.
109
206
110−Helix also needs its runtime files so make sure to copy/symlink the `runtime/` directory into the
207+> 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch
111−config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overridden
208+> grammars with `hx --grammar fetch` and compile them with
112−via the `HELIX_RUNTIME` environment variable.
209+> `hx --grammar build`. This will install them in
210+> the `runtime` directory within the user's helix config directory (more
211+> [details below](#multiple-runtime-directories)).
113
212
114−| OS | Command |
213+### Configuring Helix's runtime files
115−| -------------------- | ------------------------------------------------ |
116−| Windows (Cmd) | `xcopy /e /i runtime %AppData%\helix\runtime` |
117−| Windows (PowerShell) | `xcopy /e /i runtime $Env:AppData\helix\runtime` |
118−| Linux / macOS | `ln -s $PWD/runtime ~/.config/helix/runtime` |
119
214
120−Starting with Windows Vista you can also create symbolic links on Windows. Note that this requires
215+#### Linux and macOS
121−elevated privileges - i.e. PowerShell or Cmd must be run as administrator.
122
216
123−**PowerShell:**
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
219+your `~/.bashrc` or equivalent:
124
220
125−```powershell
221+```sh
126−New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime"
222+HELIX_RUNTIME=~/src/helix/runtime
127```
223```
128−Note: "runtime" must be the absolute path to the runtime directory.
129
224
130−**Cmd:**
225+Or, create a symbolic link:
131
226
132−```cmd
227+```sh
133−cd %appdata%\helix
228+ln -Ts $PWD/runtime ~/.config/helix/runtime
134−mklink /D runtime "<helix-repo>\runtime"
135```
229```
136
230
137−The runtime location can be overridden via the `HELIX_RUNTIME` environment variable.
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.
138
232
139−> NOTE: if `HELIX_RUNTIME` is set prior to calling `cargo install --path helix-term --locked`,
233+#### Windows
140−> tree-sitter grammars will be built in `$HELIX_RUNTIME/grammars`.
141
234
142−If you plan on keeping the repo locally, an alternative to copying/symlinking
235+Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
143−runtime files is to set `HELIX_RUNTIME=/path/to/helix/runtime`
236+`Edit environment variables for your account`) or use the `setx` command in
144−(`HELIX_RUNTIME=$PWD/runtime` if you're in the helix repo directory).
237+Cmd:
145
238
146−To use Helix in desktop environments that supports [XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html), including Gnome and KDE, copy the provided `.desktop` file to the correct folder:
239+```sh
147−
240+setx HELIX_RUNTIME "%userprofile%\source\repos\helix\runtime"
148−```bash
149−cp contrib/Helix.desktop ~/.local/share/applications
150```
241```
151
242
152−To use another terminal than the default, you will need to modify the `.desktop` file. For example, to use `kitty`:
243+> 💡 `%userprofile%` resolves to your user directory like
244+> `C:\Users\Your-Name\` for example.
153
245
154−```bash
246+Or, create a symlink in `%appdata%\helix\` that links to the source code directory:
155−sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
156−sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
157−```
158
247
159−Please note: there is no icon for Helix yet, so the system default will be used.
248+| Method | Command |
249+| ---------- | -------------------------------------------------------------------------------------- |
250+| PowerShell | `New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime"` |
251+| Cmd | `cd %appdata%\helix` <br/> `mklink /D runtime "%userprofile%\src\helix\runtime"` |
160
252
161−## Finishing up the installation
253+> 💡 On Windows, creating a symbolic link may require running PowerShell or
254+> Cmd as an administrator.
162
255
163−To make sure everything is set up as expected you should finally run the helix healthcheck via
256+#### Multiple runtime directories
164
257
165−```
258+When Helix finds multiple runtime directories it will search through them for files in the
259+following order:
260+
261+1. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
262+ developing and testing helix only).
263+2. `runtime/` subdirectory of OS-dependent helix user config directory.
264+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.
268+
269+This order also sets the priority for selecting which file will be used if multiple runtime
270+directories have files with the same name.
271+
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
291+
292+To make sure everything is set up as expected you should run the Helix health
293+check:
294+
295+```sh
166hx --health
296hx --health
167```
297```
168
298
169−For more information on the information displayed in the health check results refer to [Healthcheck](https://github.com/helix-editor/helix/wiki/Healthcheck).
299+For more information on the health check results refer to
300+[Health check](https://github.com/helix-editor/helix/wiki/Healthcheck).
301+
302+### Configure the desktop shortcut
170
303
171−### Building tree-sitter grammars
304+If your desktop environment supports the
305+[XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html)
306+you can configure Helix to show up in the application menu by copying the
307+provided `.desktop` and icon files to their correct folders:
172
308
173−Tree-sitter grammars must be fetched and compiled if not pre-packaged.
309+```sh
174−Fetch grammars with `hx --grammar fetch` (requires `git`) and compile them
310+cp contrib/Helix.desktop ~/.local/share/applications
175−with `hx --grammar build` (requires a C++ compiler).
311+cp contrib/helix.png ~/.icons # or ~/.local/share/icons
312+```
176
313
177−### Installing language servers
314+To use another terminal than the system default, you can modify the `.desktop`
315+file. For example, to use `kitty`:
178
316
179−Language servers can optionally be installed if you want their features (auto-complete, diagnostics etc.).
317+```sh
180−Follow the [instructions on the wiki page](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers) to add your language servers of choice.
318+sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
319+sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
320+```
book/src/keymap.md+73−61
@@ -14,17 +14,19 @@
14 - [Space mode](#space-mode)
14 - [Space mode](#space-mode)
15 - [Popup](#popup)
15 - [Popup](#popup)
16 - [Unimpaired](#unimpaired)
16 - [Unimpaired](#unimpaired)
17−- [Insert Mode](#insert-mode)
17+- [Insert mode](#insert-mode)
18- [Select / extend mode](#select--extend-mode)
18- [Select / extend mode](#select--extend-mode)
19- [Picker](#picker)
19- [Picker](#picker)
20- [Prompt](#prompt)
20- [Prompt](#prompt)
21
21
22> 💡 Mappings marked (**LSP**) require an active language server for the file.
22> 💡 Mappings marked (**LSP**) require an active language server for the file.
23
23
24−> 💡 Mappings marked (**TS**) require a tree-sitter grammar for the filetype.
24+> 💡 Mappings marked (**TS**) require a tree-sitter grammar for the file type.
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+
28### Movement
30### Movement
29
31
30> NOTE: Unlike Vim, `f`, `F`, `t` and `T` are not confined to the current line.
32> NOTE: Unlike Vim, `f`, `F`, `t` and `T` are not confined to the current line.
@@ -32,8 +34,8 @@
32| Key | Description | Command |
34| Key | Description | Command |
33| ----- | ----------- | ------- |
35| ----- | ----------- | ------- |
34| `h`, `Left` | Move left | `move_char_left` |
36| `h`, `Left` | Move left | `move_char_left` |
35−| `j`, `Down` | Move down | `move_line_down` |
37+| `j`, `Down` | Move down | `move_visual_line_down` |
36−| `k`, `Up` | Move up | `move_line_up` |
38+| `k`, `Up` | Move up | `move_visual_line_up` |
37| `l`, `Right` | Move right | `move_char_right` |
39| `l`, `Right` | Move right | `move_char_right` |
38| `w` | Move next word start | `move_next_word_start` |
40| `w` | Move next word start | `move_next_word_start` |
39| `b` | Move previous word start | `move_prev_word_start` |
41| `b` | Move previous word start | `move_prev_word_start` |
@@ -109,9 +111,10 @@
109| Key | Description | Command |
111| Key | Description | Command |
110| ----- | ----------- | ------- |
112| ----- | ----------- | ------- |
111| `s` | Select all regex matches inside selections | `select_regex` |
113| `s` | Select all regex matches inside selections | `select_regex` |
112−| `S` | Split selection into subselections on regex matches | `split_selection` |
114+| `S` | Split selection into sub selections on regex matches | `split_selection` |
113| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
115| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
116+| `Alt-minus` | Merge selections | `merge_selections` |
114−| `Alt-_ ` | Merge consecutive selections | `merge_consecutive_selections` |
117+| `Alt-_` | Merge consecutive selections | `merge_consecutive_selections` |
115| `&` | Align selection in columns | `align_selections` |
118| `&` | Align selection in columns | `align_selections` |
116| `_` | Trim whitespace from the selection | `trim_selections` |
119| `_` | Trim whitespace from the selection | `trim_selections` |
117| `;` | Collapse selection onto a single cursor | `collapse_selection` |
120| `;` | Collapse selection onto a single cursor | `collapse_selection` |
@@ -141,7 +144,7 @@
141
144
142### Search
145### Search
143
146
144−Search commands all operate on the `/` register by default. Use `"<char>` to operate on a different one.
147+Search commands all operate on the `/` register by default. To use a different register, use `"<char>`.
145
148
146| Key | Description | Command |
149| Key | Description | Command |
147| ----- | ----------- | ------- |
150| ----- | ----------- | ------- |
@@ -175,9 +178,8 @@ Accessed by typing `z` in [normal mode](#normal-mode).
175
178
176View mode is intended for scrolling and manipulating the view without changing
179View mode is intended for scrolling and manipulating the view without changing
177the selection. The "sticky" variant of this mode (accessed by typing `Z` in
180the selection. The "sticky" variant of this mode (accessed by typing `Z` in
178−normal mode) is persistent; use the Escape key to return to normal mode after
181+normal mode) is persistent and can be exited using the escape key. This is
179−usage (useful when you're simply looking over text and not actively editing
182+useful when you're simply looking over text and not actively editing it.
180−it).
181
183
182
184
183| Key | Description | Command |
185| Key | Description | Command |
@@ -219,13 +221,15 @@ Jumps to various locations.
219| `n` | Go to next buffer | `goto_next_buffer` |
221| `n` | Go to next buffer | `goto_next_buffer` |
220| `p` | Go to previous buffer | `goto_previous_buffer` |
222| `p` | Go to previous buffer | `goto_previous_buffer` |
221| `.` | Go to last modification in current file | `goto_last_modification` |
223| `.` | Go to last modification in current file | `goto_last_modification` |
224+| `j` | Move down textual (instead of visual) line | `move_line_down` |
225+| `k` | Move up textual (instead of visual) line | `move_line_up` |
222
226
223#### Match mode
227#### Match mode
224
228
225Accessed by typing `m` in [normal mode](#normal-mode).
229Accessed by typing `m` in [normal mode](#normal-mode).
226
230
227See the relevant section in [Usage](./usage.md) for an explanation about
231See the relevant section in [Usage](./usage.md) for an explanation about
228−[surround](./usage.md#surround) and [textobject](./usage.md#textobjects) usage.
232+[surround](./usage.md#surround) and [textobject](./usage.md#navigating-using-tree-sitter-textobjects) usage.
229
233
230| Key | Description | Command |
234| Key | Description | Command |
231| ----- | ----------- | ------- |
235| ----- | ----------- | ------- |
@@ -242,7 +246,7 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`).
242
246
243Accessed by typing `Ctrl-w` in [normal mode](#normal-mode).
247Accessed by typing `Ctrl-w` in [normal mode](#normal-mode).
244
248
245−This layer is similar to Vim keybindings as Kakoune does not support window.
249+This layer is similar to Vim keybindings as Kakoune does not support windows.
246
250
247| Key | Description | Command |
251| Key | Description | Command |
248| ----- | ------------- | ------- |
252| ----- | ------------- | ------- |
@@ -268,30 +272,32 @@ Accessed by typing `Space` in [normal mode](#normal-mode).
268
272
269This layer is a kludge of mappings, mostly pickers.
273This layer is a kludge of mappings, mostly pickers.
270
274
271−| Key | Description | Command |
275+| Key | Description | Command |
272−| ----- | ----------- | ------- |
276+| ----- | ----------- | ------- |
273−| `f` | Open file picker | `file_picker` |
277+| `f` | Open file picker | `file_picker` |
274−| `F` | Open file picker at current working directory | `file_picker_in_current_directory` |
278+| `F` | Open file picker at current working directory | `file_picker_in_current_directory` |
275−| `b` | Open buffer picker | `buffer_picker` |
279+| `b` | Open buffer picker | `buffer_picker` |
276−| `j` | Open jumplist picker | `jumplist_picker` |
280+| `j` | Open jumplist picker | `jumplist_picker` |
281+| `g` | Debug (experimental) | N/A |
277−| `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` |
282+| `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` |
278−| `s` | Open document symbol picker (**LSP**) | `symbol_picker` |
283+| `s` | Open document symbol picker (**LSP**) | `symbol_picker` |
279−| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` |
284+| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` |
280−| `d` | Open document diagnostics picker (**LSP**) | `diagnostics_picker` |
285+| `d` | Open document diagnostics picker (**LSP**) | `diagnostics_picker` |
281−| `D` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnostics_picker` |
286+| `D` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnostics_picker` |
282−| `r` | Rename symbol (**LSP**) | `rename_symbol` |
287+| `r` | Rename symbol (**LSP**) | `rename_symbol` |
283−| `a` | Apply code action (**LSP**) | `code_action` |
288+| `a` | Apply code action (**LSP**) | `code_action` |
289+| `h` | Select symbol references (**LSP**) | `select_references_to_symbol_under_cursor` |
284−| `'` | Open last fuzzy picker | `last_picker` |
290+| `'` | Open last fuzzy picker | `last_picker` |
285−| `w` | Enter [window mode](#window-mode) | N/A |
291+| `w` | Enter [window mode](#window-mode) | N/A |
286−| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
292+| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
287−| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
293+| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
288−| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` |
294+| `y` | Yank selections to clipboard | `yank_to_clipboard` |
289−| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
295+| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
290−| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
296+| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
291−| `/` | Global search in workspace folder | `global_search` |
297+| `/` | Global search in workspace folder | `global_search` |
292−| `?` | Open command palette | `command_palette` |
298+| `?` | Open command palette | `command_palette` |
293−
299+
294−> TIP: Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file.
300+> 💡 Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file.
295
301
296##### Popup
302##### Popup
297
303
@@ -304,7 +310,7 @@ Displays documentation for item under cursor.
304
310
305#### Unimpaired
311#### Unimpaired
306
312
307−Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
313+These mappings are in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
308
314
309| Key | Description | Command |
315| Key | Description | Command |
310| ----- | ----------- | ------- |
316| ----- | ----------- | ------- |
@@ -333,12 +339,15 @@ Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaire
333
339
334## Insert mode
340## Insert mode
335
341
342+Accessed by typing `i` in [normal mode](#normal-mode).
343+
336−Insert mode bindings are somewhat minimal by default. Helix is designed to
344+Insert mode bindings are minimal by default. Helix is designed to
337be a modal editor, and this is reflected in the user experience and internal
345be a modal editor, and this is reflected in the user experience and internal
338−mechanics. For example, changes to the text are only saved for undos when
346+mechanics. Changes to the text are only saved for undos when
339−escaping from insert mode to normal mode. For this reason, new users are
347+escaping from insert mode to normal mode.
348+
340−strongly encouraged to learn the modal editing paradigm to get the smoothest
349+> 💡 New users are strongly encouraged to learn the modal editing paradigm
341−experience.
350+> to get the smoothest experience.
342
351
343| Key | Description | Command |
352| Key | Description | Command |
344| ----- | ----------- | ------- |
353| ----- | ----------- | ------- |
@@ -350,7 +359,7 @@ experience.
350| `Alt-d`, `Alt-Delete` | Delete next word | `delete_word_forward` |
359| `Alt-d`, `Alt-Delete` | Delete next word | `delete_word_forward` |
351| `Ctrl-u` | Delete to start of line | `kill_to_line_start` |
360| `Ctrl-u` | Delete to start of line | `kill_to_line_start` |
352| `Ctrl-k` | Delete to end of line | `kill_to_line_end` |
361| `Ctrl-k` | Delete to end of line | `kill_to_line_end` |
353−| `Ctrl-h`, `Backspace` | Delete previous char | `delete_char_backward` |
362+| `Ctrl-h`, `Backspace`, `Shift-Backspace` | Delete previous char | `delete_char_backward` |
354| `Ctrl-d`, `Delete` | Delete next char | `delete_char_forward` |
363| `Ctrl-d`, `Delete` | Delete next char | `delete_char_forward` |
355| `Ctrl-j`, `Enter` | Insert new line | `insert_newline` |
364| `Ctrl-j`, `Enter` | Insert new line | `insert_newline` |
356
365
@@ -368,8 +377,8 @@ with modal editors.
368| `Home` | Move to line start | `goto_line_start` |
377| `Home` | Move to line start | `goto_line_start` |
369| `End` | Move to line end | `goto_line_end_newline` |
378| `End` | Move to line end | `goto_line_end_newline` |
370
379
371−If you want to disable them in insert mode as you become more comfortable with modal editing, you can use
380+As you become more comfortable with modal editing, you may want to disable some
372−the following in your `config.toml`:
381+insert mode bindings. You can do this by editing your `config.toml` file.
373
382
374```toml
383```toml
375[keys.insert]
384[keys.insert]
@@ -385,9 +394,11 @@ end = "no_op"
385
394
386## Select / extend mode
395## Select / extend mode
387
396
397+Accessed by typing `v` in [normal mode](#normal-mode).
398+
388−This mode echoes Normal mode, but changes any movements to extend
399+Select mode echoes Normal mode, but changes any movements to extend
389selections rather than replace them. Goto motions are also changed to
400selections rather than replace them. Goto motions are also changed to
390−extend, so that `vgl` for example extends the selection to the end of
401+extend, so that `vgl`, for example, extends the selection to the end of
391the line.
402the line.
392
403
393Search is also affected. By default, `n` and `N` will remove the current
404Search is also affected. By default, `n` and `N` will remove the current
@@ -400,19 +411,20 @@ you to selectively add search terms to your selections.
400
411
401Keys to use within picker. Remapping currently not supported.
412Keys to use within picker. Remapping currently not supported.
402
413
403−| Key | Description |
414+| Key | Description |
404−| ----- | ------------- |
415+| ----- | ------------- |
405−| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
416+| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
406−| `Tab`, `Down`, `Ctrl-n` | Next entry |
417+| `Tab`, `Down`, `Ctrl-n` | Next entry |
407−| `PageUp`, `Ctrl-u` | Page up |
418+| `PageUp`, `Ctrl-u` | Page up |
408−| `PageDown`, `Ctrl-d` | Page down |
419+| `PageDown`, `Ctrl-d` | Page down |
409−| `Home` | Go to first entry |
420+| `Home` | Go to first entry |
410−| `End` | Go to last entry |
421+| `End` | Go to last entry |
411−| `Enter` | Open selected |
422+| `Enter` | Open selected |
423+| `Alt-Enter` | Open selected in the background without closing the picker |
412−| `Ctrl-s` | Open horizontally |
424+| `Ctrl-s` | Open horizontally |
413−| `Ctrl-v` | Open vertically |
425+| `Ctrl-v` | Open vertically |
414−| `Ctrl-t` | Toggle preview |
426+| `Ctrl-t` | Toggle preview |
415−| `Escape`, `Ctrl-c` | Close picker |
427+| `Escape`, `Ctrl-c` | Close picker |
416
428
417## Prompt
429## Prompt
418
430
@@ -431,7 +443,7 @@ Keys to use within prompt, Remapping currently not supported.
431| `Alt-d`, `Alt-Delete`, `Ctrl-Delete` | Delete next word |
443| `Alt-d`, `Alt-Delete`, `Ctrl-Delete` | Delete next word |
432| `Ctrl-u` | Delete to start of line |
444| `Ctrl-u` | Delete to start of line |
433| `Ctrl-k` | Delete to end of line |
445| `Ctrl-k` | Delete to end of line |
434−| `Backspace`, `Ctrl-h` | Delete previous char |
446+| `Backspace`, `Ctrl-h`, `Shift-Backspace` | Delete previous char |
435| `Delete`, `Ctrl-d` | Delete next char |
447| `Delete`, `Ctrl-d` | Delete next char |
436| `Ctrl-s` | Insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later |
448| `Ctrl-s` | Insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later |
437| `Ctrl-p`, `Up` | Select previous history |
449| `Ctrl-p`, `Up` | Select previous history |
book/src/lang-support.md+2−2
@@ -1,10 +1,10 @@
1# Language Support
1# Language Support
2
2
3−The following languages and Language Servers are supported. In order to use
3+The following languages and Language Servers are supported. To use
4Language Server features, you must first [install][lsp-install-wiki] the
4Language Server features, you must first [install][lsp-install-wiki] the
5appropriate Language Server.
5appropriate Language Server.
6
6
7−Check the language support in your installed helix version with `hx --health`.
7+You can check the language support in your installed helix version with `hx --health`.
8
8
9Also see the [Language Configuration][lang-config] docs and the [Adding
9Also see the [Language Configuration][lang-config] docs and the [Adding
10Languages][adding-languages] guide for more language configuration information.
10Languages][adding-languages] guide for more language configuration information.
book/src/languages.md+117−38
@@ -5,26 +5,31 @@ in `languages.toml` files.
5
5
6## `languages.toml` files
6## `languages.toml` files
7
7
8−There are three possible `languages.toml` files. The first is compiled into
8+There are three possible locations for a `languages.toml` file:
9−Helix and lives in the [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml).
10−This provides the default configurations for languages and language servers.
11
9
12−You may define a `languages.toml` in your [configuration directory](./configuration.md)
10+1. In the Helix source code, which lives in the
13−which overrides values from the built-in language configuration. For example
11+ [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml).
14−to disable auto-LSP-formatting in Rust:
12+ It provides the default configurations for languages and language servers.
15
13
16−```toml
14+2. In your [configuration directory](./configuration.md). This overrides values
17−# in <config_dir>/helix/languages.toml
15+ from the built-in language configuration. For example, to disable
16+ auto-LSP-formatting in Rust:
18
17
18+ ```toml
19+ # in <config_dir>/helix/languages.toml
20+
19−[[language]]
21+ [language-server.mylang-lsp]
20−name = "rust"
22+ command = "mylang-lsp"
21−auto-format = false
22−```
23
23
24+ [[language]]
25+ name = "rust"
26+ auto-format = false
27+ ```
28+
29+3. In a `.helix` folder in your project. Language configuration may also be
24−Language configuration may also be overridden local to a project by creating
30+ overridden local to a project by creating a `languages.toml` file in a
25−a `languages.toml` file under a `.helix` directory. Its settings will be merged
31+ `.helix` folder. Its settings will be merged with the language configuration
26−with the language configuration in the configuration directory and the built-in
32+ in the configuration directory and the built-in configuration.
27−configuration.
28
33
29## Language configuration
34## Language configuration
30
35
@@ -39,8 +44,8 @@ injection-regex = "mylang"
39file-types = ["mylang", "myl"]
44file-types = ["mylang", "myl"]
40comment-token = "#"
45comment-token = "#"
41indent = { tab-width = 2, unit = " " }
46indent = { tab-width = 2, unit = " " }
42−language-server = { command = "mylang-lsp", args = ["--stdio"], environment = { "ENV1" = "value1", "ENV2" = "value2" } }
43formatter = { command = "mylang-formatter" , args = ["--stdin"] }
47formatter = { command = "mylang-formatter" , args = ["--stdin"] }
48+language-servers = [ "mylang-lsp" ]
44```
49```
45
50
46These configuration keys are available:
51These configuration keys are available:
@@ -48,6 +53,7 @@ These configuration keys are available:
48| Key | Description |
53| Key | Description |
49| ---- | ----------- |
54| ---- | ----------- |
50| `name` | The name of the language |
55| `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 |
51| `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 |
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 |
52| `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. |
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. |
53| `file-types` | The filetypes of the language, for example `["yml", "yaml"]`. See the file-type detection section below. |
59| `file-types` | The filetypes of the language, for example `["yml", "yaml"]`. See the file-type detection section below. |
@@ -56,16 +62,18 @@ These configuration keys are available:
56| `auto-format` | Whether to autoformat this language when saving |
62| `auto-format` | Whether to autoformat this language when saving |
57| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
63| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
58| `comment-token` | The token to use as a comment-token |
64| `comment-token` | The token to use as a comment-token |
59−| `indent` | The indent to use. Has sub keys `tab-width` and `unit` |
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) |
60−| `language-server` | The Language Server to run. See the Language Server configuration section below. |
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) |
61−| `config` | Language Server configuration |
62| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
67| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
63| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout |
68| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout |
69+| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
64−| `max-line-length` | Maximum line length. Used for the `:reflow` command and soft-wrapping |
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` |
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. |
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.
65
73
66### File-type detection and the `file-types` key
74### File-type detection and the `file-types` key
67
75
68−Helix determines which language configuration to use with the `file-types` key
76+Helix determines which language configuration to use based on the `file-types` key
69from the above section. `file-types` is a list of strings or tables, for
77from the above section. `file-types` is a list of strings or tables, for
70example:
78example:
71
79
@@ -89,31 +97,102 @@ with the following priorities:
89 replaced at runtime with the appropriate path separator for the operating
97 replaced at runtime with the appropriate path separator for the operating
90 system, so this rule would match against `.git\config` files on Windows.
98 system, so this rule would match against `.git\config` files on Windows.
91
99
92−### Language Server configuration
100+## Language Server configuration
101+
102+Language servers are configured separately in the table `language-server` in the same file as the languages `languages.toml`
93
103
94−The `language-server` field takes the following keys:
104+For example:
95
105
96−| Key | Description |
106+```toml
97−| --- | ----------- |
107+[language-server.mylang-lsp]
98−| `command` | The name of the language server binary to execute. Binaries must be in `$PATH` |
108+command = "mylang-lsp"
99−| `args` | A list of arguments to pass to the language server binary |
109+args = ["--stdio"]
100−| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
110+config = { provideFormatter = true }
101−| `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 |
111+environment = { "ENV1" = "value1", "ENV2" = "value2" }
102−| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "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+```
103
120
121+These are the available options for a language server.
122+
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 |
104−The top-level `config` field is used to configure the LSP initialization options. A `format`
127+| `config` | LSP initialization options |
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+
105−sub-table within `config` can be used to pass extra formatting options to
131+A `format` sub-table within `config` can be used to pass extra formatting options to
106−[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).
132+[Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting).
107−For example with typescript:
133+For example, with typescript:
108
134
109```toml
135```toml
110−[[language]]
136+[language-server.typescript-language-server]
111−name = "typescript"
112−auto-format = true
113# pass format options according to https://github.com/typescript-language-server/typescript-language-server#workspacedidchangeconfiguration omitting the "[language].format." prefix.
137# pass format options according to https://github.com/typescript-language-server/typescript-language-server#workspacedidchangeconfiguration omitting the "[language].format." prefix.
114config = { format = { "semicolons" = "insert", "insertSpaceBeforeFunctionParenthesis" = true } }
138config = { format = { "semicolons" = "insert", "insertSpaceBeforeFunctionParenthesis" = true } }
115```
139```
116
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+
164+```toml
165+[[language]]
166+name = "typescript"
167+language-servers = [ { name = "typescript-language-server", except-features = [ "format" ] }, "efm-lsp-prettier" ]
168+```
169+
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+
117## Tree-sitter grammar configuration
196## Tree-sitter grammar configuration
118
197
119The source for a language's tree-sitter grammar is specified in a `[[grammar]]`
198The source for a language's tree-sitter grammar is specified in a `[[grammar]]`
book/src/remapping.md+9−10
@@ -1,18 +1,18 @@
1−# Key Remapping
1+# Key remapping
2
2
3−One-way key remapping is temporarily supported via a simple TOML configuration
3+Helix currently supports one-way key remapping through a simple TOML configuration
4file. (More powerful solutions such as rebinding via commands will be
4file. (More powerful solutions such as rebinding via commands will be
5available in the future).
5available in the future).
6
6
7−To remap keys, write a `config.toml` file in your `helix` configuration
7+To remap keys, create a `config.toml` file in your `helix` configuration
8−directory (default `~/.config/helix` in Linux systems) with a structure like
8+directory (default `~/.config/helix` on Linux systems) with a structure like
9this:
9this:
10
10
11```toml
11```toml
12# At most one section each of 'keys.normal', 'keys.insert' and 'keys.select'
12# At most one section each of 'keys.normal', 'keys.insert' and 'keys.select'
13[keys.normal]
13[keys.normal]
14−C-s = ":w" # Maps the Ctrl-s to the typable command :w which is an alias for :write (save file)
14+C-s = ":w" # Maps Ctrl-s to the typable command :w which is an alias for :write (save file)
15−C-o = ":open ~/.config/helix/config.toml" # Maps the Ctrl-o to opening of the helix config file
15+C-o = ":open ~/.config/helix/config.toml" # Maps Ctrl-o to opening of the helix config file
16a = "move_char_left" # Maps the 'a' key to the move_char_left command
16a = "move_char_left" # Maps the 'a' key to the move_char_left command
17w = "move_line_up" # Maps the 'w' key move_line_up
17w = "move_line_up" # Maps the 'w' key move_line_up
18"C-S-esc" = "extend_line" # Maps Ctrl-Shift-Escape to extend_line
18"C-S-esc" = "extend_line" # Maps Ctrl-Shift-Escape to extend_line
@@ -20,10 +20,9 @@ g = { a = "code_action" } # Maps `ga` to show possible code actions
20"ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode
20"ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode
21
21
22[keys.insert]
22[keys.insert]
23−"A-x" = "normal_mode" # Maps Alt-X to enter normal mode
23+"A-x" = "normal_mode" # Maps Alt-X to enter normal mode
24j = { k = "normal_mode" } # Maps `jk` to exit insert mode
24j = { k = "normal_mode" } # Maps `jk` to exit insert mode
25```
25```
26−> NOTE: Typable commands can also be remapped, remember to keep the `:` prefix to indicate it's a typable command.
27
26
28## Minor modes
27## Minor modes
29
28
@@ -76,5 +75,5 @@ Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes
76
75
77Keys can be disabled by binding them to the `no_op` command.
76Keys can be disabled by binding them to the `no_op` command.
78
77
79−Commands can be found at [Keymap](https://docs.helix-editor.com/keymap.html) Commands.
78+A list of commands is available in the [Keymap](https://docs.helix-editor.com/keymap.html) documentation
80−> Commands can also be found in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) at the invocation of `static_commands!` macro and the `TypableCommandList`.
79+ and in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) at the invocation of `static_commands!` macro and the `TypableCommandList`.
book/src/themes.md+125−98
@@ -1,14 +1,15 @@
1# Themes
1# Themes
2
2
3−To use a theme add `theme = "<name>"` to your [`config.toml`](./configuration.md) at the very top of the file before the first section or select it during runtime using `:theme <name>`.
3+To use a theme add `theme = "<name>"` to the top of your [`config.toml`](./configuration.md) file, or select it during runtime using `:theme <name>`.
4
4
5## Creating a theme
5## Creating a theme
6
6
7−Create a file with the name of your theme as file name (i.e `mytheme.toml`) and place it in your `themes` directory (i.e `~/.config/helix/themes`). The directory might have to be created beforehand.
7+Create a file with the name of your theme as the file name (i.e `mytheme.toml`) and place it in your `themes` directory (i.e `~/.config/helix/themes` or `%AppData%\helix\themes` on Windows). The directory might have to be created beforehand.
8
8
9−The names "default" and "base16_default" are reserved for the builtin themes and cannot be overridden by user defined themes.
9+> 💡 The names "default" and "base16_default" are reserved for built-in themes
10+> and cannot be overridden by user-defined themes.
10
11
11−The default theme.toml can be found [here](https://github.com/helix-editor/helix/blob/master/theme.toml), and user submitted themes [here](https://github.com/helix-editor/helix/blob/master/runtime/themes).
12+### Overview
12
13
13Each line in the theme file is specified as below:
14Each line in the theme file is specified as below:
14
15
@@ -16,7 +17,7 @@ Each line in the theme file is specified as below:
16key = { fg = "#ffffff", bg = "#000000", underline = { color = "#ff0000", style = "curl"}, modifiers = ["bold", "italic"] }
17key = { fg = "#ffffff", bg = "#000000", underline = { color = "#ff0000", style = "curl"}, modifiers = ["bold", "italic"] }
17```
18```
18
19
19−where `key` represents what you want to style, `fg` specifies the foreground color, `bg` the background color, `underline` the underline `style`/`color`, and `modifiers` is a list of style modifiers. `bg`, `underline` and `modifiers` can be omitted to defer to the defaults.
20+Where `key` represents what you want to style, `fg` specifies the foreground color, `bg` the background color, `underline` the underline `style`/`color`, and `modifiers` is a list of style modifiers. `bg`, `underline` and `modifiers` can be omitted to defer to the defaults.
20
21
21To specify only the foreground color:
22To specify only the foreground color:
22
23
@@ -24,15 +25,30 @@ To specify only the foreground color:
24key = "#ffffff"
25key = "#ffffff"
25```
26```
26
27
27−if the key contains a dot `'.'`, it must be quoted to prevent it being parsed as a [dotted key](https://toml.io/en/v1.0.0#keys).
28+If the key contains a dot `'.'`, it must be quoted to prevent it being parsed as a [dotted key](https://toml.io/en/v1.0.0#keys).
28
29
29```toml
30```toml
30"key.key" = "#ffffff"
31"key.key" = "#ffffff"
31```
32```
32
33
34+For inspiration, you can find the default `theme.toml`
35+[here](https://github.com/helix-editor/helix/blob/master/theme.toml) and
36+user-submitted themes
37+[here](https://github.com/helix-editor/helix/blob/master/runtime/themes).
38+
39+### Using the linter
40+
41+Use the supplied linting tool to check for errors and missing scopes:
42+
43+```sh
44+cargo xtask themelint onedark # replace onedark with <name>
45+```
46+
47+## The details of theme creation
48+
33### Color palettes
49### Color palettes
34
50
35−It's recommended define a palette of named colors, and refer to them from the
51+It's recommended to define a palette of named colors, and refer to them in the
36configuration values in your theme. To do this, add a table called
52configuration values in your theme. To do this, add a table called
37`palette` to your theme file:
53`palette` to your theme file:
38
54
@@ -45,8 +61,8 @@ white = "#ffffff"
45black = "#000000"
61black = "#000000"
46```
62```
47
63
48−Remember that the `[palette]` table includes all keys after its header,
64+Keep in mind that the `[palette]` table includes all keys after its header,
49−so you should define the palette after normal theme options.
65+so it should be defined after the normal theme options.
50
66
51The default palette uses the terminal's default 16 colors, and the colors names
67The default palette uses the terminal's default 16 colors, and the colors names
52are listed below. The `[palette]` section in the config file takes precedence
68are listed below. The `[palette]` section in the config file takes precedence
@@ -54,6 +70,7 @@ over it and is merged into the default palette.
54
70
55| Color Name |
71| Color Name |
56| --- |
72| --- |
73+| `default` |
57| `black` |
74| `black` |
58| `red` |
75| `red` |
59| `green` |
76| `green` |
@@ -73,9 +90,8 @@ over it and is merged into the default palette.
73
90
74### Modifiers
91### Modifiers
75
92
76−The following values may be used as modifiers.
93+The following values may be used as modifier, provided they are supported by
77−
78−Less common modifiers might not be supported by your terminal emulator.
94+your terminal emulator.
79
95
80| Modifier |
96| Modifier |
81| --- |
97| --- |
@@ -89,14 +105,13 @@ Less common modifiers might not be supported by your terminal emulator.
89| `hidden` |
105| `hidden` |
90| `crossed_out` |
106| `crossed_out` |
91
107
92−> Note: The `underlined` modifier is deprecated and only available for backwards compatibility.
108+> 💡 The `underlined` modifier is deprecated and only available for backwards compatibility.
93> Its behavior is equivalent to setting `underline.style="line"`.
109> Its behavior is equivalent to setting `underline.style="line"`.
94
110
95−### Underline Style
111+### Underline style
96−
97−One of the following values may be used as a value for `underline.style`.
98
112
113+One of the following values may be used as a value for `underline.style`, providing it is
99−Some styles might not be supported by your terminal emulator.
114+supported by your terminal emulator.
100
115
101| Modifier |
116| Modifier |
102| --- |
117| --- |
@@ -109,7 +124,7 @@ Some styles might not be supported by your terminal emulator.
109
124
110### Inheritance
125### Inheritance
111
126
112−Extend upon other themes by setting the `inherits` property to an existing theme.
127+Extend other themes by setting the `inherits` property to an existing theme.
113
128
114```toml
129```toml
115inherits = "boo_berry"
130inherits = "boo_berry"
@@ -124,27 +139,28 @@ berry = "#2A2A4D"
124
139
125### Scopes
140### Scopes
126
141
127−The following is a list of scopes available to use for styling.
142+The following is a list of scopes available to use for styling:
128
143
129#### Syntax highlighting
144#### Syntax highlighting
130
145
131These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme).
146These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme).
132
147
133−For a given highlight produced, styling will be determined based on the longest matching theme key. For example, the highlight `function.builtin.static` would match the key `function.builtin` rather than `function`.
148+When determining styling for a highlight, the longest matching theme key will be used. For example, if the highlight is `function.builtin.static`, the key `function.builtin` will be used instead of `function`.
134
149
135We use a similar set of scopes as
150We use a similar set of scopes as
136−[SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also
151+[Sublime Text](https://www.sublimetext.com/docs/scope_naming.html). See also
137[TextMate](https://macromates.com/manual/en/language_grammars) scopes.
152[TextMate](https://macromates.com/manual/en/language_grammars) scopes.
138
153
139−- `attribute` - Class attributes, html tag attributes
154+- `attribute` - Class attributes, HTML tag attributes
140
155
141- `type` - Types
156- `type` - Types
142 - `builtin` - Primitive types provided by the language (`int`, `usize`)
157 - `builtin` - Primitive types provided by the language (`int`, `usize`)
158+ - `parameter` - Generic type parameters (`T`)
143 - `enum`
159 - `enum`
144 - `variant`
160 - `variant`
145- `constructor`
161- `constructor`
146
162
147−- `constant` (TODO: constant.other.placeholder for %v)
163+- `constant` (TODO: constant.other.placeholder for `%v`)
148 - `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
164 - `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
149 - `boolean`
165 - `boolean`
150 - `character`
166 - `character`
@@ -162,11 +178,11 @@ We use a similar set of scopes as
162
178
163- `comment` - Code comments
179- `comment` - Code comments
164 - `line` - Single line comments (`//`)
180 - `line` - Single line comments (`//`)
165− - `block` - Block comments (e.g. (`/* */`)
181+ - `block` - Block comments (e.g. (`/* */`)
166 - `documentation` - Documentation comments (e.g. `///` in Rust)
182 - `documentation` - Documentation comments (e.g. `///` in Rust)
167
183
168- `variable` - Variables
184- `variable` - Variables
169− - `builtin` - Reserved language variables (`self`, `this`, `super`, etc)
185+ - `builtin` - Reserved language variables (`self`, `this`, `super`, etc.)
170 - `parameter` - Function parameters
186 - `parameter` - Function parameters
171 - `other`
187 - `other`
172 - `member` - Fields of composite data types (e.g. structs, unions)
188 - `member` - Fields of composite data types (e.g. structs, unions)
@@ -186,10 +202,10 @@ We use a similar set of scopes as
186 - `return`
202 - `return`
187 - `exception`
203 - `exception`
188 - `operator` - `or`, `in`
204 - `operator` - `or`, `in`
189− - `directive` - Preprocessor directives (`#if` in C)
205+ - `directive` - Preprocessor directives (`#if` in C)
190 - `function` - `fn`, `func`
206 - `function` - `fn`, `func`
191 - `storage` - Keywords describing how things are stored
207 - `storage` - Keywords describing how things are stored
192− - `type` - The type of something, `class`, `function`, `var`, `let`, etc.
208+ - `type` - The type of something, `class`, `function`, `var`, `let`, etc.
193 - `modifier` - Storage modifiers like `static`, `mut`, `const`, `ref`, etc.
209 - `modifier` - Storage modifiers like `static`, `mut`, `const`, `ref`, etc.
194
210
195- `operator` - `||`, `+=`, `>`
211- `operator` - `||`, `+=`, `>`
@@ -201,6 +217,7 @@ We use a similar set of scopes as
201 - `special` (preprocessor in C)
217 - `special` (preprocessor in C)
202
218
203- `tag` - Tags (e.g. `<body>` in HTML)
219- `tag` - Tags (e.g. `<body>` in HTML)
220+ - `builtin`
204
221
205- `namespace`
222- `namespace`
206
223
@@ -213,12 +230,15 @@ We use a similar set of scopes as
213 - `list`
230 - `list`
214 - `unnumbered`
231 - `unnumbered`
215 - `numbered`
232 - `numbered`
233+ - `checked`
234+ - `unchecked`
216 - `bold`
235 - `bold`
217 - `italic`
236 - `italic`
237+ - `strikethrough`
218 - `link`
238 - `link`
219− - `url` - urls pointed to by links
239+ - `url` - URLs pointed to by links
220− - `label` - non-url link references
240+ - `label` - non-URL link references
221− - `text` - url and image descriptions in links
241+ - `text` - URL and image descriptions in links
222 - `quote`
242 - `quote`
223 - `raw`
243 - `raw`
224 - `inline`
244 - `inline`
@@ -226,85 +246,92 @@ We use a similar set of scopes as
226
246
227- `diff` - version control changes
247- `diff` - version control changes
228 - `plus` - additions
248 - `plus` - additions
249+ - `gutter` - gutter indicator
229 - `minus` - deletions
250 - `minus` - deletions
251+ - `gutter` - gutter indicator
230 - `delta` - modifications
252 - `delta` - modifications
231 - `moved` - renamed or moved files/changes
253 - `moved` - renamed or moved files/changes
254+ - `gutter` - gutter indicator
232
255
233#### Interface
256#### Interface
234
257
235−These scopes are used for theming the editor interface.
258+These scopes are used for theming the editor interface:
236
259
237- `markup`
260- `markup`
238 - `normal`
261 - `normal`
239− - `completion` - for completion doc popup ui
262+ - `completion` - for completion doc popup UI
240− - `hover` - for hover popup ui
263+ - `hover` - for hover popup UI
241 - `heading`
264 - `heading`
242− - `completion` - for completion doc popup ui
265+ - `completion` - for completion doc popup UI
243− - `hover` - for hover popup ui
266+ - `hover` - for hover popup UI
244 - `raw`
267 - `raw`
245 - `inline`
268 - `inline`
246− - `completion` - for completion doc popup ui
269+ - `completion` - for completion doc popup UI
247− - `hover` - for hover popup ui
270+ - `hover` - for hover popup UI
248−
271+
249−
272+
250−| Key | Notes |
273+| Key | Notes |
251−| --- | --- |
274+| --- | --- |
252−| `ui.background` | |
275+| `ui.background` | |
253−| `ui.background.separator` | Picker separator below input line |
276+| `ui.background.separator` | Picker separator below input line |
254−| `ui.cursor` | |
277+| `ui.cursor` | |
255−| `ui.cursor.normal` | |
278+| `ui.cursor.normal` | |
256−| `ui.cursor.insert` | |
279+| `ui.cursor.insert` | |
257−| `ui.cursor.select` | |
280+| `ui.cursor.select` | |
258−| `ui.cursor.match` | Matching bracket etc. |
281+| `ui.cursor.match` | Matching bracket etc. |
259−| `ui.cursor.primary` | Cursor with primary selection |
282+| `ui.cursor.primary` | Cursor with primary selection |
260−| `ui.cursor.primary.normal` | |
283+| `ui.cursor.primary.normal` | |
261−| `ui.cursor.primary.insert` | |
284+| `ui.cursor.primary.insert` | |
262−| `ui.cursor.primary.select` | |
285+| `ui.cursor.primary.select` | |
286+| `ui.debug.breakpoint` | Breakpoint indicator, found in the gutter |
287+| `ui.debug.active` | Indicator for the line at which debugging execution is paused at, found in the gutter |
263−| `ui.gutter` | Gutter |
288+| `ui.gutter` | Gutter |
264−| `ui.gutter.selected` | Gutter for the line the cursor is on |
289+| `ui.gutter.selected` | Gutter for the line the cursor is on |
290+| `ui.highlight.frameline` | Line at which debugging execution is paused at |
265−| `ui.linenr` | Line numbers |
291+| `ui.linenr` | Line numbers |
266−| `ui.linenr.selected` | Line number for the line the cursor is on |
292+| `ui.linenr.selected` | Line number for the line the cursor is on |
267−| `ui.statusline` | Statusline |
293+| `ui.statusline` | Statusline |
268−| `ui.statusline.inactive` | Statusline (unfocused document) |
294+| `ui.statusline.inactive` | Statusline (unfocused document) |
269−| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
295+| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
270−| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
296+| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
271−| `ui.statusline.select` | Statusline mode during select 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]) |
272−| `ui.statusline.separator` | Separator character in statusline |
298+| `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 |
273−| `ui.popup` | Documentation popups (e.g Space + k) |
302+| `ui.popup` | Documentation popups (e.g. Space + k) |
274−| `ui.popup.info` | Prompt for multiple key options |
303+| `ui.popup.info` | Prompt for multiple key options |
275−| `ui.window` | Border lines separating splits |
304+| `ui.window` | Borderlines separating splits |
276−| `ui.help` | Description box for commands |
305+| `ui.help` | Description box for commands |
277−| `ui.text` | Command prompts, popup text, etc. |
306+| `ui.text` | Default text style, command prompts, popup text, etc. |
278−| `ui.text.focus` | |
307+| `ui.text.focus` | The currently selected line in the picker |
279−| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
308+| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
280−| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
309+| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
281−| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
310+| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
282−| `ui.virtual.whitespace` | Visible whitespace characters |
311+| `ui.virtual.whitespace` | Visible whitespace characters |
283−| `ui.virtual.indent-guide` | Vertical indent width guides |
312+| `ui.virtual.indent-guide` | Vertical indent width guides |
313+| `ui.virtual.inlay-hint` | Default style for inlay hints of all kinds |
314+| `ui.virtual.inlay-hint.parameter` | Style for inlay hints of kind `parameter` (LSPs are not required to set a kind) |
315+| `ui.virtual.inlay-hint.type` | Style for inlay hints of kind `type` (LSPs are not required to set a kind) |
284−| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) |
316+| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) |
285−| `ui.menu` | Code and command completion menus |
317+| `ui.menu` | Code and command completion menus |
286−| `ui.menu.selected` | Selected autocomplete item |
318+| `ui.menu.selected` | Selected autocomplete item |
287−| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
319+| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
288−| `ui.selection` | For selections in the editing area |
320+| `ui.selection` | For selections in the editing area |
289−| `ui.selection.primary` | |
321+| `ui.selection.primary` | |
322+| `ui.highlight` | Highlighted lines in the picker preview |
290−| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
323+| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
291−| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
324+| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
292−| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
325+| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
293−| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
326+| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
294−| `warning` | Diagnostics warning (gutter) |
327+| `warning` | Diagnostics warning (gutter) |
295−| `error` | Diagnostics error (gutter) |
328+| `error` | Diagnostics error (gutter) |
296−| `info` | Diagnostics info (gutter) |
329+| `info` | Diagnostics info (gutter) |
297−| `hint` | Diagnostics hint (gutter) |
330+| `hint` | Diagnostics hint (gutter) |
298−| `diagnostic` | Diagnostics fallback style (editing area) |
331+| `diagnostic` | Diagnostics fallback style (editing area) |
299−| `diagnostic.hint` | Diagnostics hint (editing area) |
332+| `diagnostic.hint` | Diagnostics hint (editing area) |
300−| `diagnostic.info` | Diagnostics info (editing area) |
333+| `diagnostic.info` | Diagnostics info (editing area) |
301−| `diagnostic.warning` | Diagnostics warning (editing area) |
334+| `diagnostic.warning` | Diagnostics warning (editing area) |
302−| `diagnostic.error` | Diagnostics error (editing area) |
335+| `diagnostic.error` | Diagnostics error (editing area) |
303−
304−You can check compliance to spec with
305−
306−```shell
307−cargo xtask themelint onedark # replace onedark with <name>
308−```
309
336
310[editor-section]: ./configuration.md#editor-section
337[editor-section]: ./configuration.md#editor-section
book/src/usage.md+131−93
@@ -1,65 +1,151 @@
1−# Usage
1+# Using Helix
2
2
3−(Currently not fully documented, see the [keymappings](./keymap.md) list for more.)
3+<!--toc:start-->
4+- [Registers](#registers)
5+ - [User-defined registers](#user-defined-registers)
6+ - [Special registers](#special-registers)
7+- [Surround](#surround)
8+- [Selecting and manipulating text with textobjects](#selecting-and-manipulating-text-with-textobjects)
9+- [Navigating using tree-sitter textobjects](#navigating-using-tree-sitter-textobjects)
10+- [Moving the selection with syntax-aware motions](#moving-the-selection-with-syntax-aware-motions)
11+<!--toc:end-->
4
12
13+For a full interactive introduction to Helix, refer to the
5−See [tutor](https://github.com/helix-editor/helix/blob/master/runtime/tutor) (accessible via `hx --tutor` or `:tutor`) for a vimtutor-like introduction.
14+[tutor](https://github.com/helix-editor/helix/blob/master/runtime/tutor) which
15+can be accessed via the command `hx --tutor` or `:tutor`.
16+
17+> 💡 Currently, not all functionality is fully documented, please refer to the
18+> [key mappings](./keymap.md) list.
6
19
7## Registers
20## Registers
8
21
9−Vim-like registers can be used to yank and store text to be pasted later. Usage is similar, with `"` being used to select a register:
22+In Helix, registers are storage locations for text and other data, such as the
23+result of a search. Registers can be used to cut, copy, and paste text, similar
24+to the clipboard in other text editors. Usage is similar to Vim, with `"` being
25+used to select a register.
26+
27+### User-defined registers
28+
29+Helix allows you to create your own named registers for storing text, for
30+example:
10
31
11- `"ay` - Yank the current selection to register `a`.
32- `"ay` - Yank the current selection to register `a`.
12- `"op` - Paste the text in register `o` after the selection.
33- `"op` - Paste the text in register `o` after the selection.
13
34
14−If there is a selected register before invoking a change or delete command, the selection will be stored in the register and the action will be carried out:
35+If a register is selected before invoking a change or delete command, the selection will be stored in the register and the action will be carried out:
15
36
16- `"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).
17- `"md` - Store the selection in register `m` and delete it.
38- `"md` - Store the selection in register `m` and delete it.
18
39
19−### Special Registers
40+### Default 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:
20
44
21| Register character | Contains |
45| Register character | Contains |
22| --- | --- |
46| --- | --- |
23| `/` | Last search |
47| `/` | Last search |
24| `:` | Last executed command |
48| `:` | Last executed command |
25| `"` | Last yanked text |
49| `"` | Last yanked text |
26−| `_` | Black hole |
50+| `@` | Last recorded macro |
51+
52+### Special registers
27
53
28−> There is no special register for copying to system clipboard, instead special commands and keybindings are provided. See the [keymap](keymap.md#space-mode) for the specifics.
54+Some registers have special behavior when read from and written to.
29−> The black hole register works as a no-op register, meaning no data will be written to / read from it.
55+
56+| Register character | When read | When written |
57+| --- | --- | --- |
58+| `_` | No values are returned | All values are discarded |
59+| `#` | Selection indices (first selection is `1`, second is `2`, etc.) | This register is not writable |
60+| `.` | Contents of the current selections | This register is not writable |
61+| `%` | Name of the current file | This register is not writable |
62+| `+` | Reads from the system clipboard | Joins and yanks to the system clipboard |
63+| `*` | Reads from the primary clipboard | Joins and yanks to the primary clipboard |
64+
65+When yanking multiple selections to the clipboard registers, the selections
66+are joined with newlines. Pasting from these registers will paste multiple
67+selections if the clipboard was last yanked to by the Helix session. Otherwise
68+the clipboard contents are pasted as one selection.
30
69
31## Surround
70## Surround
32
71
33−Functionality similar to [vim-surround](https://github.com/tpope/vim-surround) is built into
72+Helix includes built-in functionality similar to [vim-surround](https://github.com/tpope/vim-surround).
34−helix. The keymappings have been inspired from [vim-sandwich](https://github.com/machakann/vim-sandwich):
73+The keymappings have been inspired from [vim-sandwich](https://github.com/machakann/vim-sandwich):
35
74
36−![surround demo](https://user-images.githubusercontent.com/23398472/122865801-97073180-d344-11eb-8142-8f43809982c6.gif)
75+![Surround demo](https://user-images.githubusercontent.com/23398472/122865801-97073180-d344-11eb-8142-8f43809982c6.gif)
37
76
77+| Key Sequence | Action |
78+| --------------------------------- | --------------------------------------- |
38−- `ms` - Add surround characters
79+| `ms<char>` (after selecting text) | Add surround characters to selection |
39−- `mr` - Replace surround characters
80+| `mr<char_to_replace><new_char>` | Replace the closest surround characters |
40−- `md` - Delete surround characters
81+| `md<char_to_delete>` | Delete the closest surround characters |
41
82
42−`ms` acts on a selection, so select the text first and use `ms<char>`. `mr` and `md` work
43−on the closest pairs found and selections are not required; use counts to act in outer pairs.
83+You can use counts to act on outer pairs.
44
84
45−It can also act on multiple selections (yay!). For example, to change every occurrence of `(use)` to `[use]`:
85+Surround can also act on multiple selections. For example, to change every occurrence of `(use)` to `[use]`:
46
86
47−- `%` to select the whole file
87+1. `%` to select the whole file
48−- `s` to split the selections on a search term
88+2. `s` to split the selections on a search term
49−- Input `use` and hit Enter
89+3. Input `use` and hit Enter
50−- `mr([` to replace the parens with square brackets
90+4. `mr([` to replace the parentheses with square brackets
51
91
52−Multiple characters are currently not supported, but planned.
92+Multiple characters are currently not supported, but planned for future release.
53
93
54−## Syntax-tree Motions
94+## Selecting and manipulating text with textobjects
55
95
56−`Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) move the primary
96+In Helix, textobjects are a way to select, manipulate and operate on a piece of
57−selection according to the selection's place in the syntax tree. Let's walk
97+text in a structured way. They allow you to refer to blocks of text based on
58−through an example to get familiar with them. Many languages have a syntax like
98+their structure or purpose, such as a word, sentence, paragraph, or even a
59−so for function calls:
99+function or block of code.
60
100
61−```
101+![Textobject demo](https://user-images.githubusercontent.com/23398472/124231131-81a4bb00-db2d-11eb-9d10-8e577ca7b177.gif)
102+![Textobject tree-sitter demo](https://user-images.githubusercontent.com/23398472/132537398-2a2e0a54-582b-44ab-a77f-eb818942203d.gif)
103+
104+- `ma` - Select around the object (`va` in Vim, `<alt-a>` in Kakoune)
105+- `mi` - Select inside the object (`vi` in Vim, `<alt-i>` in Kakoune)
106+
107+| Key after `mi` or `ma` | Textobject selected |
108+| --- | --- |
109+| `w` | Word |
110+| `W` | WORD |
111+| `p` | Paragraph |
112+| `(`, `[`, `'`, etc. | Specified surround pairs |
113+| `m` | The closest surround pair |
114+| `f` | Function |
115+| `t` | Type (or Class) |
116+| `a` | Argument/parameter |
117+| `c` | Comment |
118+| `T` | Test |
119+| `g` | Change |
120+
121+> 💡 `f`, `t`, etc. need a tree-sitter grammar active for the current
122+document and a special tree-sitter query file to work properly. [Only
123+some grammars][lang-support] currently have the query file implemented.
124+Contributions are welcome!
125+
126+## Navigating using tree-sitter textobjects
127+
128+Navigating between functions, classes, parameters, and other elements is
129+possible using tree-sitter and textobject queries. For
130+example to move to the next function use `]f`, to move to previous
131+type use `[t`, and so on.
132+
133+![Tree-sitter-nav-demo][tree-sitter-nav-demo]
134+
135+For the full reference see the [unimpaired][unimpaired-keybinds] section of the key bind
136+documentation.
137+
138+> 💡 This feature relies on tree-sitter textobjects
139+> and requires the corresponding query file to work properly.
140+
141+## Moving the selection with syntax-aware motions
142+
143+`Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) allow you to move the
144+selection according to its location in the syntax tree. For example, many languages have the
145+following syntax for function calls:
146+
147+```js
62−func(arg1, arg2, arg3)
148+func(arg1, arg2, arg3);
63```
149```
64
150
65A function call might be parsed by tree-sitter into a tree like the following.
151A function call might be parsed by tree-sitter into a tree like the following.
@@ -93,77 +179,29 @@ a more intuitive tree format:
93 └──────────┘ └──────────┘ └──────────┘
179 └──────────┘ └──────────┘ └──────────┘
94```
180```
95
181
96−Say we have a selection that wraps `arg1`. The selection is on the `arg1` leaf
182+If you have a selection that wraps `arg1` (see the tree above), and you use
97−in the tree above.
183+`Alt-n`, it will select the next sibling in the syntax tree: `arg2`.
98
184
99−```
185+```js
186+// before
100func([arg1], arg2, arg3)
187func([arg1], arg2, arg3)
188+// after
189+func(arg1, [arg2], arg3);
101```
190```
102
191
103−Using `Alt-n` would select the next sibling in the syntax tree: `arg2`.
192+Similarly, `Alt-o` will expand the selection to the parent node, in this case, the
193+arguments node.
104
194
105−```
195+```js
106−func(arg1, [arg2], arg3)
107−```
108−
109−While `Alt-o` would expand the selection to the parent node. In the tree above we
110−can see that we would select the `arguments` node.
111−
112−```
113−func[(arg1, arg2, arg3)]
196+func[(arg1, arg2, arg3)];
114```
197```
115
198
116There is also some nuanced behavior that prevents you from getting stuck on a
199There is also some nuanced behavior that prevents you from getting stuck on a
117−node with no sibling. If we have a selection on `arg1`, `Alt-p` would bring us
200+node with no sibling. When using `Alt-p` with a selection on `arg1`, the previous
118−to the previous child node. Since `arg1` doesn't have a sibling to its left,
201+child node will be selected. In the event that `arg1` does not have a previous
119−though, we climb the syntax tree and then take the previous selection. So
202+sibling, the selection will move up the syntax tree and select the previous
203+element. As a result, using `Alt-p` with a selection on `arg1` will move the
120−`Alt-p` will move the selection over to the "func" `identifier`.
204+selection to the "func" `identifier`.
121−
122−```
123−[func](arg1, arg2, arg3)
124−```
125−
126−## Textobjects
127−
128−![textobject-demo](https://user-images.githubusercontent.com/23398472/124231131-81a4bb00-db2d-11eb-9d10-8e577ca7b177.gif)
129−![textobject-treesitter-demo](https://user-images.githubusercontent.com/23398472/132537398-2a2e0a54-582b-44ab-a77f-eb818942203d.gif)
130−
131−- `ma` - Select around the object (`va` in Vim, `<alt-a>` in Kakoune)
132−- `mi` - Select inside the object (`vi` in Vim, `<alt-i>` in Kakoune)
133−
134−| Key after `mi` or `ma` | Textobject selected |
135−| --- | --- |
136−| `w` | Word |
137−| `W` | WORD |
138−| `p` | Paragraph |
139−| `(`, `[`, `'`, etc | Specified surround pairs |
140−| `m` | Closest surround pair |
141−| `f` | Function |
142−| `c` | Class |
143−| `a` | Argument/parameter |
144−| `o` | Comment |
145−| `t` | Test |
146−| `g` | Change |
147−
148−> NOTE: `f`, `c`, etc need a tree-sitter grammar active for the current
149−document and a special tree-sitter query file to work properly. [Only
150−some grammars][lang-support] currently have the query file implemented.
151−Contributions are welcome!
152−
153−## Tree-sitter Textobject Based Navigation
154−
155−Navigating between functions, classes, parameters, etc is made
156−possible by leveraging tree-sitter and textobjects queries. For
157−example to move to the next function use `]f`, to move to previous
158−class use `[c`, and so on.
159−
160−![tree-sitter-nav-demo][tree-sitter-nav-demo]
161−
162−See the [unimpaired][unimpaired-keybinds] section of the keybind
163−documentation for the full reference.
164−
165−> NOTE: This feature is dependent on tree-sitter based textobjects
166−and therefore requires the corresponding query file to work properly.
167
205
168[lang-support]: ./lang-support.md
206[lang-support]: ./lang-support.md
169[unimpaired-keybinds]: ./keymap.md#unimpaired
207[unimpaired-keybinds]: ./keymap.md#unimpaired
book/theme/book.jsdeleted−660
@@ -1,660 +0,0 @@
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.cssdeleted−499
@@ -1,499 +0,0 @@
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.cssdeleted−233
@@ -1,233 +0,0 @@
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.cssdeleted−54
@@ -1,54 +0,0 @@
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.cssdeleted−411
@@ -1,411 +0,0 @@
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.cssdeleted−56
@@ -1,56 +0,0 @@
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.jsdeleted−6
@@ -1,6 +0,0 @@
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+87−51
@@ -1,11 +1,11 @@
1<!DOCTYPE HTML>
1<!DOCTYPE HTML>
2−<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
2+<html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
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,10 +15,9 @@
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">
19 <meta name="description" content="{{ description }}">
18 <meta name="description" content="{{ description }}">
20 <meta name="viewport" content="width=device-width, initial-scale=1">
19 <meta name="viewport" content="width=device-width, initial-scale=1">
21− <meta name="theme-color" content="#ffffff" />
20+ <meta name="theme-color" content="#ffffff">
22
21
23 {{#if favicon_svg}}
22 {{#if favicon_svg}}
24 <link rel="icon" href="{{ path_to_root }}favicon.svg">
23 <link rel="icon" href="{{ path_to_root }}favicon.svg">
@@ -35,8 +34,6 @@
35
34
36 <!-- Fonts -->
35 <!-- Fonts -->
37 <link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
36 <link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
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">
40 {{#if copy_fonts}}
37 {{#if copy_fonts}}
41 <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
38 <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
42 {{/if}}
39 {{/if}}
@@ -53,18 +50,19 @@
53
50
54 {{#if mathjax_support}}
51 {{#if mathjax_support}}
55 <!-- MathJax -->
52 <!-- MathJax -->
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>
53+ <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
57 {{/if}}
54 {{/if}}
58 </head>
55 </head>
59− <body>
56+ <body class="sidebar-visible no-js">
57+ <div id="body-container">
60 <!-- Provide site root to javascript -->
58 <!-- Provide site root to javascript -->
61− <script type="text/javascript">
59+ <script>
62 var path_to_root = "{{ path_to_root }}";
60 var path_to_root = "{{ path_to_root }}";
63 var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
61 var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
64 </script>
62 </script>
65
63
66 <!-- Work around some values being stored in localStorage wrapped in quotes -->
64 <!-- Work around some values being stored in localStorage wrapped in quotes -->
67− <script type="text/javascript">
65+ <script>
68 try {
66 try {
69 var theme = localStorage.getItem('mdbook-theme');
67 var theme = localStorage.getItem('mdbook-theme');
70 var sidebar = localStorage.getItem('mdbook-sidebar');
68 var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -80,27 +78,34 @@
80 </script>
78 </script>
81
79
82 <!-- Set the theme before any content is loaded, prevents flash -->
80 <!-- Set the theme before any content is loaded, prevents flash -->
83− <script type="text/javascript">
81+ <script>
84 var theme;
82 var theme;
85 try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
83 try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
86 if (theme === null || theme === undefined) { theme = default_theme; }
84 if (theme === null || theme === undefined) { theme = default_theme; }
87 var html = document.querySelector('html');
85 var html = document.querySelector('html');
88− html.classList.remove('no-js')
89 html.classList.remove('{{ default_theme }}')
86 html.classList.remove('{{ default_theme }}')
90 html.classList.add(theme);
87 html.classList.add(theme);
88+ var body = document.querySelector('body');
89+ body.classList.remove('no-js')
91− html.classList.add('js');
90+ body.classList.add('js');
92 </script>
91 </script>
93
92
93+ <input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
94+
94 <!-- Hide / unhide sidebar before it is displayed -->
95 <!-- Hide / unhide sidebar before it is displayed -->
95− <script type="text/javascript">
96+ <script>
96− var html = document.querySelector('html');
97+ var body = document.querySelector('body');
97− var sidebar = 'hidden';
98+ var sidebar = null;
99+ var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
98 if (document.body.clientWidth >= 1080) {
100 if (document.body.clientWidth >= 1080) {
99 try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
101 try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
100 sidebar = sidebar || 'visible';
102 sidebar = sidebar || 'visible';
103+ } else {
104+ sidebar = 'hidden';
101 }
105 }
106+ sidebar_toggle.checked = sidebar === 'visible';
102− html.classList.remove('sidebar-visible');
107+ body.classList.remove('sidebar-visible');
103− html.classList.add("sidebar-" + sidebar);
108+ body.classList.add("sidebar-" + sidebar);
104 </script>
109 </script>
105
110
106 <nav id="sidebar" class="sidebar" aria-label="Table of contents">
111 <nav id="sidebar" class="sidebar" aria-label="Table of contents">
@@ -110,26 +115,48 @@
110 <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
115 <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
111 </nav>
116 </nav>
112
117
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+
113 <div id="page-wrapper" class="page-wrapper">
140 <div id="page-wrapper" class="page-wrapper">
114
141
115 <div class="page">
142 <div class="page">
116 {{> header}}
143 {{> header}}
117 <div id="menu-bar-hover-placeholder"></div>
144 <div id="menu-bar-hover-placeholder"></div>
118− <div id="menu-bar" class="menu-bar sticky bordered">
145+ <div id="menu-bar" class="menu-bar sticky">
119 <div class="left-buttons">
146 <div class="left-buttons">
120− <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
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">
121 <i class="fa fa-bars"></i>
148 <i class="fa fa-bars"></i>
122− </button>
149+ </label>
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">
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">
124 <i class="fa fa-paint-brush"></i>
151 <i class="fa fa-paint-brush"></i>
125 </button>
152 </button>
126 <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
153 <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
127− <li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li>
154+ <li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
128− <li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
155+ <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
129− <li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
156+ <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
130− <li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
157+ <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
131− <li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
158+ <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
132− <li role="none"><button role="menuitem" class="theme" id="colibri">{{ theme_option "Colibri" }}</button></li>
159+ <li role="none"><button role="menuitem" class="theme" id="colibri">Colibri</button></li>
133 </ul>
160 </ul>
134 {{#if search_enabled}}
161 {{#if search_enabled}}
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">
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">
@@ -151,13 +178,19 @@
151 <i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
178 <i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
152 </a>
179 </a>
153 {{/if}}
180 {{/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+
154 </div>
187 </div>
155 </div>
188 </div>
156
189
157 {{#if search_enabled}}
190 {{#if search_enabled}}
158 <div id="search-wrapper" class="hidden">
191 <div id="search-wrapper" class="hidden">
159 <form id="searchbar-outer" class="searchbar-outer">
192 <form id="searchbar-outer" class="searchbar-outer">
160− <input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
193+ <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
161 </form>
194 </form>
162 <div id="searchresults-outer" class="searchresults-outer hidden">
195 <div id="searchresults-outer" class="searchresults-outer hidden">
163 <div id="searchresults-header" class="searchresults-header"></div>
196 <div id="searchresults-header" class="searchresults-header"></div>
@@ -168,7 +201,7 @@
168 {{/if}}
201 {{/if}}
169
202
170 <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
203 <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
171− <script type="text/javascript">
204+ <script>
172 document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
205 document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
173 document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
206 document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
174 Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
207 Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -190,7 +223,7 @@
190 {{/previous}}
223 {{/previous}}
191
224
192 {{#next}}
225 {{#next}}
193− <a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
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">
194 <i class="fa fa-angle-right"></i>
227 <i class="fa fa-angle-right"></i>
195 </a>
228 </a>
196 {{/next}}
229 {{/next}}
@@ -208,7 +241,7 @@
208 {{/previous}}
241 {{/previous}}
209
242
210 {{#next}}
243 {{#next}}
211− <a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
244+ <a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
212 <i class="fa fa-angle-right"></i>
245 <i class="fa fa-angle-right"></i>
213 </a>
246 </a>
214 {{/next}}
247 {{/next}}
@@ -216,10 +249,12 @@
216
249
217 </div>
250 </div>
218
251
219− {{#if livereload}}
252+ {{#if live_reload_endpoint}}
220 <!-- Livereload script (if served using the cli tool) -->
253 <!-- Livereload script (if served using the cli tool) -->
221− <script type="text/javascript">
254+ <script>
255+ const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
256+ const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
222− var socket = new WebSocket("{{{livereload}}}");
257+ const socket = new WebSocket(wsAddress);
223 socket.onmessage = function (event) {
258 socket.onmessage = function (event) {
224 if (event.data === "reload") {
259 if (event.data === "reload") {
225 socket.close();
260 socket.close();
@@ -235,7 +270,7 @@
235
270
236 {{#if google_analytics}}
271 {{#if google_analytics}}
237 <!-- Google Analytics Tag -->
272 <!-- Google Analytics Tag -->
238− <script type="text/javascript">
273+ <script>
239 var localAddrs = ["localhost", "127.0.0.1", ""];
274 var localAddrs = ["localhost", "127.0.0.1", ""];
240
275
241 // make sure we don't activate google analytics if the developer is
276 // make sure we don't activate google analytics if the developer is
@@ -253,43 +288,43 @@
253 {{/if}}
288 {{/if}}
254
289
255 {{#if playground_line_numbers}}
290 {{#if playground_line_numbers}}
256− <script type="text/javascript">
291+ <script>
257 window.playground_line_numbers = true;
292 window.playground_line_numbers = true;
258 </script>
293 </script>
259 {{/if}}
294 {{/if}}
260
295
261 {{#if playground_copyable}}
296 {{#if playground_copyable}}
262− <script type="text/javascript">
297+ <script>
263 window.playground_copyable = true;
298 window.playground_copyable = true;
264 </script>
299 </script>
265 {{/if}}
300 {{/if}}
266
301
267 {{#if playground_js}}
302 {{#if playground_js}}
268− <script src="{{ path_to_root }}ace.js" type="text/javascript" charset="utf-8"></script>
303+ <script src="{{ path_to_root }}ace.js"></script>
269− <script src="{{ path_to_root }}editor.js" type="text/javascript" charset="utf-8"></script>
304+ <script src="{{ path_to_root }}editor.js"></script>
270− <script src="{{ path_to_root }}mode-rust.js" type="text/javascript" charset="utf-8"></script>
305+ <script src="{{ path_to_root }}mode-rust.js"></script>
271− <script src="{{ path_to_root }}theme-dawn.js" type="text/javascript" charset="utf-8"></script>
306+ <script src="{{ path_to_root }}theme-dawn.js"></script>
272− <script src="{{ path_to_root }}theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
307+ <script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
273 {{/if}}
308 {{/if}}
274
309
275 {{#if search_js}}
310 {{#if search_js}}
276− <script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
311+ <script src="{{ path_to_root }}elasticlunr.min.js"></script>
277− <script src="{{ path_to_root }}mark.min.js" type="text/javascript" charset="utf-8"></script>
312+ <script src="{{ path_to_root }}mark.min.js"></script>
278− <script src="{{ path_to_root }}searcher.js" type="text/javascript" charset="utf-8"></script>
313+ <script src="{{ path_to_root }}searcher.js"></script>
279 {{/if}}
314 {{/if}}
280
315
281− <script src="{{ path_to_root }}clipboard.min.js" type="text/javascript" charset="utf-8"></script>
316+ <script src="{{ path_to_root }}clipboard.min.js"></script>
282− <script src="{{ path_to_root }}highlight.js" type="text/javascript" charset="utf-8"></script>
317+ <script src="{{ path_to_root }}highlight.js"></script>
283− <script src="{{ path_to_root }}book.js" type="text/javascript" charset="utf-8"></script>
318+ <script src="{{ path_to_root }}book.js"></script>
284
319
285 <!-- Custom JS scripts -->
320 <!-- Custom JS scripts -->
286 {{#each additional_js}}
321 {{#each additional_js}}
287− <script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
322+ <script src="{{ ../path_to_root }}{{this}}"></script>
288 {{/each}}
323 {{/each}}
289
324
290 {{#if is_print}}
325 {{#if is_print}}
291 {{#if mathjax_support}}
326 {{#if mathjax_support}}
292− <script type="text/javascript">
327+ <script>
293 window.addEventListener('load', function() {
328 window.addEventListener('load', function() {
294 MathJax.Hub.Register.StartupHook('End', function() {
329 MathJax.Hub.Register.StartupHook('End', function() {
295 window.setTimeout(window.print, 100);
330 window.setTimeout(window.print, 100);
@@ -297,7 +332,7 @@
297 });
332 });
298 </script>
333 </script>
299 {{else}}
334 {{else}}
300− <script type="text/javascript">
335+ <script>
301 window.addEventListener('load', function() {
336 window.addEventListener('load', function() {
302 window.setTimeout(window.print, 100);
337 window.setTimeout(window.print, 100);
303 });
338 });
@@ -305,5 +340,6 @@
305 {{/if}}
340 {{/if}}
306 {{/if}}
341 {{/if}}
307
342
343+ </div>
308 </body>
344 </body>
309</html>
345</html>
contrib/Helix.appdata.xml+21
@@ -5,6 +5,7 @@
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>
8
9
9 <description>
10 <description>
10 <p>
11 <p>
@@ -17,6 +18,17 @@
17 <li>Smart, incremental syntax highlighting and code editing via tree-sitter</li>
18 <li>Smart, incremental syntax highlighting and code editing via tree-sitter</li>
18 </ul>
19 </ul>
19 </description>
20 </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>
20
32
21 <launchable type="desktop-id">Helix.desktop</launchable>
33 <launchable type="desktop-id">Helix.desktop</launchable>
22
34
@@ -36,6 +48,15 @@
36 <content_rating type="oars-1.1" />
48 <content_rating type="oars-1.1" />
37
49
38 <releases>
50 <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">
58+ <url>https://helix-editor.com/news/release-23-03-highlights/</url>
59+ </release>
39 <release version="22.12" date="2022-12-6">
60 <release version="22.12" date="2022-12-6">
40 <url>https://helix-editor.com/news/release-22-12-highlights/</url>
61 <url>https://helix-editor.com/news/release-22-12-highlights/</url>
41 </release>
62 </release>
contrib/Helix.desktop+3−1
@@ -1,6 +1,7 @@
1[Desktop Entry]
1[Desktop Entry]
2Name=Helix
2Name=Helix
3GenericName=Text Editor
3GenericName=Text Editor
4+GenericName[ar]=مُحَرِّرُ نُصُوص
4GenericName[de]=Texteditor
5GenericName[de]=Texteditor
5GenericName[fr]=Éditeur de texte
6GenericName[fr]=Éditeur de texte
6GenericName[ru]=Текстовый редактор
7GenericName[ru]=Текстовый редактор
@@ -9,7 +10,7 @@ GenericName[tr]=Metin Düzenleyici
9Comment=Edit text files
10Comment=Edit text files
10Comment[af]=Redigeer tekslêers
11Comment[af]=Redigeer tekslêers
11Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ
12Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ
12−Comment[ar]=حرّر ملفات نصية
13+Comment[ar]=مُحَرِّرُ مِلَفَّاتٍ نَصِّيَّة
13Comment[az]=Mətn fayllarını redaktə edin
14Comment[az]=Mətn fayllarını redaktə edin
14Comment[be]=Рэдагаваньне тэкставых файлаў
15Comment[be]=Рэдагаваньне тэкставых файлаў
15Comment[bg]=Редактиране на текстови файлове
16Comment[bg]=Редактиране на текстови файлове
@@ -79,6 +80,7 @@ Exec=hx %F
79Terminal=true
80Terminal=true
80Type=Application
81Type=Application
81Keywords=Text;editor;
82Keywords=Text;editor;
83+Keywords[ar]=نص;نصوص;محرر;
82Keywords[fr]=Texte;éditeur;
84Keywords[fr]=Texte;éditeur;
83Keywords[ru]=текст;текстовый редактор;
85Keywords[ru]=текст;текстовый редактор;
84Keywords[sr]=Текст;едитор;
86Keywords[sr]=Текст;едитор;
docs/CONTRIBUTING.md+17−4
@@ -29,11 +29,23 @@ 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 and
32+inside the project. We use [xtask][xtask] as an ad-hoc task runner.
33−thus do not require any dependencies other than `cargo` (You don't have
34−to `cargo install` anything either).
35
33
34+To preview the book itself, install [mdbook][mdbook]. Then, run
35+
36+```shell
37+mdbook serve book
38+```
39+
40+and visit [http://localhost:3000](http://localhost:3000).
41+
42+# Testing
43+
44+## Unit tests/Documentation tests
45+
46+Run `cargo test --workspace` to run unit tests and documentation tests in all packages.
47+
36−# Integration tests
48+## Integration tests
37
49
38Integration tests for helix-term can be run with `cargo integration-test`. Code
50Integration tests for helix-term can be run with `cargo integration-test`. Code
39contributors are strongly encouraged to write integration tests for their code.
51contributors are strongly encouraged to write integration tests for their code.
@@ -52,4 +64,5 @@ The current MSRV and future changes to the MSRV are listed in the [Firefox docum
52[architecture.md]: ./architecture.md
64[architecture.md]: ./architecture.md
53[docs]: https://docs.helix-editor.com/
65[docs]: https://docs.helix-editor.com/
54[xtask]: https://github.com/matklad/cargo-xtask
66[xtask]: https://github.com/matklad/cargo-xtask
67+[mdbook]: https://rust-lang.github.io/mdBook/guide/installation.html
55[helpers.rs]: ../helix-term/tests/test/helpers.rs
68[helpers.rs]: ../helix-term/tests/test/helpers.rs
docs/releases.md+13−6
@@ -1,16 +1,23 @@
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. In these instructions
4+`YY.0M(.MICRO)`, for example, `22.05` for May of 2022, or in a patch release,
5−we'll use `<tag>` as a placeholder for the tag being published.
5+`22.05.1`. In these instructions we'll use `<tag>` as a placeholder for the tag
6+being published.
6
7
7* Merge the changelog PR
8* Merge the changelog PR
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)
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)
9* Tag and push
10* Tag and push
10 * `git tag -s -m "<tag>" -a <tag> && git push`
11 * `git tag -s -m "<tag>" -a <tag> && git push`
11 * Make sure to switch to master and pull first
12 * Make sure to switch to master and pull first
12−* Edit the `VERSION` file and change the date to the next planned release
13+* Edit the `Cargo.toml` file and change the date in the `version` field 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.
13− * Releases are planned to happen every two months, so `22.05` would change to `22.07`
18+ * Releases are planned to happen every two months, so `22.05.0` would change to `22.07.0`
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`
14* Wait for the Release CI to finish
21* Wait for the Release CI to finish
15 * It will automatically turn the git tag into a GitHub release when it uploads artifacts
22 * It will automatically turn the git tag into a GitHub release when it uploads artifacts
16* Edit the new release
23* Edit the new release
@@ -30,7 +37,7 @@ we'll use `<tag>` as a placeholder for the tag being published.
30
37
31The changelog is currently created manually by reading through commits in the
38The changelog is currently created manually by reading through commits in the
32log since the last release. GitHub's compare view is a nice way to approach
39log since the last release. GitHub's compare view is a nice way to approach
33−this. For example when creating the 22.07 release notes, this compare link
40+this. For example, when creating the 22.07 release notes, this compare link
34may be used
41may be used
35
42
36```
43```
@@ -57,4 +64,4 @@ versions for convenience:
57> release. For the full log, check out the git log.
64> release. For the full log, check out the git log.
58
65
59Typically, small changes like dependencies or documentation updates, refactors,
66Typically, small changes like dependencies or documentation updates, refactors,
60−or meta changes like GitHub Actions work are left out.
67+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. Right now we're thinking Wasm-based plugins.
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.
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+39−173
@@ -1,87 +1,35 @@
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": {
20 "inputs": {
4 "inputs": {
21− "alejandra": [
22− "nci"
23− ],
24− "all-cabal-json": [
25− "nci"
26− ],
27− "crane": "crane",
28− "devshell": [
29− "nci"
30− ],
31− "flake-parts": [
32− "nci",
33− "parts"
34− ],
35− "flake-utils-pre-commit": [
36− "nci"
37− ],
38− "ghc-utils": [
39− "nci"
40− ],
41− "gomod2nix": [
42− "nci"
43− ],
44− "mach-nix": [
45− "nci"
46− ],
47− "nix-pypi-fetcher": [
48− "nci"
49− ],
50 "nixpkgs": [
5 "nixpkgs": [
51− "nci",
52 "nixpkgs"
6 "nixpkgs"
53− ],
54− "poetry2nix": [
55− "nci"
56− ],
57− "pre-commit-hooks": [
58− "nci"
59− ],
60− "pruned-racket-catalog": [
61− "nci"
62 ]
7 ]
63 },
8 },
64 "locked": {
9 "locked": {
65− "lastModified": 1677289985,
10+ "lastModified": 1701025348,
66− "narHash": "sha256-lUp06cTTlWubeBGMZqPl9jODM99LpWMcwxRiscFAUJg=",
11+ "narHash": "sha256-42GHmYH+GF7VjwGSt+fVT1CQuNpGanJbNgVHTAZppUM=",
67− "owner": "nix-community",
12+ "owner": "ipetkov",
68− "repo": "dream2nix",
13+ "repo": "crane",
69− "rev": "28b973a8d4c30cc1cbb3377ea2023a76bc3fb889",
14+ "rev": "42afaeb1a0325194a7cdb526332d2cb92fddd07b",
70 "type": "github"
15 "type": "github"
71 },
16 },
72 "original": {
17 "original": {
73− "owner": "nix-community",
18+ "owner": "ipetkov",
74− "repo": "dream2nix",
19+ "repo": "crane",
75 "type": "github"
20 "type": "github"
76 }
21 }
77 },
22 },
78 "flake-utils": {
23 "flake-utils": {
24+ "inputs": {
25+ "systems": "systems"
26+ },
79 "locked": {
27 "locked": {
80− "lastModified": 1659877975,
28+ "lastModified": 1694529238,
81− "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
29+ "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
82 "owner": "numtide",
30 "owner": "numtide",
83 "repo": "flake-utils",
31 "repo": "flake-utils",
84− "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
32+ "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
85 "type": "github"
33 "type": "github"
86 },
34 },
87 "original": {
35 "original": {
@@ -90,55 +38,13 @@
90 "type": "github"
38 "type": "github"
91 }
39 }
92 },
40 },
93− "mk-naked-shell": {
94− "flake": false,
95− "locked": {
96− "lastModified": 1676572903,
97− "narHash": "sha256-oQoDHHUTxNVSURfkFcYLuAK+btjs30T4rbEUtCUyKy8=",
98− "owner": "yusdacra",
99− "repo": "mk-naked-shell",
100− "rev": "aeca9f8aa592f5e8f71f407d081cb26fd30c5a57",
101− "type": "github"
102− },
103− "original": {
104− "owner": "yusdacra",
105− "repo": "mk-naked-shell",
106− "type": "github"
107− }
108− },
109− "nci": {
110− "inputs": {
111− "dream2nix": "dream2nix",
112− "mk-naked-shell": "mk-naked-shell",
113− "nixpkgs": [
114− "nixpkgs"
115− ],
116− "parts": "parts",
117− "rust-overlay": [
118− "rust-overlay"
119− ]
120− },
121− "locked": {
122− "lastModified": 1677297103,
123− "narHash": "sha256-ArlJIbp9NGV9yvhZdV0SOUFfRlI/kHeKoCk30NbSiLc=",
124− "owner": "yusdacra",
125− "repo": "nix-cargo-integration",
126− "rev": "a79272a2cb0942392bb3a5bf9a3ec6bc568795b2",
127− "type": "github"
128− },
129− "original": {
130− "owner": "yusdacra",
131− "repo": "nix-cargo-integration",
132− "type": "github"
133− }
134− },
135 "nixpkgs": {
41 "nixpkgs": {
136 "locked": {
42 "locked": {
137− "lastModified": 1677063315,
43+ "lastModified": 1700794826,
138− "narHash": "sha256-qiB4ajTeAOVnVSAwCNEEkoybrAlA+cpeiBxLobHndE8=",
44+ "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=",
139 "owner": "nixos",
45 "owner": "nixos",
140 "repo": "nixpkgs",
46 "repo": "nixpkgs",
141− "rev": "988cc958c57ce4350ec248d2d53087777f9e1949",
47+ "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8",
142 "type": "github"
48 "type": "github"
143 },
49 },
144 "original": {
50 "original": {
@@ -148,84 +54,29 @@
148 "type": "github"
54 "type": "github"
149 }
55 }
150 },
56 },
151− "nixpkgs-lib": {
152− "locked": {
153− "dir": "lib",
154− "lastModified": 1675183161,
155− "narHash": "sha256-Zq8sNgAxDckpn7tJo7V1afRSk2eoVbu3OjI1QklGLNg=",
156− "owner": "NixOS",
157− "repo": "nixpkgs",
158− "rev": "e1e1b192c1a5aab2960bf0a0bd53a2e8124fa18e",
159− "type": "github"
160− },
161− "original": {
162− "dir": "lib",
163− "owner": "NixOS",
164− "ref": "nixos-unstable",
165− "repo": "nixpkgs",
166− "type": "github"
167− }
168− },
169− "parts": {
170− "inputs": {
171− "nixpkgs-lib": [
172− "nci",
173− "nixpkgs"
174− ]
175− },
176− "locked": {
177− "lastModified": 1675933616,
178− "narHash": "sha256-/rczJkJHtx16IFxMmAWu5nNYcSXNg1YYXTHoGjLrLUA=",
179− "owner": "hercules-ci",
180− "repo": "flake-parts",
181− "rev": "47478a4a003e745402acf63be7f9a092d51b83d7",
182− "type": "github"
183− },
184− "original": {
185− "owner": "hercules-ci",
186− "repo": "flake-parts",
187− "type": "github"
188− }
189− },
190− "parts_2": {
191− "inputs": {
192− "nixpkgs-lib": "nixpkgs-lib"
193− },
194− "locked": {
195− "lastModified": 1675933616,
196− "narHash": "sha256-/rczJkJHtx16IFxMmAWu5nNYcSXNg1YYXTHoGjLrLUA=",
197− "owner": "hercules-ci",
198− "repo": "flake-parts",
199− "rev": "47478a4a003e745402acf63be7f9a092d51b83d7",
200− "type": "github"
201− },
202− "original": {
203− "owner": "hercules-ci",
204− "repo": "flake-parts",
205− "type": "github"
206− }
207− },
208 "root": {
57 "root": {
209 "inputs": {
58 "inputs": {
210− "nci": "nci",
59+ "crane": "crane",
60+ "flake-utils": "flake-utils",
211 "nixpkgs": "nixpkgs",
61 "nixpkgs": "nixpkgs",
212− "parts": "parts_2",
213 "rust-overlay": "rust-overlay"
62 "rust-overlay": "rust-overlay"
214 }
63 }
215 },
64 },
216 "rust-overlay": {
65 "rust-overlay": {
217 "inputs": {
66 "inputs": {
218− "flake-utils": "flake-utils",
67+ "flake-utils": [
68+ "flake-utils"
69+ ],
219 "nixpkgs": [
70 "nixpkgs": [
220 "nixpkgs"
71 "nixpkgs"
221 ]
72 ]
222 },
73 },
223 "locked": {
74 "locked": {
224− "lastModified": 1677292251,
75+ "lastModified": 1701137803,
225− "narHash": "sha256-D+6q5Z2MQn3UFJtqsM5/AvVHi3NXKZTIMZt1JGq/spA=",
76+ "narHash": "sha256-0LcPAdql5IhQSUXJx3Zna0dYTgdIoYO7zUrsKgiBd04=",
226 "owner": "oxalica",
77 "owner": "oxalica",
227 "repo": "rust-overlay",
78 "repo": "rust-overlay",
228− "rev": "34cdbf6ad480ce13a6a526f57d8b9e609f3d65dc",
79+ "rev": "9dd940c967502f844eacea52a61e9596268d4f70",
229 "type": "github"
80 "type": "github"
230 },
81 },
231 "original": {
82 "original": {
@@ -233,6 +84,21 @@
233 "repo": "rust-overlay",
84 "repo": "rust-overlay",
234 "type": "github"
85 "type": "github"
235 }
86 }
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+ }
236 }
102 }
237 },
103 },
238 "root": "root",
104 "root": "root",
flake.nix+171−142
@@ -3,164 +3,193 @@
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";
6 rust-overlay = {
7 rust-overlay = {
7 url = "github:oxalica/rust-overlay";
8 url = "github:oxalica/rust-overlay";
9+ inputs = {
8− inputs.nixpkgs.follows = "nixpkgs";
10+ nixpkgs.follows = "nixpkgs";
11+ flake-utils.follows = "flake-utils";
12+ };
9 };
13 };
10− nci = {
14+ crane = {
11− url = "github:yusdacra/nix-cargo-integration";
15+ url = "github:ipetkov/crane";
12 inputs.nixpkgs.follows = "nixpkgs";
16 inputs.nixpkgs.follows = "nixpkgs";
13− inputs.rust-overlay.follows = "rust-overlay";
14 };
17 };
15− parts.url = "github:hercules-ci/flake-parts";
16 };
18 };
17
19
18− outputs = inp: let
20+ outputs = {
19− mkRootPath = rel:
21+ self,
20− builtins.path {
22+ nixpkgs,
21− path = "${toString ./.}/${rel}";
23+ crane,
22− name = rel;
24+ flake-utils,
23− };
25+ rust-overlay,
24− filteredSource = let
26+ ...
25− pathsToIgnore = [
27+ }:
26− ".envrc"
28+ flake-utils.lib.eachDefaultSystem (system: let
27− ".ignore"
29+ pkgs = import nixpkgs {
28− ".github"
30+ inherit system;
29− ".gitignore"
31+ overlays = [(import rust-overlay)];
30− "logo.svg"
31− "logo_dark.svg"
32− "logo_light.svg"
33− "rust-toolchain.toml"
34− "rustfmt.toml"
35− "runtime"
36− "screenshot.png"
37− "book"
38− "contrib"
39− "docs"
40− "README.md"
41− "CHANGELOG.md"
42− "shell.nix"
43− "default.nix"
44− "grammars.nix"
45− "flake.nix"
46− "flake.lock"
47− ];
48− ignorePaths = path: type: let
49− inherit (inp.nixpkgs) lib;
50− # split the nix store path into its components
51− components = lib.splitString "/" path;
52− # drop off the `/nix/hash-source` section from the path
53− relPathComponents = lib.drop 4 components;
54− # reassemble the path components
55− relPath = lib.concatStringsSep "/" relPathComponents;
56− in
57− lib.all (p: ! (lib.hasPrefix p relPath)) pathsToIgnore;
58− in
59− builtins.path {
60− name = "helix-source";
61− path = toString ./.;
62− # filter out unnecessary paths
63− filter = ignorePaths;
64 };
32 };
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
81− makeOverridableHelix = old: config: let
82− grammars = pkgs.callPackage ./grammars.nix config;
83− runtimeDir = pkgs.runCommand "helix-runtime" {} ''
84− mkdir -p $out
85− ln -s ${mkRootPath "runtime"}/* $out
33+ mkRootPath = rel:
86− rm -r $out/grammars
34+ builtins.path {
87− ln -s ${grammars} $out/grammars
35+ path = "${toString ./.}/${rel}";
88− '';
89− helix-wrapped =
90− pkgs.runCommand
91− old.name
36+ name = rel;
92− {
37+ };
93− inherit (old) pname version;
38+ filteredSource = let
94− meta = old.meta or {};
39+ pathsToIgnore = [
95− passthru =
40+ ".envrc"
96− (old.passthru or {})
41+ ".ignore"
97− // {
42+ ".github"
98− unwrapped = old;
43+ ".gitignore"
99− };
44+ "logo_dark.svg"
100− nativeBuildInputs = [pkgs.makeWrapper];
45+ "logo_light.svg"
101− makeWrapperArgs = config.makeWrapperArgs or [];
46+ "rust-toolchain.toml"
102− }
47+ "rustfmt.toml"
103− ''
48+ "runtime"
104− cp -rs --no-preserve=mode,ownership ${old} $out
49+ "screenshot.png"
105− wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
50+ "book"
106− '';
51+ "docs"
52+ "README.md"
53+ "CHANGELOG.md"
54+ "shell.nix"
55+ "default.nix"
56+ "grammars.nix"
57+ "flake.nix"
58+ "flake.lock"
59+ ];
60+ ignorePaths = path: type: let
61+ inherit (nixpkgs) lib;
62+ # split the nix store path into its components
63+ components = lib.splitString "/" path;
64+ # drop off the `/nix/hash-source` section from the path
65+ relPathComponents = lib.drop 4 components;
66+ # reassemble the path components
67+ relPath = lib.concatStringsSep "/" relPathComponents;
107 in
68 in
108− helix-wrapped
69+ lib.all (p: ! (lib.hasPrefix p relPath)) pathsToIgnore;
109− // {
70+ in
71+ builtins.path {
72+ name = "helix-source";
73+ path = toString ./.;
74+ # filter out unnecessary paths
75+ filter = ignorePaths;
76+ };
110− override = makeOverridableHelix old;
77+ makeOverridableHelix = old: config: let
78+ grammars = pkgs.callPackage ./grammars.nix config;
79+ runtimeDir = pkgs.runCommand "helix-runtime" {} ''
80+ mkdir -p $out
81+ ln -s ${mkRootPath "runtime"}/* $out
82+ rm -r $out/grammars
83+ ln -s ${grammars} $out/grammars
84+ '';
85+ helix-wrapped =
86+ pkgs.runCommand
87+ old.name
88+ {
89+ inherit (old) pname version;
90+ meta = old.meta or {};
111 passthru =
91 passthru =
112− helix-wrapped.passthru
92+ (old.passthru or {})
113 // {
93 // {
114− wrapper = old: makeOverridableHelix old config;
94+ unwrapped = old;
115 };
95 };
116− };
96+ nativeBuildInputs = [pkgs.makeWrapper];
117− stdenv =
97+ makeWrapperArgs = config.makeWrapperArgs or [];
118− if pkgs.stdenv.isLinux
98+ }
119− then pkgs.stdenv
99+ ''
120− else pkgs.clangStdenv;
100+ cp -rs --no-preserve=mode,ownership ${old} $out
121− rustFlagsEnv =
101+ wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
122− if stdenv.isLinux
102+ '';
123− then ''$RUSTFLAGS -C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment''
124− else "$RUSTFLAGS";
125− in {
103+ in
126− # by default NCI adds rust-analyzer component, but helix toolchain doesn't have rust-analyzer
104+ helix-wrapped
127− nci.toolchains.shell.components = ["rust-src" "rustfmt" "clippy"];
105+ // {
128− nci.projects."helix-project".relPath = "";
106+ override = makeOverridableHelix old;
129− nci.crates."helix-term" = {
107+ passthru =
130− overrides = {
108+ helix-wrapped.passthru
131− add-meta.override = _: {meta.mainProgram = "hx";};
109+ // {
132− add-inputs.overrideAttrs = prev: {
110+ wrapper = old: makeOverridableHelix old config;
133− buildInputs = (prev.buildInputs or []) ++ [stdenv.cc.cc.lib];
134− };
135− disable-grammar-builds = {
136− # disable fetching and building of tree-sitter grammars in the helix-term build.rs
137− HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
138 };
111 };
139− disable-tests = {checkPhase = ":";};
140− set-stdenv.override = _: {inherit stdenv;};
141− set-filtered-src.override = _: {src = filteredSource;};
142− };
143 };
112 };
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+ };
144
152
145− packages.helix-unwrapped = config.nci.outputs."helix-term".packages.release;
153+ checks = {
146− packages.helix-unwrapped-dev = config.nci.outputs."helix-term".packages.dev;
154+ # Build the crate itself
147− packages.helix = makeOverridableHelix config.packages.helix-unwrapped {};
155+ inherit (self.packages.${system}) helix;
148− packages.helix-dev = makeOverridableHelix config.packages.helix-unwrapped-dev {};
149− packages.default = config.packages.helix;
150
156
151− devShells.default = config.nci.outputs."helix-project".devShell.overrideAttrs (old: {
157+ clippy = craneLibMSRV.cargoClippy (commonArgs
152− nativeBuildInputs =
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};
153− (old.nativeBuildInputs or [])
178+ nativeBuildInputs = with pkgs;
154− ++ (with pkgs; [lld_13 cargo-flamegraph rust-analyzer])
179+ [lld_13 cargo-flamegraph rust-analyzer]
155− ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin)
180+ ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin)
156− ++ (lib.optional stdenv.isLinux pkgs.lldb)
181+ ++ (lib.optional stdenv.isLinux pkgs.lldb)
157− ++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation);
182+ ++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation);
158− shellHook = ''
183+ shellHook = ''
159− export HELIX_RUNTIME="$PWD/runtime"
184+ export HELIX_RUNTIME="$PWD/runtime"
160− export RUST_BACKTRACE="1"
185+ export RUST_BACKTRACE="1"
161− export RUSTFLAGS="${rustFlagsEnv}"
186+ export RUSTFLAGS="${rustFlagsEnv}"
162− '';
187+ '';
163− });
188+ };
189+ })
190+ // {
191+ overlays.default = final: prev: {
192+ inherit (self.packages.${final.system}) helix;
164 };
193 };
165 };
194 };
166
195
grammars.nix+24−21
@@ -5,6 +5,7 @@
5 runCommand,
5 runCommand,
6 yj,
6 yj,
7 includeGrammarIf ? _: true,
7 includeGrammarIf ? _: true,
8+ grammarOverlays ? [],
8 ...
9 ...
9}: let
10}: let
10 # HACK: nix < 2.6 has a bug in the toml parser, so we convert to JSON
11 # HACK: nix < 2.6 has a bug in the toml parser, so we convert to JSON
@@ -48,22 +49,22 @@
48 then sourceGitHub
49 then sourceGitHub
49 else sourceGit;
50 else sourceGit;
50 in
51 in
51− stdenv.mkDerivation rec {
52+ stdenv.mkDerivation {
52 # see https://github.com/NixOS/nixpkgs/blob/fbdd1a7c0bc29af5325e0d7dd70e804a972eb465/pkgs/development/tools/parsing/tree-sitter/grammar.nix
53 # see https://github.com/NixOS/nixpkgs/blob/fbdd1a7c0bc29af5325e0d7dd70e804a972eb465/pkgs/development/tools/parsing/tree-sitter/grammar.nix
53
54
54 pname = "helix-tree-sitter-${grammar.name}";
55 pname = "helix-tree-sitter-${grammar.name}";
55 version = grammar.source.rev;
56 version = grammar.source.rev;
56
57
57− src =
58+ src = source;
58− if builtins.hasAttr "subpath" grammar.source
59+ sourceRoot = if builtins.hasAttr "subpath" grammar.source then
59− then "${source}/${grammar.source.subpath}"
60+ "source/${grammar.source.subpath}"
61+ else
60− else source;
62+ "source";
61
63
62− dontUnpack = true;
63 dontConfigure = true;
64 dontConfigure = true;
64
65
65 FLAGS = [
66 FLAGS = [
66− "-I${src}/src"
67+ "-Isrc"
67 "-g"
68 "-g"
68 "-O3"
69 "-O3"
69 "-fPIC"
70 "-fPIC"
@@ -76,13 +77,13 @@
76 buildPhase = ''
77 buildPhase = ''
77 runHook preBuild
78 runHook preBuild
78
79
79− if [[ -e "$src/src/scanner.cc" ]]; then
80+ if [[ -e src/scanner.cc ]]; then
80− $CXX -c "$src/src/scanner.cc" -o scanner.o $FLAGS
81+ $CXX -c src/scanner.cc -o scanner.o $FLAGS
81− elif [[ -e "$src/src/scanner.c" ]]; then
82+ elif [[ -e src/scanner.c ]]; then
82− $CC -c "$src/src/scanner.c" -o scanner.o $FLAGS
83+ $CC -c src/scanner.c -o scanner.o $FLAGS
83 fi
84 fi
84
85
85− $CC -c "$src/src/parser.c" -o parser.o $FLAGS
86+ $CC -c src/parser.c -o parser.o $FLAGS
86 $CXX -shared -o $NAME.so *.o
87 $CXX -shared -o $NAME.so *.o
87
88
88 ls -al
89 ls -al
@@ -105,15 +106,17 @@
105 '';
106 '';
106 };
107 };
107 grammarsToBuild = builtins.filter includeGrammarIf gitGrammars;
108 grammarsToBuild = builtins.filter includeGrammarIf gitGrammars;
108− builtGrammars =
109− builtins.map (grammar: {
109+ builtGrammars = builtins.map (grammar: {
110− inherit (grammar) name;
110+ inherit (grammar) name;
111− artifact = buildGrammar grammar;
111+ value = buildGrammar grammar;
112− })
113− grammarsToBuild;
112+ }) grammarsToBuild;
113+ extensibleGrammars =
114+ lib.makeExtensible (self: builtins.listToAttrs builtGrammars);
115+ overlayedGrammars = lib.pipe extensibleGrammars
116+ (builtins.map (overlay: grammar: grammar.extend overlay) grammarOverlays);
114− grammarLinks =
117+ grammarLinks = lib.mapAttrsToList
115− builtins.map (grammar: "ln -s ${grammar.artifact}/${grammar.name}.so $out/${grammar.name}.so")
118+ (name: artifact: "ln -s ${artifact}/${name}.so $out/${name}.so")
116− builtGrammars;
119+ (lib.filterAttrs (n: v: lib.isDerivation v) overlayedGrammars);
117in
120in
118 runCommand "consolidated-helix-grammars" {} ''
121 runCommand "consolidated-helix-grammars" {} ''
119 mkdir -p $out
122 mkdir -p $out
helix-core/Cargo.toml+22−16
@@ -1,37 +1,39 @@
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"
7description = "Helix editor core editing primitives"
3description = "Helix editor core editing primitives"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
11include = ["src/**/*", "README.md"]
4include = ["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
12
13
13[features]
14[features]
14unicode-lines = ["ropey/unicode_lines"]
15unicode-lines = ["ropey/unicode_lines"]
15integration = []
16integration = []
16
17
17[dependencies]
18[dependencies]
18−helix-loader = { version = "0.6", path = "../helix-loader" }
19+helix-loader = { path = "../helix-loader" }
19
20
20−ropey = { version = "1.6.0", default-features = false, features = ["simd"] }
21+ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
21−smallvec = "1.10"
22+smallvec = "1.11"
22smartstring = "1.0.1"
23smartstring = "1.0.1"
23unicode-segmentation = "1.10"
24unicode-segmentation = "1.10"
24unicode-width = "0.1"
25unicode-width = "0.1"
25unicode-general-category = "0.6"
26unicode-general-category = "0.6"
26# slab = "0.4.2"
27# slab = "0.4.2"
27slotmap = "1.0"
28slotmap = "1.0"
28−tree-sitter = "0.20"
29+tree-sitter.workspace = true
29−once_cell = "1.17"
30+once_cell = "1.19"
30arc-swap = "1"
31arc-swap = "1"
31regex = "1"
32regex = "1"
32−bitflags = "1.3"
33+bitflags = "2.4"
33−ahash = "0.8.3"
34+ahash = "0.8.6"
34−hashbrown = { version = "0.13.2", features = ["raw"] }
35+hashbrown = { version = "0.14.3", features = ["raw"] }
36+dunce = "1.0"
35
37
36log = "0.4"
38log = "0.4"
37serde = { version = "1.0", features = ["derive"] }
39serde = { version = "1.0", features = ["derive"] }
@@ -44,8 +46,12 @@ encoding_rs = "0.8"
44
46
45chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
47chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
46
48
47−etcetera = "0.4"
49+etcetera = "0.8"
48textwrap = "0.16.0"
50textwrap = "0.16.0"
49
51
52+nucleo.workspace = true
53+parking_lot = "0.12"
54+
50[dev-dependencies]
55[dev-dependencies]
51quickcheck = { version = "1", default-features = false }
56quickcheck = { version = "1", default-features = false }
57+indoc = "2.0.4"
helix-core/src/diagnostic.rs+6−1
@@ -35,14 +35,19 @@ pub enum DiagnosticTag {
35 Deprecated,
35 Deprecated,
36}
36}
37
37
38−/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html)
38+/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.94.0/lsp_types/struct.Diagnostic.html)
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,
42 pub line: usize,
46 pub line: usize,
43 pub message: String,
47 pub message: String,
44 pub severity: Option<Severity>,
48 pub severity: Option<Severity>,
45 pub code: Option<NumberOrString>,
49 pub code: Option<NumberOrString>,
50+ pub language_server_id: usize,
46 pub tags: Vec<DiagnosticTag>,
51 pub tags: Vec<DiagnosticTag>,
47 pub source: Option<String>,
52 pub source: Option<String>,
48 pub data: Option<serde_json::Value>,
53 pub data: Option<serde_json::Value>,
helix-core/src/doc_formatter/test.rs+8−48
@@ -119,16 +119,7 @@ fn overlay() {
119 "foobar",
119 "foobar",
120 0,
120 0,
121 false,
121 false,
122− &[
123− Overlay {
122+ &[Overlay::new(0, "X"), Overlay::new(2, "\t")],
124− char_idx: 0,
125− grapheme: "X".into(),
126− },
127− Overlay {
128− char_idx: 2,
129− grapheme: "\t".into(),
130− },
131− ]
132 ),
123 ),
133 "Xo bar "
124 "Xo bar "
134 );
125 );
@@ -138,18 +129,9 @@ fn overlay() {
138 0,
129 0,
139 true,
130 true,
140 &[
131 &[
141− Overlay {
132+ Overlay::new(2, "\t"),
142− char_idx: 2,
143− grapheme: "\t".into(),
144− },
145− Overlay {
133+ Overlay::new(5, "\t"),
146− char_idx: 5,
147− grapheme: "\t".into(),
148− },
149− Overlay {
134+ Overlay::new(16, "X"),
150− char_idx: 16,
151− grapheme: "X".into(),
152− },
153 ]
135 ]
154 ),
136 ),
155 "fo f o foo \n.foo Xoo foo foo \n.foo foo foo "
137 "fo f o foo \n.foo Xoo foo foo \n.foo foo foo "
@@ -170,24 +152,14 @@ fn annotate_text(text: &str, softwrap: bool, annotations: &[InlineAnnotation]) -
170#[test]
152#[test]
171fn annotation() {
153fn annotation() {
172 assert_eq!(
154 assert_eq!(
173− annotate_text(
174− "bar",
175− false,
176− &[InlineAnnotation {
155+ annotate_text("bar", false, &[InlineAnnotation::new(0, "foo")]),
177− char_idx: 0,
178− text: "foo".into(),
179− }]
180− ),
181 "foobar "
156 "foobar "
182 );
157 );
183 assert_eq!(
158 assert_eq!(
184 annotate_text(
159 annotate_text(
185 &"foo ".repeat(10),
160 &"foo ".repeat(10),
186 true,
161 true,
187− &[InlineAnnotation {
162+ &[InlineAnnotation::new(0, "foo ")]
188− char_idx: 0,
189− text: "foo ".into(),
190− }]
191 ),
163 ),
192 "foo foo foo foo \n.foo foo foo foo \n.foo foo foo "
164 "foo foo foo foo \n.foo foo foo foo \n.foo foo foo "
193 );
165 );
@@ -199,20 +171,8 @@ fn annotation_and_overlay() {
199 "bbar".into(),
171 "bbar".into(),
200 &TextFormat::new_test(false),
172 &TextFormat::new_test(false),
201 TextAnnotations::default()
173 TextAnnotations::default()
202− .add_inline_annotations(
174+ .add_inline_annotations(Rc::new([InlineAnnotation::new(0, "fooo")]), None)
203− Rc::new([InlineAnnotation {
204− char_idx: 0,
205− text: "fooo".into(),
206− }]),
207− None
208− )
209− .add_overlay(
210− Rc::new([Overlay {
175+ .add_overlay(Rc::new([Overlay::new(0, "\t")]), None),
211− char_idx: 0,
212− grapheme: "\t".into(),
213− }]),
214− None
215− ),
216 0,
176 0,
217 )
177 )
218 .0
178 .0
helix-core/src/fuzzy.rsnew file+43
@@ -0,0 +1,43 @@
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,
484+ len: (i32::try_from(len).unwrap() as u32) | Self::MASK_OWNED,
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(&Rope::new())),
75+ transaction: Transaction::from(ChangeSet::new("".into())),
76− inversion: Transaction::from(ChangeSet::new(&Rope::new())),
76+ inversion: Transaction::from(ChangeSet::new("".into())),
77 timestamp: Instant::now(),
77 timestamp: Instant::now(),
78 }],
78 }],
79 current: 0,
79 current: 0,
helix-core/src/indent.rs+618−145
@@ -1,12 +1,14 @@
1−use std::collections::HashMap;
1+use std::{borrow::Cow, 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− syntax::{LanguageConfiguration, RopeProvider, Syntax},
9+ syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
8 tree_sitter::Node,
10 tree_sitter::Node,
9− Rope, RopeSlice,
11+ Position, Rope, RopeGraphemes, RopeSlice,
10};
12};
11
13
12/// Enum representing indentation style.
14/// Enum representing indentation style.
@@ -18,6 +20,10 @@ pub enum IndentStyle {
18 Spaces(u8),
20 Spaces(u8),
19}
21}
20
22
23+// 16 spaces
24+const INDENTS: &str = " ";
25+pub const MAX_INDENT: u8 = 16;
26+
21impl IndentStyle {
27impl IndentStyle {
22 /// Creates an `IndentStyle` from an indentation string.
28 /// Creates an `IndentStyle` from an indentation string.
23 ///
29 ///
@@ -26,10 +32,10 @@ impl IndentStyle {
26 #[inline]
32 #[inline]
27 pub fn from_str(indent: &str) -> Self {
33 pub fn from_str(indent: &str) -> Self {
28 // XXX: do we care about validating the input more than this? Probably not...?
34 // XXX: do we care about validating the input more than this? Probably not...?
29− debug_assert!(!indent.is_empty() && indent.len() <= 8);
35+ debug_assert!(!indent.is_empty() && indent.len() <= MAX_INDENT as usize);
30
36
31 if indent.starts_with(' ') {
37 if indent.starts_with(' ') {
32− IndentStyle::Spaces(indent.len() as u8)
38+ IndentStyle::Spaces(indent.len().clamp(1, MAX_INDENT as usize) as u8)
33 } else {
39 } else {
34 IndentStyle::Tabs
40 IndentStyle::Tabs
35 }
41 }
@@ -39,20 +45,13 @@ impl IndentStyle {
39 pub fn as_str(&self) -> &'static str {
45 pub fn as_str(&self) -> &'static str {
40 match *self {
46 match *self {
41 IndentStyle::Tabs => "\t",
47 IndentStyle::Tabs => "\t",
42− IndentStyle::Spaces(1) => " ",
43− IndentStyle::Spaces(2) => " ",
44− IndentStyle::Spaces(3) => " ",
45− IndentStyle::Spaces(4) => " ",
46− IndentStyle::Spaces(5) => " ",
47− IndentStyle::Spaces(6) => " ",
48− IndentStyle::Spaces(7) => " ",
49− IndentStyle::Spaces(8) => " ",
50−
51− // Unsupported indentation style. This should never happen,
52− // but just in case fall back to two spaces.
53 IndentStyle::Spaces(n) => {
48 IndentStyle::Spaces(n) => {
49+ // Unsupported indentation style. This should never happen,
54− debug_assert!(n > 0 && n <= 8); // Always triggers. `debug_panic!()` wanted.
50+ debug_assert!(n > 0 && n <= MAX_INDENT);
55− " "
51+
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]
56 }
55 }
57 }
56 }
58 }
57 }
@@ -74,9 +73,9 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
74 // Build a histogram of the indentation *increases* between
73 // Build a histogram of the indentation *increases* between
75 // subsequent lines, ignoring lines that are all whitespace.
74 // subsequent lines, ignoring lines that are all whitespace.
76 //
75 //
77− // Index 0 is for tabs, the rest are 1-8 spaces.
76+ // Index 0 is for tabs, the rest are 1-MAX_INDENT spaces.
78− let histogram: [usize; 9] = {
77+ let histogram: [usize; MAX_INDENT as usize + 1] = {
79− let mut histogram = [0; 9];
78+ let mut histogram = [0; MAX_INDENT as usize + 1];
80 let mut prev_line_is_tabs = false;
79 let mut prev_line_is_tabs = false;
81 let mut prev_line_leading_count = 0usize;
80 let mut prev_line_leading_count = 0usize;
82
81
@@ -135,7 +134,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
135 histogram[0] += 1;
134 histogram[0] += 1;
136 } else {
135 } else {
137 let amount = leading_count - prev_line_leading_count;
136 let amount = leading_count - prev_line_leading_count;
138− if amount <= 8 {
137+ if amount <= MAX_INDENT as usize {
139 histogram[amount] += 1;
138 histogram[amount] += 1;
140 }
139 }
141 }
140 }
@@ -189,7 +188,7 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: us
189 let mut len = 0;
188 let mut len = 0;
190 for ch in line.chars() {
189 for ch in line.chars() {
191 match ch {
190 match ch {
192− '\t' => len += tab_width,
191+ '\t' => len += tab_width_at(len, tab_width as u16),
193 ' ' => len += 1,
192 ' ' => len += 1,
194 _ => break,
193 _ => break,
195 }
194 }
@@ -198,6 +197,56 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: us
198 len / indent_width
197 len / indent_width
199}
198}
200
199
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+
201/// Computes for node and all ancestors whether they are the first node on their line.
250/// Computes for node and all ancestors whether they are the first node on their line.
202/// The first entry in the return value represents the root node, the last one the node itself
251/// The first entry in the return value represents the root node, the last one the node itself
203fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bool> {
252fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bool> {
@@ -239,68 +288,140 @@ fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bo
239/// This is usually constructed in one of 2 ways:
288/// This is usually constructed in one of 2 ways:
240/// - Successively add indent captures to get the (added) indent from a single line
289/// - Successively add indent captures to get the (added) indent from a single line
241/// - Successively add the indent results for each line
290/// - 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
242−#[derive(Default)]
294+#[derive(Default, Debug, PartialEq, Eq, Clone)]
243−pub struct Indentation {
295+pub struct Indentation<'a> {
244− /// The total indent (the number of indent levels) is defined as max(0, indent-outdent).
245− /// The string that this results in depends on the indent style (spaces or tabs, etc.)
246 indent: usize,
296 indent: usize,
297+ indent_always: usize,
247 outdent: usize,
298 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>>,
248}
303}
304+
249−impl Indentation {
305+impl<'a> Indentation<'a> {
250 /// Add some other [Indentation] to this.
306 /// Add some other [Indentation] to this.
251− /// The added indent should be the total added indent from one line
307+ /// 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).
252− fn add_line(&mut self, added: &Indentation) {
309+ fn add_line(&mut self, added: Indentation<'a>) {
253− if added.indent > 0 && added.outdent == 0 {
310+ // Align overrides the indent from outer scopes.
254− self.indent += 1;
311+ if self.align.is_some() {
255− } else if added.outdent > 0 && added.indent == 0 {
312+ return;
256− self.outdent += 1;
257 }
313 }
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;
258 }
322 }
323+
259 /// Add an indent capture to this indent.
324 /// Add an indent capture to this indent.
260− /// All the captures that are added in this way should be on the same line.
325+ /// Only captures that apply to the same line should be added together in this way (otherwise use `add_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).
261− fn add_capture(&mut self, added: IndentCaptureType) {
328+ fn add_capture(&mut self, added: IndentCaptureType<'a>) {
262 match added {
329 match added {
263 IndentCaptureType::Indent => {
330 IndentCaptureType::Indent => {
331+ if self.indent_always == 0 {
264− self.indent = 1;
332+ 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;
265 }
340 }
266 IndentCaptureType::Outdent => {
341 IndentCaptureType::Outdent => {
342+ if self.outdent_always == 0 {
267− self.outdent = 1;
343+ 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+ }
268 }
354 }
269 }
355 }
270 }
356 }
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,
271− fn as_string(&self, indent_style: &IndentStyle) -> String {
366+ indent_style: &IndentStyle,
272− let indent_level = if self.indent >= self.outdent {
367+ tab_width: usize,
273− self.indent - self.outdent
368+ ) -> Option<String> {
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+ ))
274 } else {
380 } else {
275− log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
381+ // If the alignment of both lines is different, we cannot compare their indentation in any meaningful way
276− 0
382+ None
277− };
383+ }
278− indent_style.as_str().repeat(indent_level)
384+ }
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+ )
279 }
393 }
280}
394}
281
395
282/// An indent definition which corresponds to a capture from the indent query
396/// An indent definition which corresponds to a capture from the indent query
397+#[derive(Debug)]
283−struct IndentCapture {
398+struct IndentCapture<'a> {
284− capture_type: IndentCaptureType,
399+ capture_type: IndentCaptureType<'a>,
285 scope: IndentScope,
400 scope: IndentScope,
286}
401}
287−#[derive(Clone, Copy)]
402+#[derive(Debug, Clone, PartialEq)]
288−enum IndentCaptureType {
403+enum IndentCaptureType<'a> {
289 Indent,
404 Indent,
405+ IndentAlways,
290 Outdent,
406 Outdent,
407+ OutdentAlways,
408+ /// Alignment given as a string of whitespace
409+ Align(RopeSlice<'a>),
291}
410}
411+
292−impl IndentCaptureType {
412+impl<'a> IndentCaptureType<'a> {
293 fn default_scope(&self) -> IndentScope {
413 fn default_scope(&self) -> IndentScope {
294 match self {
414 match self {
295− IndentCaptureType::Indent => IndentScope::Tail,
415+ IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
296− IndentCaptureType::Outdent => IndentScope::All,
416+ IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All,
417+ IndentCaptureType::Align(_) => IndentScope::All,
297 }
418 }
298 }
419 }
299}
420}
300/// This defines which part of a node an [IndentCapture] applies to.
421/// This defines which part of a node an [IndentCapture] applies to.
301/// Each [IndentCaptureType] has a default scope, but the scope can be changed
422/// Each [IndentCaptureType] has a default scope, but the scope can be changed
302/// with `#set!` property declarations.
423/// with `#set!` property declarations.
303−#[derive(Clone, Copy)]
424+#[derive(Debug, Clone, Copy)]
304enum IndentScope {
425enum IndentScope {
305 /// The indent applies to the whole node
426 /// The indent applies to the whole node
306 All,
427 All,
@@ -310,6 +431,7 @@ enum IndentScope {
310
431
311/// A capture from the indent query which does not define an indent but extends
432/// A capture from the indent query which does not define an indent but extends
312/// the range of a node. This is used before the indent is calculated.
433/// the range of a node. This is used before the indent is calculated.
434+#[derive(Debug)]
313enum ExtendCapture {
435enum ExtendCapture {
314 Extend,
436 Extend,
315 PreventOnce,
437 PreventOnce,
@@ -318,24 +440,41 @@ enum ExtendCapture {
318/// The result of running a tree-sitter indent query. This stores for
440/// The result of running a tree-sitter indent query. This stores for
319/// each node (identified by its ID) the relevant captures (already filtered
441/// each node (identified by its ID) the relevant captures (already filtered
320/// by predicates).
442/// by predicates).
443+#[derive(Debug)]
321−struct IndentQueryResult {
444+struct IndentQueryResult<'a> {
322− indent_captures: HashMap<usize, Vec<IndentCapture>>,
445+ indent_captures: HashMap<usize, Vec<IndentCapture<'a>>>,
323 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
446 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
324}
447}
325
448
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+
326−fn query_indents(
466+fn query_indents<'a>(
327 query: &Query,
467 query: &Query,
328 syntax: &Syntax,
468 syntax: &Syntax,
329 cursor: &mut QueryCursor,
469 cursor: &mut QueryCursor,
330− text: RopeSlice,
470+ text: RopeSlice<'a>,
331 range: std::ops::Range<usize>,
471 range: std::ops::Range<usize>,
332− // Position of the (optional) newly inserted line break.
472+ new_line_byte_pos: Option<usize>,
333− // Given as (line, byte_pos)
334− new_line_break: Option<(usize, usize)>,
335−) -> IndentQueryResult {
473+) -> IndentQueryResult<'a> {
336 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
474 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
337 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
475 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
338 cursor.set_byte_range(range);
476 cursor.set_byte_range(range);
477+
339 // Iterate over all captures from the query
478 // Iterate over all captures from the query
340 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
479 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
341 // Skip matches where not all custom predicates are fulfilled
480 // Skip matches where not all custom predicates are fulfilled
@@ -362,21 +501,13 @@ fn query_indents(
362 Some(QueryPredicateArg::Capture(capt1)),
501 Some(QueryPredicateArg::Capture(capt1)),
363 Some(QueryPredicateArg::Capture(capt2))
502 Some(QueryPredicateArg::Capture(capt2))
364 ) => {
503 ) => {
365− let get_line_num = |node: Node| {
366− let mut node_line = node.start_position().row;
367− // Adjust for the new line that will be inserted
368− if let Some((line, byte)) = new_line_break {
369− if node_line==line && node.start_byte()>=byte {
370− node_line += 1;
371− }
372− }
373− node_line
374− };
375 let n1 = m.nodes_for_capture_index(*capt1).next();
504 let n1 = m.nodes_for_capture_index(*capt1).next();
376 let n2 = m.nodes_for_capture_index(*capt2).next();
505 let n2 = m.nodes_for_capture_index(*capt2).next();
377 match (n1, n2) {
506 match (n1, n2) {
378 (Some(n1), Some(n2)) => {
507 (Some(n1), Some(n2)) => {
379− let same_line = get_line_num(n1)==get_line_num(n2);
508+ let n1_line = get_node_start_line(n1, new_line_byte_pos);
509+ let n2_line = get_node_start_line(n2, new_line_byte_pos);
510+ let same_line = n1_line == n2_line;
380 same_line==(pred.operator.as_ref()=="same-line?")
511 same_line==(pred.operator.as_ref()=="same-line?")
381 }
512 }
382 _ => true,
513 _ => true,
@@ -387,6 +518,23 @@ fn query_indents(
387 }
518 }
388 }
519 }
389 }
520 }
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+ },
390 _ => {
538 _ => {
391 panic!(
539 panic!(
392 "Invalid indent query: Unknown predicate (\"{}\")",
540 "Invalid indent query: Unknown predicate (\"{}\")",
@@ -397,11 +545,28 @@ fn query_indents(
397 }) {
545 }) {
398 continue;
546 continue;
399 }
547 }
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;
400 for capture in m.captures {
553 for capture in m.captures {
401 let capture_name = query.capture_names()[capture.index as usize].as_str();
554 let capture_name = query.capture_names()[capture.index as usize].as_str();
402 let capture_type = match capture_name {
555 let capture_type = match capture_name {
403 "indent" => IndentCaptureType::Indent,
556 "indent" => IndentCaptureType::Indent,
557+ "indent.always" => IndentCaptureType::IndentAlways,
404 "outdent" => IndentCaptureType::Outdent,
558 "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+ }
405 "extend" => {
570 "extend" => {
406 extend_captures
571 extend_captures
407 .entry(capture.node.id())
572 .entry(capture.node.id())
@@ -451,17 +616,40 @@ fn query_indents(
451 }
616 }
452 }
617 }
453 }
618 }
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+ }
454 indent_captures
638 indent_captures
455− .entry(capture.node.id())
639+ .entry(node_id)
456− // Most entries only need to contain a single IndentCapture
457 .or_insert_with(|| Vec::with_capacity(1))
640 .or_insert_with(|| Vec::with_capacity(1))
458− .push(indent_capture);
641+ .push(capture);
459 }
642 }
460 }
643 }
644+
461− IndentQueryResult {
645+ let result = IndentQueryResult {
462 indent_captures,
646 indent_captures,
463 extend_captures,
647 extend_captures,
464− }
648+ };
649+
650+ log::trace!("indent result = {:?}", result);
651+
652+ result
465}
653}
466
654
467/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
655/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
@@ -531,61 +719,27 @@ fn extend_nodes<'a>(
531 }
719 }
532}
720}
533
721
534−/// Use the syntax tree to determine the indentation for a given position.
722+/// Prepare an indent query by computing:
535−/// This can be used in 2 ways:
723+/// - The node from which to start the query (this is non-trivial due to `@extend` captures)
536−///
724+/// - The indent captures for all relevant nodes.
537−/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
538−/// - In this case, pos should be inside the first tree-sitter node on that line.
539−/// In most cases, this can just be the first non-whitespace on that line.
540−/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
541−/// after pos were moved to a new line.
542−///
543−/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
544−/// Each of these nodes produces some [Indentation] for:
545−///
546−/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
547−/// - The line after the node. This is defined by:
548−/// - The scope `tail`.
549−/// - The scope `all` if this node is not the first node on its line.
550−/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
551−/// The indents from different nodes for the same line are then combined.
552−/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
553−///
554−/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
555−/// ```ignore
556−/// some_function(|| {
557−/// // Both the function parameters as well as the contained block should be indented.
558−/// // Because they are on the same line, this only yields one indent level
559−/// });
560−/// ```
561−///
562−/// ```ignore
563−/// some_function(
564−/// param1,
565−/// || {
566−/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
567−/// },
568−/// );
569−/// ```
570#[allow(clippy::too_many_arguments)]
725#[allow(clippy::too_many_arguments)]
571−pub fn treesitter_indent_for_pos(
726+fn init_indent_query<'a, 'b>(
572 query: &Query,
727 query: &Query,
573− syntax: &Syntax,
728+ syntax: &'a Syntax,
574− indent_style: &IndentStyle,
729+ text: RopeSlice<'b>,
575 tab_width: usize,
730 tab_width: usize,
576 indent_width: usize,
731 indent_width: usize,
577− text: RopeSlice,
578 line: usize,
732 line: usize,
579− pos: usize,
733+ byte_pos: usize,
580− new_line: bool,
734+ new_line_byte_pos: Option<usize>,
581−) -> Option<String> {
735+) -> Option<(Node<'a>, HashMap<usize, Vec<IndentCapture<'b>>>)> {
582− let byte_pos = text.char_to_byte(pos);
583 // The innermost tree-sitter node which is considered for the indent
736 // The innermost tree-sitter node which is considered for the indent
584 // computation. It may change if some predeceding node is extended
737 // computation. It may change if some predeceding node is extended
585 let mut node = syntax
738 let mut node = syntax
586 .tree()
739 .tree()
587 .root_node()
740 .root_node()
588 .descendant_for_byte_range(byte_pos, byte_pos)?;
741 .descendant_for_byte_range(byte_pos, byte_pos)?;
742+
589 let (query_result, deepest_preceding) = {
743 let (query_result, deepest_preceding) = {
590 // The query range should intersect with all nodes directly preceding
744 // The query range should intersect with all nodes directly preceding
591 // the position of the indent query in case one of them is extended.
745 // the position of the indent query in case one of them is extended.
@@ -616,13 +770,12 @@ pub fn treesitter_indent_for_pos(
616 &mut cursor,
770 &mut cursor,
617 text,
771 text,
618 query_range,
772 query_range,
619− new_line.then_some((line, byte_pos)),
773+ new_line_byte_pos,
620 );
774 );
621 ts_parser.cursors.push(cursor);
775 ts_parser.cursors.push(cursor);
622 (query_result, deepest_preceding)
776 (query_result, deepest_preceding)
623 })
777 })
624 };
778 };
625− let indent_captures = query_result.indent_captures;
626 let extend_captures = query_result.extend_captures;
779 let extend_captures = query_result.extend_captures;
627
780
628 // Check for extend captures, potentially changing the node that the indent calculation starts with
781 // Check for extend captures, potentially changing the node that the indent calculation starts with
@@ -637,6 +790,68 @@ pub fn treesitter_indent_for_pos(
637 indent_width,
790 indent_width,
638 );
791 );
639 }
792 }
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+ )?;
640 let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos));
855 let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos));
641
856
642 let mut result = Indentation::default();
857 let mut result = Indentation::default();
@@ -644,12 +859,16 @@ pub fn treesitter_indent_for_pos(
644 // even if there are multiple "indent" nodes on the same line
859 // even if there are multiple "indent" nodes on the same line
645 let mut indent_for_line = Indentation::default();
860 let mut indent_for_line = Indentation::default();
646 let mut indent_for_line_below = Indentation::default();
861 let mut indent_for_line_below = Indentation::default();
862+
647 loop {
863 loop {
648 // This can safely be unwrapped because `first_in_line` contains
864 // This can safely be unwrapped because `first_in_line` contains
649 // one entry for each ancestor of the node (which is what we iterate over)
865 // one entry for each ancestor of the node (which is what we iterate over)
650 let is_first = *first_in_line.last().unwrap();
866 let is_first = *first_in_line.last().unwrap();
867+
651− // Apply all indent definitions for this node
868+ // 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.
652− if let Some(definitions) = indent_captures.get(&node.id()) {
871+ if let Some(definitions) = indent_captures.remove(&node.id()) {
653 for definition in definitions {
872 for definition in definitions {
654 match definition.scope {
873 match definition.scope {
655 IndentScope::All => {
874 IndentScope::All => {
@@ -667,28 +886,22 @@ pub fn treesitter_indent_for_pos(
667 }
886 }
668
887
669 if let Some(parent) = node.parent() {
888 if let Some(parent) = node.parent() {
670− let mut node_line = node.start_position().row;
889+ let node_line = get_node_start_line(node, new_line_byte_pos);
671− let mut parent_line = parent.start_position().row;
890+ let parent_line = get_node_start_line(parent, new_line_byte_pos);
672− if node_line == line && new_line {
891+
673− // Also consider the line that will be inserted
674− if node.start_byte() >= byte_pos {
675− node_line += 1;
676− }
677− if parent.start_byte() >= byte_pos {
678− parent_line += 1;
679− }
680− };
681 if node_line != parent_line {
892 if node_line != parent_line {
893+ // Don't add indent for the line below the line of the query
682 if node_line < line + (new_line as usize) {
894 if node_line < line + (new_line as usize) {
683− // Don't add indent for the line below the line of the query
684− result.add_line(&indent_for_line_below);
895+ result.add_line(indent_for_line_below);
685 }
896 }
897+
686 if node_line == parent_line + 1 {
898 if node_line == parent_line + 1 {
687 indent_for_line_below = indent_for_line;
899 indent_for_line_below = indent_for_line;
688 } else {
900 } else {
689− result.add_line(&indent_for_line);
901+ result.add_line(indent_for_line);
690 indent_for_line_below = Indentation::default();
902 indent_for_line_below = Indentation::default();
691 }
903 }
904+
692 indent_for_line = Indentation::default();
905 indent_for_line = Indentation::default();
693 }
906 }
694
907
@@ -700,13 +913,13 @@ pub fn treesitter_indent_for_pos(
700 if (node.start_position().row < line)
913 if (node.start_position().row < line)
701 || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
914 || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
702 {
915 {
703− result.add_line(&indent_for_line_below);
916+ result.add_line(indent_for_line_below);
704 }
917 }
705− result.add_line(&indent_for_line);
918+ result.add_line(indent_for_line);
706 break;
919 break;
707 }
920 }
708 }
921 }
709− Some(result.as_string(indent_style))
922+ Some(result)
710}
923}
711
924
712/// Returns the indentation for a new line.
925/// Returns the indentation for a new line.
@@ -715,6 +928,7 @@ pub fn treesitter_indent_for_pos(
715pub fn indent_for_newline(
928pub fn indent_for_newline(
716 language_config: Option<&LanguageConfiguration>,
929 language_config: Option<&LanguageConfiguration>,
717 syntax: Option<&Syntax>,
930 syntax: Option<&Syntax>,
931+ indent_heuristic: &IndentationHeuristic,
718 indent_style: &IndentStyle,
932 indent_style: &IndentStyle,
719 tab_width: usize,
933 tab_width: usize,
720 text: RopeSlice,
934 text: RopeSlice,
@@ -723,14 +937,18 @@ pub fn indent_for_newline(
723 current_line: usize,
937 current_line: usize,
724) -> String {
938) -> String {
725 let indent_width = indent_style.indent_width(tab_width);
939 let indent_width = indent_style.indent_width(tab_width);
940+ if let (
941+ IndentationHeuristic::TreeSitter | IndentationHeuristic::Hybrid,
942+ Some(query),
726− if let (Some(query), Some(syntax)) = (
943+ Some(syntax),
944+ ) = (
945+ indent_heuristic,
727 language_config.and_then(|config| config.indent_query()),
946 language_config.and_then(|config| config.indent_query()),
728 syntax,
947 syntax,
729 ) {
948 ) {
730 if let Some(indent) = treesitter_indent_for_pos(
949 if let Some(indent) = treesitter_indent_for_pos(
731 query,
950 query,
732 syntax,
951 syntax,
733− indent_style,
734 tab_width,
952 tab_width,
735 indent_width,
953 indent_width,
736 text,
954 text,
@@ -738,9 +956,57 @@ pub fn indent_for_newline(
738 line_before_end_pos,
956 line_before_end_pos,
739 true,
957 true,
740 ) {
958 ) {
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+ })() {
741− return indent;
998+ 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);
742 };
1007 };
743 }
1008 }
1009+ // Fallback in case we either don't have indent queries or they failed for some reason
744 let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
1010 let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
745 indent_style.as_str().repeat(indent_level)
1011 indent_style.as_str().repeat(indent_level)
746}
1012}
@@ -796,4 +1062,211 @@ mod test {
796 3
1062 3
797 );
1063 );
798 }
1064 }
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+ }
799}
1272}
helix-core/src/lib.rs+8−49
@@ -7,6 +7,7 @@ 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;
10pub mod graphemes;
11pub mod graphemes;
11pub mod history;
12pub mod history;
12pub mod increment;
13pub mod increment;
@@ -18,7 +19,6 @@ pub mod movement;
18pub mod object;
19pub mod object;
19pub mod path;
20pub mod path;
20mod position;
21mod position;
21−pub mod register;
22pub mod search;
22pub mod search;
23pub mod selection;
23pub mod selection;
24pub mod shellwords;
24pub mod shellwords;
@@ -36,55 +36,14 @@ pub mod unicode {
36 pub use unicode_width as width;
36 pub use unicode_width as width;
37}
37}
38
38
39+pub use helix_loader::find_workspace;
40+
39pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
41pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
40 line.chars().position(|ch| !ch.is_whitespace())
42 line.chars().position(|ch| !ch.is_whitespace())
41}
43}
44+mod rope_reader;
42
45
43−/// Find project root.
46+pub use rope_reader::RopeReader;
44−///
45−/// Order of detection:
46−/// * Top-most folder containing a root marker in current git repository
47−/// * Git repository root if no marker detected
48−/// * Top-most folder containing a root marker if not git repository detected
49−/// * Current working directory as fallback
50−pub fn find_root(root: Option<&str>, root_markers: &[String]) -> std::path::PathBuf {
51− let current_dir = std::env::current_dir().expect("unable to determine current directory");
52−
53− let root = match root {
54− Some(root) => {
55− let root = std::path::Path::new(root);
56− if root.is_absolute() {
57− root.to_path_buf()
58− } else {
59− current_dir.join(root)
60− }
61− }
62− None => current_dir.clone(),
63− };
64−
65− let mut top_marker = None;
66− for ancestor in root.ancestors() {
67− if root_markers
68− .iter()
69− .any(|marker| ancestor.join(marker).exists())
70− {
71− top_marker = Some(ancestor);
72− }
73−
74− if ancestor.join(".git").exists() {
75− // Top marker is repo root if not root marker was detected yet
76− if top_marker.is_none() {
77− top_marker = Some(ancestor);
78− }
79− // Don't go higher than repo if we're in one
80− break;
81− }
82− }
83−
84− // Return the found top marker or the current_dir as fallback
85− top_marker.map_or(current_dir, |a| a.to_path_buf())
86−}
87−
88pub use ropey::{self, str_utils, Rope, RopeBuilder, RopeSlice};
47pub use ropey::{self, str_utils, Rope, RopeBuilder, RopeSlice};
89
48
90// pub use tendril::StrTendril as Tendril;
49// pub use tendril::StrTendril as Tendril;
@@ -98,7 +57,7 @@ pub use {regex, tree_sitter};
98pub use graphemes::RopeGraphemes;
57pub use graphemes::RopeGraphemes;
99pub use position::{
58pub use position::{
100 char_idx_at_visual_offset, coords_at_pos, pos_at_coords, visual_offset_from_anchor,
59 char_idx_at_visual_offset, coords_at_pos, pos_at_coords, visual_offset_from_anchor,
101− visual_offset_from_block, Position,
60+ visual_offset_from_block, Position, VisualOffsetError,
102};
61};
103#[allow(deprecated)]
62#[allow(deprecated)]
104pub use position::{pos_at_visual_coords, visual_coords_at_pos};
63pub use position::{pos_at_visual_coords, visual_coords_at_pos};
@@ -109,5 +68,5 @@ pub use syntax::Syntax;
109
68
110pub use diagnostic::Diagnostic;
69pub use diagnostic::Diagnostic;
111
70
112−pub use line_ending::{LineEnding, DEFAULT_LINE_ENDING};
71+pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
113−pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction};
72+pub use transaction::{Assoc, Change, ChangeSet, Deletion, 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 DEFAULT_LINE_ENDING: LineEnding = LineEnding::Crlf;
4+pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::Crlf;
5#[cfg(not(target_os = "windows"))]
5#[cfg(not(target_os = "windows"))]
6−pub const DEFAULT_LINE_ENDING: LineEnding = LineEnding::LF;
6+pub const NATIVE_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+232−29
@@ -1,7 +1,15 @@
1+use std::iter;
2+
3+use ropey::RopeSlice;
1use tree_sitter::Node;
4use tree_sitter::Node;
2
5
3−use crate::{Rope, Syntax};
6+use crate::movement::Direction::{self, Backward, Forward};
7+use crate::Syntax;
8+
9+const MAX_PLAINTEXT_SCAN: usize = 10000;
10+const MATCH_LIMIT: usize = 16;
4
11
12+// Limit matching pairs to only ( ) { } [ ] < > ' ' " "
5const PAIRS: &[(char, char)] = &[
13const PAIRS: &[(char, char)] = &[
6 ('(', ')'),
14 ('(', ')'),
7 ('{', '}'),
15 ('{', '}'),
@@ -11,17 +19,17 @@ const PAIRS: &[(char, char)] = &[
11 ('\"', '\"'),
19 ('\"', '\"'),
12];
20];
13
21
14−// limit matching pairs to only ( ) { } [ ] < > ' ' " "
15−
16−// Returns the position of the matching bracket under cursor.
22+/// Returns the position of the matching bracket under cursor.
17−//
23+///
18−// If the cursor is one the opening bracket, the position of
24+/// If the cursor is on the opening bracket, the position of
19−// the closing bracket is returned. If the cursor in the closing
25+/// the closing bracket is returned. If the cursor on the closing
20−// bracket, the position of the opening bracket is returned.
26+/// bracket, the position of the opening bracket is returned.
21−//
27+///
22−// If the cursor is not on a bracket, `None` is returned.
28+/// If the cursor is not on a bracket, `None` is returned.
29+///
30+/// If no matching bracket is found, `None` is returned.
23#[must_use]
31#[must_use]
24−pub fn find_matching_bracket(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
32+pub fn find_matching_bracket(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
25 if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) {
33 if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) {
26 return None;
34 return None;
27 }
35 }
@@ -39,46 +47,159 @@ pub fn find_matching_bracket(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<
39//
47//
40// If no surrounding scope is found, the function returns `None`.
48// If no surrounding scope is found, the function returns `None`.
41#[must_use]
49#[must_use]
42−pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
50+pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
43 find_pair(syntax, doc, pos, true)
51 find_pair(syntax, doc, pos, true)
44}
52}
45
53
54+fn find_pair(
55+ syntax: &Syntax,
56+ doc: RopeSlice,
57+ pos_: usize,
46−fn find_pair(syntax: &Syntax, doc: &Rope, pos: usize, traverse_parents: bool) -> Option<usize> {
58+ traverse_parents: bool,
59+) -> Option<usize> {
47 let tree = syntax.tree();
60 let tree = syntax.tree();
48− let pos = doc.char_to_byte(pos);
61+ let pos = doc.char_to_byte(pos_);
49
62
50− let mut node = tree.root_node().named_descendant_for_byte_range(pos, pos)?;
63+ let mut node = tree.root_node().descendant_for_byte_range(pos, pos)?;
51
64
52 loop {
65 loop {
66+ if node.is_named() {
53− let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
67+ let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
54− let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
68+ 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+ }
55
74
56− if is_valid_pair(doc, start_char, end_char) {
75+ // We return the end char if the cursor is either on the start char
57− if end_byte == pos {
76+ // or at some arbitrary position between start and end char.
77+ if traverse_parents || start_byte == pos {
58− return Some(start_char);
78+ return Some(end_char);
79+ }
59 }
80 }
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);
63 }
81 }
82+ // this node itselt wasn't a pair but maybe its siblings are
64
83
65− if traverse_parents {
84+ // check if we are *on* the pair (special cased so we don't look
66− node = node.parent()?;
85+ // at the current node twice and to jump to the start on that case)
67− } else {
86+ if let Some(open) = as_close_pair(doc, &node) {
87+ if let Some(pair_start) = find_pair_end(doc, node.prev_sibling(), open, Backward) {
68− return None;
88+ return Some(pair_start);
89+ }
69 }
90 }
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;
70 }
124 }
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
71}
188}
72
189
73fn is_valid_bracket(c: char) -> bool {
190fn is_valid_bracket(c: char) -> bool {
74 PAIRS.iter().any(|(l, r)| *l == c || *r == c)
191 PAIRS.iter().any(|(l, r)| *l == c || *r == c)
75}
192}
76
193
194+fn is_forward_bracket(c: char) -> bool {
195+ PAIRS.iter().any(|(l, _)| *l == c)
196+}
197+
77−fn is_valid_pair(doc: &Rope, start_char: usize, end_char: usize) -> bool {
198+fn is_valid_pair(doc: RopeSlice, start_char: usize, end_char: usize) -> bool {
78 PAIRS.contains(&(doc.char(start_char), doc.char(end_char)))
199 PAIRS.contains(&(doc.char(start_char), doc.char(end_char)))
79}
200}
80
201
81−fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> {
202+fn surrounding_bytes(doc: RopeSlice, node: &Node) -> Option<(usize, usize)> {
82 let len = doc.len_bytes();
203 let len = doc.len_bytes();
83
204
84 let start_byte = node.start_byte();
205 let start_byte = node.start_byte();
@@ -90,3 +211,85 @@ fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> {
90
211
91 Some((start_byte, end_byte))
212 Some((start_byte, end_byte))
92}
213}
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+195−15
@@ -1,4 +1,4 @@
1−use std::iter;
1+use std::{cmp::Reverse, 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,
19+ visual_offset_from_block, Range, RopeSlice, Selection, Syntax,
20};
20};
21
21
22#[derive(Debug, Copy, Clone, PartialEq, Eq)]
22#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -62,7 +62,7 @@ pub fn move_vertically_visual(
62 annotations: &mut TextAnnotations,
62 annotations: &mut TextAnnotations,
63) -> Range {
63) -> Range {
64 if !text_fmt.soft_wrap {
64 if !text_fmt.soft_wrap {
65− move_vertically(slice, range, dir, count, behaviour, text_fmt, annotations);
65+ return move_vertically(slice, range, dir, count, behaviour, text_fmt, annotations);
66 }
66 }
67 annotations.clear_line_annotations();
67 annotations.clear_line_annotations();
68 let pos = range.cursor(slice);
68 let pos = range.cursor(slice);
@@ -177,6 +177,10 @@ 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+
180pub fn move_next_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
184pub fn move_next_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
181 word_move(slice, range, count, WordMotionTarget::NextLongWordStart)
185 word_move(slice, range, count, WordMotionTarget::NextLongWordStart)
182}
186}
@@ -189,8 +193,8 @@ pub fn move_prev_long_word_start(slice: RopeSlice, range: Range, count: usize) -
189 word_move(slice, range, count, WordMotionTarget::PrevLongWordStart)
193 word_move(slice, range, count, WordMotionTarget::PrevLongWordStart)
190}
194}
191
195
192−pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
196+pub fn move_prev_long_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
193− word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
197+ word_move(slice, range, count, WordMotionTarget::PrevLongWordEnd)
194}
198}
195
199
196fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
200fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
@@ -199,6 +203,7 @@ fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTar
199 WordMotionTarget::PrevWordStart
203 WordMotionTarget::PrevWordStart
200 | WordMotionTarget::PrevLongWordStart
204 | WordMotionTarget::PrevLongWordStart
201 | WordMotionTarget::PrevWordEnd
205 | WordMotionTarget::PrevWordEnd
206+ | WordMotionTarget::PrevLongWordEnd
202 );
207 );
203
208
204 // Special-case early-out.
209 // Special-case early-out.
@@ -377,6 +382,7 @@ pub enum WordMotionTarget {
377 NextLongWordStart,
382 NextLongWordStart,
378 NextLongWordEnd,
383 NextLongWordEnd,
379 PrevLongWordStart,
384 PrevLongWordStart,
385+ PrevLongWordEnd,
380}
386}
381
387
382pub trait CharHelpers {
388pub trait CharHelpers {
@@ -393,6 +399,7 @@ impl CharHelpers for Chars<'_> {
393 WordMotionTarget::PrevWordStart
399 WordMotionTarget::PrevWordStart
394 | WordMotionTarget::PrevLongWordStart
400 | WordMotionTarget::PrevLongWordStart
395 | WordMotionTarget::PrevWordEnd
401 | WordMotionTarget::PrevWordEnd
402+ | WordMotionTarget::PrevLongWordEnd
396 );
403 );
397
404
398 // Reverse the iterator if needed for the motion direction.
405 // Reverse the iterator if needed for the motion direction.
@@ -479,7 +486,7 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
479 is_word_boundary(prev_ch, next_ch)
486 is_word_boundary(prev_ch, next_ch)
480 && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
487 && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
481 }
488 }
482− WordMotionTarget::NextLongWordStart => {
489+ WordMotionTarget::NextLongWordStart | WordMotionTarget::PrevLongWordEnd => {
483 is_long_word_boundary(prev_ch, next_ch)
490 is_long_word_boundary(prev_ch, next_ch)
484 && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
491 && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
485 }
492 }
@@ -520,10 +527,10 @@ pub fn goto_treesitter_object(
520 let node = match dir {
527 let node = match dir {
521 Direction::Forward => nodes
528 Direction::Forward => nodes
522 .filter(|n| n.start_byte() > byte_pos)
529 .filter(|n| n.start_byte() > byte_pos)
523− .min_by_key(|n| n.start_byte())?,
530+ .min_by_key(|n| (n.start_byte(), Reverse(n.end_byte())))?,
524 Direction::Backward => nodes
531 Direction::Backward => nodes
525 .filter(|n| n.end_byte() < byte_pos)
532 .filter(|n| n.end_byte() < byte_pos)
526− .max_by_key(|n| n.end_byte())?,
533+ .max_by_key(|n| (n.end_byte(), Reverse(n.start_byte())))?,
527 };
534 };
528
535
529 let len = slice.len_bytes();
536 let len = slice.len_bytes();
@@ -549,6 +556,85 @@ pub fn goto_treesitter_object(
549 last_range
556 last_range
550}
557}
551
558
559+fn find_parent_start(mut node: Node) -> Option<Node> {
560+ let start = node.start_byte();
561+
562+ while node.start_byte() >= start || !node.is_named() {
563+ node = node.parent()?;
564+ }
565+
566+ Some(node)
567+}
568+
569+pub fn move_parent_node_end(
570+ syntax: &Syntax,
571+ text: RopeSlice,
572+ selection: Selection,
573+ dir: Direction,
574+ movement: Movement,
575+) -> Selection {
576+ let tree = syntax.tree();
577+
578+ selection.transform(|range| {
579+ let start_from = text.char_to_byte(range.from());
580+ let start_to = text.char_to_byte(range.to());
581+
582+ let mut node = match tree
583+ .root_node()
584+ .named_descendant_for_byte_range(start_from, start_to)
585+ {
586+ Some(node) => node,
587+ None => {
588+ log::debug!(
589+ "no descendant found for byte range: {} - {}",
590+ start_from,
591+ start_to
592+ );
593+ return range;
594+ }
595+ };
596+
597+ let mut end_head = match dir {
598+ // moving forward, we always want to move one past the end of the
599+ // current node, so use the end byte of the current node, which is an exclusive
600+ // end of the range
601+ Direction::Forward => text.byte_to_char(node.end_byte()),
602+
603+ // moving backward, we want the cursor to land on the start char of
604+ // the current node, or if it is already at the start of a node, to traverse up to
605+ // the parent
606+ Direction::Backward => {
607+ let end_head = text.byte_to_char(node.start_byte());
608+
609+ // if we're already on the beginning, look up to the parent
610+ if end_head == range.cursor(text) {
611+ node = find_parent_start(node).unwrap_or(node);
612+ text.byte_to_char(node.start_byte())
613+ } else {
614+ end_head
615+ }
616+ }
617+ };
618+
619+ if movement == Movement::Move {
620+ // preserve direction of original range
621+ if range.direction() == Direction::Forward {
622+ Range::new(end_head, end_head + 1)
623+ } else {
624+ Range::new(end_head + 1, end_head)
625+ }
626+ } else {
627+ // if we end up with a forward range, then adjust it to be one past
628+ // where we want
629+ if end_head >= range.anchor {
630+ end_head += 1;
631+ }
632+
633+ Range::new(range.anchor, end_head)
634+ }
635+ })
636+}
637+
552#[cfg(test)]
638#[cfg(test)]
553mod test {
639mod test {
554 use ropey::Rope;
640 use ropey::Rope;
@@ -1445,6 +1531,100 @@ mod test {
1445 }
1531 }
1446 }
1532 }
1447
1533
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+
1448 #[test]
1628 #[test]
1449 fn test_behaviour_when_moving_to_prev_paragraph_single() {
1629 fn test_behaviour_when_moving_to_prev_paragraph_single() {
1450 let tests = [
1630 let tests = [
@@ -1474,7 +1654,7 @@ mod test {
1474 let text = Rope::from(s.as_str());
1654 let text = Rope::from(s.as_str());
1475 let selection =
1655 let selection =
1476 selection.transform(|r| move_prev_paragraph(text.slice(..), r, 1, Movement::Move));
1656 selection.transform(|r| move_prev_paragraph(text.slice(..), r, 1, Movement::Move));
1477− let actual = crate::test::plain(&s, selection);
1657+ let actual = crate::test::plain(s.as_ref(), &selection);
1478 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1658 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1479 }
1659 }
1480 }
1660 }
@@ -1497,7 +1677,7 @@ mod test {
1497 let text = Rope::from(s.as_str());
1677 let text = Rope::from(s.as_str());
1498 let selection =
1678 let selection =
1499 selection.transform(|r| move_prev_paragraph(text.slice(..), r, 2, Movement::Move));
1679 selection.transform(|r| move_prev_paragraph(text.slice(..), r, 2, Movement::Move));
1500− let actual = crate::test::plain(&s, selection);
1680+ let actual = crate::test::plain(s.as_ref(), &selection);
1501 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1681 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1502 }
1682 }
1503 }
1683 }
@@ -1520,7 +1700,7 @@ mod test {
1520 let text = Rope::from(s.as_str());
1700 let text = Rope::from(s.as_str());
1521 let selection = selection
1701 let selection = selection
1522 .transform(|r| move_prev_paragraph(text.slice(..), r, 1, Movement::Extend));
1702 .transform(|r| move_prev_paragraph(text.slice(..), r, 1, Movement::Extend));
1523− let actual = crate::test::plain(&s, selection);
1703+ let actual = crate::test::plain(s.as_ref(), &selection);
1524 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1704 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1525 }
1705 }
1526 }
1706 }
@@ -1540,7 +1720,7 @@ mod test {
1540 "a\nb\n\n#[goto\nthird\n\n|]#paragraph",
1720 "a\nb\n\n#[goto\nthird\n\n|]#paragraph",
1541 ),
1721 ),
1542 (
1722 (
1543− "a\nb#[\n|]#\ngoto\nsecond\n\nparagraph",
1723+ "a\nb#[\n|]#\n\ngoto\nsecond\n\nparagraph",
1544 "a\nb#[\n\n|]#goto\nsecond\n\nparagraph",
1724 "a\nb#[\n\n|]#goto\nsecond\n\nparagraph",
1545 ),
1725 ),
1546 (
1726 (
@@ -1562,7 +1742,7 @@ mod test {
1562 let text = Rope::from(s.as_str());
1742 let text = Rope::from(s.as_str());
1563 let selection =
1743 let selection =
1564 selection.transform(|r| move_next_paragraph(text.slice(..), r, 1, Movement::Move));
1744 selection.transform(|r| move_next_paragraph(text.slice(..), r, 1, Movement::Move));
1565− let actual = crate::test::plain(&s, selection);
1745+ let actual = crate::test::plain(s.as_ref(), &selection);
1566 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1746 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1567 }
1747 }
1568 }
1748 }
@@ -1585,7 +1765,7 @@ mod test {
1585 let text = Rope::from(s.as_str());
1765 let text = Rope::from(s.as_str());
1586 let selection =
1766 let selection =
1587 selection.transform(|r| move_next_paragraph(text.slice(..), r, 2, Movement::Move));
1767 selection.transform(|r| move_next_paragraph(text.slice(..), r, 2, Movement::Move));
1588− let actual = crate::test::plain(&s, selection);
1768+ let actual = crate::test::plain(s.as_ref(), &selection);
1589 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1769 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1590 }
1770 }
1591 }
1771 }
@@ -1608,7 +1788,7 @@ mod test {
1608 let text = Rope::from(s.as_str());
1788 let text = Rope::from(s.as_str());
1609 let selection = selection
1789 let selection = selection
1610 .transform(|r| move_next_paragraph(text.slice(..), r, 1, Movement::Extend));
1790 .transform(|r| move_next_paragraph(text.slice(..), r, 1, Movement::Extend));
1611− let actual = crate::test::plain(&s, selection);
1791+ let actual = crate::test::plain(s.as_ref(), &selection);
1612 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1792 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
1613 }
1793 }
1614 }
1794 }
helix-core/src/path.rs+27−8
@@ -40,6 +40,21 @@ pub fn expand_tilde(path: &Path) -> PathBuf {
40/// needs to improve on.
40/// needs to improve on.
41/// Copied from cargo: <https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81>
41/// Copied from cargo: <https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81>
42pub fn get_normalized_path(path: &Path) -> PathBuf {
42pub fn get_normalized_path(path: &Path) -> PathBuf {
43+ // normalization strategy is to canonicalize first ancestor path that exists (i.e., canonicalize as much as possible),
44+ // then run handrolled normalization on the non-existent remainder
45+ let (base, path) = path
46+ .ancestors()
47+ .find_map(|base| {
48+ let canonicalized_base = dunce::canonicalize(base).ok()?;
49+ let remainder = path.strip_prefix(base).ok()?.into();
50+ Some((canonicalized_base, remainder))
51+ })
52+ .unwrap_or_else(|| (PathBuf::new(), PathBuf::from(path)));
53+
54+ if path.as_os_str().is_empty() {
55+ return base;
56+ }
57+
43 let mut components = path.components().peekable();
58 let mut components = path.components().peekable();
44 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
59 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
45 components.next();
60 components.next();
@@ -63,32 +78,36 @@ pub fn get_normalized_path(path: &Path) -> PathBuf {
63 }
78 }
64 }
79 }
65 }
80 }
66− ret
81+ base.join(ret)
67}
82}
68
83
69/// Returns the canonical, absolute form of a path with all intermediate components normalized.
84/// Returns the canonical, absolute form of a path with all intermediate components normalized.
70///
85///
71/// 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
72/// here if the path exists, just normalize it's components.
87/// here if the path exists, just normalize it's components.
73−pub fn get_canonicalized_path(path: &Path) -> std::io::Result<PathBuf> {
88+pub fn get_canonicalized_path(path: &Path) -> PathBuf {
74 let path = expand_tilde(path);
89 let path = expand_tilde(path);
75 let path = if path.is_relative() {
90 let path = if path.is_relative() {
76− std::env::current_dir().map(|current_dir| current_dir.join(path))?
91+ helix_loader::current_working_dir().join(path)
77 } else {
92 } else {
78 path
93 path
79 };
94 };
80
95
81− Ok(get_normalized_path(path.as_path()))
96+ get_normalized_path(path.as_path())
82}
97}
83
98
84pub fn get_relative_path(path: &Path) -> PathBuf {
99pub fn get_relative_path(path: &Path) -> PathBuf {
100+ let path = PathBuf::from(path);
85 let path = if path.is_absolute() {
101 let path = if path.is_absolute() {
86− let cwdir = std::env::current_dir().expect("couldn't determine current directory");
102+ let cwdir = get_normalized_path(&helix_loader::current_working_dir());
103+ get_normalized_path(&path)
87− path.strip_prefix(cwdir).unwrap_or(path)
104+ .strip_prefix(cwdir)
105+ .map(PathBuf::from)
106+ .unwrap_or(path)
88 } else {
107 } else {
89 path
108 path
90 };
109 };
91− fold_home_dir(path)
110+ fold_home_dir(&path)
92}
111}
93
112
94/// Returns a truncated filepath where the basepart of the path is reduced to the first
113/// Returns a truncated filepath where the basepart of the path is reduced to the first
@@ -121,7 +140,7 @@ pub fn get_relative_path(path: &Path) -> PathBuf {
121/// ```
140/// ```
122///
141///
123pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
142pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
124− let cwd = std::env::current_dir().unwrap_or_default();
143+ let cwd = helix_loader::current_working_dir();
125 let path = path
144 let path = path
126 .as_ref()
145 .as_ref()
127 .strip_prefix(cwd)
146 .strip_prefix(cwd)
helix-core/src/position.rs+41−16
@@ -109,7 +109,7 @@ pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Po
109/// softwrapping positions are estimated with an O(1) algorithm
109/// softwrapping positions are estimated with an O(1) algorithm
110/// to ensure consistent performance for large lines (currently unimplemented)
110/// to ensure consistent performance for large lines (currently unimplemented)
111///
111///
112−/// Usualy you want to use `visual_offset_from_anchor` instead but this function
112+/// Usually you want to use `visual_offset_from_anchor` instead but this function
113/// can be useful (and faster) if
113/// can be useful (and faster) if
114/// * You already know the visual position of the block
114/// * You already know the visual position of the block
115/// * You only care about the horizontal offset (column) and not the vertical offset (row)
115/// * You only care about the horizontal offset (column) and not the vertical offset (row)
@@ -137,6 +137,12 @@ pub fn visual_offset_from_block(
137 (last_pos, block_start)
137 (last_pos, block_start)
138}
138}
139
139
140+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
141+pub enum VisualOffsetError {
142+ PosBeforeAnchorRow,
143+ PosAfterMaxRow,
144+}
145+
140/// Returns the visual offset from the start of the visual line
146/// Returns the visual offset from the start of the visual line
141/// that contains anchor.
147/// that contains anchor.
142pub fn visual_offset_from_anchor(
148pub fn visual_offset_from_anchor(
@@ -146,28 +152,46 @@ pub fn visual_offset_from_anchor(
146 text_fmt: &TextFormat,
152 text_fmt: &TextFormat,
147 annotations: &TextAnnotations,
153 annotations: &TextAnnotations,
148 max_rows: usize,
154 max_rows: usize,
149−) -> Option<(Position, usize)> {
155+) -> Result<(Position, usize), VisualOffsetError> {
150 let (formatter, block_start) =
156 let (formatter, block_start) =
151 DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
157 DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
152 let mut char_pos = block_start;
158 let mut char_pos = block_start;
153 let mut anchor_line = None;
159 let mut anchor_line = None;
160+ let mut found_pos = None;
154 let mut last_pos = Position::default();
161 let mut last_pos = Position::default();
155
162
163+ if pos < block_start {
164+ return Err(VisualOffsetError::PosBeforeAnchorRow);
165+ }
166+
156 for (grapheme, vpos) in formatter {
167 for (grapheme, vpos) in formatter {
157 last_pos = vpos;
168 last_pos = vpos;
158 char_pos += grapheme.doc_chars();
169 char_pos += grapheme.doc_chars();
159
170
160− if char_pos > anchor && anchor_line.is_none() {
161− anchor_line = Some(last_pos.row);
162− }
163 if char_pos > pos {
171 if char_pos > pos {
172+ if let Some(anchor_line) = anchor_line {
164− last_pos.row -= anchor_line.unwrap();
173+ last_pos.row -= anchor_line;
174+ return Ok((last_pos, block_start));
175+ } else {
176+ found_pos = Some(last_pos);
177+ }
178+ }
179+ if char_pos > anchor && anchor_line.is_none() {
180+ if let Some(mut found_pos) = found_pos {
181+ return if found_pos.row == last_pos.row {
182+ found_pos.row = 0;
165− return Some((last_pos, block_start));
183+ Ok((found_pos, block_start))
184+ } else {
185+ Err(VisualOffsetError::PosBeforeAnchorRow)
186+ };
187+ } else {
188+ anchor_line = Some(last_pos.row);
189+ }
166 }
190 }
167
191
168 if let Some(anchor_line) = anchor_line {
192 if let Some(anchor_line) = anchor_line {
169 if vpos.row >= anchor_line + max_rows {
193 if vpos.row >= anchor_line + max_rows {
170− return None;
194+ return Err(VisualOffsetError::PosAfterMaxRow);
171 }
195 }
172 }
196 }
173 }
197 }
@@ -175,7 +199,7 @@ pub fn visual_offset_from_anchor(
175 let anchor_line = anchor_line.unwrap_or(last_pos.row);
199 let anchor_line = anchor_line.unwrap_or(last_pos.row);
176 last_pos.row -= anchor_line;
200 last_pos.row -= anchor_line;
177
201
178− Some((last_pos, block_start))
202+ Ok((last_pos, block_start))
179}
203}
180
204
181/// Convert (line, column) coordinates to a character index.
205/// Convert (line, column) coordinates to a character index.
@@ -267,7 +291,7 @@ pub fn pos_at_visual_coords(text: RopeSlice, coords: Position, tab_width: usize)
267///
291///
268/// If no (text) grapheme starts at exactly at the specified column the
292/// If no (text) grapheme starts at exactly at the specified column the
269/// start of the grapheme to the left is returned. If there is no grapheme
293/// start of the grapheme to the left is returned. If there is no grapheme
270−/// to the left (for example if the line starts with virtual text) then the positiong
294+/// to the left (for example if the line starts with virtual text) then the positioning
271/// of the next grapheme to the right is returned.
295/// of the next grapheme to the right is returned.
272///
296///
273/// If the `line` coordinate is beyond the end of the file, the EOF
297/// If the `line` coordinate is beyond the end of the file, the EOF
@@ -285,18 +309,19 @@ pub fn pos_at_visual_coords(text: RopeSlice, coords: Position, tab_width: usize)
285/// on the visual line is returned if the visual line contains any text:
309/// on the visual line is returned if the visual line contains any text:
286/// If the visual line at the specified offset is a virtual line generated by a `LineAnnotation`
310/// If the visual line at the specified offset is a virtual line generated by a `LineAnnotation`
287/// the previous char_index is returned, together with the remaining vertical offset (`virtual_lines`)
311/// the previous char_index is returned, together with the remaining vertical offset (`virtual_lines`)
288−pub fn char_idx_at_visual_offset<'a>(
312+pub fn char_idx_at_visual_offset(
289− text: RopeSlice<'a>,
313+ text: RopeSlice,
290 mut anchor: usize,
314 mut anchor: usize,
291 mut row_offset: isize,
315 mut row_offset: isize,
292 column: usize,
316 column: usize,
293 text_fmt: &TextFormat,
317 text_fmt: &TextFormat,
294 annotations: &TextAnnotations,
318 annotations: &TextAnnotations,
295) -> (usize, usize) {
319) -> (usize, usize) {
320+ let mut pos = anchor;
296 // convert row relative to visual line containing anchor to row relative to a block containing anchor (anchor may change)
321 // convert row relative to visual line containing anchor to row relative to a block containing anchor (anchor may change)
297 loop {
322 loop {
298 let (visual_pos_in_block, block_char_offset) =
323 let (visual_pos_in_block, block_char_offset) =
299− visual_offset_from_block(text, anchor, anchor, text_fmt, annotations);
324+ visual_offset_from_block(text, anchor, pos, text_fmt, annotations);
300 row_offset += visual_pos_in_block.row as isize;
325 row_offset += visual_pos_in_block.row as isize;
301 anchor = block_char_offset;
326 anchor = block_char_offset;
302 if row_offset >= 0 {
327 if row_offset >= 0 {
@@ -308,10 +333,10 @@ pub fn char_idx_at_visual_offset<'a>(
308 break;
333 break;
309 }
334 }
310 // the row_offset is negative so we need to look at the previous block
335 // the row_offset is negative so we need to look at the previous block
311− // set the anchor to the last char before the current block
336+ // set the anchor to the last char before the current block so that we can compute
312− // this char index is also always a line earlier so increase the row_offset by 1
337+ // the distance of this block from the start of the previous block
338+ pos = anchor;
313 anchor -= 1;
339 anchor -= 1;
314− row_offset += 1;
315 }
340 }
316
341
317 char_idx_at_visual_block_offset(
342 char_idx_at_visual_block_offset(
helix-core/src/register.rsdeleted−81
@@ -1,81 +0,0 @@
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−}
helix-core/src/rope_reader.rsnew file+37
@@ -0,0 +1,37 @@
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+102−49
@@ -38,7 +38,7 @@ use std::borrow::Cow;
38/// Ranges are considered to be inclusive on the left and
38/// Ranges are considered to be inclusive on the left and
39/// exclusive on the right, regardless of anchor-head ordering.
39/// exclusive on the right, regardless of anchor-head ordering.
40/// This means, for example, that non-zero-width ranges that
40/// This means, for example, that non-zero-width ranges that
41−/// are directly adjecent, sharing an edge, do not overlap.
41+/// are directly adjacent, sharing an edge, do not overlap.
42/// However, a zero-width range will overlap with the shared
42/// However, a zero-width range will overlap with the shared
43/// left-edge of another range.
43/// left-edge of another range.
44///
44///
@@ -161,34 +161,35 @@ 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 the same position
164+ /// Map a range through a set of changes. Returns a new range representing
165− /// after the changes are applied.
165+ /// the same position after the changes are applied. Note that this
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
166− pub fn map(self, changes: &ChangeSet) -> Self {
170+ pub fn map(mut self, changes: &ChangeSet) -> Self {
167 use std::cmp::Ordering;
171 use std::cmp::Ordering;
168− let (anchor, head) = match self.anchor.cmp(&self.head) {
172+ if changes.is_empty() {
169− Ordering::Equal => (
173+ return self;
170− changes.map_pos(self.anchor, Assoc::After),
171− changes.map_pos(self.head, Assoc::After),
172− ),
173− Ordering::Less => (
174− changes.map_pos(self.anchor, Assoc::After),
175− changes.map_pos(self.head, Assoc::Before),
176− ),
177− Ordering::Greater => (
178− changes.map_pos(self.anchor, Assoc::Before),
179− changes.map_pos(self.head, Assoc::After),
180− ),
181− };
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,
190− old_visual_position: None,
191 }
174 }
175+
176+ let positions_to_map = match self.anchor.cmp(&self.head) {
177+ Ordering::Equal => [
178+ (&mut self.anchor, Assoc::After),
179+ (&mut self.head, Assoc::After),
180+ ],
181+ Ordering::Less => [
182+ (&mut self.anchor, Assoc::After),
183+ (&mut self.head, Assoc::Before),
184+ ],
185+ Ordering::Greater => [
186+ (&mut self.head, Assoc::After),
187+ (&mut self.anchor, Assoc::Before),
188+ ],
189+ };
190+ changes.update_positions(positions_to_map.into_iter());
191+ self.old_visual_position = None;
192+ self
192 }
193 }
193
194
194 /// Extend the range to cover at least `from` `to`.
195 /// Extend the range to cover at least `from` `to`.
@@ -451,17 +452,36 @@ impl Selection {
451 /// Map selections over a set of changes. Useful for adjusting the selection position after
452 /// Map selections over a set of changes. Useful for adjusting the selection position after
452 /// applying changes to a document.
453 /// applying changes to a document.
453 pub fn map(self, changes: &ChangeSet) -> Self {
454 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 {
454 if changes.is_empty() {
461 if changes.is_empty() {
455 return self;
462 return self;
456 }
463 }
457
464
458− Self::new(
465+ let positions_to_map = self.ranges.iter_mut().flat_map(|range| {
459− self.ranges
466+ use std::cmp::Ordering;
460− .into_iter()
467+ range.old_visual_position = None;
461− .map(|range| range.map(changes))
468+ match range.anchor.cmp(&range.head) {
462− .collect(),
469+ Ordering::Equal => [
463− self.primary_index,
470+ (&mut range.anchor, Assoc::After),
464− )
471+ (&mut range.head, Assoc::After),
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
465 }
485 }
466
486
467 pub fn ranges(&self) -> &[Range] {
487 pub fn ranges(&self) -> &[Range] {
@@ -497,6 +517,9 @@ impl Selection {
497
517
498 /// Normalizes a `Selection`.
518 /// Normalizes a `Selection`.
499 fn normalize(mut self) -> Self {
519 fn normalize(mut self) -> Self {
520+ if self.len() < 2 {
521+ return self;
522+ }
500 let mut primary = self.ranges[self.primary_index];
523 let mut primary = self.ranges[self.primary_index];
501 self.ranges.sort_unstable_by_key(Range::from);
524 self.ranges.sort_unstable_by_key(Range::from);
502
525
@@ -522,7 +545,14 @@ impl Selection {
522 self
545 self
523 }
546 }
524
547
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+
525− // Merges all ranges that are consecutive
555+ /// Merges all ranges that are consecutive.
526 pub fn merge_consecutive_ranges(mut self) -> Self {
556 pub fn merge_consecutive_ranges(mut self) -> Self {
527 let mut primary = self.ranges[self.primary_index];
557 let mut primary = self.ranges[self.primary_index];
528
558
@@ -554,17 +584,12 @@ impl Selection {
554 assert!(!ranges.is_empty());
584 assert!(!ranges.is_empty());
555 debug_assert!(primary_index < ranges.len());
585 debug_assert!(primary_index < ranges.len());
556
586
557− let mut selection = Self {
587+ let selection = Self {
558 ranges,
588 ranges,
559 primary_index,
589 primary_index,
560 };
590 };
561
591
562− if selection.ranges.len() > 1 {
592+ selection.normalize()
563− // TODO: only normalize if needed (any ranges out of order)
564− selection = selection.normalize();
565− }
566−
567− selection
568 }
593 }
569
594
570 /// Takes a closure and maps each `Range` over the closure.
595 /// Takes a closure and maps each `Range` over the closure.
@@ -578,6 +603,16 @@ impl Selection {
578 self.normalize()
603 self.normalize()
579 }
604 }
580
605
606+ /// Takes a closure and maps each `Range` over the closure to multiple `Range`s.
607+ pub fn transform_iter<F, I>(mut self, f: F) -> Self
608+ where
609+ F: FnMut(Range) -> I,
610+ I: Iterator<Item = Range>,
611+ {
612+ self.ranges = self.ranges.into_iter().flat_map(f).collect();
613+ self.normalize()
614+ }
615+
581 // Ensures the selection adheres to the following invariants:
616 // Ensures the selection adheres to the following invariants:
582 // 1. All ranges are grapheme aligned.
617 // 1. All ranges are grapheme aligned.
583 // 2. All ranges are at least 1 character wide, unless at the
618 // 2. All ranges are at least 1 character wide, unless at the
@@ -595,11 +630,19 @@ impl Selection {
595 self.transform(|range| Range::point(range.cursor(text)))
630 self.transform(|range| Range::point(range.cursor(text)))
596 }
631 }
597
632
598− pub fn fragments<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = Cow<str>> + 'a {
633+ pub fn fragments<'a>(
634+ &'a self,
635+ text: RopeSlice<'a>,
636+ ) -> impl DoubleEndedIterator<Item = Cow<'a, str>> + ExactSizeIterator<Item = Cow<str>> + 'a
637+ {
599 self.ranges.iter().map(move |range| range.fragment(text))
638 self.ranges.iter().map(move |range| range.fragment(text))
600 }
639 }
601
640
602− pub fn slices<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = RopeSlice> + 'a {
641+ pub fn slices<'a>(
642+ &'a self,
643+ text: RopeSlice<'a>,
644+ ) -> impl DoubleEndedIterator<Item = RopeSlice<'a>> + ExactSizeIterator<Item = RopeSlice<'a>> + 'a
645+ {
603 self.ranges.iter().map(move |range| range.slice(text))
646 self.ranges.iter().map(move |range| range.slice(text))
604 }
647 }
605
648
@@ -615,11 +658,6 @@ impl Selection {
615
658
616 // returns true if self ⊇ other
659 // returns true if self ⊇ other
617 pub fn contains(&self, other: &Selection) -> bool {
660 pub fn contains(&self, other: &Selection) -> bool {
618− // can't contain other if it is larger
619− if other.len() > self.len() {
620− return false;
621− }
622−
623 let (mut iter_self, mut iter_other) = (self.iter(), other.iter());
661 let (mut iter_self, mut iter_other) = (self.iter(), other.iter());
624 let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next());
662 let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next());
625
663
@@ -656,6 +694,15 @@ impl<'a> IntoIterator for &'a Selection {
656 }
694 }
657}
695}
658
696
697+impl IntoIterator for Selection {
698+ type Item = Range;
699+ type IntoIter = smallvec::IntoIter<[Range; 1]>;
700+
701+ fn into_iter(self) -> smallvec::IntoIter<[Range; 1]> {
702+ self.ranges.into_iter()
703+ }
704+}
705+
659// TODO: checkSelection -> check if valid for doc length && sorted
706// TODO: checkSelection -> check if valid for doc length && sorted
660
707
661pub fn keep_or_remove_matches(
708pub fn keep_or_remove_matches(
@@ -1230,5 +1277,11 @@ mod test {
1230 vec!((3, 4), (7, 9))
1277 vec!((3, 4), (7, 9))
1231 ));
1278 ));
1232 assert!(!contains(vec!((1, 1), (5, 6)), vec!((1, 6))));
1279 assert!(!contains(vec!((1, 1), (5, 6)), vec!((1, 6))));
1280+
1281+ // multiple ranges of other are all contained in some ranges of self,
1282+ assert!(contains(
1283+ vec!((1, 4), (7, 10)),
1284+ vec!((1, 2), (3, 4), (7, 9))
1285+ ));
1233 }
1286 }
1234}
1287}
helix-core/src/shellwords.rs+2−2
@@ -294,14 +294,14 @@ mod test {
294 #[test]
294 #[test]
295 fn test_lists() {
295 fn test_lists() {
296 let input =
296 let input =
297− r#":set statusline.center ["file-type","file-encoding"] '["list", "in", "qoutes"]'"#;
297+ r#":set statusline.center ["file-type","file-encoding"] '["list", "in", "quotes"]'"#;
298 let shellwords = Shellwords::from(input);
298 let shellwords = Shellwords::from(input);
299 let result = shellwords.words().to_vec();
299 let result = shellwords.words().to_vec();
300 let expected = vec![
300 let expected = vec![
301 Cow::from(":set"),
301 Cow::from(":set"),
302 Cow::from("statusline.center"),
302 Cow::from("statusline.center"),
303 Cow::from(r#"["file-type","file-encoding"]"#),
303 Cow::from(r#"["file-type","file-encoding"]"#),
304− Cow::from(r#"["list", "in", "qoutes"]"#),
304+ Cow::from(r#"["list", "in", "quotes"]"#),
305 ];
305 ];
306 assert_eq!(expected, result);
306 assert_eq!(expected, result);
307 }
307 }
helix-core/src/surround.rs+111−29
@@ -275,51 +275,133 @@ mod test {
275
275
276 #[test]
276 #[test]
277 fn test_get_surround_pos() {
277 fn test_get_surround_pos() {
278+ #[rustfmt::skip]
279+ let (doc, selection, expectations) =
280+ rope_with_selections_and_expectations(
278− let doc = Rope::from("(some) (chars)\n(newline)");
281+ "(some) (chars)\n(newline)",
279− let slice = doc.slice(..);
282+ "_ ^ _ _ ^ _\n_ ^ _"
280− let selection = Selection::new(
281− SmallVec::from_slice(&[Range::point(2), Range::point(9), Range::point(20)]),
282− 0,
283− );
283+ );
284
284
285− // cursor on s[o]me, c[h]ars, newl[i]ne
286 assert_eq!(
285 assert_eq!(
287− get_surround_pos(slice, &selection, Some('('), 1)
286+ get_surround_pos(doc.slice(..), &selection, Some('('), 1).unwrap(),
288− .unwrap()
287+ expectations
289− .as_slice(),
290− &[0, 5, 7, 13, 15, 23]
291 );
288 );
292 }
289 }
293
290
294 #[test]
291 #[test]
295− fn test_get_surround_pos_bail() {
292+ fn test_get_surround_pos_bail_different_surround_chars() {
293+ #[rustfmt::skip]
294+ let (doc, selection, _) =
295+ rope_with_selections_and_expectations(
296− let doc = Rope::from("[some]\n(chars)xx\n(newline)");
296+ "[some]\n(chars)xx\n(newline)",
297− let slice = doc.slice(..);
297+ " ^ \n ^ \n "
298+ );
298
299
299− let selection =
300− Selection::new(SmallVec::from_slice(&[Range::point(2), Range::point(9)]), 0);
301− // cursor on s[o]me, c[h]ars
302 assert_eq!(
300 assert_eq!(
303− get_surround_pos(slice, &selection, Some('('), 1),
301+ get_surround_pos(doc.slice(..), &selection, Some('('), 1),
304− Err(Error::PairNotFound) // different surround chars
302+ Err(Error::PairNotFound)
305 );
303 );
304+ }
305+
306+ #[test]
307+ fn test_get_surround_pos_bail_overlapping_surround_chars() {
308+ #[rustfmt::skip]
309+ let (doc, selection, _) =
310+ rope_with_selections_and_expectations(
311+ "[some]\n(chars)xx\n(newline)",
312+ " \n ^ \n ^ "
313+ );
306
314
307− let selection = Selection::new(
308− SmallVec::from_slice(&[Range::point(14), Range::point(24)]),
309− 0,
310− );
311− // cursor on [x]x, newli[n]e
312 assert_eq!(
315 assert_eq!(
313− get_surround_pos(slice, &selection, Some('('), 1),
316+ get_surround_pos(doc.slice(..), &selection, Some('('), 1),
314 Err(Error::PairNotFound) // overlapping surround chars
317 Err(Error::PairNotFound) // overlapping surround chars
315 );
318 );
319+ }
320+
321+ #[test]
322+ fn test_get_surround_pos_bail_cursor_overlap() {
323+ #[rustfmt::skip]
324+ let (doc, selection, _) =
325+ rope_with_selections_and_expectations(
326+ "[some]\n(chars)xx\n(newline)",
327+ " ^^ \n \n "
328+ );
316
329
317− let selection =
318− Selection::new(SmallVec::from_slice(&[Range::point(2), Range::point(3)]), 0);
319− // cursor on s[o][m]e
320 assert_eq!(
330 assert_eq!(
321− get_surround_pos(slice, &selection, Some('['), 1),
331+ get_surround_pos(doc.slice(..), &selection, Some('['), 1),
322 Err(Error::CursorOverlap)
332 Err(Error::CursorOverlap)
323 );
333 );
324 }
334 }
335+
336+ #[test]
337+ fn test_find_nth_pairs_pos_quote_success() {
338+ #[rustfmt::skip]
339+ let (doc, selection, expectations) =
340+ rope_with_selections_and_expectations(
341+ "some 'quoted text' on this 'line'\n'and this one'",
342+ " _ ^ _ \n "
343+ );
344+
345+ assert_eq!(2, expectations.len());
346+ assert_eq!(
347+ find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 1)
348+ .expect("find should succeed"),
349+ (expectations[0], expectations[1])
350+ )
351+ }
352+
353+ #[test]
354+ fn test_find_nth_pairs_pos_nested_quote_success() {
355+ #[rustfmt::skip]
356+ let (doc, selection, expectations) =
357+ rope_with_selections_and_expectations(
358+ "some 'nested 'quoted' text' on this 'line'\n'and this one'",
359+ " _ ^ _ \n "
360+ );
361+
362+ assert_eq!(2, expectations.len());
363+ assert_eq!(
364+ find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 2)
365+ .expect("find should succeed"),
366+ (expectations[0], expectations[1])
367+ )
368+ }
369+
370+ #[test]
371+ fn test_find_nth_pairs_pos_inside_quote_ambiguous() {
372+ #[rustfmt::skip]
373+ let (doc, selection, _) =
374+ rope_with_selections_and_expectations(
375+ "some 'nested 'quoted' text' on this 'line'\n'and this one'",
376+ " ^ \n "
377+ );
378+
379+ assert_eq!(
380+ find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 1),
381+ Err(Error::CursorOnAmbiguousPair)
382+ )
383+ }
384+
385+ // Create a Rope and a matching Selection using a specification language.
386+ // ^ is a single-point selection.
387+ // _ is an expected index. These are returned as a Vec<usize> for use in assertions.
388+ fn rope_with_selections_and_expectations(
389+ text: &str,
390+ spec: &str,
391+ ) -> (Rope, Selection, Vec<usize>) {
392+ if text.len() != spec.len() {
393+ panic!("specification must match text length -- are newlines aligned?");
394+ }
395+
396+ let rope = Rope::from(text);
397+
398+ let selections: SmallVec<[Range; 1]> = spec
399+ .match_indices('^')
400+ .map(|(i, _)| Range::point(i))
401+ .collect();
402+
403+ let expectations: Vec<usize> = spec.match_indices('_').map(|(i, _)| i).collect();
404+
405+ (rope, Selection::new(selections, 0), expectations)
406+ }
325}
407}
helix-core/src/syntax.rs+444−170
@@ -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− Rope, RopeSlice, Tendril,
7+ RopeSlice, Tendril,
8};
8};
9
9
10use ahash::RandomState;
10use ahash::RandomState;
@@ -16,17 +16,17 @@ 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, VecDeque},
19+ collections::{HashMap, HashSet, VecDeque},
20− fmt,
20+ fmt::{self, Display},
21 hash::{Hash, Hasher},
21 hash::{Hash, Hasher},
22 mem::{replace, transmute},
22 mem::{replace, transmute},
23− path::Path,
23+ path::{Path, PathBuf},
24 str::FromStr,
24 str::FromStr,
25 sync::Arc,
25 sync::Arc,
26};
26};
27
27
28use once_cell::sync::{Lazy, OnceCell};
28use once_cell::sync::{Lazy, OnceCell};
29−use serde::{Deserialize, Serialize};
29+use serde::{ser::SerializeSeq, 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,6 +48,21 @@ 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+
51pub fn deserialize_auto_pairs<'de, D>(deserializer: D) -> Result<Option<AutoPairs>, D::Error>
66pub fn deserialize_auto_pairs<'de, D>(deserializer: D) -> Result<Option<AutoPairs>, D::Error>
52where
67where
53 D: serde::Deserializer<'de>,
68 D: serde::Deserializer<'de>,
@@ -60,8 +75,11 @@ fn default_timeout() -> u64 {
60}
75}
61
76
62#[derive(Debug, Serialize, Deserialize)]
77#[derive(Debug, Serialize, Deserialize)]
78+#[serde(rename_all = "kebab-case")]
63pub struct Configuration {
79pub struct Configuration {
64 pub language: Vec<LanguageConfiguration>,
80 pub language: Vec<LanguageConfiguration>,
81+ #[serde(default)]
82+ pub language_server: HashMap<String, LanguageServerConfiguration>,
65}
83}
66
84
67impl Default for Configuration {
85impl Default for Configuration {
@@ -75,17 +93,19 @@ impl Default for Configuration {
75#[serde(rename_all = "kebab-case", deny_unknown_fields)]
93#[serde(rename_all = "kebab-case", deny_unknown_fields)]
76pub struct LanguageConfiguration {
94pub struct LanguageConfiguration {
77 #[serde(rename = "name")]
95 #[serde(rename = "name")]
78− pub language_id: String, // c-sharp, rust
96+ pub language_id: String, // c-sharp, rust, tsx
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
79 pub scope: String, // source.rust
100 pub scope: String, // source.rust
80 pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
101 pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
81 #[serde(default)]
102 #[serde(default)]
82 pub shebangs: Vec<String>, // interpreter(s) associated with language
103 pub shebangs: Vec<String>, // interpreter(s) associated with language
104+ #[serde(default)]
83− pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
105+ pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
84 pub comment_token: Option<String>,
106 pub comment_token: Option<String>,
85− pub max_line_length: Option<usize>,
107+ pub text_width: Option<usize>,
86−
108+ pub soft_wrap: Option<SoftWrap>,
87− #[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
88− pub config: Option<serde_json::Value>,
89
109
90 #[serde(default)]
110 #[serde(default)]
91 pub auto_format: bool,
111 pub auto_format: bool,
@@ -106,8 +126,13 @@ pub struct LanguageConfiguration {
106 #[serde(skip)]
126 #[serde(skip)]
107 pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
127 pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
108 // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
128 // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
129+ #[serde(
130+ default,
109− #[serde(skip_serializing_if = "Option::is_none")]
131+ skip_serializing_if = "Vec::is_empty",
110− pub language_server: Option<LanguageServerConfiguration>,
132+ serialize_with = "serialize_lang_features",
133+ deserialize_with = "deserialize_lang_features"
134+ )]
135+ pub language_servers: Vec<LanguageServerFeatures>,
111 #[serde(skip_serializing_if = "Option::is_none")]
136 #[serde(skip_serializing_if = "Option::is_none")]
112 pub indent: Option<IndentationConfiguration>,
137 pub indent: Option<IndentationConfiguration>,
113
138
@@ -126,6 +151,12 @@ pub struct LanguageConfiguration {
126 pub auto_pairs: Option<AutoPairs>,
151 pub auto_pairs: Option<AutoPairs>,
127
152
128 pub rulers: Option<Vec<u16>>, // if set, override editor's rulers
153 pub rulers: Option<Vec<u16>>, // if set, override editor's rulers
154+
155+ /// 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.
157+ pub workspace_lsp_roots: Option<Vec<PathBuf>>,
158+ #[serde(default)]
159+ pub persistent_diagnostic_sources: Vec<String>,
129}
160}
130
161
131#[derive(Debug, PartialEq, Eq, Hash)]
162#[derive(Debug, PartialEq, Eq, Hash)]
@@ -182,9 +213,9 @@ impl<'de> Deserialize<'de> for FileType {
182 M: serde::de::MapAccess<'de>,
213 M: serde::de::MapAccess<'de>,
183 {
214 {
184 match map.next_entry::<String, String>()? {
215 match map.next_entry::<String, String>()? {
185− Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix(
216+ Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
186− suffix.replace('/', &std::path::MAIN_SEPARATOR.to_string()),
217+ suffix.replace('/', std::path::MAIN_SEPARATOR_STR)
187− )),
218+ })),
188 Some((key, _value)) => Err(serde::de::Error::custom(format!(
219 Some((key, _value)) => Err(serde::de::Error::custom(format!(
189 "unknown key in `file-types` list: {}",
220 "unknown key in `file-types` list: {}",
190 key
221 key
@@ -200,6 +231,133 @@ impl<'de> Deserialize<'de> for FileType {
200 }
231 }
201}
232}
202
233
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+
203#[derive(Debug, Serialize, Deserialize)]
361#[derive(Debug, Serialize, Deserialize)]
204#[serde(rename_all = "kebab-case")]
362#[serde(rename_all = "kebab-case")]
205pub struct LanguageServerConfiguration {
363pub struct LanguageServerConfiguration {
@@ -209,9 +367,10 @@ pub struct LanguageServerConfiguration {
209 pub args: Vec<String>,
367 pub args: Vec<String>,
210 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
368 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
211 pub environment: HashMap<String, String>,
369 pub environment: HashMap<String, String>,
370+ #[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
371+ pub config: Option<serde_json::Value>,
212 #[serde(default = "default_timeout")]
372 #[serde(default = "default_timeout")]
213 pub timeout: u64,
373 pub timeout: u64,
214− pub language_id: Option<String>,
215}
374}
216
375
217#[derive(Debug, Clone, Serialize, Deserialize)]
376#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -280,10 +439,27 @@ pub struct DebuggerQuirks {
280#[derive(Debug, Serialize, Deserialize)]
439#[derive(Debug, Serialize, Deserialize)]
281#[serde(rename_all = "kebab-case")]
440#[serde(rename_all = "kebab-case")]
282pub struct IndentationConfiguration {
441pub struct IndentationConfiguration {
442+ #[serde(deserialize_with = "deserialize_tab_width")]
283 pub tab_width: usize,
443 pub tab_width: usize,
284 pub unit: String,
444 pub unit: String,
285}
445}
286
446
447+/// How the indentation for a newly inserted line should be determined.
448+/// If the selected heuristic is not available (e.g. because the current
449+/// language has no tree-sitter indent queries), a simpler one will be used.
450+#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
451+#[serde(rename_all = "kebab-case")]
452+pub enum IndentationHeuristic {
453+ /// Just copy the indentation of the line that the cursor is currently on.
454+ Simple,
455+ /// Use tree-sitter indent queries to compute the expected absolute indentation level of the new line.
456+ TreeSitter,
457+ /// Use tree-sitter indent queries to compute the expected difference in indentation between the new line
458+ /// and the line before. Add this to the actual indentation level of the line before.
459+ #[default]
460+ Hybrid,
461+}
462+
287/// Configuration for auto pairs
463/// Configuration for auto pairs
288#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
464#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
289#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
465#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
@@ -546,6 +722,35 @@ impl LanguageConfiguration {
546 .ok()
722 .ok()
547 }
723 }
548}
724}
725+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
726+#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
727+pub struct SoftWrap {
728+ /// Soft wrap lines that exceed viewport width. Default to off
729+ // NOTE: Option on purpose because the struct is shared between language config and global config.
730+ // By default the option is None so that the language config falls back to the global config unless explicitly set.
731+ pub enable: Option<bool>,
732+ /// Maximum space left free at the end of the line.
733+ /// This space is used to wrap text at word boundaries. If that is not possible within this limit
734+ /// the word is simply split at the end of the line.
735+ ///
736+ /// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views.
737+ ///
738+ /// Default to 20
739+ pub max_wrap: Option<u16>,
740+ /// Maximum number of indentation that can be carried over from the previous line when softwrapping.
741+ /// If a line is indented further then this limit it is rendered at the start of the viewport instead.
742+ ///
743+ /// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views.
744+ ///
745+ /// Default to 40
746+ pub max_indent_retain: Option<u16>,
747+ /// Indicator placed at the beginning of softwrapped lines
748+ ///
749+ /// Defaults to ↪
750+ pub wrap_indicator: Option<String>,
751+ /// Softwrap at `text_width` instead of viewport width if it is shorter
752+ pub wrap_at_text_width: Option<bool>,
753+}
549
754
550// Expose loader as Lazy<> global since it's always static?
755// Expose loader as Lazy<> global since it's always static?
551
756
@@ -557,6 +762,8 @@ pub struct Loader {
557 language_config_ids_by_suffix: HashMap<String, usize>,
762 language_config_ids_by_suffix: HashMap<String, usize>,
558 language_config_ids_by_shebang: HashMap<String, usize>,
763 language_config_ids_by_shebang: HashMap<String, usize>,
559
764
765+ language_server_configs: HashMap<String, LanguageServerConfiguration>,
766+
560 scopes: ArcSwap<Vec<String>>,
767 scopes: ArcSwap<Vec<String>>,
561}
768}
562
769
@@ -564,6 +771,7 @@ impl Loader {
564 pub fn new(config: Configuration) -> Self {
771 pub fn new(config: Configuration) -> Self {
565 let mut loader = Self {
772 let mut loader = Self {
566 language_configs: Vec::new(),
773 language_configs: Vec::new(),
774+ language_server_configs: config.language_server,
567 language_config_ids_by_extension: HashMap::new(),
775 language_config_ids_by_extension: HashMap::new(),
568 language_config_ids_by_suffix: HashMap::new(),
776 language_config_ids_by_suffix: HashMap::new(),
569 language_config_ids_by_shebang: HashMap::new(),
777 language_config_ids_by_shebang: HashMap::new(),
@@ -626,11 +834,13 @@ impl Loader {
626 // TODO: content_regex handling conflict resolution
834 // TODO: content_regex handling conflict resolution
627 }
835 }
628
836
629− pub fn language_config_for_shebang(&self, source: &Rope) -> Option<Arc<LanguageConfiguration>> {
837+ pub fn language_config_for_shebang(
838+ &self,
839+ source: RopeSlice,
840+ ) -> Option<Arc<LanguageConfiguration>> {
630 let line = Cow::from(source.line(0));
841 let line = Cow::from(source.line(0));
631− static SHEBANG_REGEX: Lazy<Regex> = Lazy::new(|| {
842+ static SHEBANG_REGEX: Lazy<Regex> =
632− Regex::new(r"^#!\s*(?:\S*[/\\](?:env\s+(?:\-\S+\s+)*)?)?([^\s\.\d]+)").unwrap()
843+ Lazy::new(|| Regex::new(&["^", SHEBANG].concat()).unwrap());
633− });
634 let configuration_id = SHEBANG_REGEX
844 let configuration_id = SHEBANG_REGEX
635 .captures(&line)
845 .captures(&line)
636 .and_then(|cap| self.language_config_ids_by_shebang.get(&cap[1]));
846 .and_then(|cap| self.language_config_ids_by_shebang.get(&cap[1]));
@@ -652,15 +862,14 @@ impl Loader {
652 .cloned()
862 .cloned()
653 }
863 }
654
864
655− pub fn language_configuration_for_injection_string(
865+ /// Unlike language_config_for_language_id, which only returns Some for an exact id, this
656− &self,
866+ /// function will perform a regex match on the given string to find the closest language match.
657− string: &str,
658− ) -> Option<Arc<LanguageConfiguration>> {
867+ pub fn language_config_for_name(&self, name: &str) -> Option<Arc<LanguageConfiguration>> {
659 let mut best_match_length = 0;
868 let mut best_match_length = 0;
660 let mut best_match_position = None;
869 let mut best_match_position = None;
661 for (i, configuration) in self.language_configs.iter().enumerate() {
870 for (i, configuration) in self.language_configs.iter().enumerate() {
662 if let Some(injection_regex) = &configuration.injection_regex {
871 if let Some(injection_regex) = &configuration.injection_regex {
663− if let Some(mat) = injection_regex.find(string) {
872+ if let Some(mat) = injection_regex.find(name) {
664 let length = mat.end() - mat.start();
873 let length = mat.end() - mat.start();
665 if length > best_match_length {
874 if length > best_match_length {
666 best_match_position = Some(i);
875 best_match_position = Some(i);
@@ -670,17 +879,30 @@ impl Loader {
670 }
879 }
671 }
880 }
672
881
673− if let Some(i) = best_match_position {
882+ best_match_position.map(|i| self.language_configs[i].clone())
674− let configuration = &self.language_configs[i];
883+ }
675− return Some(configuration.clone());
884+
885+ pub fn language_configuration_for_injection_string(
886+ &self,
887+ capture: &InjectionLanguageMarker,
888+ ) -> Option<Arc<LanguageConfiguration>> {
889+ match capture {
890+ InjectionLanguageMarker::Name(string) => self.language_config_for_name(string),
891+ InjectionLanguageMarker::Filename(file) => self.language_config_for_file_name(file),
892+ InjectionLanguageMarker::Shebang(shebang) => {
893+ self.language_config_for_language_id(shebang)
894+ }
676 }
895 }
677− None
678 }
896 }
679
897
680 pub fn language_configs(&self) -> impl Iterator<Item = &Arc<LanguageConfiguration>> {
898 pub fn language_configs(&self) -> impl Iterator<Item = &Arc<LanguageConfiguration>> {
681 self.language_configs.iter()
899 self.language_configs.iter()
682 }
900 }
683
901
902+ pub fn language_server_configs(&self) -> &HashMap<String, LanguageServerConfiguration> {
903+ &self.language_server_configs
904+ }
905+
684 pub fn set_scopes(&self, scopes: Vec<String>) {
906 pub fn set_scopes(&self, scopes: Vec<String>) {
685 self.scopes.store(Arc::new(scopes));
907 self.scopes.store(Arc::new(scopes));
686
908
@@ -724,7 +946,11 @@ fn byte_range_to_str(range: std::ops::Range<usize>, source: RopeSlice) -> Cow<st
724}
946}
725
947
726impl Syntax {
948impl Syntax {
949+ pub fn new(
950+ source: RopeSlice,
727− pub fn new(source: &Rope, config: Arc<HighlightConfiguration>, loader: Arc<Loader>) -> Self {
951+ config: Arc<HighlightConfiguration>,
952+ loader: Arc<Loader>,
953+ ) -> Option<Self> {
728 let root_layer = LanguageLayer {
954 let root_layer = LanguageLayer {
729 tree: None,
955 tree: None,
730 config,
956 config,
@@ -749,24 +975,26 @@ impl Syntax {
749 loader,
975 loader,
750 };
976 };
751
977
752− syntax
753− .update(source, source, &ChangeSet::new(source))
978+ let res = syntax.update(source, source, &ChangeSet::new(source));
754− .unwrap();
755
979
980+ if res.is_err() {
981+ log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
982+ return None;
983+ }
756− syntax
984+ Some(syntax)
757 }
985 }
758
986
759 pub fn update(
987 pub fn update(
760 &mut self,
988 &mut self,
761− old_source: &Rope,
989+ old_source: RopeSlice,
762− source: &Rope,
990+ source: RopeSlice,
763 changeset: &ChangeSet,
991 changeset: &ChangeSet,
764 ) -> Result<(), Error> {
992 ) -> Result<(), Error> {
765 let mut queue = VecDeque::new();
993 let mut queue = VecDeque::new();
766 queue.push_back(self.root);
994 queue.push_back(self.root);
767
995
768 let scopes = self.loader.scopes.load();
996 let scopes = self.loader.scopes.load();
769− let injection_callback = |language: &str| {
997+ let injection_callback = |language: &InjectionLanguageMarker| {
770 self.loader
998 self.loader
771 .language_configuration_for_injection_string(language)
999 .language_configuration_for_injection_string(language)
772 .and_then(|language_config| language_config.highlight_config(&scopes))
1000 .and_then(|language_config| language_config.highlight_config(&scopes))
@@ -881,6 +1109,7 @@ impl Syntax {
881
1109
882 PARSER.with(|ts_parser| {
1110 PARSER.with(|ts_parser| {
883 let ts_parser = &mut ts_parser.borrow_mut();
1111 let ts_parser = &mut ts_parser.borrow_mut();
1112+ ts_parser.parser.set_timeout_micros(1000 * 500); // half a second is pretty generours
884 let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new);
1113 let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new);
885 // TODO: might need to set cursor range
1114 // TODO: might need to set cursor range
886 cursor.set_byte_range(0..usize::MAX);
1115 cursor.set_byte_range(0..usize::MAX);
@@ -925,14 +1154,37 @@ impl Syntax {
925 layer.tree().root_node(),
1154 layer.tree().root_node(),
926 RopeProvider(source_slice),
1155 RopeProvider(source_slice),
927 );
1156 );
1157+ let mut combined_injections = vec![
1158+ (None, Vec::new(), IncludedChildren::default());
1159+ layer.config.combined_injections_patterns.len()
1160+ ];
928 let mut injections = Vec::new();
1161 let mut injections = Vec::new();
1162+ let mut last_injection_end = 0;
929 for mat in matches {
1163 for mat in matches {
930− let (language_name, content_node, included_children) = injection_for_match(
1164+ let (injection_capture, content_node, included_children) = layer
931− &layer.config,
1165+ .config
932− &layer.config.injections_query,
1166+ .injection_for_match(&layer.config.injections_query, &mat, source_slice);
933− &mat,
1167+
934− source_slice,
1168+ // in case this is a combined injection save it for more processing later
935− );
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+ }
936
1188
937 // Explicitly remove this match so that none of its other captures will remain
1189 // Explicitly remove this match so that none of its other captures will remain
938 // in the stream of captures.
1190 // in the stream of captures.
@@ -940,59 +1192,31 @@ impl Syntax {
940
1192
941 // If a language is found with the given name, then add a new language layer
1193 // If a language is found with the given name, then add a new language layer
942 // to the highlighted document.
1194 // to the highlighted document.
943− if let (Some(language_name), Some(content_node)) = (language_name, content_node)
1195+ if let (Some(injection_capture), Some(content_node)) =
1196+ (injection_capture, content_node)
944 {
1197 {
945− if let Some(config) = (injection_callback)(&language_name) {
1198+ if let Some(config) = (injection_callback)(&injection_capture) {
946 let ranges =
1199 let ranges =
947 intersect_ranges(&layer.ranges, &[content_node], included_children);
1200 intersect_ranges(&layer.ranges, &[content_node], included_children);
948
1201
949 if !ranges.is_empty() {
1202 if !ranges.is_empty() {
1203+ if content_node.start_byte() < last_injection_end {
1204+ continue;
1205+ }
1206+ last_injection_end = content_node.end_byte();
950 injections.push((config, ranges));
1207 injections.push((config, ranges));
951 }
1208 }
952 }
1209 }
953 }
1210 }
954 }
1211 }
955
1212
956− // Process combined injections.
957− if let Some(combined_injections_query) = &layer.config.combined_injections_query {
958− let mut injections_by_pattern_index =
959− vec![
960− (None, Vec::new(), IncludedChildren::default());
961− combined_injections_query.pattern_count()
962− ];
963− let matches = cursor.matches(
964− combined_injections_query,
965− layer.tree().root_node(),
966− RopeProvider(source_slice),
967− );
968− for mat in matches {
969− let entry = &mut injections_by_pattern_index[mat.pattern_index];
970− let (language_name, content_node, included_children) = injection_for_match(
971− &layer.config,
972− combined_injections_query,
973− &mat,
974− source_slice,
975− );
976− if language_name.is_some() {
977− entry.0 = language_name;
978− }
979− if let Some(content_node) = content_node {
980− entry.1.push(content_node);
981− }
982− entry.2 = included_children;
983− }
984− for (lang_name, content_nodes, included_children) in injections_by_pattern_index
1213+ for (lang_name, content_nodes, included_children) in combined_injections {
985− {
986− if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) {
1214+ if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) {
987− if let Some(config) = (injection_callback)(&lang_name) {
1215+ if let Some(config) = (injection_callback)(&lang_name) {
988− let ranges = intersect_ranges(
1216+ let ranges =
989− &layer.ranges,
990− &content_nodes,
991− included_children,
1217+ intersect_ranges(&layer.ranges, &content_nodes, included_children);
992− );
993− if !ranges.is_empty() {
1218+ if !ranges.is_empty() {
994− injections.push((config, ranges));
1219+ injections.push((config, ranges));
995− }
996 }
1220 }
997 }
1221 }
998 }
1222 }
@@ -1129,6 +1353,7 @@ impl Syntax {
1129bitflags! {
1353bitflags! {
1130 /// Flags that track the status of a layer
1354 /// Flags that track the status of a layer
1131 /// in the `Sytaxn::update` function
1355 /// in the `Sytaxn::update` function
1356+ #[derive(Debug)]
1132 struct LayerUpdateFlags : u32{
1357 struct LayerUpdateFlags : u32{
1133 const MODIFIED = 0b001;
1358 const MODIFIED = 0b001;
1134 const MOVED = 0b010;
1359 const MOVED = 0b010;
@@ -1181,7 +1406,7 @@ impl LanguageLayer {
1181 self.tree.as_ref().unwrap()
1406 self.tree.as_ref().unwrap()
1182 }
1407 }
1183
1408
1184− fn parse(&mut self, parser: &mut Parser, source: &Rope) -> Result<(), Error> {
1409+ fn parse(&mut self, parser: &mut Parser, source: RopeSlice) -> Result<(), Error> {
1185 parser
1410 parser
1186 .set_included_ranges(&self.ranges)
1411 .set_included_ranges(&self.ranges)
1187 .map_err(|_| Error::InvalidRanges)?;
1412 .map_err(|_| Error::InvalidRanges)?;
@@ -1196,7 +1421,7 @@ impl LanguageLayer {
1196 &mut |byte, _| {
1421 &mut |byte, _| {
1197 if byte <= source.len_bytes() {
1422 if byte <= source.len_bytes() {
1198 let (chunk, start_byte, _, _) = source.chunk_at_byte(byte);
1423 let (chunk, start_byte, _, _) = source.chunk_at_byte(byte);
1199− chunk[byte - start_byte..].as_bytes()
1424+ &chunk.as_bytes()[byte - start_byte..]
1200 } else {
1425 } else {
1201 // out of range
1426 // out of range
1202 &[]
1427 &[]
@@ -1212,7 +1437,7 @@ impl LanguageLayer {
1212}
1437}
1213
1438
1214pub(crate) fn generate_edits(
1439pub(crate) fn generate_edits(
1215− old_text: &Rope,
1440+ old_text: RopeSlice,
1216 changeset: &ChangeSet,
1441 changeset: &ChangeSet,
1217) -> Vec<tree_sitter::InputEdit> {
1442) -> Vec<tree_sitter::InputEdit> {
1218 use Operation::*;
1443 use Operation::*;
@@ -1228,7 +1453,7 @@ pub(crate) fn generate_edits(
1228
1453
1229 // TODO; this is a lot easier with Change instead of Operation.
1454 // TODO; this is a lot easier with Change instead of Operation.
1230
1455
1231− fn point_at_pos(text: &Rope, pos: usize) -> (usize, Point) {
1456+ fn point_at_pos(text: RopeSlice, pos: usize) -> (usize, Point) {
1232 let byte = text.char_to_byte(pos); // <- attempted to index past end
1457 let byte = text.char_to_byte(pos); // <- attempted to index past end
1233 let line = text.char_to_line(pos);
1458 let line = text.char_to_line(pos);
1234 let line_start_byte = text.line_to_byte(line);
1459 let line_start_byte = text.line_to_byte(line);
@@ -1354,12 +1579,14 @@ pub struct HighlightConfiguration {
1354 pub language: Grammar,
1579 pub language: Grammar,
1355 pub query: Query,
1580 pub query: Query,
1356 injections_query: Query,
1581 injections_query: Query,
1357− combined_injections_query: Option<Query>,
1582+ combined_injections_patterns: Vec<usize>,
1358 highlights_pattern_index: usize,
1583 highlights_pattern_index: usize,
1359 highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
1584 highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
1360 non_local_variable_patterns: Vec<bool>,
1585 non_local_variable_patterns: Vec<bool>,
1361 injection_content_capture_index: Option<u32>,
1586 injection_content_capture_index: Option<u32>,
1362 injection_language_capture_index: Option<u32>,
1587 injection_language_capture_index: Option<u32>,
1588+ injection_filename_capture_index: Option<u32>,
1589+ injection_shebang_capture_index: Option<u32>,
1363 local_scope_capture_index: Option<u32>,
1590 local_scope_capture_index: Option<u32>,
1364 local_def_capture_index: Option<u32>,
1591 local_def_capture_index: Option<u32>,
1365 local_def_value_capture_index: Option<u32>,
1592 local_def_value_capture_index: Option<u32>,
@@ -1403,7 +1630,7 @@ impl<'a> Iterator for ChunksBytes<'a> {
1403}
1630}
1404
1631
1405pub struct RopeProvider<'a>(pub RopeSlice<'a>);
1632pub struct RopeProvider<'a>(pub RopeSlice<'a>);
1406−impl<'a> TextProvider<'a> for RopeProvider<'a> {
1633+impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> {
1407 type I = ChunksBytes<'a>;
1634 type I = ChunksBytes<'a>;
1408
1635
1409 fn text(&mut self, node: Node) -> Self::I {
1636 fn text(&mut self, node: Node) -> Self::I {
@@ -1417,7 +1644,7 @@ impl<'a> TextProvider<'a> for RopeProvider<'a> {
1417struct HighlightIterLayer<'a> {
1644struct HighlightIterLayer<'a> {
1418 _tree: Option<Tree>,
1645 _tree: Option<Tree>,
1419 cursor: QueryCursor,
1646 cursor: QueryCursor,
1420− captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>>>>,
1647+ captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>, &'a [u8]>>>,
1421 config: &'a HighlightConfiguration,
1648 config: &'a HighlightConfiguration,
1422 highlight_end_stack: Vec<usize>,
1649 highlight_end_stack: Vec<usize>,
1423 scope_stack: Vec<LocalScope<'a>>,
1650 scope_stack: Vec<LocalScope<'a>>,
@@ -1468,26 +1695,15 @@ impl HighlightConfiguration {
1468 }
1695 }
1469 }
1696 }
1470
1697
1471− let mut injections_query = Query::new(language, injection_query)?;
1698+ let injections_query = Query::new(language, injection_query)?;
1472−
1473− // Construct a separate query just for dealing with the 'combined injections'.
1474− // Disable the combined injection patterns in the main query.
1475− let mut combined_injections_query = Query::new(language, injection_query)?;
1476− let mut has_combined_queries = false;
1477− for pattern_index in 0..injections_query.pattern_count() {
1699+ let combined_injections_patterns = (0..injections_query.pattern_count())
1700+ .filter(|&i| {
1701+ injections_query
1478− let settings = injections_query.property_settings(pattern_index);
1702+ .property_settings(i)
1703+ .iter()
1479− if settings.iter().any(|s| &*s.key == "injection.combined") {
1704+ .any(|s| &*s.key == "injection.combined")
1480− has_combined_queries = true;
1705+ })
1481− injections_query.disable_pattern(pattern_index);
1706+ .collect();
1482− } else {
1483− combined_injections_query.disable_pattern(pattern_index);
1484− }
1485− }
1486− let combined_injections_query = if has_combined_queries {
1487− Some(combined_injections_query)
1488− } else {
1489− None
1490− };
1491
1707
1492 // Find all of the highlighting patterns that are disabled for nodes that
1708 // Find all of the highlighting patterns that are disabled for nodes that
1493 // have been identified as local variables.
1709 // have been identified as local variables.
@@ -1503,6 +1719,8 @@ impl HighlightConfiguration {
1503 // Store the numeric ids for all of the special captures.
1719 // Store the numeric ids for all of the special captures.
1504 let mut injection_content_capture_index = None;
1720 let mut injection_content_capture_index = None;
1505 let mut injection_language_capture_index = None;
1721 let mut injection_language_capture_index = None;
1722+ let mut injection_filename_capture_index = None;
1723+ let mut injection_shebang_capture_index = None;
1506 let mut local_def_capture_index = None;
1724 let mut local_def_capture_index = None;
1507 let mut local_def_value_capture_index = None;
1725 let mut local_def_value_capture_index = None;
1508 let mut local_ref_capture_index = None;
1726 let mut local_ref_capture_index = None;
@@ -1523,6 +1741,8 @@ impl HighlightConfiguration {
1523 match name.as_str() {
1741 match name.as_str() {
1524 "injection.content" => injection_content_capture_index = i,
1742 "injection.content" => injection_content_capture_index = i,
1525 "injection.language" => injection_language_capture_index = i,
1743 "injection.language" => injection_language_capture_index = i,
1744+ "injection.filename" => injection_filename_capture_index = i,
1745+ "injection.shebang" => injection_shebang_capture_index = i,
1526 _ => {}
1746 _ => {}
1527 }
1747 }
1528 }
1748 }
@@ -1532,12 +1752,14 @@ impl HighlightConfiguration {
1532 language,
1752 language,
1533 query,
1753 query,
1534 injections_query,
1754 injections_query,
1535− combined_injections_query,
1755+ combined_injections_patterns,
1536 highlights_pattern_index,
1756 highlights_pattern_index,
1537 highlight_indices,
1757 highlight_indices,
1538 non_local_variable_patterns,
1758 non_local_variable_patterns,
1539 injection_content_capture_index,
1759 injection_content_capture_index,
1540 injection_language_capture_index,
1760 injection_language_capture_index,
1761+ injection_filename_capture_index,
1762+ injection_shebang_capture_index,
1541 local_scope_capture_index,
1763 local_scope_capture_index,
1542 local_def_capture_index,
1764 local_def_capture_index,
1543 local_def_value_capture_index,
1765 local_def_value_capture_index,
@@ -1596,6 +1818,90 @@ impl HighlightConfiguration {
1596
1818
1597 self.highlight_indices.store(Arc::new(indices));
1819 self.highlight_indices.store(Arc::new(indices));
1598 }
1820 }
1821+
1822+ fn injection_pair<'a>(
1823+ &self,
1824+ query_match: &QueryMatch<'a, 'a>,
1825+ source: RopeSlice<'a>,
1826+ ) -> (Option<InjectionLanguageMarker<'a>>, Option<Node<'a>>) {
1827+ let mut injection_capture = None;
1828+ let mut content_node = None;
1829+
1830+ for capture in query_match.captures {
1831+ let index = Some(capture.index);
1832+ if index == self.injection_language_capture_index {
1833+ let name = byte_range_to_str(capture.node.byte_range(), source);
1834+ injection_capture = Some(InjectionLanguageMarker::Name(name));
1835+ } else if index == self.injection_filename_capture_index {
1836+ let name = byte_range_to_str(capture.node.byte_range(), source);
1837+ let path = Path::new(name.as_ref()).to_path_buf();
1838+ injection_capture = Some(InjectionLanguageMarker::Filename(path.into()));
1839+ } else if index == self.injection_shebang_capture_index {
1840+ let node_slice = source.byte_slice(capture.node.byte_range());
1841+
1842+ // some languages allow space and newlines before the actual string content
1843+ // so a shebang could be on either the first or second line
1844+ let lines = if let Ok(end) = node_slice.try_line_to_byte(2) {
1845+ node_slice.byte_slice(..end)
1846+ } else {
1847+ node_slice
1848+ };
1849+
1850+ static SHEBANG_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(SHEBANG).unwrap());
1851+
1852+ injection_capture = SHEBANG_REGEX
1853+ .captures(&Cow::from(lines))
1854+ .map(|cap| InjectionLanguageMarker::Shebang(cap[1].to_owned()))
1855+ } else if index == self.injection_content_capture_index {
1856+ content_node = Some(capture.node);
1857+ }
1858+ }
1859+ (injection_capture, content_node)
1860+ }
1861+
1862+ fn injection_for_match<'a>(
1863+ &self,
1864+ query: &'a Query,
1865+ query_match: &QueryMatch<'a, 'a>,
1866+ source: RopeSlice<'a>,
1867+ ) -> (
1868+ Option<InjectionLanguageMarker<'a>>,
1869+ Option<Node<'a>>,
1870+ IncludedChildren,
1871+ ) {
1872+ let (mut injection_capture, content_node) = self.injection_pair(query_match, source);
1873+
1874+ let mut included_children = IncludedChildren::default();
1875+ for prop in query.property_settings(query_match.pattern_index) {
1876+ match prop.key.as_ref() {
1877+ // In addition to specifying the language name via the text of a
1878+ // captured node, it can also be hard-coded via a `#set!` predicate
1879+ // that sets the injection.language key.
1880+ "injection.language" if injection_capture.is_none() => {
1881+ injection_capture = prop
1882+ .value
1883+ .as_ref()
1884+ .map(|s| InjectionLanguageMarker::Name(s.as_ref().into()));
1885+ }
1886+
1887+ // By default, injections do not include the *children* of an
1888+ // `injection.content` node - only the ranges that belong to the
1889+ // node itself. This can be changed using a `#set!` predicate that
1890+ // sets the `injection.include-children` key.
1891+ "injection.include-children" => included_children = IncludedChildren::All,
1892+
1893+ // Some queries might only exclude named children but include unnamed
1894+ // children in their `injection.content` node. This can be enabled using
1895+ // a `#set!` predicate that sets the `injection.include-unnamed-children` key.
1896+ "injection.include-unnamed-children" => {
1897+ included_children = IncludedChildren::Unnamed
1898+ }
1899+ _ => {}
1900+ }
1901+ }
1902+
1903+ (injection_capture, content_node, included_children)
1904+ }
1599}
1905}
1600
1906
1601impl<'a> HighlightIterLayer<'a> {
1907impl<'a> HighlightIterLayer<'a> {
@@ -2007,56 +2313,15 @@ impl<'a> Iterator for HighlightIter<'a> {
2007 }
2313 }
2008}
2314}
2009
2315
2010−fn injection_for_match<'a>(
2316+#[derive(Debug, Clone)]
2011− config: &HighlightConfiguration,
2317+pub enum InjectionLanguageMarker<'a> {
2012− query: &'a Query,
2318+ Name(Cow<'a, str>),
2013− query_match: &QueryMatch<'a, 'a>,
2319+ Filename(Cow<'a, Path>),
2014− source: RopeSlice<'a>,
2320+ Shebang(String),
2015−) -> (Option<Cow<'a, str>>, Option<Node<'a>>, IncludedChildren) {
2016− let content_capture_index = config.injection_content_capture_index;
2017− let language_capture_index = config.injection_language_capture_index;
2018−
2019− let mut language_name = None;
2020− let mut content_node = None;
2021− for capture in query_match.captures {
2022− let index = Some(capture.index);
2023− if index == language_capture_index {
2024− let name = byte_range_to_str(capture.node.byte_range(), source);
2025− language_name = Some(name);
2026− } else if index == content_capture_index {
2027− content_node = Some(capture.node);
2028− }
2029− }
2030−
2031− let mut included_children = IncludedChildren::default();
2032− for prop in query.property_settings(query_match.pattern_index) {
2033− match prop.key.as_ref() {
2034− // In addition to specifying the language name via the text of a
2035− // captured node, it can also be hard-coded via a `#set!` predicate
2036− // that sets the injection.language key.
2037− "injection.language" => {
2038− if language_name.is_none() {
2039− language_name = prop.value.as_ref().map(|s| s.as_ref().into())
2040− }
2041− }
2042−
2043− // By default, injections do not include the *children* of an
2044− // `injection.content` node - only the ranges that belong to the
2045− // node itself. This can be changed using a `#set!` predicate that
2046− // sets the `injection.include-children` key.
2047− "injection.include-children" => included_children = IncludedChildren::All,
2048−
2049− // Some queries might only exclude named children but include unnamed
2050− // children in their `injection.content` node. This can be enabled using
2051− // a `#set!` predicate that sets the `injection.include-unnamed-children` key.
2052− "injection.include-unnamed-children" => included_children = IncludedChildren::Unnamed,
2053− _ => {}
2054− }
2055− }
2056−
2057− (language_name, content_node, included_children)
2058}
2321}
2059
2322
2323+const SHEBANG: &str = r"#!\s*(?:\S*[/\\](?:env\s+(?:\-\S+\s+)*)?)?([^\s\.\d]+)";
2324+
2060pub struct Merge<I> {
2325pub struct Merge<I> {
2061 iter: I,
2326 iter: I,
2062 spans: Box<dyn Iterator<Item = (usize, std::ops::Range<usize>)>>,
2327 spans: Box<dyn Iterator<Item = (usize, std::ops::Range<usize>)>>,
@@ -2272,7 +2537,10 @@ mod test {
2272 "#,
2537 "#,
2273 );
2538 );
2274
2539
2275− let loader = Loader::new(Configuration { language: vec![] });
2540+ let loader = Loader::new(Configuration {
2541+ language: vec![],
2542+ language_server: HashMap::new(),
2543+ });
2276 let language = get_language("rust").unwrap();
2544 let language = get_language("rust").unwrap();
2277
2545
2278 let query = Query::new(language, query_str).unwrap();
2546 let query = Query::new(language, query_str).unwrap();
@@ -2280,7 +2548,7 @@ mod test {
2280 let mut cursor = QueryCursor::new();
2548 let mut cursor = QueryCursor::new();
2281
2549
2282 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2550 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2283− let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2551+ let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
2284
2552
2285 let root = syntax.tree().root_node();
2553 let root = syntax.tree().root_node();
2286 let mut test = |capture, range| {
2554 let mut test = |capture, range| {
@@ -2331,7 +2599,10 @@ mod test {
2331 .map(String::from)
2599 .map(String::from)
2332 .collect();
2600 .collect();
2333
2601
2334− let loader = Loader::new(Configuration { language: vec![] });
2602+ let loader = Loader::new(Configuration {
2603+ language: vec![],
2604+ language_server: HashMap::new(),
2605+ });
2335
2606
2336 let language = get_language("rust").unwrap();
2607 let language = get_language("rust").unwrap();
2337 let config = HighlightConfiguration::new(
2608 let config = HighlightConfiguration::new(
@@ -2351,7 +2622,7 @@ mod test {
2351 fn main() {}
2622 fn main() {}
2352 ",
2623 ",
2353 );
2624 );
2354− let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2625+ let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
2355 let tree = syntax.tree();
2626 let tree = syntax.tree();
2356 let root = tree.root_node();
2627 let root = tree.root_node();
2357 assert_eq!(root.kind(), "source_file");
2628 assert_eq!(root.kind(), "source_file");
@@ -2378,7 +2649,7 @@ mod test {
2378 &doc,
2649 &doc,
2379 vec![(6, 11, Some("test".into())), (12, 17, None)].into_iter(),
2650 vec![(6, 11, Some("test".into())), (12, 17, None)].into_iter(),
2380 );
2651 );
2381− let edits = generate_edits(&doc, transaction.changes());
2652+ let edits = generate_edits(doc.slice(..), transaction.changes());
2382 // transaction.apply(&mut state);
2653 // transaction.apply(&mut state);
2383
2654
2384 assert_eq!(
2655 assert_eq!(
@@ -2407,7 +2678,7 @@ mod test {
2407 let mut doc = Rope::from("fn test() {}");
2678 let mut doc = Rope::from("fn test() {}");
2408 let transaction =
2679 let transaction =
2409 Transaction::change(&doc, vec![(8, 8, Some("a: u32".into()))].into_iter());
2680 Transaction::change(&doc, vec![(8, 8, Some("a: u32".into()))].into_iter());
2410− let edits = generate_edits(&doc, transaction.changes());
2681+ let edits = generate_edits(doc.slice(..), transaction.changes());
2411 transaction.apply(&mut doc);
2682 transaction.apply(&mut doc);
2412
2683
2413 assert_eq!(doc, "fn test(a: u32) {}");
2684 assert_eq!(doc, "fn test(a: u32) {}");
@@ -2434,11 +2705,14 @@ mod test {
2434 ) {
2705 ) {
2435 let source = Rope::from_str(source);
2706 let source = Rope::from_str(source);
2436
2707
2437− let loader = Loader::new(Configuration { language: vec![] });
2708+ let loader = Loader::new(Configuration {
2709+ language: vec![],
2710+ language_server: HashMap::new(),
2711+ });
2438 let language = get_language(language_name).unwrap();
2712 let language = get_language(language_name).unwrap();
2439
2713
2440 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2714 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2441− let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2715+ let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
2442
2716
2443 let root = syntax
2717 let root = syntax
2444 .tree()
2718 .tree()
helix-core/src/test.rs+139−18
@@ -1,7 +1,9 @@
1//! Test helpers.
1//! Test helpers.
2use crate::{Range, Selection};
2use crate::{Range, Selection};
3+use ropey::Rope;
3use smallvec::SmallVec;
4use smallvec::SmallVec;
4use std::cmp::Reverse;
5use std::cmp::Reverse;
6+use unicode_segmentation::UnicodeSegmentation;
5
7
6/// Convert annotated test string to test string and selection.
8/// Convert annotated test string to test string and selection.
7///
9///
@@ -10,6 +12,10 @@ use std::cmp::Reverse;
10/// `#[` for primary selection with head after anchor followed by `|]#`.
12/// `#[` for primary selection with head after anchor followed by `|]#`.
11/// `#(` for secondary selection with head after anchor followed by `|)#`.
13/// `#(` for secondary selection with head after anchor followed by `|)#`.
12///
14///
15+/// If the selection contains any LF or CRLF sequences, which are immediately
16+/// followed by the same grapheme, then the subsequent one is removed. This is
17+/// to allow representing having the cursor over the end of the line.
18+///
13/// # Examples
19/// # Examples
14///
20///
15/// ```
21/// ```
@@ -30,23 +36,23 @@ use std::cmp::Reverse;
30pub fn print(s: &str) -> (String, Selection) {
36pub fn print(s: &str) -> (String, Selection) {
31 let mut primary_idx = None;
37 let mut primary_idx = None;
32 let mut ranges = SmallVec::new();
38 let mut ranges = SmallVec::new();
33− let mut iter = s.chars().peekable();
39+ let mut iter = UnicodeSegmentation::graphemes(s, true).peekable();
34 let mut left = String::with_capacity(s.len());
40 let mut left = String::with_capacity(s.len());
35
41
36 'outer: while let Some(c) = iter.next() {
42 'outer: while let Some(c) = iter.next() {
37 let start = left.chars().count();
43 let start = left.chars().count();
38
44
39− if c != '#' {
45+ if c != "#" {
40− left.push(c);
46+ left.push_str(c);
41 continue;
47 continue;
42 }
48 }
43
49
44 let (is_primary, close_pair) = match iter.next() {
50 let (is_primary, close_pair) = match iter.next() {
45− Some('[') => (true, ']'),
51+ Some("[") => (true, "]"),
46− Some('(') => (false, ')'),
52+ Some("(") => (false, ")"),
47 Some(ch) => {
53 Some(ch) => {
48 left.push('#');
54 left.push('#');
49− left.push(ch);
55+ left.push_str(ch);
50 continue;
56 continue;
51 }
57 }
52 None => break,
58 None => break,
@@ -56,24 +62,45 @@ pub fn print(s: &str) -> (String, Selection) {
56 panic!("primary `#[` already appeared {:?} {:?}", left, s);
62 panic!("primary `#[` already appeared {:?} {:?}", left, s);
57 }
63 }
58
64
59− let head_at_beg = iter.next_if_eq(&'|').is_some();
65+ let head_at_beg = iter.next_if_eq(&"|").is_some();
66+ let last_grapheme = |s: &str| {
67+ UnicodeSegmentation::graphemes(s, true)
68+ .last()
69+ .map(String::from)
70+ };
60
71
61 while let Some(c) = iter.next() {
72 while let Some(c) = iter.next() {
73+ let next = iter.peek();
74+ let mut prev = last_grapheme(left.as_str());
75+
62− if !(c == close_pair && iter.peek() == Some(&'#')) {
76+ if !(c == close_pair && next == Some(&"#")) {
63− left.push(c);
77+ left.push_str(c);
64 continue;
78 continue;
65 }
79 }
66
80
67 if !head_at_beg {
81 if !head_at_beg {
68− let prev = left.pop().unwrap();
82+ match &prev {
69− if prev != '|' {
83+ Some(p) if p != "|" => {
70− left.push(prev);
71− left.push(c);
84+ left.push_str(c);
72− continue;
85+ continue;
86+ }
87+ Some(p) if p == "|" => {
88+ left.pop().unwrap(); // pop the |
89+ prev = last_grapheme(left.as_str());
90+ }
91+ _ => (),
73 }
92 }
74 }
93 }
75
94
76 iter.next(); // skip "#"
95 iter.next(); // skip "#"
96+ let next = iter.peek();
97+
98+ // skip explicit line end inside selection
99+ if (prev == Some(String::from("\r\n")) || prev == Some(String::from("\n")))
100+ && next.map(|n| String::from(*n)) == prev
101+ {
102+ iter.next();
103+ }
77
104
78 if is_primary {
105 if is_primary {
79 primary_idx = Some(ranges.len());
106 primary_idx = Some(ranges.len());
@@ -118,14 +145,16 @@ pub fn print(s: &str) -> (String, Selection) {
118/// use smallvec::smallvec;
145/// use smallvec::smallvec;
119///
146///
120/// assert_eq!(
147/// assert_eq!(
121−/// plain("abc", Selection::new(smallvec![Range::new(0, 1), Range::new(3, 2)], 0)),
148+/// plain("abc", &Selection::new(smallvec![Range::new(0, 1), Range::new(3, 2)], 0)),
122/// "#[a|]#b#(|c)#".to_owned()
149/// "#[a|]#b#(|c)#".to_owned()
123/// );
150/// );
124/// ```
151/// ```
125−pub fn plain(s: &str, selection: Selection) -> String {
152+pub fn plain<R: Into<Rope>>(s: R, selection: &Selection) -> String {
153+ let s = s.into();
126 let primary = selection.primary_index();
154 let primary = selection.primary_index();
127− let mut out = String::with_capacity(s.len() + 5 * selection.len());
155+ let mut out = String::with_capacity(s.len_bytes() + 5 * selection.len());
128− out.push_str(s);
156+ out.push_str(&s.to_string());
157+
129 let mut insertion: Vec<_> = selection
158 let mut insertion: Vec<_> = selection
130 .iter()
159 .iter()
131 .enumerate()
160 .enumerate()
@@ -138,7 +167,9 @@ pub fn plain(s: &str, selection: Selection) -> String {
138 (false, false) => [(range.anchor, ")#"), (range.head, "#(|")],
167 (false, false) => [(range.anchor, ")#"), (range.head, "#(|")],
139 }
168 }
140 })
169 })
170+ .map(|(char_idx, marker)| (s.char_to_byte(char_idx), marker))
141 .collect();
171 .collect();
172+
142 // insert in reverse order
173 // insert in reverse order
143 insertion.sort_unstable_by_key(|k| Reverse(k.0));
174 insertion.sort_unstable_by_key(|k| Reverse(k.0));
144 for (i, s) in insertion {
175 for (i, s) in insertion {
@@ -262,4 +293,94 @@ mod test {
262 print("hello #[|👨‍👩‍👧‍👦]# goodbye")
293 print("hello #[|👨‍👩‍👧‍👦]# goodbye")
263 );
294 );
264 }
295 }
296+
297+ #[test]
298+ fn plain_single() {
299+ assert_eq!("#[|h]#ello", plain("hello", &Selection::single(1, 0)));
300+ assert_eq!("#[h|]#ello", plain("hello", &Selection::single(0, 1)));
301+ assert_eq!("#[|hell]#o", plain("hello", &Selection::single(4, 0)));
302+ assert_eq!("#[hell|]#o", plain("hello", &Selection::single(0, 4)));
303+ assert_eq!("#[|hello]#", plain("hello", &Selection::single(5, 0)));
304+ assert_eq!("#[hello|]#", plain("hello", &Selection::single(0, 5)));
305+ }
306+
307+ #[test]
308+ fn plain_multi() {
309+ assert_eq!(
310+ plain(
311+ "hello",
312+ &Selection::new(
313+ SmallVec::from_slice(&[Range::new(1, 0), Range::new(5, 4)]),
314+ 0
315+ )
316+ ),
317+ String::from("#[|h]#ell#(|o)#")
318+ );
319+ assert_eq!(
320+ plain(
321+ "hello",
322+ &Selection::new(
323+ SmallVec::from_slice(&[Range::new(0, 1), Range::new(4, 5)]),
324+ 0
325+ )
326+ ),
327+ String::from("#[h|]#ell#(o|)#")
328+ );
329+ assert_eq!(
330+ plain(
331+ "hello",
332+ &Selection::new(
333+ SmallVec::from_slice(&[Range::new(2, 0), Range::new(5, 3)]),
334+ 0
335+ )
336+ ),
337+ String::from("#[|he]#l#(|lo)#")
338+ );
339+ assert_eq!(
340+ plain(
341+ "hello\r\nhello\r\nhello\r\n",
342+ &Selection::new(
343+ SmallVec::from_slice(&[
344+ Range::new(7, 5),
345+ Range::new(21, 19),
346+ Range::new(14, 12)
347+ ]),
348+ 0
349+ )
350+ ),
351+ String::from("hello#[|\r\n]#hello#(|\r\n)#hello#(|\r\n)#")
352+ );
353+ }
354+
355+ #[test]
356+ fn plain_multi_byte_code_point() {
357+ assert_eq!(
358+ plain("„“", &Selection::single(1, 0)),
359+ String::from("#[|„]#“")
360+ );
361+ assert_eq!(
362+ plain("„“", &Selection::single(2, 1)),
363+ String::from("„#[|“]#")
364+ );
365+ assert_eq!(
366+ plain("„“", &Selection::single(0, 1)),
367+ String::from("#[„|]#“")
368+ );
369+ assert_eq!(
370+ plain("„“", &Selection::single(1, 2)),
371+ String::from("„#[“|]#")
372+ );
373+ assert_eq!(
374+ plain("they said „hello“", &Selection::single(11, 10)),
375+ String::from("they said #[|„]#hello“")
376+ );
377+ }
378+
379+ #[test]
380+ fn plain_multi_code_point_grapheme() {
381+ assert_eq!(
382+ plain("hello 👨‍👩‍👧‍👦 goodbye", &Selection::single(13, 6)),
383+ String::from("hello #[|👨‍👩‍👧‍👦]# goodbye")
384+ );
385+ }
265}
386}
helix-core/src/text_annotations.rs+26−26
@@ -1,5 +1,4 @@
1use std::cell::Cell;
1use std::cell::Cell;
2−use std::convert::identity;
3use std::ops::Range;
2use std::ops::Range;
4use std::rc::Rc;
3use std::rc::Rc;
5
4
@@ -15,6 +14,15 @@ pub struct InlineAnnotation {
15 pub char_idx: usize,
14 pub char_idx: usize,
16}
15}
17
16
17+impl InlineAnnotation {
18+ pub fn new(char_idx: usize, text: impl Into<Tendril>) -> Self {
19+ Self {
20+ char_idx,
21+ text: text.into(),
22+ }
23+ }
24+}
25+
18/// Represents a **single Grapheme** that is part of the document
26/// Represents a **single Grapheme** that is part of the document
19/// that start at `char_idx` that will be replaced with
27/// that start at `char_idx` that will be replaced with
20/// a different `grapheme`.
28/// a different `grapheme`.
@@ -33,22 +41,13 @@ pub struct InlineAnnotation {
33/// use helix_core::text_annotations::Overlay;
41/// use helix_core::text_annotations::Overlay;
34///
42///
35/// // replaces a
43/// // replaces a
36−/// Overlay {
44+/// Overlay::new(0, "X");
37−/// char_idx: 0,
38−/// grapheme: "X".into(),
39−/// };
40///
45///
41/// // replaces X͎̊͢͜͝͡
46/// // replaces X͎̊͢͜͝͡
42−/// Overlay{
47+/// Overlay::new(1, "\t");
43−/// char_idx: 1,
44−/// grapheme: "\t".into(),
45−/// };
46///
48///
47/// // replaces b
49/// // replaces b
48−/// Overlay{
50+/// Overlay::new(6, "X̢̢̟͖̲͌̋̇͑͝");
49−/// char_idx: 6,
50−/// grapheme: "X̢̢̟͖̲͌̋̇͑͝".into(),
51−/// };
52/// ```
51/// ```
53///
52///
54/// The following examples are invalid uses
53/// The following examples are invalid uses
@@ -57,16 +56,10 @@ pub struct InlineAnnotation {
57/// use helix_core::text_annotations::Overlay;
56/// use helix_core::text_annotations::Overlay;
58///
57///
59/// // overlay is not aligned at grapheme boundary
58/// // overlay is not aligned at grapheme boundary
60−/// Overlay{
59+/// Overlay::new(3, "x");
61−/// char_idx: 3,
62−/// grapheme: "x".into(),
63−/// };
64///
60///
65/// // overlay contains multiple graphemes
61/// // overlay contains multiple graphemes
66−/// Overlay{
62+/// Overlay::new(0, "xy");
67−/// char_idx: 0,
68−/// grapheme: "xy".into(),
69−/// };
70/// ```
63/// ```
71#[derive(Debug, Clone)]
64#[derive(Debug, Clone)]
72pub struct Overlay {
65pub struct Overlay {
@@ -74,6 +67,15 @@ pub struct Overlay {
74 pub grapheme: Tendril,
67 pub grapheme: Tendril,
75}
68}
76
69
70+impl Overlay {
71+ pub fn new(char_idx: usize, grapheme: impl Into<Tendril>) -> Self {
72+ Self {
73+ char_idx,
74+ grapheme: grapheme.into(),
75+ }
76+ }
77+}
78+
77/// Line annotations allow for virtual text between normal
79/// Line annotations allow for virtual text between normal
78/// text lines. They cause `height` empty lines to be inserted
80/// text lines. They cause `height` empty lines to be inserted
79/// below the document line that contains `anchor_char_idx`.
81/// below the document line that contains `anchor_char_idx`.
@@ -110,9 +112,7 @@ impl<A, M> Layer<A, M> {
110 pub fn reset_pos(&self, char_idx: usize, get_char_idx: impl Fn(&A) -> usize) {
112 pub fn reset_pos(&self, char_idx: usize, get_char_idx: impl Fn(&A) -> usize) {
111 let new_index = self
113 let new_index = self
112 .annotations
114 .annotations
113− .binary_search_by_key(&char_idx, get_char_idx)
115+ .partition_point(|annot| get_char_idx(annot) < char_idx);
114− .unwrap_or_else(identity);
115−
116 self.current_index.set(new_index);
116 self.current_index.set(new_index);
117 }
117 }
118
118
@@ -172,7 +172,7 @@ impl TextAnnotations {
172 for char_idx in char_range {
172 for char_idx in char_range {
173 if let Some((_, Some(highlight))) = self.overlay_at(char_idx) {
173 if let Some((_, Some(highlight))) = self.overlay_at(char_idx) {
174 // we don't know the number of chars the original grapheme takes
174 // we don't know the number of chars the original grapheme takes
175− // however it doesn't matter as highlight bounderies are automatically
175+ // however it doesn't matter as highlight boundaries are automatically
176 // aligned to grapheme boundaries in the rendering code
176 // aligned to grapheme boundaries in the rendering code
177 highlights.push((highlight.0, char_idx..char_idx + 1))
177 highlights.push((highlight.0, char_idx..char_idx + 1))
178 }
178 }
@@ -203,7 +203,7 @@ impl TextAnnotations {
203
203
204 /// Add new grapheme overlays.
204 /// Add new grapheme overlays.
205 ///
205 ///
206− /// The overlayed grapheme will be rendered with `highlight`
206+ /// The overlaid grapheme will be rendered with `highlight`
207 /// patched on top of `ui.text`.
207 /// patched on top of `ui.text`.
208 ///
208 ///
209 /// The overlays **must be sorted** by their `char_idx`.
209 /// The overlays **must be sorted** by their `char_idx`.
helix-core/src/textobject.rs+3−3
@@ -437,7 +437,7 @@ mod test {
437 let text = Rope::from(s.as_str());
437 let text = Rope::from(s.as_str());
438 let selection = selection
438 let selection = selection
439 .transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Inside, 1));
439 .transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Inside, 1));
440− let actual = crate::test::plain(&s, selection);
440+ let actual = crate::test::plain(s.as_ref(), &selection);
441 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
441 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
442 }
442 }
443 }
443 }
@@ -460,7 +460,7 @@ mod test {
460 let text = Rope::from(s.as_str());
460 let text = Rope::from(s.as_str());
461 let selection = selection
461 let selection = selection
462 .transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Inside, 2));
462 .transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Inside, 2));
463− let actual = crate::test::plain(&s, selection);
463+ let actual = crate::test::plain(s.as_ref(), &selection);
464 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
464 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
465 }
465 }
466 }
466 }
@@ -491,7 +491,7 @@ mod test {
491 let text = Rope::from(s.as_str());
491 let text = Rope::from(s.as_str());
492 let selection = selection
492 let selection = selection
493 .transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Around, 1));
493 .transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Around, 1));
494− let actual = crate::test::plain(&s, selection);
494+ let actual = crate::test::plain(s.as_ref(), &selection);
495 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
495 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
496 }
496 }
497 }
497 }
helix-core/src/transaction.rs+309−51
@@ -1,8 +1,12 @@
1+use ropey::RopeSlice;
2+use smallvec::SmallVec;
3+
1−use crate::{Range, Rope, Selection, Tendril};
4+use crate::{chars::char_is_word, Range, Rope, Selection, Tendril};
2−use std::borrow::Cow;
5+use std::{borrow::Cow, iter::once};
3
6
4/// (from, to, replacement)
7/// (from, to, replacement)
5pub type Change = (usize, usize, Option<Tendril>);
8pub type Change = (usize, usize, Option<Tendril>);
9+pub type Deletion = (usize, usize);
6
10
7// TODO: pub(crate)
11// TODO: pub(crate)
8#[derive(Debug, Clone, PartialEq, Eq)]
12#[derive(Debug, Clone, PartialEq, Eq)]
@@ -19,6 +23,30 @@ pub enum Operation {
19pub enum Assoc {
23pub enum Assoc {
20 Before,
24 Before,
21 After,
25 After,
26+ /// Acts like `After` if a word character is inserted
27+ /// after the position, otherwise acts like `Before`
28+ AfterWord,
29+ /// Acts like `Before` if a word character is inserted
30+ /// before the position, otherwise acts like `After`
31+ BeforeWord,
32+}
33+
34+impl Assoc {
35+ /// Whether to stick to gaps
36+ fn stay_at_gaps(self) -> bool {
37+ !matches!(self, Self::BeforeWord | Self::AfterWord)
38+ }
39+
40+ fn insert_offset(self, s: &str) -> usize {
41+ let chars = s.chars().count();
42+ match self {
43+ Assoc::After => chars,
44+ Assoc::AfterWord => s.chars().take_while(|&c| char_is_word(c)).count(),
45+ // return position before inserted text
46+ Assoc::Before => 0,
47+ Assoc::BeforeWord => chars - s.chars().rev().take_while(|&c| char_is_word(c)).count(),
48+ }
49+ }
22}
50}
23
51
24#[derive(Debug, Default, Clone, PartialEq, Eq)]
52#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -39,7 +67,7 @@ impl ChangeSet {
39 }
67 }
40
68
41 #[must_use]
69 #[must_use]
42− pub fn new(doc: &Rope) -> Self {
70+ pub fn new(doc: RopeSlice) -> Self {
43 let len = doc.len_chars();
71 let len = doc.len_chars();
44 Self {
72 Self {
45 changes: Vec::new(),
73 changes: Vec::new(),
@@ -323,20 +351,75 @@ impl ChangeSet {
323 self.changes.is_empty() || self.changes == [Operation::Retain(self.len)]
351 self.changes.is_empty() || self.changes == [Operation::Retain(self.len)]
324 }
352 }
325
353
326− /// Map a position through the changes.
354+ /// Map a (mostly) *sorted* list of positions through the changes.
327 ///
355 ///
328− /// `assoc` indicates which size to associate the position with. `Before` will keep the
356+ /// This is equivalent to updating each position with `map_pos`:
329− /// position close to the character before, and will place it before insertions over that
357+ ///
330− /// range, or at that point. `After` will move it forward, placing it at the end of such
358+ /// ``` no-compile
331− /// insertions.
359+ /// for (pos, assoc) in positions {
332− pub fn map_pos(&self, pos: usize, assoc: Assoc) -> usize {
360+ /// *pos = changes.map_pos(*pos, assoc);
361+ /// }
362+ /// ```
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)>) {
333 use Operation::*;
369 use Operation::*;
370+
371+ let mut positions = positions.peekable();
372+
334 let mut old_pos = 0;
373 let mut old_pos = 0;
335 let mut new_pos = 0;
374 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+ }
336
414
415+ let Some((i, change)) = iter.next() else {
416+ map!(
417+ |pos, _| (old_pos == pos).then_some(new_pos),
337− let mut iter = self.changes.iter().peekable();
418+ self.changes.len()
419+ );
420+ break;
421+ };
338
422
339− while let Some(change) = iter.next() {
340 let len = match change {
423 let len = match change {
341 Delete(i) | Retain(i) => *i,
424 Delete(i) | Retain(i) => *i,
342 Insert(_) => 0,
425 Insert(_) => 0,
@@ -345,61 +428,65 @@ impl ChangeSet {
345
428
346 match change {
429 match change {
347 Retain(_) => {
430 Retain(_) => {
348− if old_end > pos {
431+ map!(
349− return new_pos + (pos - old_pos);
432+ |pos, _| (old_end > pos).then_some(new_pos + (pos - old_pos)),
350− }
433+ i
434+ );
351 new_pos += len;
435 new_pos += len;
352 }
436 }
353 Delete(_) => {
437 Delete(_) => {
354 // in range
438 // in range
355− if old_end > pos {
439+ map!(|pos, _| (old_end > pos).then_some(new_pos), i);
356− return new_pos;
357− }
358 }
440 }
359 Insert(s) => {
441 Insert(s) => {
360− let ins = s.chars().count();
361−
362 // a subsequent delete means a replace, consume it
442 // a subsequent delete means a replace, consume it
363− if let Some(Delete(len)) = iter.peek() {
443+ if let Some((_, Delete(len))) = iter.peek() {
364 iter.next();
444 iter.next();
365
445
366 old_end = old_pos + len;
446 old_end = old_pos + len;
367 // in range of replaced text
447 // in range of replaced text
448+ map!(
368− if old_end > pos {
449+ |pos, assoc: Assoc| (old_end > pos).then(|| {
369− // at point or tracking before
450+ // at point or tracking before
370− if pos == old_pos || assoc == Assoc::Before {
451+ if pos == old_pos && assoc.stay_at_gaps() {
371− return new_pos;
452+ new_pos
372− } else {
453+ } else {
373− // place to end of insert
454+ // place to end of insert
374− return new_pos + ins;
455+ new_pos + assoc.insert_offset(s)
456+ }
375− }
457+ }),
376− }
458+ i
459+ );
377 } else {
460 } else {
378 // at insert point
461 // at insert point
462+ map!(
379− if old_pos == pos {
463+ |pos, assoc: Assoc| (old_pos == pos).then(|| {
380− // return position before inserted text
464+ // return position before inserted text
381− if assoc == Assoc::Before {
465+ new_pos + assoc.insert_offset(s)
382− return new_pos;
383− } else {
384− // after text
385− return new_pos + ins;
386− }
466+ }),
387− }
467+ i
468+ );
388 }
469 }
389
470
390− new_pos += ins;
471+ new_pos += s.chars().count();
391 }
472 }
392 }
473 }
393 old_pos = old_end;
474 old_pos = old_end;
394 }
475 }
476+ let out_of_bounds: Vec<_> = positions.collect();
395
477
396− if pos > old_pos {
397− panic!(
398− "Position {} is out of range for changeset len {}!",
478+ panic!("Positions {out_of_bounds:?} are out of range for changeset len {old_pos}!",)
399− pos, old_pos
479+ }
400− )
480+
401− }
481+ /// Map a position through the changes.
402− new_pos
482+ ///
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
403 }
490 }
404
491
405 pub fn changes_iter(&self) -> ChangeIterator {
492 pub fn changes_iter(&self) -> ChangeIterator {
@@ -419,7 +506,7 @@ impl Transaction {
419 /// Create a new, empty transaction.
506 /// Create a new, empty transaction.
420 pub fn new(doc: &Rope) -> Self {
507 pub fn new(doc: &Rope) -> Self {
421 Self {
508 Self {
422− changes: ChangeSet::new(doc),
509+ changes: ChangeSet::new(doc.slice(..)),
423 selection: None,
510 selection: None,
424 }
511 }
425 }
512 }
@@ -466,6 +553,33 @@ impl Transaction {
466 self
553 self
467 }
554 }
468
555
556+ /// Generate a transaction from a set of potentially overlapping changes. The `change_ranges`
557+ /// iterator yield the range (of removed text) in the old document for each edit. If any change
558+ /// overlaps with a range overlaps with a previous range then that range is ignored.
559+ ///
560+ /// The `process_change` callback is called for each edit that is not ignored (in the order
561+ /// yielded by `changes`) and should return the new text that the associated range will be
562+ /// replaced with.
563+ ///
564+ /// To make this function more flexible the iterator can yield additional data for each change
565+ /// that is passed to `process_change`
566+ pub fn change_ignore_overlapping<T>(
567+ doc: &Rope,
568+ change_ranges: impl Iterator<Item = (usize, usize, T)>,
569+ mut process_change: impl FnMut(usize, usize, T) -> Option<Tendril>,
570+ ) -> Self {
571+ let mut last = 0;
572+ let changes = change_ranges.filter_map(|(from, to, data)| {
573+ if from < last {
574+ return None;
575+ }
576+ let tendril = process_change(from, to, data);
577+ last = to;
578+ Some((from, to, tendril))
579+ });
580+ Self::change(doc, changes)
581+ }
582+
469 /// Generate a transaction from a set of changes.
583 /// Generate a transaction from a set of changes.
470 pub fn change<I>(doc: &Rope, changes: I) -> Self
584 pub fn change<I>(doc: &Rope, changes: I) -> Self
471 where
585 where
@@ -505,6 +619,46 @@ impl Transaction {
505 Self::from(changeset)
619 Self::from(changeset)
506 }
620 }
507
621
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+
508 /// Generate a transaction with a change per selection range.
662 /// Generate a transaction with a change per selection range.
509 pub fn change_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
663 pub fn change_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
510 where
664 where
@@ -513,6 +667,54 @@ impl Transaction {
513 Self::change(doc, selection.iter().map(f))
667 Self::change(doc, selection.iter().map(f))
514 }
668 }
515
669
670+ pub fn change_by_selection_ignore_overlapping(
671+ doc: &Rope,
672+ selection: &Selection,
673+ mut change_range: impl FnMut(&Range) -> (usize, usize),
674+ mut create_tendril: impl FnMut(usize, usize) -> Option<Tendril>,
675+ ) -> (Transaction, Selection) {
676+ let mut last_selection_idx = None;
677+ let mut new_primary_idx = None;
678+ let mut ranges: SmallVec<[Range; 1]> = SmallVec::new();
679+ let process_change = |change_start, change_end, (idx, range): (usize, &Range)| {
680+ // update the primary idx
681+ if idx == selection.primary_index() {
682+ new_primary_idx = Some(idx);
683+ } else if new_primary_idx.is_none() {
684+ if idx > selection.primary_index() {
685+ new_primary_idx = last_selection_idx;
686+ } else {
687+ last_selection_idx = Some(idx);
688+ }
689+ }
690+ ranges.push(*range);
691+ create_tendril(change_start, change_end)
692+ };
693+ let transaction = Self::change_ignore_overlapping(
694+ doc,
695+ selection.iter().enumerate().map(|range| {
696+ let (change_start, change_end) = change_range(range.1);
697+ (change_start, change_end, range)
698+ }),
699+ process_change,
700+ );
701+
702+ (
703+ transaction,
704+ Selection::new(ranges, new_primary_idx.unwrap_or(0)),
705+ )
706+ }
707+
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+
516 /// Insert text at each selection head.
718 /// Insert text at each selection head.
517 pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self {
719 pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self {
518 Self::change_by_selection(doc, selection, |range| {
720 Self::change_by_selection(doc, selection, |range| {
@@ -685,6 +887,62 @@ mod test {
685 };
887 };
686 assert_eq!(cs.map_pos(2, Assoc::Before), 2);
888 assert_eq!(cs.map_pos(2, Assoc::Before), 2);
687 assert_eq!(cs.map_pos(2, Assoc::After), 2);
889 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);
688 }
946 }
689
947
690 #[test]
948 #[test]
@@ -751,9 +1009,9 @@ mod test {
751 #[test]
1009 #[test]
752 fn combine_with_empty() {
1010 fn combine_with_empty() {
753 let empty = Rope::from("");
1011 let empty = Rope::from("");
754− let a = ChangeSet::new(&empty);
1012+ let a = ChangeSet::new(empty.slice(..));
755
1013
756− let mut b = ChangeSet::new(&empty);
1014+ let mut b = ChangeSet::new(empty.slice(..));
757 b.insert("a".into());
1015 b.insert("a".into());
758
1016
759 let changes = a.compose(b);
1017 let changes = a.compose(b);
@@ -767,9 +1025,9 @@ mod test {
767 const TEST_CASE: &str = "Hello, これはヘリックスエディターです!";
1025 const TEST_CASE: &str = "Hello, これはヘリックスエディターです!";
768
1026
769 let empty = Rope::from("");
1027 let empty = Rope::from("");
770− let a = ChangeSet::new(&empty);
1028+ let a = ChangeSet::new(empty.slice(..));
771
1029
772− let mut b = ChangeSet::new(&empty);
1030+ let mut b = ChangeSet::new(empty.slice(..));
773 b.insert(TEST_CASE.into());
1031 b.insert(TEST_CASE.into());
774
1032
775 let changes = a.compose(b);
1033 let changes = a.compose(b);
helix-core/src/wrap.rs+4−2
@@ -1,7 +1,9 @@
1use smartstring::{LazyCompact, SmartString};
1use smartstring::{LazyCompact, SmartString};
2+use textwrap::{Options, WordSplitter::NoHyphenation};
2
3
3/// Given a slice of text, return the text re-wrapped to fit it
4/// Given a slice of text, return the text re-wrapped to fit it
4/// within the given width.
5/// within the given width.
5−pub fn reflow_hard_wrap(text: &str, max_line_len: usize) -> SmartString<LazyCompact> {
6+pub fn reflow_hard_wrap(text: &str, text_width: usize) -> SmartString<LazyCompact> {
7+ let options = Options::new(text_width).word_splitter(NoHyphenation);
6− textwrap::refill(text, max_line_len).into()
8+ textwrap::refill(text, options).into()
7}
9}
helix-core/tests/data/indent/cpp.cppnew file+48
@@ -0,0 +1,48 @@
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.rsdeleted−1
@@ -1 +0,0 @@
1−../../../src/indent.rs
helix-core/tests/data/indent/languages.toml+13
@@ -11,3 +11,16 @@ 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+187−26
@@ -1,36 +1,192 @@
1use helix_core::{
1use helix_core::{
2− indent::{treesitter_indent_for_pos, IndentStyle},
2+ indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle},
3− syntax::Loader,
3+ syntax::{Configuration, Loader},
4 Syntax,
4 Syntax,
5};
5};
6+use ropey::Rope;
6−use std::path::PathBuf;
7+use std::{ops::Range, path::PathBuf, process::Command};
7
8
8#[test]
9#[test]
9fn test_treesitter_indent_rust() {
10fn test_treesitter_indent_rust() {
10− test_treesitter_indent("rust.rs", "source.rust");
11+ standard_treesitter_test("rust.rs", "source.rust");
11}
12}
13+
14+#[test]
15+fn test_treesitter_indent_cpp() {
16+ standard_treesitter_test("cpp.cpp", "source.cpp");
17+}
18+
19+#[test]
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+}
121+
12#[test]
122#[test]
13−fn test_treesitter_indent_rust_2() {
123+fn test_indent_level_for_line_with_spaces() {
14− test_treesitter_indent("indent.rs", "source.rust");
124+ let tab_width: usize = 4;
15− // TODO Use commands.rs as indentation test.
125+ let indent_width: usize = 4;
16− // Currently this fails because we can't align the parameters of a closure yet
126+
17− // test_treesitter_indent("commands.rs", "source.rust");
127+ let line = ropey::Rope::from_str(" Indented with 8 spaces");
128+
129+ let indent_level = indent_level_for_line(line.slice(0..), tab_width, indent_width);
130+ assert_eq!(indent_level, 2)
18}
131}
19
132
20−fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
133+#[test]
134+fn test_indent_level_for_line_with_tabs() {
135+ let tab_width: usize = 4;
136+ let indent_width: usize = 4;
137+
138+ let line = ropey::Rope::from_str("\t\tIndented with 2 tabs");
139+
140+ let indent_level = indent_level_for_line(line.slice(0..), tab_width, indent_width);
141+ assert_eq!(indent_level, 2)
142+}
143+
144+#[test]
145+fn test_indent_level_for_line_with_spaces_and_tabs() {
146+ let tab_width: usize = 4;
147+ let indent_width: usize = 4;
148+
149+ let line = ropey::Rope::from_str(" \t \tIndented with mix of spaces and tabs");
150+
151+ let indent_level = indent_level_for_line(line.slice(0..), tab_width, indent_width);
152+ assert_eq!(indent_level, 2)
153+}
154+
155+fn indent_tests_dir() -> PathBuf {
21 let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
156 let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
22 test_dir.push("tests/data/indent");
157 test_dir.push("tests/data/indent");
158+ test_dir
159+}
23
160
24− let mut test_file = test_dir.clone();
161+fn indent_test_path(name: &str) -> PathBuf {
25− test_file.push(file_name);
162+ let mut path = indent_tests_dir();
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);
26− let test_file = std::fs::File::open(test_file).unwrap();
176+ let test_file = std::fs::File::open(test_path).unwrap();
27 let doc = ropey::Rope::from_reader(test_file).unwrap();
177 let doc = ropey::Rope::from_reader(test_file).unwrap();
178+ test_treesitter_indent(file_name, doc, lang_scope, Vec::new())
179+}
28
180
29− let mut config_file = test_dir;
181+/// Test that all the lines in the given file are indented as expected.
30− config_file.push("languages.toml");
182+/// ignored_lines is a list of (1-indexed) line ranges that are excluded from this test.
31− let config = std::fs::read_to_string(config_file).unwrap();
183+fn test_treesitter_indent(
32− let config = toml::from_str(&config).unwrap();
184+ test_name: &str,
185+ doc: Rope,
186+ lang_scope: &str,
187+ ignored_lines: Vec<std::ops::Range<usize>>,
188+) {
33− let loader = Loader::new(config);
189+ let loader = Loader::new(indent_tests_config());
34
190
35 // set runtime path so we can find the queries
191 // set runtime path so we can find the queries
36 let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
192 let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
@@ -38,30 +194,35 @@ fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
38 std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
194 std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
39
195
40 let language_config = loader.language_config_for_scope(lang_scope).unwrap();
196 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);
41 let highlight_config = language_config.highlight_config(&[]).unwrap();
198 let highlight_config = language_config.highlight_config(&[]).unwrap();
42− let syntax = Syntax::new(&doc, highlight_config, std::sync::Arc::new(loader));
43− let indent_query = language_config.indent_query().unwrap();
44 let text = doc.slice(..);
199 let text = doc.slice(..);
200+ let syntax = Syntax::new(text, highlight_config, std::sync::Arc::new(loader)).unwrap();
201+ let indent_query = language_config.indent_query().unwrap();
45
202
46 for i in 0..doc.len_lines() {
203 for i in 0..doc.len_lines() {
47 let line = text.line(i);
204 let line = text.line(i);
205+ if ignored_lines.iter().any(|range| range.contains(&(i + 1))) {
206+ continue;
207+ }
48 if let Some(pos) = helix_core::find_first_non_whitespace_char(line) {
208 if let Some(pos) = helix_core::find_first_non_whitespace_char(line) {
49− let tab_and_indent_width: usize = 4;
209+ let tab_width: usize = 4;
50 let suggested_indent = treesitter_indent_for_pos(
210 let suggested_indent = treesitter_indent_for_pos(
51 indent_query,
211 indent_query,
52 &syntax,
212 &syntax,
53− &IndentStyle::Spaces(tab_and_indent_width as u8),
213+ tab_width,
54− tab_and_indent_width,
214+ indent_style.indent_width(tab_width),
55− tab_and_indent_width,
56 text,
215 text,
57 i,
216 i,
58 text.line_to_char(i) + pos,
217 text.line_to_char(i) + pos,
59 false,
218 false,
60 )
219 )
61− .unwrap();
220+ .unwrap()
221+ .to_string(&indent_style, tab_width);
62 assert!(
222 assert!(
63 line.get_slice(..pos).map_or(false, |s| s == suggested_indent),
223 line.get_slice(..pos).map_or(false, |s| s == suggested_indent),
64− "Wrong indentation on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
224+ "Wrong indentation for file {:?} on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
225+ test_name,
65 i+1,
226 i+1,
66 line.slice(..line.len_chars()-1),
227 line.slice(..line.len_chars()-1),
67 suggested_indent,
228 suggested_indent,
helix-dap/Cargo.toml+11−9
@@ -1,25 +1,27 @@
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"
7description = "DAP client implementation for Helix project"
3description = "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
8−categories = ["editor"]
9+categories.workspace = true
9−repository = "https://github.com/helix-editor/helix"
10+repository.workspace = true
10−homepage = "https://helix-editor.com"
11+homepage.workspace = true
11
12
12# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13
14
14[dependencies]
15[dependencies]
15−helix-core = { version = "0.6", path = "../helix-core" }
16+helix-core = { path = "../helix-core" }
17+
16anyhow = "1.0"
18anyhow = "1.0"
17log = "0.4"
19log = "0.4"
18serde = { version = "1.0", features = ["derive"] }
20serde = { version = "1.0", features = ["derive"] }
19serde_json = "1.0"
21serde_json = "1.0"
20thiserror = "1.0"
22thiserror = "1.0"
21tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] }
23tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] }
22−which = "4.4"
24+which = "5.0.0"
23
25
24[dev-dependencies]
26[dev-dependencies]
25fern = "0.6"
27fern = "0.6"
helix-dap/src/client.rs+27−6
@@ -33,6 +33,7 @@ pub struct Client {
33 server_tx: UnboundedSender<Payload>,
33 server_tx: UnboundedSender<Payload>,
34 request_counter: AtomicU64,
34 request_counter: AtomicU64,
35 connection_type: Option<ConnectionType>,
35 connection_type: Option<ConnectionType>,
36+ starting_request_args: Option<Value>,
36 pub caps: Option<DebuggerCapabilities>,
37 pub caps: Option<DebuggerCapabilities>,
37 // thread_id -> frames
38 // thread_id -> frames
38 pub stack_frames: HashMap<ThreadId, Vec<StackFrame>>,
39 pub stack_frames: HashMap<ThreadId, Vec<StackFrame>>,
@@ -61,12 +62,10 @@ impl Client {
61 if command.is_empty() {
62 if command.is_empty() {
62 return Result::Err(Error::Other(anyhow!("Command not provided")));
63 return Result::Err(Error::Other(anyhow!("Command not provided")));
63 }
64 }
64− if transport == "tcp" && port_arg.is_some() {
65+ match (transport, port_arg) {
65− Self::tcp_process(command, args, port_arg.unwrap(), id).await
66+ ("tcp", Some(port_arg)) => Self::tcp_process(command, args, port_arg, id).await,
66− } else if transport == "stdio" {
67− Self::stdio(command, args, id)
67+ ("stdio", _) => Self::stdio(command, args, id),
68− } else {
69− Result::Err(Error::Other(anyhow!("Incorrect transport {}", transport)))
68+ _ => Result::Err(Error::Other(anyhow!("Incorrect transport {}", transport))),
70 }
69 }
71 }
70 }
72
71
@@ -87,6 +86,7 @@ impl Client {
87 request_counter: AtomicU64::new(0),
86 request_counter: AtomicU64::new(0),
88 caps: None,
87 caps: None,
89 connection_type: None,
88 connection_type: None,
89+ starting_request_args: None,
90 stack_frames: HashMap::new(),
90 stack_frames: HashMap::new(),
91 thread_states: HashMap::new(),
91 thread_states: HashMap::new(),
92 thread_id: None,
92 thread_id: None,
@@ -158,6 +158,10 @@ impl Client {
158 )
158 )
159 }
159 }
160
160
161+ pub fn starting_request_args(&self) -> &Option<Value> {
162+ &self.starting_request_args
163+ }
164+
161 pub async fn tcp_process(
165 pub async fn tcp_process(
162 cmd: &str,
166 cmd: &str,
163 args: Vec<&str>,
167 args: Vec<&str>,
@@ -356,14 +360,25 @@ impl Client {
356
360
357 pub fn launch(&mut self, args: serde_json::Value) -> impl Future<Output = Result<Value>> {
361 pub fn launch(&mut self, args: serde_json::Value) -> impl Future<Output = Result<Value>> {
358 self.connection_type = Some(ConnectionType::Launch);
362 self.connection_type = Some(ConnectionType::Launch);
363+ self.starting_request_args = Some(args.clone());
359 self.call::<requests::Launch>(args)
364 self.call::<requests::Launch>(args)
360 }
365 }
361
366
362 pub fn attach(&mut self, args: serde_json::Value) -> impl Future<Output = Result<Value>> {
367 pub fn attach(&mut self, args: serde_json::Value) -> impl Future<Output = Result<Value>> {
363 self.connection_type = Some(ConnectionType::Attach);
368 self.connection_type = Some(ConnectionType::Attach);
369+ self.starting_request_args = Some(args.clone());
364 self.call::<requests::Attach>(args)
370 self.call::<requests::Attach>(args)
365 }
371 }
366
372
373+ pub fn restart(&self) -> impl Future<Output = Result<Value>> {
374+ let args = if let Some(args) = &self.starting_request_args {
375+ args.clone()
376+ } else {
377+ Value::Null
378+ };
379+ self.call::<requests::Restart>(args)
380+ }
381+
367 pub async fn set_breakpoints(
382 pub async fn set_breakpoints(
368 &self,
383 &self,
369 file: PathBuf,
384 file: PathBuf,
@@ -495,4 +510,10 @@ impl Client {
495
510
496 self.call::<requests::SetExceptionBreakpoints>(args)
511 self.call::<requests::SetExceptionBreakpoints>(args)
497 }
512 }
513+
514+ pub fn current_stack_frame(&self) -> Option<&StackFrame> {
515+ self.stack_frames
516+ .get(&self.thread_id?)?
517+ .get(self.active_frame?)
518+ }
498}
519}
helix-dap/src/transport.rs+28−18
@@ -230,38 +230,48 @@ impl Transport {
230 }
230 }
231 }
231 }
232
232
233− async fn recv(
233+ async fn recv_inner(
234 transport: Arc<Self>,
234 transport: Arc<Self>,
235 mut server_stdout: Box<dyn AsyncBufRead + Unpin + Send>,
235 mut server_stdout: Box<dyn AsyncBufRead + Unpin + Send>,
236 client_tx: UnboundedSender<Payload>,
236 client_tx: UnboundedSender<Payload>,
237− ) {
237+ ) -> Result<()> {
238 let mut recv_buffer = String::new();
238 let mut recv_buffer = String::new();
239 loop {
239 loop {
240− match Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await {
240+ let msg = Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await?;
241− Ok(msg) => {
242− transport
243− .process_server_message(&client_tx, msg)
241+ transport.process_server_message(&client_tx, msg).await?;
244− .await
245− .unwrap();
246− }
247− Err(err) => {
248− error!("err: <- {:?}", err);
249− break;
250− }
251− }
252 }
242 }
253 }
243 }
254
244
255− async fn send(
245+ async fn recv(
246+ transport: Arc<Self>,
247+ server_stdout: Box<dyn AsyncBufRead + Unpin + Send>,
248+ client_tx: UnboundedSender<Payload>,
249+ ) {
250+ if let Err(err) = Self::recv_inner(transport, server_stdout, client_tx).await {
251+ error!("err: <- {:?}", err);
252+ }
253+ }
254+
255+ async fn send_inner(
256 transport: Arc<Self>,
256 transport: Arc<Self>,
257 mut server_stdin: Box<dyn AsyncWrite + Unpin + Send>,
257 mut server_stdin: Box<dyn AsyncWrite + Unpin + Send>,
258 mut client_rx: UnboundedReceiver<Payload>,
258 mut client_rx: UnboundedReceiver<Payload>,
259− ) {
259+ ) -> Result<()> {
260 while let Some(payload) = client_rx.recv().await {
260 while let Some(payload) = client_rx.recv().await {
261 transport
261 transport
262 .send_payload_to_server(&mut server_stdin, payload)
262 .send_payload_to_server(&mut server_stdin, payload)
263− .await
263+ .await?;
264− .unwrap()
264+ }
265+ Ok(())
266+ }
267+
268+ async fn send(
269+ transport: Arc<Self>,
270+ server_stdin: Box<dyn AsyncWrite + Unpin + Send>,
271+ client_rx: UnboundedReceiver<Payload>,
272+ ) {
273+ if let Err(err) = Self::send_inner(transport, server_stdin, client_rx).await {
274+ error!("err: <- {:?}", err);
265 }
275 }
266 }
276 }
267
277
helix-dap/src/types.rs+11−2
@@ -378,7 +378,7 @@ pub mod requests {
378
378
379 impl Request for Launch {
379 impl Request for Launch {
380 type Arguments = Value;
380 type Arguments = Value;
381− type Result = Value;
381+ type Result = ();
382 const COMMAND: &'static str = "launch";
382 const COMMAND: &'static str = "launch";
383 }
383 }
384
384
@@ -387,7 +387,7 @@ pub mod requests {
387
387
388 impl Request for Attach {
388 impl Request for Attach {
389 type Arguments = Value;
389 type Arguments = Value;
390− type Result = Value;
390+ type Result = ();
391 const COMMAND: &'static str = "attach";
391 const COMMAND: &'static str = "attach";
392 }
392 }
393
393
@@ -402,6 +402,15 @@ pub mod requests {
402 pub suspend_debuggee: Option<bool>,
402 pub suspend_debuggee: Option<bool>,
403 }
403 }
404
404
405+ #[derive(Debug)]
406+ pub enum Restart {}
407+
408+ impl Request for Restart {
409+ type Arguments = Value;
410+ type Result = ();
411+ const COMMAND: &'static str = "restart";
412+ }
413+
405 #[derive(Debug)]
414 #[derive(Debug)]
406 pub enum Disconnect {}
415 pub enum Disconnect {}
407
416
helix-event/Cargo.tomlnew file+16
@@ -0,0 +1,16 @@
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.rsnew file+8
@@ -0,0 +1,8 @@
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.rsnew file+49
@@ -0,0 +1,49 @@
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+16−12
@@ -1,13 +1,14 @@
1[package]
1[package]
2name = "helix-loader"
2name = "helix-loader"
3−version = "0.6.0"
3+description = "Build bootstrapping for Helix crates"
4−description = "A post-modern text editor."
4+version.workspace = true
5−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5+authors.workspace = true
6−edition = "2021"
6+edition.workspace = true
7−license = "MPL-2.0"
7+license.workspace = true
8+rust-version.workspace = true
8−categories = ["editor"]
9+categories.workspace = true
9−repository = "https://github.com/helix-editor/helix"
10+repository.workspace = true
10−homepage = "https://helix-editor.com"
11+homepage.workspace = true
11
12
12[[bin]]
13[[bin]]
13name = "hx-loader"
14name = "hx-loader"
@@ -17,16 +18,19 @@ path = "src/main.rs"
17anyhow = "1"
18anyhow = "1"
18serde = { version = "1.0", features = ["derive"] }
19serde = { version = "1.0", features = ["derive"] }
19toml = "0.7"
20toml = "0.7"
20−etcetera = "0.4"
21+etcetera = "0.8"
21−tree-sitter = "0.20"
22+tree-sitter.workspace = true
22−once_cell = "1.17"
23+once_cell = "1.19"
23log = "0.4"
24log = "0.4"
25+which = "5.0.0"
24
26
25# TODO: these two should be on !wasm32 only
27# TODO: these two should be on !wasm32 only
26
28
27# cloning/compiling tree-sitter grammars
29# cloning/compiling tree-sitter grammars
28cc = { version = "1" }
30cc = { version = "1" }
29threadpool = { version = "1.0" }
31threadpool = { version = "1.0" }
32+tempfile = "3.8.1"
33+dunce = "1.0.4"
30
34
31[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
35[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
32−libloading = "0.7"
36+libloading = "0.8"
helix-loader/build.rs+56−5
@@ -1,7 +1,18 @@
1use std::borrow::Cow;
1use std::borrow::Cow;
2+use std::path::Path;
2use std::process::Command;
3use std::process::Command;
3
4
4−const VERSION: &str = include_str!("../VERSION");
5+const MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR");
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+}
5
16
6fn main() {
17fn main() {
7 let git_hash = Command::new("git")
18 let git_hash = Command::new("git")
@@ -11,9 +22,10 @@ fn main() {
11 .filter(|output| output.status.success())
22 .filter(|output| output.status.success())
12 .and_then(|x| String::from_utf8(x.stdout).ok());
23 .and_then(|x| String::from_utf8(x.stdout).ok());
13
24
25+ let calver = get_calver();
14− let version: Cow<_> = match git_hash {
26+ let version: Cow<_> = match &git_hash {
15− Some(git_hash) => format!("{} ({})", VERSION, &git_hash[..8]).into(),
27+ Some(git_hash) => format!("{} ({})", calver, &git_hash[..8]).into(),
16− None => VERSION.into(),
28+ None => calver.into(),
17 };
29 };
18
30
19 println!(
31 println!(
@@ -21,6 +33,45 @@ fn main() {
21 std::env::var("TARGET").unwrap()
33 std::env::var("TARGET").unwrap()
22 );
34 );
23
35
24− println!("cargo:rerun-if-changed=../VERSION");
25 println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
36 println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
37+
38+ if git_hash.is_none() {
39+ return;
40+ }
41+
42+ // we need to revparse because the git dir could be anywhere if you are
43+ // using detached worktrees but there is no good way to obtain an OsString
44+ // from command output so for now we can't accept non-utf8 paths here
45+ // probably rare enouch where it doesn't matter tough we could use gitoxide
46+ // here but that would be make it a hard dependency and slow compile times
47+ let Some(git_dir): Option<String> = Command::new("git")
48+ .args(["rev-parse", "--git-dir"])
49+ .output()
50+ .ok()
51+ .filter(|output| output.status.success())
52+ .and_then(|x| String::from_utf8(x.stdout).ok())
53+ else {
54+ return;
55+ };
56+ // If heads starts pointing at something else (different branch)
57+ // we need to return
58+ let head = Path::new(&git_dir).join("HEAD");
59+ if head.exists() {
60+ println!("cargo:rerun-if-changed={}", head.display());
61+ }
62+ // if the thing head points to (branch) itself changes
63+ // we need to return
64+ let Some(head_ref): Option<String> = Command::new("git")
65+ .args(["symbolic-ref", "HEAD"])
66+ .output()
67+ .ok()
68+ .filter(|output| output.status.success())
69+ .and_then(|x| String::from_utf8(x.stdout).ok())
70+ else {
71+ return;
72+ };
73+ let head_ref = Path::new(&git_dir).join(head_ref);
74+ if head_ref.exists() {
75+ println!("cargo:rerun-if-changed={}", head_ref.display());
76+ }
26}
77}
helix-loader/src/config.rs+32−31
@@ -9,37 +9,38 @@ pub fn default_lang_config() -> toml::Value {
9
9
10/// User configured languages.toml file, merged with the default config.
10/// User configured languages.toml file, merged with the default config.
11pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
11pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
12− let config = crate::local_config_dirs()
12+ let config = [
13− .into_iter()
14− .chain([crate::config_dir()].into_iter())
13+ crate::config_dir(),
14+ crate::find_workspace().0.join(".helix"),
15+ ]
16+ .into_iter()
15− .map(|path| path.join("languages.toml"))
17+ .map(|path| path.join("languages.toml"))
16− .filter_map(|file| {
18+ .filter_map(|file| {
17− std::fs::read_to_string(file)
19+ std::fs::read_to_string(file)
18− .map(|config| toml::from_str(&config))
20+ .map(|config| toml::from_str(&config))
19− .ok()
21+ .ok()
20− })
22+ })
21− .collect::<Result<Vec<_>, _>>()?
23+ .collect::<Result<Vec<_>, _>>()?
22− .into_iter()
23− .chain([default_lang_config()].into_iter())
24+ .into_iter()
24− .fold(toml::Value::Table(toml::value::Table::default()), |a, b| {
25+ .fold(default_lang_config(), |a, b| {
25− // combines for example
26+ // combines for example
26− // b:
27+ // b:
27− // [[language]]
28+ // [[language]]
28− // name = "toml"
29+ // name = "toml"
29− // language-server = { command = "taplo", args = ["lsp", "stdio"] }
30+ // language-server = { command = "taplo", args = ["lsp", "stdio"] }
30− //
31+ //
31− // a:
32+ // a:
32− // [[language]]
33+ // [[language]]
33− // language-server = { command = "/usr/bin/taplo" }
34+ // language-server = { command = "/usr/bin/taplo" }
34− //
35+ //
35− // into:
36+ // into:
36− // [[language]]
37+ // [[language]]
37− // name = "toml"
38+ // name = "toml"
38− // language-server = { command = "/usr/bin/taplo" }
39+ // language-server = { command = "/usr/bin/taplo" }
39− //
40+ //
40− // thus it overrides the third depth-level of b with values of a if they exist, but otherwise merges their values
41+ // thus it overrides the third depth-level of b with values of a if they exist, but otherwise merges their values
41− crate::merge_toml_values(b, a, 3)
42+ crate::merge_toml_values(a, b, 3)
42− });
43+ });
43
44
44 Ok(config)
45 Ok(config)
45}
46}
helix-loader/src/grammar.rs+135−55
@@ -1,4 +1,4 @@
1−use anyhow::{anyhow, Context, Result};
1+use anyhow::{anyhow, bail, Context, Result};
2use serde::{Deserialize, Serialize};
2use serde::{Deserialize, Serialize};
3use std::fs;
3use std::fs;
4use std::time::SystemTime;
4use std::time::SystemTime;
@@ -8,6 +8,7 @@ use std::{
8 process::Command,
8 process::Command,
9 sync::mpsc::channel,
9 sync::mpsc::channel,
10};
10};
11+use tempfile::TempPath;
11use tree_sitter::Language;
12use tree_sitter::Language;
12
13
13#[cfg(unix)]
14#[cfg(unix)]
@@ -67,8 +68,9 @@ pub fn get_language(name: &str) -> Result<Language> {
67#[cfg(not(target_arch = "wasm32"))]
68#[cfg(not(target_arch = "wasm32"))]
68pub fn get_language(name: &str) -> Result<Language> {
69pub fn get_language(name: &str) -> Result<Language> {
69 use libloading::{Library, Symbol};
70 use libloading::{Library, Symbol};
70− let mut library_path = crate::runtime_dir().join("grammars").join(name);
71+ let mut rel_library_path = PathBuf::new().join("grammars").join(name);
71− library_path.set_extension(DYLIB_EXTENSION);
72+ rel_library_path.set_extension(DYLIB_EXTENSION);
73+ let library_path = crate::runtime_file(&rel_library_path);
72
74
73 let library = unsafe { Library::new(&library_path) }
75 let library = unsafe { Library::new(&library_path) }
74 .with_context(|| format!("Error opening dynamic library {:?}", library_path))?;
76 .with_context(|| format!("Error opening dynamic library {:?}", library_path))?;
@@ -83,7 +85,16 @@ pub fn get_language(name: &str) -> Result<Language> {
83 Ok(language)
85 Ok(language)
84}
86}
85
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+
86pub fn fetch_grammars() -> Result<()> {
95pub fn fetch_grammars() -> Result<()> {
96+ ensure_git_is_available()?;
97+
87 // We do not need to fetch local grammars.
98 // We do not need to fetch local grammars.
88 let mut grammars = get_grammar_configs()?;
99 let mut grammars = get_grammar_configs()?;
89 grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. }));
100 grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. }));
@@ -96,15 +107,12 @@ pub fn fetch_grammars() -> Result<()> {
96 let mut git_up_to_date = 0;
107 let mut git_up_to_date = 0;
97 let mut non_git = Vec::new();
108 let mut non_git = Vec::new();
98
109
99− for res in results {
110+ for (grammar_id, res) in results {
100 match res {
111 match res {
101 Ok(FetchStatus::GitUpToDate) => git_up_to_date += 1,
112 Ok(FetchStatus::GitUpToDate) => git_up_to_date += 1,
102− Ok(FetchStatus::GitUpdated {
103− grammar_id,
104− revision,
105− }) => git_updated.push((grammar_id, revision)),
113+ Ok(FetchStatus::GitUpdated { revision }) => git_updated.push((grammar_id, revision)),
106− Ok(FetchStatus::NonGit { grammar_id }) => non_git.push(grammar_id),
114+ Ok(FetchStatus::NonGit) => non_git.push(grammar_id),
107− Err(e) => errors.push(e),
115+ Err(e) => errors.push((grammar_id, e)),
108 }
116 }
109 }
117 }
110
118
@@ -136,16 +144,18 @@ pub fn fetch_grammars() -> Result<()> {
136
144
137 if !errors.is_empty() {
145 if !errors.is_empty() {
138 let len = errors.len();
146 let len = errors.len();
139− println!("{} grammars failed to fetch", len);
140− for (i, error) in errors.into_iter().enumerate() {
147+ for (i, (grammar, error)) in errors.into_iter().enumerate() {
141− println!("\tFailure {}/{}: {}", i + 1, len, error);
148+ println!("Failure {}/{len}: {grammar} {error}", i + 1);
142 }
149 }
150+ bail!("{len} grammars failed to fetch");
143 }
151 }
144
152
145 Ok(())
153 Ok(())
146}
154}
147
155
148pub fn build_grammars(target: Option<String>) -> Result<()> {
156pub fn build_grammars(target: Option<String>) -> Result<()> {
157+ ensure_git_is_available()?;
158+
149 let grammars = get_grammar_configs()?;
159 let grammars = get_grammar_configs()?;
150 println!("Building {} grammars", grammars.len());
160 println!("Building {} grammars", grammars.len());
151 let results = run_parallel(grammars, move |grammar| {
161 let results = run_parallel(grammars, move |grammar| {
@@ -156,11 +166,11 @@ pub fn build_grammars(target: Option<String>) -> Result<()> {
156 let mut already_built = 0;
166 let mut already_built = 0;
157 let mut built = Vec::new();
167 let mut built = Vec::new();
158
168
159− for res in results {
169+ for (grammar_id, res) in results {
160 match res {
170 match res {
161 Ok(BuildStatus::AlreadyBuilt) => already_built += 1,
171 Ok(BuildStatus::AlreadyBuilt) => already_built += 1,
162− Ok(BuildStatus::Built { grammar_id }) => built.push(grammar_id),
172+ Ok(BuildStatus::Built) => built.push(grammar_id),
163− Err(e) => errors.push(e),
173+ Err(e) => errors.push((grammar_id, e)),
164 }
174 }
165 }
175 }
166
176
@@ -177,10 +187,10 @@ pub fn build_grammars(target: Option<String>) -> Result<()> {
177
187
178 if !errors.is_empty() {
188 if !errors.is_empty() {
179 let len = errors.len();
189 let len = errors.len();
180− println!("{} grammars failed to build", len);
181− for (i, error) in errors.into_iter().enumerate() {
190+ for (i, (grammar_id, error)) in errors.into_iter().enumerate() {
182− println!("\tFailure {}/{}: {}", i, len, error);
191+ println!("Failure {}/{len}: {grammar_id} {error}", i + 1);
183 }
192 }
193+ bail!("{len} grammars failed to build");
184 }
194 }
185
195
186 Ok(())
196 Ok(())
@@ -212,7 +222,7 @@ fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
212 Ok(grammars)
222 Ok(grammars)
213}
223}
214
224
215−fn run_parallel<F, Res>(grammars: Vec<GrammarConfiguration>, job: F) -> Vec<Result<Res>>
225+fn run_parallel<F, Res>(grammars: Vec<GrammarConfiguration>, job: F) -> Vec<(String, Result<Res>)>
216where
226where
217 F: Fn(GrammarConfiguration) -> Result<Res> + Send + 'static + Clone,
227 F: Fn(GrammarConfiguration) -> Result<Res> + Send + 'static + Clone,
218 Res: Send + 'static,
228 Res: Send + 'static,
@@ -227,7 +237,7 @@ where
227 pool.execute(move || {
237 pool.execute(move || {
228 // Ignore any SendErrors, if any job in another thread has encountered an
238 // Ignore any SendErrors, if any job in another thread has encountered an
229 // error the Receiver will be closed causing this send to fail.
239 // error the Receiver will be closed causing this send to fail.
230− let _ = tx.send(job(grammar));
240+ let _ = tx.send((grammar.grammar_id.clone(), job(grammar)));
231 });
241 });
232 }
242 }
233
243
@@ -238,13 +248,8 @@ where
238
248
239enum FetchStatus {
249enum FetchStatus {
240 GitUpToDate,
250 GitUpToDate,
241− GitUpdated {
242− grammar_id: String,
243− revision: String,
251+ GitUpdated { revision: String },
244− },
245− NonGit {
252+ NonGit,
246− grammar_id: String,
247− },
248}
253}
249
254
250fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
255fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
@@ -252,7 +257,9 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
252 remote, revision, ..
257 remote, revision, ..
253 } = grammar.source
258 } = grammar.source
254 {
259 {
255− let grammar_dir = crate::runtime_dir()
260+ let grammar_dir = crate::runtime_dirs()
261+ .first()
262+ .expect("No runtime directories provided") // guaranteed by post-condition
256 .join("grammars")
263 .join("grammars")
257 .join("sources")
264 .join("sources")
258 .join(&grammar.grammar_id);
265 .join(&grammar.grammar_id);
@@ -283,17 +290,12 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
283 )?;
290 )?;
284 git(&grammar_dir, ["checkout", &revision])?;
291 git(&grammar_dir, ["checkout", &revision])?;
285
292
286− Ok(FetchStatus::GitUpdated {
293+ Ok(FetchStatus::GitUpdated { revision })
287− grammar_id: grammar.grammar_id,
288− revision,
289− })
290 } else {
294 } else {
291 Ok(FetchStatus::GitUpToDate)
295 Ok(FetchStatus::GitUpToDate)
292 }
296 }
293 } else {
297 } else {
294− Ok(FetchStatus::NonGit {
298+ Ok(FetchStatus::NonGit)
295− grammar_id: grammar.grammar_id,
296− })
297 }
299 }
298}
300}
299
301
@@ -343,14 +345,16 @@ where
343
345
344enum BuildStatus {
346enum BuildStatus {
345 AlreadyBuilt,
347 AlreadyBuilt,
346− Built { grammar_id: String },
348+ Built,
347}
349}
348
350
349fn build_grammar(grammar: GrammarConfiguration, target: Option<&str>) -> Result<BuildStatus> {
351fn build_grammar(grammar: GrammarConfiguration, target: Option<&str>) -> Result<BuildStatus> {
350 let grammar_dir = if let GrammarSource::Local { path } = &grammar.source {
352 let grammar_dir = if let GrammarSource::Local { path } = &grammar.source {
351 PathBuf::from(&path)
353 PathBuf::from(&path)
352 } else {
354 } else {
353− crate::runtime_dir()
355+ crate::runtime_dirs()
356+ .first()
357+ .expect("No runtime directories provided") // guaranteed by post-condition
354 .join("grammars")
358 .join("grammars")
355 .join("sources")
359 .join("sources")
356 .join(&grammar.grammar_id)
360 .join(&grammar.grammar_id)
@@ -401,10 +405,25 @@ fn build_tree_sitter_library(
401 None
405 None
402 }
406 }
403 };
407 };
404− let parser_lib_path = crate::runtime_dir().join("grammars");
408+ let parser_lib_path = crate::runtime_dirs()
409+ .first()
410+ .expect("No runtime directories provided") // guaranteed by post-condition
411+ .join("grammars");
405 let mut library_path = parser_lib_path.join(&grammar.grammar_id);
412 let mut library_path = parser_lib_path.join(&grammar.grammar_id);
406 library_path.set_extension(DYLIB_EXTENSION);
413 library_path.set_extension(DYLIB_EXTENSION);
407
414
415+ // if we are running inside a buildscript emit cargo metadata
416+ // to detect if we are running from a buildscript check some env variables
417+ // that cargo only sets for build scripts
418+ if std::env::var("OUT_DIR").is_ok() && std::env::var("CARGO").is_ok() {
419+ if let Some(scanner_path) = scanner_path.as_ref().and_then(|path| path.to_str()) {
420+ println!("cargo:rerun-if-changed={scanner_path}");
421+ }
422+ if let Some(parser_path) = parser_path.to_str() {
423+ println!("cargo:rerun-if-changed={parser_path}");
424+ }
425+ }
426+
408 let recompile = needs_recompile(&library_path, &parser_path, &scanner_path)
427 let recompile = needs_recompile(&library_path, &parser_path, &scanner_path)
409 .context("Failed to compare source and binary timestamps")?;
428 .context("Failed to compare source and binary timestamps")?;
410
429
@@ -425,16 +444,53 @@ fn build_tree_sitter_library(
425 for (key, value) in compiler.env() {
444 for (key, value) in compiler.env() {
426 command.env(key, value);
445 command.env(key, value);
427 }
446 }
447+
428 command.args(compiler.args());
448 command.args(compiler.args());
449+ // used to delay dropping the temporary object file until after the compilation is complete
450+ let _path_guard;
429
451
430− if cfg!(all(windows, target_env = "msvc")) {
452+ if compiler.is_like_msvc() {
431 command
453 command
432 .args(["/nologo", "/LD", "/I"])
454 .args(["/nologo", "/LD", "/I"])
433 .arg(header_path)
455 .arg(header_path)
434 .arg("/Od")
456 .arg("/Od")
435− .arg("/utf-8");
457+ .arg("/utf-8")
458+ .arg("/std:c11");
436 if let Some(scanner_path) = scanner_path.as_ref() {
459 if let Some(scanner_path) = scanner_path.as_ref() {
460+ if scanner_path.extension() == Some("c".as_ref()) {
437− command.arg(scanner_path);
461+ command.arg(scanner_path);
462+ } else {
463+ let mut cpp_command = Command::new(compiler.path());
464+ cpp_command.current_dir(src_path);
465+ for (key, value) in compiler.env() {
466+ cpp_command.env(key, value);
467+ }
468+ cpp_command.args(compiler.args());
469+ let object_file =
470+ library_path.with_file_name(format!("{}_scanner.obj", &grammar.grammar_id));
471+ cpp_command
472+ .args(["/nologo", "/LD", "/I"])
473+ .arg(header_path)
474+ .arg("/Od")
475+ .arg("/utf-8")
476+ .arg("/std:c++14")
477+ .arg(format!("/Fo{}", object_file.display()))
478+ .arg("/c")
479+ .arg(scanner_path);
480+ let output = cpp_command
481+ .output()
482+ .context("Failed to execute C++ compiler")?;
483+
484+ if !output.status.success() {
485+ return Err(anyhow!(
486+ "Parser compilation failed.\nStdout: {}\nStderr: {}",
487+ String::from_utf8_lossy(&output.stdout),
488+ String::from_utf8_lossy(&output.stderr)
489+ ));
490+ }
491+ command.arg(&object_file);
492+ _path_guard = TempPath::from_path(object_file);
493+ }
438 }
494 }
439
495
440 command
496 command
@@ -446,20 +502,49 @@ fn build_tree_sitter_library(
446 .arg("-shared")
502 .arg("-shared")
447 .arg("-fPIC")
503 .arg("-fPIC")
448 .arg("-fno-exceptions")
504 .arg("-fno-exceptions")
449− .arg("-g")
450 .arg("-I")
505 .arg("-I")
451 .arg(header_path)
506 .arg(header_path)
452 .arg("-o")
507 .arg("-o")
453− .arg(&library_path)
508+ .arg(&library_path);
454− .arg("-O3");
509+
455 if let Some(scanner_path) = scanner_path.as_ref() {
510 if let Some(scanner_path) = scanner_path.as_ref() {
456 if scanner_path.extension() == Some("c".as_ref()) {
511 if scanner_path.extension() == Some("c".as_ref()) {
457− command.arg("-xc").arg("-std=c99").arg(scanner_path);
512+ command.arg("-xc").arg("-std=c11").arg(scanner_path);
458 } else {
513 } else {
514+ let mut cpp_command = Command::new(compiler.path());
515+ cpp_command.current_dir(src_path);
516+ for (key, value) in compiler.env() {
517+ cpp_command.env(key, value);
518+ }
519+ cpp_command.args(compiler.args());
520+ let object_file =
521+ library_path.with_file_name(format!("{}_scanner.o", &grammar.grammar_id));
522+ cpp_command
523+ .arg("-fPIC")
524+ .arg("-fno-exceptions")
525+ .arg("-I")
526+ .arg(header_path)
527+ .arg("-o")
528+ .arg(&object_file)
529+ .arg("-std=c++14")
530+ .arg("-c")
459− command.arg(scanner_path);
531+ .arg(scanner_path);
532+ let output = cpp_command
533+ .output()
534+ .context("Failed to execute C++ compiler")?;
535+ if !output.status.success() {
536+ return Err(anyhow!(
537+ "Parser compilation failed.\nStdout: {}\nStderr: {}",
538+ String::from_utf8_lossy(&output.stdout),
539+ String::from_utf8_lossy(&output.stderr)
540+ ));
541+ }
542+
543+ command.arg(&object_file);
544+ _path_guard = TempPath::from_path(object_file);
460 }
545 }
461 }
546 }
462− command.arg("-xc").arg(parser_path);
547+ command.arg("-xc").arg("-std=c11").arg(parser_path);
463 if cfg!(all(
548 if cfg!(all(
464 unix,
549 unix,
465 not(any(target_os = "macos", target_os = "illumos"))
550 not(any(target_os = "macos", target_os = "illumos"))
@@ -479,9 +564,7 @@ fn build_tree_sitter_library(
479 ));
564 ));
480 }
565 }
481
566
482− Ok(BuildStatus::Built {
567+ Ok(BuildStatus::Built)
483− grammar_id: grammar.grammar_id,
484− })
485}
568}
486
569
487fn needs_recompile(
570fn needs_recompile(
@@ -511,9 +594,6 @@ fn mtime(path: &Path) -> Result<SystemTime> {
511/// Gives the contents of a file from a language's `runtime/queries/<lang>`
594/// Gives the contents of a file from a language's `runtime/queries/<lang>`
512/// directory
595/// directory
513pub fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::io::Error> {
596pub fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::io::Error> {
514− let path = crate::RUNTIME_DIR
597+ let path = crate::runtime_file(&PathBuf::new().join("queries").join(language).join(filename));
515− .join("queries")
516− .join(language)
517− .join(filename);
518 std::fs::read_to_string(path)
598 std::fs::read_to_string(path)
519}
599}
helix-loader/src/lib.rs+165−54
@@ -2,54 +2,143 @@ pub mod config;
2pub mod grammar;
2pub mod grammar;
3
3
4use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
4use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
5−use std::path::PathBuf;
5+use std::path::{Path, PathBuf};
6+use std::sync::RwLock;
6
7
7pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH");
8pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH");
8
9
10+static CWD: RwLock<Option<PathBuf>> = RwLock::new(None);
11+
9−pub static RUNTIME_DIR: once_cell::sync::Lazy<PathBuf> = once_cell::sync::Lazy::new(runtime_dir);
12+static RUNTIME_DIRS: once_cell::sync::Lazy<Vec<PathBuf>> =
13+ once_cell::sync::Lazy::new(prioritize_runtime_dirs);
10
14
11static CONFIG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
15static CONFIG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
12
16
13−pub fn initialize_config_file(specified_file: Option<PathBuf>) {
17+static LOG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
14− let config_file = specified_file.unwrap_or_else(|| {
15− let config_dir = config_dir();
16
18
17− if !config_dir.exists() {
19+// Get the current working directory.
18− std::fs::create_dir_all(&config_dir).ok();
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();
19− }
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());
20
32
21− config_dir.join("config.toml")
33+ path
22− });
34+}
35+
36+pub fn set_current_working_dir(path: impl AsRef<Path>) -> std::io::Result<()> {
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− // We should only initialize this value once.
44+pub fn initialize_config_file(specified_file: Option<PathBuf>) {
45+ let config_file = specified_file.unwrap_or_else(default_config_file);
46+ ensure_parent_dir(&config_file);
25 CONFIG_FILE.set(config_file).ok();
47 CONFIG_FILE.set(config_file).ok();
26}
48}
27
49
28−pub fn runtime_dir() -> PathBuf {
50+pub fn initialize_log_file(specified_file: Option<PathBuf>) {
29− if let Ok(dir) = std::env::var("HELIX_RUNTIME") {
51+ let log_file = specified_file.unwrap_or_else(default_log_file);
30− return dir.into();
52+ ensure_parent_dir(&log_file);
53+ LOG_FILE.set(log_file).ok();
31− }
54+}
32
55
56+/// A list of runtime directories from highest to lowest priority
57+///
58+/// The priority is:
59+///
60+/// 1. sibling directory to `CARGO_MANIFEST_DIR` (if environment variable is set)
61+/// 2. subdirectory of user config directory (always included)
62+/// 3. `HELIX_RUNTIME` (if environment variable is set)
63+/// 4. `HELIX_DEFAULT_RUNTIME` (if environment variable is set *at build time*)
64+/// 5. subdirectory of path to helix executable (always included)
65+///
66+/// Postcondition: returns at least two paths (they might not exist).
67+fn prioritize_runtime_dirs() -> Vec<PathBuf> {
68+ const RT_DIR: &str = "runtime";
69+ // Adding higher priority first
70+ let mut rt_dirs = Vec::new();
33 if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
71 if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
34 // this is the directory of the crate being run by cargo, we need the workspace path so we take the parent
72 // this is the directory of the crate being run by cargo, we need the workspace path so we take the parent
35− let path = std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR);
73+ let path = PathBuf::from(dir).parent().unwrap().join(RT_DIR);
36 log::debug!("runtime dir: {}", path.to_string_lossy());
74 log::debug!("runtime dir: {}", path.to_string_lossy());
37− return path;
75+ rt_dirs.push(path);
38 }
76 }
39
77
40− const RT_DIR: &str = "runtime";
41− let conf_dir = config_dir().join(RT_DIR);
78+ let conf_rt_dir = config_dir().join(RT_DIR);
42− if conf_dir.exists() {
79+ rt_dirs.push(conf_rt_dir);
43− return conf_dir;
80+
81+ if let Ok(dir) = std::env::var("HELIX_RUNTIME") {
82+ rt_dirs.push(dir.into());
83+ }
84+
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());
44 }
91 }
45
92
46 // fallback to location of the executable being run
93 // fallback to location of the executable being run
47 // canonicalize the path in case the executable is symlinked
94 // canonicalize the path in case the executable is symlinked
48− std::env::current_exe()
95+ let exe_rt_dir = std::env::current_exe()
49 .ok()
96 .ok()
50 .and_then(|path| std::fs::canonicalize(path).ok())
97 .and_then(|path| std::fs::canonicalize(path).ok())
51 .and_then(|path| path.parent().map(|path| path.to_path_buf().join(RT_DIR)))
98 .and_then(|path| path.parent().map(|path| path.to_path_buf().join(RT_DIR)))
52− .unwrap()
99+ .unwrap();
100+ rt_dirs.push(exe_rt_dir);
101+ rt_dirs
102+}
103+
104+/// Runtime directories ordered from highest to lowest priority
105+///
106+/// All directories should be checked when looking for files.
107+///
108+/// Postcondition: returns at least one path (it might not exist).
109+pub fn runtime_dirs() -> &'static [PathBuf] {
110+ &RUNTIME_DIRS
111+}
112+
113+/// Find file with path relative to runtime directory
114+///
115+/// `rel_path` should be the relative path from within the `runtime/` directory.
116+/// The valid runtime directories are searched in priority order and the first
117+/// file found to exist is returned, otherwise None.
118+fn find_runtime_file(rel_path: &Path) -> Option<PathBuf> {
119+ RUNTIME_DIRS.iter().find_map(|rt_dir| {
120+ let path = rt_dir.join(rel_path);
121+ if path.exists() {
122+ Some(path)
123+ } else {
124+ None
125+ }
126+ })
127+}
128+
129+/// Find file with path relative to runtime directory
130+///
131+/// `rel_path` should be the relative path from within the `runtime/` directory.
132+/// The valid runtime directories are searched in priority order and the first
133+/// file found to exist is returned, otherwise the path to the final attempt
134+/// that failed.
135+pub fn runtime_file(rel_path: &Path) -> PathBuf {
136+ find_runtime_file(rel_path).unwrap_or_else(|| {
137+ RUNTIME_DIRS
138+ .last()
139+ .map(|dir| dir.join(rel_path))
140+ .unwrap_or_default()
141+ })
53}
142}
54
143
55pub fn config_dir() -> PathBuf {
144pub fn config_dir() -> PathBuf {
@@ -60,15 +149,6 @@ pub fn config_dir() -> PathBuf {
60 path
149 path
61}
150}
62
151
63−pub fn local_config_dirs() -> Vec<PathBuf> {
64− let directories = find_local_config_dirs()
65− .into_iter()
66− .map(|path| path.join(".helix"))
67− .collect();
68− log::debug!("Located configuration folders: {:?}", directories);
69− directories
70−}
71−
72pub fn cache_dir() -> PathBuf {
152pub fn cache_dir() -> PathBuf {
73 // TODO: allow env var override
153 // TODO: allow env var override
74 let strategy = choose_base_strategy().expect("Unable to find the config directory!");
154 let strategy = choose_base_strategy().expect("Unable to find the config directory!");
@@ -78,34 +158,23 @@ pub fn cache_dir() -> PathBuf {
78}
158}
79
159
80pub fn config_file() -> PathBuf {
160pub fn config_file() -> PathBuf {
81− CONFIG_FILE
82− .get()
83− .map(|path| path.to_path_buf())
161+ CONFIG_FILE.get().map(|path| path.to_path_buf()).unwrap()
84− .unwrap_or_else(|| config_dir().join("config.toml"))
85}
162}
86
163
87−pub fn lang_config_file() -> PathBuf {
164+pub fn log_file() -> PathBuf {
88− config_dir().join("languages.toml")
165+ LOG_FILE.get().map(|path| path.to_path_buf()).unwrap()
89}
166}
90
167
91−pub fn log_file() -> PathBuf {
168+pub fn workspace_config_file() -> PathBuf {
92− cache_dir().join("helix.log")
169+ find_workspace().0.join(".helix").join("config.toml")
93}
170}
94
171
95−pub fn find_local_config_dirs() -> Vec<PathBuf> {
172+pub fn lang_config_file() -> PathBuf {
96− let current_dir = std::env::current_dir().expect("unable to determine current directory");
173+ config_dir().join("languages.toml")
97− let mut directories = Vec::new();
174+}
98
175
99− for ancestor in current_dir.ancestors() {
176+pub fn default_log_file() -> PathBuf {
100− if ancestor.join(".git").exists() {
177+ cache_dir().join("helix.log")
101− directories.push(ancestor.to_path_buf());
102− // Don't go higher than repo if we're in one
103− break;
104− } else if ancestor.join(".helix").is_dir() {
105− directories.push(ancestor.to_path_buf());
106− }
107− }
108− directories
109}
178}
110
179
111/// Merge two TOML documents, merging values from `right` onto `left`
180/// Merge two TOML documents, merging values from `right` onto `left`
@@ -177,13 +246,55 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi
177 }
246 }
178}
247}
179
248
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+
180#[cfg(test)]
279#[cfg(test)]
181mod merge_toml_tests {
280mod merge_toml_tests {
182 use std::str;
281 use std::str;
183
282
184− use super::merge_toml_values;
283+ use super::{current_working_dir, merge_toml_values, set_current_working_dir};
185 use toml::Value;
284 use toml::Value;
186
285
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+
187 #[test]
298 #[test]
188 fn language_toml_map_merges() {
299 fn language_toml_map_merges() {
189 const USER: &str = r#"
300 const USER: &str = r#"
helix-lsp/Cargo.toml+17−13
@@ -1,28 +1,32 @@
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"
7description = "LSP client implementation for Helix project"
3description = "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
8−categories = ["editor"]
9+categories.workspace = true
9−repository = "https://github.com/helix-editor/helix"
10+repository.workspace = true
10−homepage = "https://helix-editor.com"
11+homepage.workspace = true
11
12
12# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13
14
14[dependencies]
15[dependencies]
15−helix-core = { version = "0.6", path = "../helix-core" }
16+helix-core = { path = "../helix-core" }
16−helix-loader = { version = "0.6", path = "../helix-loader" }
17+helix-loader = { path = "../helix-loader" }
18+helix-parsec = { path = "../helix-parsec" }
17
19
18anyhow = "1.0"
20anyhow = "1.0"
19futures-executor = "0.3"
21futures-executor = "0.3"
20futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
22futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
23+globset = "0.4.14"
21log = "0.4"
24log = "0.4"
22−lsp-types = { version = "0.94" }
25+lsp-types = { version = "0.95" }
23serde = { version = "1.0", features = ["derive"] }
26serde = { version = "1.0", features = ["derive"] }
24serde_json = "1.0"
27serde_json = "1.0"
25thiserror = "1.0"
28thiserror = "1.0"
26−tokio = { version = "1.25", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
29+tokio = { version = "1.35", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
27−tokio-stream = "0.1.12"
30+tokio-stream = "0.1.14"
28−which = "4.4"
31+which = "5.0.0"
32+parking_lot = "0.12.1"
helix-lsp/src/client.rs+463−55
@@ -1,22 +1,27 @@
1use crate::{
1use crate::{
2− jsonrpc,
2+ find_lsp_workspace, jsonrpc,
3 transport::{Payload, Transport},
3 transport::{Payload, Transport},
4 Call, Error, OffsetEncoding, Result,
4 Call, Error, OffsetEncoding, Result,
5};
5};
6
6
7−use helix_core::{find_root, ChangeSet, Rope};
7+use helix_core::{find_workspace, path, syntax::LanguageServerFeature, ChangeSet, Rope};
8use helix_loader::{self, VERSION_AND_GIT_HASH};
8use helix_loader::{self, VERSION_AND_GIT_HASH};
9+use lsp::{
10+ notification::DidChangeWorkspaceFolders, CodeActionCapabilityResolveSupport,
9−use lsp::PositionEncodingKind;
11+ DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, WorkspaceFolder,
12+ WorkspaceFoldersChangeEvent,
13+};
10use lsp_types as lsp;
14use lsp_types as lsp;
15+use parking_lot::Mutex;
11use serde::Deserialize;
16use serde::Deserialize;
12use serde_json::Value;
17use serde_json::Value;
13−use std::collections::HashMap;
14use std::future::Future;
18use std::future::Future;
15use std::process::Stdio;
19use std::process::Stdio;
16use std::sync::{
20use std::sync::{
17 atomic::{AtomicU64, Ordering},
21 atomic::{AtomicU64, Ordering},
18 Arc,
22 Arc,
19};
23};
24+use std::{collections::HashMap, path::PathBuf};
20use tokio::{
25use tokio::{
21 io::{BufReader, BufWriter},
26 io::{BufReader, BufWriter},
22 process::{Child, Command},
27 process::{Child, Command},
@@ -26,9 +31,21 @@ use tokio::{
26 },
31 },
27};
32};
28
33
34+fn workspace_for_uri(uri: lsp::Url) -> WorkspaceFolder {
35+ lsp::WorkspaceFolder {
36+ name: uri
37+ .path_segments()
38+ .and_then(|segments| segments.last())
39+ .map(|basename| basename.to_string())
40+ .unwrap_or_default(),
41+ uri,
42+ }
43+}
44+
29#[derive(Debug)]
45#[derive(Debug)]
30pub struct Client {
46pub struct Client {
31 id: usize,
47 id: usize,
48+ name: String,
32 _process: Child,
49 _process: Child,
33 server_tx: UnboundedSender<Payload>,
50 server_tx: UnboundedSender<Payload>,
34 request_counter: AtomicU64,
51 request_counter: AtomicU64,
@@ -36,20 +53,131 @@ pub struct Client {
36 config: Option<Value>,
53 config: Option<Value>,
37 root_path: std::path::PathBuf,
54 root_path: std::path::PathBuf,
38 root_uri: Option<lsp::Url>,
55 root_uri: Option<lsp::Url>,
39− workspace_folders: Vec<lsp::WorkspaceFolder>,
56+ workspace_folders: Mutex<Vec<lsp::WorkspaceFolder>>,
57+ initialize_notify: Arc<Notify>,
58+ /// workspace folders added while the server is still initializing
40 req_timeout: u64,
59 req_timeout: u64,
41}
60}
42
61
43impl Client {
62impl Client {
63+ pub fn try_add_doc(
64+ self: &Arc<Self>,
65+ root_markers: &[String],
66+ manual_roots: &[PathBuf],
67+ doc_path: Option<&std::path::PathBuf>,
68+ may_support_workspace: bool,
69+ ) -> bool {
70+ let (workspace, workspace_is_cwd) = find_workspace();
71+ let workspace = path::get_normalized_path(&workspace);
72+ let root = find_lsp_workspace(
73+ doc_path
74+ .and_then(|x| x.parent().and_then(|x| x.to_str()))
75+ .unwrap_or("."),
76+ root_markers,
77+ manual_roots,
78+ &workspace,
79+ workspace_is_cwd,
80+ );
81+ let root_uri = root
82+ .as_ref()
83+ .and_then(|root| lsp::Url::from_file_path(root).ok());
84+
85+ if self.root_path == root.unwrap_or(workspace)
86+ || root_uri.as_ref().map_or(false, |root_uri| {
87+ self.workspace_folders
88+ .lock()
89+ .iter()
90+ .any(|workspace| &workspace.uri == root_uri)
91+ })
92+ {
93+ // workspace URI is already registered so we can use this client
94+ return true;
95+ }
96+
97+ // this server definitely doesn't support multiple workspace, no need to check capabilities
98+ if !may_support_workspace {
99+ return false;
100+ }
101+
102+ let Some(capabilities) = self.capabilities.get() else {
103+ let client = Arc::clone(self);
104+ // initialization hasn't finished yet, deal with this new root later
105+ // TODO: In the edgecase that a **new root** is added
106+ // for an LSP that **doesn't support workspace_folders** before initaliation is finished
107+ // the new roots are ignored.
108+ // That particular edgecase would require retroactively spawning new LSP
109+ // clients and therefore also require us to retroactively update the corresponding
110+ // documents LSP client handle. It's doable but a pretty weird edgecase so let's
111+ // wait and see if anyone ever runs into it.
112+ tokio::spawn(async move {
113+ client.initialize_notify.notified().await;
114+ if let Some(workspace_folders_caps) = client
115+ .capabilities()
116+ .workspace
117+ .as_ref()
118+ .and_then(|cap| cap.workspace_folders.as_ref())
119+ .filter(|cap| cap.supported.unwrap_or(false))
120+ {
121+ client.add_workspace_folder(
122+ root_uri,
123+ &workspace_folders_caps.change_notifications,
124+ );
125+ }
126+ });
127+ return true;
128+ };
129+
130+ if let Some(workspace_folders_caps) = capabilities
131+ .workspace
132+ .as_ref()
133+ .and_then(|cap| cap.workspace_folders.as_ref())
134+ .filter(|cap| cap.supported.unwrap_or(false))
135+ {
136+ self.add_workspace_folder(root_uri, &workspace_folders_caps.change_notifications);
137+ true
138+ } else {
139+ // the server doesn't support multi workspaces, we need a new client
140+ false
141+ }
142+ }
143+
144+ fn add_workspace_folder(
145+ &self,
146+ root_uri: Option<lsp::Url>,
147+ change_notifications: &Option<OneOf<bool, String>>,
148+ ) {
149+ // root_uri is None just means that there isn't really any LSP workspace
150+ // associated with this file. For servers that support multiple workspaces
151+ // there is just one server so we can always just use that shared instance.
152+ // No need to add a new workspace root here as there is no logical root for this file
153+ // let the server deal with this
154+ let Some(root_uri) = root_uri else {
155+ return;
156+ };
157+
158+ // server supports workspace folders, let's add the new root to the list
159+ self.workspace_folders
160+ .lock()
161+ .push(workspace_for_uri(root_uri.clone()));
162+ if &Some(OneOf::Left(false)) == change_notifications {
163+ // server specifically opted out of DidWorkspaceChange notifications
164+ // let's assume the server will request the workspace folders itself
165+ // and that we can therefore reuse the client (but are done now)
166+ return;
167+ }
168+ tokio::spawn(self.did_change_workspace(vec![workspace_for_uri(root_uri)], Vec::new()));
169+ }
170+
44− #[allow(clippy::type_complexity)]
171+ #[allow(clippy::type_complexity, clippy::too_many_arguments)]
45− #[allow(clippy::too_many_arguments)]
46 pub fn start(
172 pub fn start(
47 cmd: &str,
173 cmd: &str,
48 args: &[String],
174 args: &[String],
49 config: Option<Value>,
175 config: Option<Value>,
50 server_environment: HashMap<String, String>,
176 server_environment: HashMap<String, String>,
51 root_markers: &[String],
177 root_markers: &[String],
178+ manual_roots: &[PathBuf],
52 id: usize,
179 id: usize,
180+ name: String,
53 req_timeout: u64,
181 req_timeout: u64,
54 doc_path: Option<&std::path::PathBuf>,
182 doc_path: Option<&std::path::PathBuf>,
55 ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
183 ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
@@ -74,47 +202,51 @@ impl Client {
74 let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
202 let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
75
203
76 let (server_rx, server_tx, initialize_notify) =
204 let (server_rx, server_tx, initialize_notify) =
77− Transport::start(reader, writer, stderr, id);
205+ Transport::start(reader, writer, stderr, id, name.clone());
78−
206+ let (workspace, workspace_is_cwd) = find_workspace();
79− let root_path = find_root(
207+ let workspace = path::get_normalized_path(&workspace);
208+ let root = find_lsp_workspace(
209+ doc_path
80− doc_path.and_then(|x| x.parent().and_then(|x| x.to_str())),
210+ .and_then(|x| x.parent().and_then(|x| x.to_str()))
211+ .unwrap_or("."),
81 root_markers,
212 root_markers,
213+ manual_roots,
214+ &workspace,
215+ workspace_is_cwd,
82 );
216 );
83
217
218+ // `root_uri` and `workspace_folder` can be empty in case there is no workspace
219+ // `root_url` can not, use `workspace` as a fallback
220+ let root_path = root.clone().unwrap_or_else(|| workspace.clone());
84− let root_uri = lsp::Url::from_file_path(root_path.clone()).ok();
221+ let root_uri = root.and_then(|root| lsp::Url::from_file_path(root).ok());
85
222
86− // TODO: support multiple workspace folders
87 let workspace_folders = root_uri
223 let workspace_folders = root_uri
88 .clone()
224 .clone()
89− .map(|root| {
225+ .map(|root| vec![workspace_for_uri(root)])
90− vec![lsp::WorkspaceFolder {
91− name: root
92− .path_segments()
93− .and_then(|segments| segments.last())
94− .map(|basename| basename.to_string())
95− .unwrap_or_default(),
96− uri: root,
97− }]
98− })
99 .unwrap_or_default();
226 .unwrap_or_default();
100
227
101 let client = Self {
228 let client = Self {
102 id,
229 id,
230+ name,
103 _process: process,
231 _process: process,
104 server_tx,
232 server_tx,
105 request_counter: AtomicU64::new(0),
233 request_counter: AtomicU64::new(0),
106 capabilities: OnceCell::new(),
234 capabilities: OnceCell::new(),
107 config,
235 config,
108 req_timeout,
236 req_timeout,
109−
110 root_path,
237 root_path,
111 root_uri,
238 root_uri,
112− workspace_folders,
239+ workspace_folders: Mutex::new(workspace_folders),
240+ initialize_notify: initialize_notify.clone(),
113 };
241 };
114
242
115 Ok((client, server_rx, initialize_notify))
243 Ok((client, server_rx, initialize_notify))
116 }
244 }
117
245
246+ pub fn name(&self) -> &str {
247+ &self.name
248+ }
249+
118 pub fn id(&self) -> usize {
250 pub fn id(&self) -> usize {
119 self.id
251 self.id
120 }
252 }
@@ -145,6 +277,87 @@ impl Client {
145 .expect("language server not yet initialized!")
277 .expect("language server not yet initialized!")
146 }
278 }
147
279
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+
148 pub fn offset_encoding(&self) -> OffsetEncoding {
361 pub fn offset_encoding(&self) -> OffsetEncoding {
149 self.capabilities()
362 self.capabilities()
150 .position_encoding
363 .position_encoding
@@ -154,7 +367,7 @@ impl Client {
154 "utf-16" => Some(OffsetEncoding::Utf16),
367 "utf-16" => Some(OffsetEncoding::Utf16),
155 "utf-32" => Some(OffsetEncoding::Utf32),
368 "utf-32" => Some(OffsetEncoding::Utf32),
156 encoding => {
369 encoding => {
157− log::error!("Server provided invalid position encording {encoding}, defaulting to utf-16");
370+ log::error!("Server provided invalid position encoding {encoding}, defaulting to utf-16");
158 None
371 None
159 },
372 },
160 })
373 })
@@ -165,8 +378,10 @@ impl Client {
165 self.config.as_ref()
378 self.config.as_ref()
166 }
379 }
167
380
168− pub fn workspace_folders(&self) -> &[lsp::WorkspaceFolder] {
381+ pub async fn workspace_folders(
382+ &self,
383+ ) -> parking_lot::MutexGuard<'_, Vec<lsp::WorkspaceFolder>> {
169− &self.workspace_folders
384+ self.workspace_folders.lock()
170 }
385 }
171
386
172 /// Execute a RPC request on the language server.
387 /// Execute a RPC request on the language server.
@@ -186,12 +401,22 @@ impl Client {
186 &self,
401 &self,
187 params: R::Params,
402 params: R::Params,
188 ) -> impl Future<Output = Result<Value>>
403 ) -> impl Future<Output = Result<Value>>
404+ where
405+ R::Params: serde::Serialize,
406+ {
407+ self.call_with_timeout::<R>(params, self.req_timeout)
408+ }
409+
410+ fn call_with_timeout<R: lsp::request::Request>(
411+ &self,
412+ params: R::Params,
413+ timeout_secs: u64,
414+ ) -> impl Future<Output = Result<Value>>
189 where
415 where
190 R::Params: serde::Serialize,
416 R::Params: serde::Serialize,
191 {
417 {
192 let server_tx = self.server_tx.clone();
418 let server_tx = self.server_tx.clone();
193 let id = self.next_request_id();
419 let id = self.next_request_id();
194− let timeout_secs = self.req_timeout;
195
420
196 async move {
421 async move {
197 use std::time::Duration;
422 use std::time::Duration;
@@ -286,7 +511,7 @@ impl Client {
286 // General messages
511 // General messages
287 // -------------------------------------------------------------------------------------------
512 // -------------------------------------------------------------------------------------------
288
513
289− pub(crate) async fn initialize(&self) -> Result<lsp::InitializeResult> {
514+ pub(crate) async fn initialize(&self, enable_snippets: bool) -> Result<lsp::InitializeResult> {
290 if let Some(config) = &self.config {
515 if let Some(config) = &self.config {
291 log::info!("Using custom LSP config: {}", config);
516 log::info!("Using custom LSP config: {}", config);
292 }
517 }
@@ -294,7 +519,7 @@ impl Client {
294 #[allow(deprecated)]
519 #[allow(deprecated)]
295 let params = lsp::InitializeParams {
520 let params = lsp::InitializeParams {
296 process_id: Some(std::process::id()),
521 process_id: Some(std::process::id()),
297− workspace_folders: Some(self.workspace_folders.clone()),
522+ workspace_folders: Some(self.workspace_folders.lock().clone()),
298 // root_path is obsolete, but some clients like pyright still use it so we specify both.
523 // root_path is obsolete, but some clients like pyright still use it so we specify both.
299 // clients will prefer _uri if possible
524 // clients will prefer _uri if possible
300 root_path: self.root_path.to_str().map(|path| path.to_owned()),
525 root_path: self.root_path.to_str().map(|path| path.to_owned()),
@@ -315,12 +540,35 @@ impl Client {
315 execute_command: Some(lsp::DynamicRegistrationClientCapabilities {
540 execute_command: Some(lsp::DynamicRegistrationClientCapabilities {
316 dynamic_registration: Some(false),
541 dynamic_registration: Some(false),
317 }),
542 }),
543+ inlay_hint: Some(lsp::InlayHintWorkspaceClientCapabilities {
544+ refresh_support: Some(false),
545+ }),
546+ workspace_edit: Some(lsp::WorkspaceEditClientCapabilities {
547+ document_changes: Some(true),
548+ resource_operations: Some(vec![
549+ lsp::ResourceOperationKind::Create,
550+ lsp::ResourceOperationKind::Rename,
551+ lsp::ResourceOperationKind::Delete,
552+ ]),
553+ failure_handling: Some(lsp::FailureHandlingKind::Abort),
554+ normalizes_line_endings: Some(false),
555+ change_annotation_support: None,
556+ }),
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+ }),
318 ..Default::default()
566 ..Default::default()
319 }),
567 }),
320 text_document: Some(lsp::TextDocumentClientCapabilities {
568 text_document: Some(lsp::TextDocumentClientCapabilities {
321 completion: Some(lsp::CompletionClientCapabilities {
569 completion: Some(lsp::CompletionClientCapabilities {
322 completion_item: Some(lsp::CompletionItemCapability {
570 completion_item: Some(lsp::CompletionItemCapability {
323− snippet_support: Some(false),
571+ snippet_support: Some(enable_snippets),
324 resolve_support: Some(lsp::CompletionItemCapabilityResolveSupport {
572 resolve_support: Some(lsp::CompletionItemCapabilityResolveSupport {
325 properties: vec![
573 properties: vec![
326 String::from("documentation"),
574 String::from("documentation"),
@@ -359,7 +607,7 @@ impl Client {
359 }),
607 }),
360 rename: Some(lsp::RenameClientCapabilities {
608 rename: Some(lsp::RenameClientCapabilities {
361 dynamic_registration: Some(false),
609 dynamic_registration: Some(false),
362− prepare_support: Some(false),
610+ prepare_support: Some(true),
363 prepare_support_default_behavior: None,
611 prepare_support_default_behavior: None,
364 honors_change_annotations: Some(false),
612 honors_change_annotations: Some(false),
365 }),
613 }),
@@ -381,11 +629,22 @@ impl Client {
381 .collect(),
629 .collect(),
382 },
630 },
383 }),
631 }),
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+ }),
384 ..Default::default()
638 ..Default::default()
385 }),
639 }),
386 publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
640 publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
641+ version_support: Some(true),
387 ..Default::default()
642 ..Default::default()
388 }),
643 }),
644+ inlay_hint: Some(lsp::InlayHintClientCapabilities {
645+ dynamic_registration: Some(false),
646+ resolve_support: None,
647+ }),
389 ..Default::default()
648 ..Default::default()
390 }),
649 }),
391 window: Some(lsp::WindowClientCapabilities {
650 window: Some(lsp::WindowClientCapabilities {
@@ -394,8 +653,8 @@ impl Client {
394 }),
653 }),
395 general: Some(lsp::GeneralClientCapabilities {
654 general: Some(lsp::GeneralClientCapabilities {
396 position_encodings: Some(vec![
655 position_encodings: Some(vec![
397− PositionEncodingKind::UTF32,
398 PositionEncodingKind::UTF8,
656 PositionEncodingKind::UTF8,
657+ PositionEncodingKind::UTF32,
399 PositionEncodingKind::UTF16,
658 PositionEncodingKind::UTF16,
400 ]),
659 ]),
401 ..Default::default()
660 ..Default::default()
@@ -408,6 +667,7 @@ impl Client {
408 version: Some(String::from(VERSION_AND_GIT_HASH)),
667 version: Some(String::from(VERSION_AND_GIT_HASH)),
409 }),
668 }),
410 locale: None, // TODO
669 locale: None, // TODO
670+ work_done_progress_params: lsp::WorkDoneProgressParams::default(),
411 };
671 };
412
672
413 self.request::<lsp::request::Initialize>(params).await
673 self.request::<lsp::request::Initialize>(params).await
@@ -446,6 +706,75 @@ impl Client {
446 )
706 )
447 }
707 }
448
708
709+ pub fn did_change_workspace(
710+ &self,
711+ added: Vec<WorkspaceFolder>,
712+ removed: Vec<WorkspaceFolder>,
713+ ) -> impl Future<Output = Result<()>> {
714+ self.notify::<DidChangeWorkspaceFolders>(DidChangeWorkspaceFoldersParams {
715+ event: WorkspaceFoldersChangeEvent { added, removed },
716+ })
717+ }
718+
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+
449 // -------------------------------------------------------------------------------------------
778 // -------------------------------------------------------------------------------------------
450 // Text document
779 // Text document
451 // -------------------------------------------------------------------------------------------
780 // -------------------------------------------------------------------------------------------
@@ -489,7 +818,11 @@ impl Client {
489 // Calculation is therefore a bunch trickier.
818 // Calculation is therefore a bunch trickier.
490
819
491 use helix_core::RopeSlice;
820 use helix_core::RopeSlice;
821+ fn traverse(
492− fn traverse(pos: lsp::Position, text: RopeSlice) -> lsp::Position {
822+ pos: lsp::Position,
823+ text: RopeSlice,
824+ offset_encoding: OffsetEncoding,
825+ ) -> lsp::Position {
493 let lsp::Position {
826 let lsp::Position {
494 mut line,
827 mut line,
495 mut character,
828 mut character,
@@ -506,7 +839,11 @@ impl Client {
506 line += 1;
839 line += 1;
507 character = 0;
840 character = 0;
508 } else {
841 } else {
842+ character += match offset_encoding {
843+ OffsetEncoding::Utf8 => ch.len_utf8() as u32,
509− character += ch.len_utf16() as u32;
844+ OffsetEncoding::Utf16 => ch.len_utf16() as u32,
845+ OffsetEncoding::Utf32 => 1,
846+ };
510 }
847 }
511 }
848 }
512 lsp::Position { line, character }
849 lsp::Position { line, character }
@@ -527,7 +864,7 @@ impl Client {
527 }
864 }
528 Delete(_) => {
865 Delete(_) => {
529 let start = pos_to_lsp_pos(new_text, new_pos, offset_encoding);
866 let start = pos_to_lsp_pos(new_text, new_pos, offset_encoding);
530− let end = traverse(start, old_text.slice(old_pos..old_end));
867+ let end = traverse(start, old_text.slice(old_pos..old_end), offset_encoding);
531
868
532 // deletion
869 // deletion
533 changes.push(lsp::TextDocumentContentChangeEvent {
870 changes.push(lsp::TextDocumentContentChangeEvent {
@@ -544,7 +881,8 @@ impl Client {
544 // a subsequent delete means a replace, consume it
881 // a subsequent delete means a replace, consume it
545 let end = if let Some(Delete(len)) = iter.peek() {
882 let end = if let Some(Delete(len)) = iter.peek() {
546 old_end = old_pos + len;
883 old_end = old_pos + len;
884+ let end =
547− let end = traverse(start, old_text.slice(old_pos..old_end));
885+ traverse(start, old_text.slice(old_pos..old_end), offset_encoding);
548
886
549 iter.next();
887 iter.next();
550
888
@@ -632,20 +970,19 @@ impl Client {
632 ) -> Option<impl Future<Output = Result<()>>> {
970 ) -> Option<impl Future<Output = Result<()>>> {
633 let capabilities = self.capabilities.get().unwrap();
971 let capabilities = self.capabilities.get().unwrap();
634
972
635− let include_text = match &capabilities.text_document_sync {
973+ let include_text = match &capabilities.text_document_sync.as_ref()? {
636− Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
974+ lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
637− save: Some(options),
975+ save: options,
638 ..
976 ..
639− })) => match options {
977+ }) => match options.as_ref()? {
640 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
978 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
641 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
979 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
642 include_text,
980 include_text,
643 }) => include_text.unwrap_or(false),
981 }) => include_text.unwrap_or(false),
644− // Supported(false)
982+ lsp::TextDocumentSyncSaveOptions::Supported(false) => return None,
645− _ => return None,
646 },
983 },
647− // unsupported
984+ // see: https://github.com/microsoft/language-server-protocol/issues/288
648− _ => return None,
985+ lsp::TextDocumentSyncCapability::Kind(..) => false,
649 };
986 };
650
987
651 Some(self.notify::<lsp::notification::DidSaveTextDocument>(
988 Some(self.notify::<lsp::notification::DidSaveTextDocument>(
@@ -702,6 +1039,24 @@ impl Client {
702 Some(self.call::<lsp::request::ResolveCompletionItem>(completion_item))
1039 Some(self.call::<lsp::request::ResolveCompletionItem>(completion_item))
703 }
1040 }
704
1041
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+
705 pub fn text_document_signature_help(
1060 pub fn text_document_signature_help(
706 &self,
1061 &self,
707 text_document: lsp::TextDocumentIdentifier,
1062 text_document: lsp::TextDocumentIdentifier,
@@ -726,6 +1081,31 @@ impl Client {
726 Some(self.call::<lsp::request::SignatureHelpRequest>(params))
1081 Some(self.call::<lsp::request::SignatureHelpRequest>(params))
727 }
1082 }
728
1083
1084+ pub fn text_document_range_inlay_hints(
1085+ &self,
1086+ text_document: lsp::TextDocumentIdentifier,
1087+ range: lsp::Range,
1088+ work_done_token: Option<lsp::ProgressToken>,
1089+ ) -> Option<impl Future<Output = Result<Value>>> {
1090+ let capabilities = self.capabilities.get().unwrap();
1091+
1092+ match capabilities.inlay_hint_provider {
1093+ Some(
1094+ lsp::OneOf::Left(true)
1095+ | lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_)),
1096+ ) => (),
1097+ _ => return None,
1098+ }
1099+
1100+ let params = lsp::InlayHintParams {
1101+ text_document,
1102+ range,
1103+ work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
1104+ };
1105+
1106+ Some(self.call::<lsp::request::InlayHintRequest>(params))
1107+ }
1108+
729 pub fn text_document_hover(
1109 pub fn text_document_hover(
730 &self,
1110 &self,
731 text_document: lsp::TextDocumentIdentifier,
1111 text_document: lsp::TextDocumentIdentifier,
@@ -986,6 +1366,7 @@ impl Client {
986 &self,
1366 &self,
987 text_document: lsp::TextDocumentIdentifier,
1367 text_document: lsp::TextDocumentIdentifier,
988 position: lsp::Position,
1368 position: lsp::Position,
1369+ include_declaration: bool,
989 work_done_token: Option<lsp::ProgressToken>,
1370 work_done_token: Option<lsp::ProgressToken>,
990 ) -> Option<impl Future<Output = Result<Value>>> {
1371 ) -> Option<impl Future<Output = Result<Value>>> {
991 let capabilities = self.capabilities.get().unwrap();
1372 let capabilities = self.capabilities.get().unwrap();
@@ -1002,7 +1383,7 @@ impl Client {
1002 position,
1383 position,
1003 },
1384 },
1004 context: lsp::ReferenceContext {
1385 context: lsp::ReferenceContext {
1005− include_declaration: true,
1386+ include_declaration,
1006 },
1387 },
1007 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
1388 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
1008 partial_result_params: lsp::PartialResultParams {
1389 partial_result_params: lsp::PartialResultParams {
@@ -1034,6 +1415,29 @@ impl Client {
1034 Some(self.call::<lsp::request::DocumentSymbolRequest>(params))
1415 Some(self.call::<lsp::request::DocumentSymbolRequest>(params))
1035 }
1416 }
1036
1417
1418+ pub fn prepare_rename(
1419+ &self,
1420+ text_document: lsp::TextDocumentIdentifier,
1421+ position: lsp::Position,
1422+ ) -> Option<impl Future<Output = Result<Value>>> {
1423+ let capabilities = self.capabilities.get().unwrap();
1424+
1425+ match capabilities.rename_provider {
1426+ Some(lsp::OneOf::Right(lsp::RenameOptions {
1427+ prepare_provider: Some(true),
1428+ ..
1429+ })) => (),
1430+ _ => return None,
1431+ }
1432+
1433+ let params = lsp::TextDocumentPositionParams {
1434+ text_document,
1435+ position,
1436+ };
1437+
1438+ Some(self.call::<lsp::request::PrepareRenameRequest>(params))
1439+ }
1440+
1037 // empty string to get all symbols
1441 // empty string to get all symbols
1038 pub fn workspace_symbols(&self, query: String) -> Option<impl Future<Output = Result<Value>>> {
1442 pub fn workspace_symbols(&self, query: String) -> Option<impl Future<Output = Result<Value>>> {
1039 let capabilities = self.capabilities.get().unwrap();
1443 let capabilities = self.capabilities.get().unwrap();
@@ -1087,14 +1491,9 @@ impl Client {
1087 position: lsp::Position,
1491 position: lsp::Position,
1088 new_name: String,
1492 new_name: String,
1089 ) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
1493 ) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
1090− let capabilities = self.capabilities.get().unwrap();
1494+ if !self.supports_feature(LanguageServerFeature::RenameSymbol) {
1091−
1092− // Return early if the language server does not support renaming.
1093− match capabilities.rename_provider {
1094− Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_)) => (),
1095− // None | Some(false)
1096− _ => return None,
1495+ return None;
1097− };
1496+ }
1098
1497
1099 let params = lsp::RenameParams {
1498 let params = lsp::RenameParams {
1100 text_document_position: lsp::TextDocumentPositionParams {
1499 text_document_position: lsp::TextDocumentPositionParams {
@@ -1132,4 +1531,13 @@ impl Client {
1132
1531
1133 Some(self.call::<lsp::request::ExecuteCommand>(params))
1532 Some(self.call::<lsp::request::ExecuteCommand>(params))
1134 }
1533 }
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+ }
1135}
1543}
helix-lsp/src/file_event.rsnew file+193
@@ -0,0 +1,193 @@
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+449−102
@@ -1,5 +1,7 @@
1mod client;
1mod client;
2+pub mod file_event;
2pub mod jsonrpc;
3pub mod jsonrpc;
4+pub mod snippet;
3mod transport;
5mod transport;
4
6
5pub use client::Client;
7pub use client::Client;
@@ -9,22 +11,23 @@ pub use lsp::{Position, Url};
9pub use lsp_types as lsp;
11pub use lsp_types as lsp;
10
12
11use futures_util::stream::select_all::SelectAll;
13use futures_util::stream::select_all::SelectAll;
14+use helix_core::{
15+ path,
12−use helix_core::syntax::{LanguageConfiguration, LanguageServerConfiguration};
16+ syntax::{LanguageConfiguration, LanguageServerConfiguration, LanguageServerFeatures},
17+};
13use tokio::sync::mpsc::UnboundedReceiver;
18use tokio::sync::mpsc::UnboundedReceiver;
14
19
15use std::{
20use std::{
16− collections::{hash_map::Entry, HashMap},
21+ collections::HashMap,
22+ path::{Path, PathBuf},
17− sync::{
23+ sync::Arc,
18− atomic::{AtomicUsize, Ordering},
19− Arc,
20− },
21};
24};
22
25
23use thiserror::Error;
26use thiserror::Error;
24use tokio_stream::wrappers::UnboundedReceiverStream;
27use tokio_stream::wrappers::UnboundedReceiverStream;
25
28
26pub type Result<T> = core::result::Result<T, Error>;
29pub type Result<T> = core::result::Result<T, Error>;
27−type LanguageId = String;
30+pub type LanguageServerName = String;
28
31
29#[derive(Error, Debug)]
32#[derive(Error, Debug)]
30pub enum Error {
33pub enum Error {
@@ -44,7 +47,7 @@ pub enum Error {
44 Other(#[from] anyhow::Error),
47 Other(#[from] anyhow::Error),
45}
48}
46
49
47−#[derive(Clone, Copy, Debug, Default)]
50+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
48pub enum OffsetEncoding {
51pub enum OffsetEncoding {
49 /// UTF-8 code units aka bytes
52 /// UTF-8 code units aka bytes
50 Utf8,
53 Utf8,
@@ -58,6 +61,7 @@ pub enum OffsetEncoding {
58pub mod util {
61pub mod util {
59 use super::*;
62 use super::*;
60 use helix_core::line_ending::{line_end_byte_index, line_end_char_index};
63 use helix_core::line_ending::{line_end_byte_index, line_end_char_index};
64+ use helix_core::{chars, RopeSlice, SmallVec};
61 use helix_core::{diagnostic::NumberOrString, Range, Rope, Selection, Tendril, Transaction};
65 use helix_core::{diagnostic::NumberOrString, Range, Rope, Selection, Tendril, Transaction};
62
66
63 /// Converts a diagnostic in the document to [`lsp::Diagnostic`].
67 /// Converts a diagnostic in the document to [`lsp::Diagnostic`].
@@ -126,7 +130,11 @@ pub mod util {
126 ) -> Option<usize> {
130 ) -> Option<usize> {
127 let pos_line = pos.line as usize;
131 let pos_line = pos.line as usize;
128 if pos_line > doc.len_lines() - 1 {
132 if pos_line > doc.len_lines() - 1 {
129− return None;
133+ // If it extends past the end, truncate it to the end. This is because the
134+ // way the LSP describes the range including the last newline is by
135+ // specifying a line number after what we would call the last line.
136+ log::warn!("LSP position {pos:?} out of range assuming EOF");
137+ return Some(doc.len_chars());
130 }
138 }
131
139
132 // We need to be careful here to fully comply ith the LSP spec.
140 // We need to be careful here to fully comply ith the LSP spec.
@@ -142,10 +150,10 @@ pub mod util {
142 // > ‘\n’, ‘\r\n’ and ‘\r’. Positions are line end character agnostic.
150 // > ‘\n’, ‘\r\n’ and ‘\r’. Positions are line end character agnostic.
143 // > So you can not specify a position that denotes \r|\n or \n| where | represents the character offset.
151 // > So you can not specify a position that denotes \r|\n or \n| where | represents the character offset.
144 //
152 //
145− // This means that while the line must be in bounds the `charater`
153+ // This means that while the line must be in bounds the `character`
146 // must be capped to the end of the line.
154 // must be capped to the end of the line.
147 // Note that the end of the line here is **before** the line terminator
155 // Note that the end of the line here is **before** the line terminator
148− // so we must use `line_end_char_index` istead of `doc.line_to_char(pos_line + 1)`
156+ // so we must use `line_end_char_index` instead of `doc.line_to_char(pos_line + 1)`
149 //
157 //
150 // FIXME: Helix does not fully comply with the LSP spec for line terminators.
158 // FIXME: Helix does not fully comply with the LSP spec for line terminators.
151 // The LSP standard requires that line terminators are ['\n', '\r\n', '\r'].
159 // The LSP standard requires that line terminators are ['\n', '\r\n', '\r'].
@@ -236,49 +244,251 @@ pub mod util {
236
244
237 pub fn lsp_range_to_range(
245 pub fn lsp_range_to_range(
238 doc: &Rope,
246 doc: &Rope,
239− range: lsp::Range,
247+ mut range: lsp::Range,
240 offset_encoding: OffsetEncoding,
248 offset_encoding: OffsetEncoding,
241 ) -> Option<Range> {
249 ) -> Option<Range> {
250+ // This is sort of an edgecase. It's not clear from the spec how to deal with
251+ // ranges where end < start. They don't make much sense but vscode simply caps start to end
252+ // and because it's not specified quite a few LS rely on this as a result (for example the TS server)
253+ if range.start > range.end {
254+ log::error!(
255+ "Invalid LSP range start {:?} > end {:?}, using an empty range at the end instead",
256+ range.start,
257+ range.end
258+ );
259+ range.start = range.end;
260+ }
242 let start = lsp_pos_to_pos(doc, range.start, offset_encoding)?;
261 let start = lsp_pos_to_pos(doc, range.start, offset_encoding)?;
243 let end = lsp_pos_to_pos(doc, range.end, offset_encoding)?;
262 let end = lsp_pos_to_pos(doc, range.end, offset_encoding)?;
244
263
245 Some(Range::new(start, end))
264 Some(Range::new(start, end))
246 }
265 }
247
266
267+ /// If the LS did not provide a range for the completion or the range of the
268+ /// primary cursor can not be used for the secondary cursor, this function
269+ /// can be used to find the completion range for a cursor
270+ fn find_completion_range(text: RopeSlice, replace_mode: bool, cursor: usize) -> (usize, usize) {
271+ let start = cursor
272+ - text
273+ .chars_at(cursor)
274+ .reversed()
275+ .take_while(|ch| chars::char_is_word(*ch))
276+ .count();
277+ let mut end = cursor;
278+ if replace_mode {
279+ end += text
280+ .chars_at(cursor)
281+ .skip(1)
282+ .take_while(|ch| chars::char_is_word(*ch))
283+ .count();
284+ }
285+ (start, end)
286+ }
287+ fn completion_range(
288+ text: RopeSlice,
289+ edit_offset: Option<(i128, i128)>,
290+ replace_mode: bool,
291+ cursor: usize,
292+ ) -> Option<(usize, usize)> {
293+ let res = match edit_offset {
294+ Some((start_offset, end_offset)) => {
295+ let start_offset = cursor as i128 + start_offset;
296+ if start_offset < 0 {
297+ return None;
298+ }
299+ let end_offset = cursor as i128 + end_offset;
300+ if end_offset > text.len_chars() as i128 {
301+ return None;
302+ }
303+ (start_offset as usize, end_offset as usize)
304+ }
305+ None => find_completion_range(text, replace_mode, cursor),
306+ };
307+ Some(res)
308+ }
309+
248 /// Creates a [Transaction] from the [lsp::TextEdit] in a completion response.
310 /// Creates a [Transaction] from the [lsp::TextEdit] in a completion response.
249 /// The transaction applies the edit to all cursors.
311 /// The transaction applies the edit to all cursors.
250 pub fn generate_transaction_from_completion_edit(
312 pub fn generate_transaction_from_completion_edit(
251 doc: &Rope,
313 doc: &Rope,
252 selection: &Selection,
314 selection: &Selection,
253− edit: lsp::TextEdit,
315+ edit_offset: Option<(i128, i128)>,
254− offset_encoding: OffsetEncoding,
316+ replace_mode: bool,
317+ new_text: String,
255 ) -> Transaction {
318 ) -> Transaction {
256− let replacement: Option<Tendril> = if edit.new_text.is_empty() {
319+ let replacement: Option<Tendril> = if new_text.is_empty() {
257 None
320 None
258 } else {
321 } else {
259− Some(edit.new_text.into())
322+ Some(new_text.into())
260 };
323 };
261
324
262 let text = doc.slice(..);
325 let text = doc.slice(..);
326+ let (removed_start, removed_end) = completion_range(
327+ text,
328+ edit_offset,
329+ replace_mode,
263− let primary_cursor = selection.primary().cursor(text);
330+ selection.primary().cursor(text),
331+ )
332+ .expect("transaction must be valid for primary selection");
333+ let removed_text = text.slice(removed_start..removed_end);
264
334
265− let start_offset = match lsp_pos_to_pos(doc, edit.range.start, offset_encoding) {
335+ let (transaction, mut selection) = Transaction::change_by_selection_ignore_overlapping(
266− Some(start) => start as i128 - primary_cursor as i128,
336+ doc,
267− None => return Transaction::new(doc),
337+ selection,
268− };
338+ |range| {
269− let end_offset = match lsp_pos_to_pos(doc, edit.range.end, offset_encoding) {
339+ let cursor = range.cursor(text);
270− Some(end) => end as i128 - primary_cursor as i128,
340+ completion_range(text, edit_offset, replace_mode, cursor)
271− None => return Transaction::new(doc),
341+ .filter(|(start, end)| text.slice(start..end) == removed_text)
342+ .unwrap_or_else(|| find_completion_range(text, replace_mode, cursor))
343+ },
344+ |_, _| replacement.clone(),
345+ );
346+ if transaction.changes().is_empty() {
347+ return transaction;
348+ }
349+ selection = selection.map(transaction.changes());
350+ transaction.with_selection(selection)
351+ }
352+
353+ /// Creates a [Transaction] from the [snippet::Snippet] in a completion response.
354+ /// The transaction applies the edit to all cursors.
355+ #[allow(clippy::too_many_arguments)]
356+ pub fn generate_transaction_from_snippet(
357+ doc: &Rope,
358+ selection: &Selection,
359+ edit_offset: Option<(i128, i128)>,
360+ replace_mode: bool,
361+ snippet: snippet::Snippet,
362+ line_ending: &str,
363+ include_placeholder: bool,
364+ tab_width: usize,
365+ indent_width: usize,
366+ ) -> Transaction {
367+ let text = doc.slice(..);
368+
369+ let mut off = 0i128;
370+ let mut mapped_doc = doc.clone();
371+ let mut selection_tabstops: SmallVec<[_; 1]> = SmallVec::new();
372+ let (removed_start, removed_end) = completion_range(
373+ text,
374+ edit_offset,
375+ replace_mode,
376+ selection.primary().cursor(text),
377+ )
378+ .expect("transaction must be valid for primary selection");
379+ let removed_text = text.slice(removed_start..removed_end);
380+
381+ let (transaction, mut selection) = Transaction::change_by_selection_ignore_overlapping(
382+ doc,
383+ selection,
384+ |range| {
385+ let cursor = range.cursor(text);
386+ completion_range(text, edit_offset, replace_mode, cursor)
387+ .filter(|(start, end)| text.slice(start..end) == removed_text)
388+ .unwrap_or_else(|| find_completion_range(text, replace_mode, cursor))
389+ },
390+ |replacement_start, replacement_end| {
391+ let mapped_replacement_start = (replacement_start as i128 + off) as usize;
392+ let mapped_replacement_end = (replacement_end as i128 + off) as usize;
393+
394+ let line_idx = mapped_doc.char_to_line(mapped_replacement_start);
395+ let indent_level = helix_core::indent::indent_level_for_line(
396+ mapped_doc.line(line_idx),
397+ tab_width,
398+ indent_width,
399+ ) * indent_width;
400+
401+ let newline_with_offset = format!(
402+ "{line_ending}{blank:indent_level$}",
403+ line_ending = line_ending,
404+ blank = ""
405+ );
406+
407+ let (replacement, tabstops) =
408+ snippet::render(&snippet, &newline_with_offset, include_placeholder);
409+ selection_tabstops.push((mapped_replacement_start, tabstops));
410+ mapped_doc.remove(mapped_replacement_start..mapped_replacement_end);
411+ mapped_doc.insert(mapped_replacement_start, &replacement);
412+ off +=
413+ replacement_start as i128 - replacement_end as i128 + replacement.len() as i128;
414+
415+ Some(replacement)
416+ },
417+ );
418+
419+ let changes = transaction.changes();
420+ if changes.is_empty() {
421+ return transaction;
272− };
422+ }
273
423
274− Transaction::change_by_selection(doc, selection, |range| {
424+ // Don't normalize to avoid merging/reording selections which would
275− let cursor = range.cursor(text);
425+ // break the association between tabstops and selections. Most ranges
276− (
426+ // will be replaced by tabstops anyways and the final selection will be
277− (cursor as i128 + start_offset) as usize,
427+ // normalized anyways
278− (cursor as i128 + end_offset) as usize,
428+ selection = selection.map_no_normalize(changes);
279− replacement.clone(),
429+ let mut mapped_selection = SmallVec::with_capacity(selection.len());
280− )
430+ let mut mapped_primary_idx = 0;
281− })
431+ let primary_range = selection.primary();
432+ for (range, (tabstop_anchor, tabstops)) in selection.into_iter().zip(selection_tabstops) {
433+ if range == primary_range {
434+ mapped_primary_idx = mapped_selection.len()
435+ }
436+
437+ let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty());
438+ let Some(tabstops) = tabstops else {
439+ // no tabstop normal mapping
440+ mapped_selection.push(range);
441+ continue;
442+ };
443+
444+ // expand the selection to cover the tabstop to retain the helix selection semantic
445+ // the tabstop closest to the range simply replaces `head` while anchor remains in place
446+ // the remaining tabstops receive their own single-width cursor
447+ if range.head < range.anchor {
448+ let last_idx = tabstops.len() - 1;
449+ let last_tabstop = tabstop_anchor + tabstops[last_idx].0;
450+
451+ // if selection is forward but was moved to the right it is
452+ // contained entirely in the replacement text, just do a point
453+ // selection (fallback below)
454+ if range.anchor > last_tabstop {
455+ let range = Range::new(range.anchor, last_tabstop);
456+ mapped_selection.push(range);
457+ let rem_tabstops = tabstops[..last_idx]
458+ .iter()
459+ .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
460+ mapped_selection.extend(rem_tabstops);
461+ continue;
462+ }
463+ } else {
464+ let first_tabstop = tabstop_anchor + tabstops[0].0;
465+
466+ // if selection is forward but was moved to the right it is
467+ // contained entirely in the replacement text, just do a point
468+ // selection (fallback below)
469+ if range.anchor < first_tabstop {
470+ // we can't properly compute the the next grapheme
471+ // here because the transaction hasn't been applied yet
472+ // that is not a problem because the range gets grapheme aligned anyway
473+ // tough so just adding one will always cause head to be grapheme
474+ // aligned correctly when applied to the document
475+ let range = Range::new(range.anchor, first_tabstop + 1);
476+ mapped_selection.push(range);
477+ let rem_tabstops = tabstops[1..]
478+ .iter()
479+ .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
480+ mapped_selection.extend(rem_tabstops);
481+ continue;
482+ }
483+ };
484+
485+ let tabstops = tabstops
486+ .iter()
487+ .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
488+ mapped_selection.extend(tabstops);
489+ }
490+
491+ transaction.with_selection(Selection::new(mapped_selection, mapped_primary_idx))
282 }
492 }
283
493
284 pub fn generate_transaction_from_edits(
494 pub fn generate_transaction_from_edits(
@@ -337,6 +547,8 @@ pub enum MethodCall {
337 ApplyWorkspaceEdit(lsp::ApplyWorkspaceEditParams),
547 ApplyWorkspaceEdit(lsp::ApplyWorkspaceEditParams),
338 WorkspaceFolders,
548 WorkspaceFolders,
339 WorkspaceConfiguration(lsp::ConfigurationParams),
549 WorkspaceConfiguration(lsp::ConfigurationParams),
550+ RegisterCapability(lsp::RegistrationParams),
551+ UnregisterCapability(lsp::UnregistrationParams),
340}
552}
341
553
342impl MethodCall {
554impl MethodCall {
@@ -356,6 +568,14 @@ impl MethodCall {
356 let params: lsp::ConfigurationParams = params.parse()?;
568 let params: lsp::ConfigurationParams = params.parse()?;
357 Self::WorkspaceConfiguration(params)
569 Self::WorkspaceConfiguration(params)
358 }
570 }
571+ lsp::request::RegisterCapability::METHOD => {
572+ let params: lsp::RegistrationParams = params.parse()?;
573+ Self::RegisterCapability(params)
574+ }
575+ lsp::request::UnregisterCapability::METHOD => {
576+ let params: lsp::UnregistrationParams = params.parse()?;
577+ Self::UnregisterCapability(params)
578+ }
359 _ => {
579 _ => {
360 return Err(Error::Unhandled);
580 return Err(Error::Unhandled);
361 }
581 }
@@ -411,99 +631,162 @@ impl Notification {
411
631
412#[derive(Debug)]
632#[derive(Debug)]
413pub struct Registry {
633pub struct Registry {
414− inner: HashMap<LanguageId, (usize, Arc<Client>)>,
634+ inner: HashMap<LanguageServerName, Vec<Arc<Client>>>,
415−
635+ syn_loader: Arc<helix_core::syntax::Loader>,
416− counter: AtomicUsize,
636+ counter: usize,
417 pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
637 pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
418−}
638+ pub file_event_handler: file_event::Handler,
419−
420−impl Default for Registry {
421− fn default() -> Self {
422− Self::new()
423− }
424}
639}
425
640
426impl Registry {
641impl Registry {
427− pub fn new() -> Self {
642+ pub fn new(syn_loader: Arc<helix_core::syntax::Loader>) -> Self {
428 Self {
643 Self {
429 inner: HashMap::new(),
644 inner: HashMap::new(),
645+ syn_loader,
430− counter: AtomicUsize::new(0),
646+ counter: 0,
431 incoming: SelectAll::new(),
647 incoming: SelectAll::new(),
648+ file_event_handler: file_event::Handler::new(),
432 }
649 }
433 }
650 }
434
651
435 pub fn get_by_id(&self, id: usize) -> Option<&Client> {
652 pub fn get_by_id(&self, id: usize) -> Option<&Client> {
436 self.inner
653 self.inner
437 .values()
654 .values()
438− .find(|(client_id, _)| client_id == &id)
655+ .flatten()
656+ .find(|client| client.id() == id)
439− .map(|(_, client)| client.as_ref())
657+ .map(|client| &**client)
440 }
658 }
441
659
442 pub fn remove_by_id(&mut self, id: usize) {
660 pub fn remove_by_id(&mut self, id: usize) {
443− self.inner.retain(|_, (client_id, _)| client_id != &id)
661+ self.file_event_handler.remove_client(id);
662+ self.inner.retain(|_, language_servers| {
663+ language_servers.retain(|ls| id != ls.id());
664+ !language_servers.is_empty()
665+ });
666+ }
667+
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)
444 }
694 }
445
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
446 pub fn restart(
699 pub fn restart(
447 &mut self,
700 &mut self,
448 language_config: &LanguageConfiguration,
701 language_config: &LanguageConfiguration,
449 doc_path: Option<&std::path::PathBuf>,
702 doc_path: Option<&std::path::PathBuf>,
703+ root_dirs: &[PathBuf],
704+ enable_snippets: bool,
450− ) -> Result<Option<Arc<Client>>> {
705+ ) -> Result<Vec<Arc<Client>>> {
451− let config = match &language_config.language_server {
706+ language_config
452− Some(config) => config,
707+ .language_servers
453− None => return Ok(None),
708+ .iter()
454− };
709+ .filter_map(|LanguageServerFeatures { name, .. }| {
455−
710+ if self.inner.contains_key(name) {
456− let scope = language_config.scope.clone();
711+ let client = match self.start_client(
457−
712+ name.clone(),
458− match self.inner.entry(scope) {
459− Entry::Vacant(_) => Ok(None),
460− Entry::Occupied(mut entry) => {
461− // initialize a new client
462− let id = self.counter.fetch_add(1, Ordering::Relaxed);
463−
464− let NewClientResult(client, incoming) =
465− start_client(id, language_config, config, doc_path)?;
713+ language_config,
466− self.incoming.push(UnboundedReceiverStream::new(incoming));
714+ doc_path,
467−
715+ root_dirs,
716+ enable_snippets,
717+ ) {
718+ Ok(client) => client,
719+ error => return Some(error),
720+ };
721+ let old_clients = self
722+ .inner
468− let (_, old_client) = entry.insert((id, client.clone()));
723+ .insert(name.clone(), vec![client.clone()])
724+ .unwrap();
725+
726+ for old_client in old_clients {
727+ self.file_event_handler.remove_client(old_client.id());
728+ tokio::spawn(async move {
729+ let _ = old_client.force_shutdown().await;
730+ });
731+ }
732+
733+ Some(Ok(client))
734+ } else {
735+ None
736+ }
737+ })
738+ .collect()
739+ }
469
740
741+ pub fn stop(&mut self, name: &str) {
742+ if let Some(clients) = self.inner.remove(name) {
743+ for client in clients {
744+ self.file_event_handler.remove_client(client.id());
470 tokio::spawn(async move {
745 tokio::spawn(async move {
471− let _ = old_client.force_shutdown().await;
746+ let _ = client.force_shutdown().await;
472 });
747 });
473−
474− Ok(Some(client))
475 }
748 }
476 }
749 }
477 }
750 }
478
751
479− pub fn get(
752+ pub fn get<'a>(
480− &mut self,
753+ &'a mut self,
481− language_config: &LanguageConfiguration,
754+ language_config: &'a LanguageConfiguration,
482− doc_path: Option<&std::path::PathBuf>,
755+ doc_path: Option<&'a std::path::PathBuf>,
756+ root_dirs: &'a [PathBuf],
757+ enable_snippets: bool,
483− ) -> Result<Option<Arc<Client>>> {
758+ ) -> impl Iterator<Item = (LanguageServerName, Result<Arc<Client>>)> + 'a {
484− let config = match &language_config.language_server {
759+ language_config.language_servers.iter().map(
485− Some(config) => config,
760+ move |LanguageServerFeatures { name, .. }| {
486− None => return Ok(None),
761+ if let Some(clients) = self.inner.get(name) {
487− };
762+ if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| {
488−
763+ client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
489− match self.inner.entry(language_config.scope.clone()) {
764+ }) {
490− Entry::Occupied(entry) => Ok(Some(entry.get().1.clone())),
765+ return (name.to_owned(), Ok(client.clone()));
491− Entry::Vacant(entry) => {
766+ }
492− // initialize a new client
767+ }
493− let id = self.counter.fetch_add(1, Ordering::Relaxed);
768+ match self.start_client(
494−
769+ name.clone(),
495− let NewClientResult(client, incoming) =
496− start_client(id, language_config, config, doc_path)?;
770+ language_config,
497− self.incoming.push(UnboundedReceiverStream::new(incoming));
771+ doc_path,
498−
772+ root_dirs,
499− entry.insert((id, client.clone()));
773+ enable_snippets,
500− Ok(Some(client))
774+ ) {
775+ Ok(client) => {
776+ self.inner
777+ .entry(name.to_owned())
778+ .or_default()
779+ .push(client.clone());
780+ (name.clone(), Ok(client))
781+ }
782+ Err(err) => (name.to_owned(), Err(err)),
783+ }
501− }
784+ },
502− }
785+ )
503 }
786 }
504
787
505 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
788 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
506− self.inner.values().map(|(_, client)| client)
789+ self.inner.values().flatten()
507 }
790 }
508}
791}
509
792
@@ -585,23 +868,28 @@ impl LspProgressMap {
585 }
868 }
586}
869}
587
870
588−struct NewClientResult(Arc<Client>, UnboundedReceiver<(usize, Call)>);
871+struct NewClient(Arc<Client>, UnboundedReceiver<(usize, Call)>);
589
872
590/// start_client takes both a LanguageConfiguration and a LanguageServerConfiguration to ensure that
873/// start_client takes both a LanguageConfiguration and a LanguageServerConfiguration to ensure that
591/// it is only called when it makes sense.
874/// it is only called when it makes sense.
592fn start_client(
875fn start_client(
593 id: usize,
876 id: usize,
877+ name: String,
594 config: &LanguageConfiguration,
878 config: &LanguageConfiguration,
595 ls_config: &LanguageServerConfiguration,
879 ls_config: &LanguageServerConfiguration,
596 doc_path: Option<&std::path::PathBuf>,
880 doc_path: Option<&std::path::PathBuf>,
597−) -> Result<NewClientResult> {
881+ root_dirs: &[PathBuf],
882+ enable_snippets: bool,
883+) -> Result<NewClient> {
598 let (client, incoming, initialize_notify) = Client::start(
884 let (client, incoming, initialize_notify) = Client::start(
599 &ls_config.command,
885 &ls_config.command,
600 &ls_config.args,
886 &ls_config.args,
601− config.config.clone(),
887+ ls_config.config.clone(),
602 ls_config.environment.clone(),
888 ls_config.environment.clone(),
603 &config.roots,
889 &config.roots,
890+ config.workspace_lsp_roots.as_deref().unwrap_or(root_dirs),
604 id,
891 id,
892+ name,
605 ls_config.timeout,
893 ls_config.timeout,
606 doc_path,
894 doc_path,
607 )?;
895 )?;
@@ -616,7 +904,7 @@ fn start_client(
616 .capabilities
904 .capabilities
617 .get_or_try_init(|| {
905 .get_or_try_init(|| {
618 _client
906 _client
619− .initialize()
907+ .initialize(enable_snippets)
620 .map_ok(|response| response.capabilities)
908 .map_ok(|response| response.capabilities)
621 })
909 })
622 .await;
910 .await;
@@ -635,7 +923,66 @@ fn start_client(
635 initialize_notify.notify_one();
923 initialize_notify.notify_one();
636 });
924 });
637
925
638− Ok(NewClientResult(client, incoming))
926+ Ok(NewClient(client, incoming))
927+}
928+
929+/// Find an LSP workspace of a file using the following mechanism:
930+/// * if the file is outside `workspace` return `None`
931+/// * start at `file` and search the file tree upward
932+/// * stop the search at the first `root_dirs` entry that contains `file`
933+/// * if no `root_dirs` matches `file` stop at workspace
934+/// * Returns the top most directory that contains a `root_marker`
935+/// * If no root marker and we stopped at a `root_dirs` entry, return the directory we stopped at
936+/// * If we stopped at `workspace` instead and `workspace_is_cwd == false` return `None`
937+/// * If we stopped at `workspace` instead and `workspace_is_cwd == true` return `workspace`
938+pub fn find_lsp_workspace(
939+ file: &str,
940+ root_markers: &[String],
941+ root_dirs: &[PathBuf],
942+ workspace: &Path,
943+ workspace_is_cwd: bool,
944+) -> Option<PathBuf> {
945+ let file = std::path::Path::new(file);
946+ let mut file = if file.is_absolute() {
947+ file.to_path_buf()
948+ } else {
949+ let current_dir = helix_loader::current_working_dir();
950+ current_dir.join(file)
951+ };
952+ file = path::get_normalized_path(&file);
953+
954+ if !file.starts_with(workspace) {
955+ return None;
956+ }
957+
958+ let mut top_marker = None;
959+ for ancestor in file.ancestors() {
960+ if root_markers
961+ .iter()
962+ .any(|marker| ancestor.join(marker).exists())
963+ {
964+ top_marker = Some(ancestor);
965+ }
966+
967+ if root_dirs
968+ .iter()
969+ .any(|root_dir| path::get_normalized_path(&workspace.join(root_dir)) == ancestor)
970+ {
971+ // if the worskapce is the cwd do not search any higher for workspaces
972+ // but specify
973+ return Some(top_marker.unwrap_or(workspace).to_owned());
974+ }
975+ if ancestor == workspace {
976+ // if the workspace is the CWD, let the LSP decide what the workspace
977+ // is
978+ return top_marker
979+ .or_else(|| (!workspace_is_cwd).then_some(workspace))
980+ .map(Path::to_owned);
981+ }
982+ }
983+
984+ debug_assert!(false, "workspace must be an ancestor of <file>");
985+ None
639}
986}
640
987
641#[cfg(test)]
988#[cfg(test)]
@@ -656,16 +1003,16 @@ mod tests {
656
1003
657 test_case!("", (0, 0) => Some(0));
1004 test_case!("", (0, 0) => Some(0));
658 test_case!("", (0, 1) => Some(0));
1005 test_case!("", (0, 1) => Some(0));
659− test_case!("", (1, 0) => None);
1006+ test_case!("", (1, 0) => Some(0));
660 test_case!("\n\n", (0, 0) => Some(0));
1007 test_case!("\n\n", (0, 0) => Some(0));
661 test_case!("\n\n", (1, 0) => Some(1));
1008 test_case!("\n\n", (1, 0) => Some(1));
662 test_case!("\n\n", (1, 1) => Some(1));
1009 test_case!("\n\n", (1, 1) => Some(1));
663 test_case!("\n\n", (2, 0) => Some(2));
1010 test_case!("\n\n", (2, 0) => Some(2));
664− test_case!("\n\n", (3, 0) => None);
1011+ test_case!("\n\n", (3, 0) => Some(2));
665 test_case!("test\n\n\n\ncase", (4, 3) => Some(11));
1012 test_case!("test\n\n\n\ncase", (4, 3) => Some(11));
666 test_case!("test\n\n\n\ncase", (4, 4) => Some(12));
1013 test_case!("test\n\n\n\ncase", (4, 4) => Some(12));
667 test_case!("test\n\n\n\ncase", (4, 5) => Some(12));
1014 test_case!("test\n\n\n\ncase", (4, 5) => Some(12));
668− test_case!("", (u32::MAX, u32::MAX) => None);
1015+ test_case!("", (u32::MAX, u32::MAX) => Some(0));
669 }
1016 }
670
1017
671 #[test]
1018 #[test]
helix-lsp/src/snippet.rsnew file+1010
@@ -0,0 +1,1010 @@
1+use std::borrow::Cow;
2+
3+use anyhow::{anyhow, Result};
4+use helix_core::{smallvec, SmallVec, Tendril};
5+
6+#[derive(Debug, PartialEq, Eq)]
7+pub enum CaseChange {
8+ Upcase,
9+ Downcase,
10+ Capitalize,
11+}
12+
13+#[derive(Debug, PartialEq, Eq)]
14+pub enum FormatItem {
15+ Text(Tendril),
16+ Capture(usize),
17+ CaseChange(usize, CaseChange),
18+ Conditional(usize, Option<Tendril>, Option<Tendril>),
19+}
20+
21+#[derive(Debug, PartialEq, Eq)]
22+pub struct Regex {
23+ value: Tendril,
24+ replacement: Vec<FormatItem>,
25+ options: Tendril,
26+}
27+
28+#[derive(Debug, PartialEq, Eq)]
29+pub enum SnippetElement<'a> {
30+ Tabstop {
31+ tabstop: usize,
32+ },
33+ Placeholder {
34+ tabstop: usize,
35+ value: Vec<SnippetElement<'a>>,
36+ },
37+ Choice {
38+ tabstop: usize,
39+ choices: Vec<Tendril>,
40+ },
41+ Variable {
42+ name: &'a str,
43+ default: Option<Vec<SnippetElement<'a>>>,
44+ regex: Option<Regex>,
45+ },
46+ Text(Tendril),
47+}
48+
49+#[derive(Debug, PartialEq, Eq)]
50+pub struct Snippet<'a> {
51+ elements: Vec<SnippetElement<'a>>,
52+}
53+
54+pub fn parse(s: &str) -> Result<Snippet<'_>> {
55+ parser::parse(s).map_err(|rest| anyhow!("Failed to parse snippet. Remaining input: {}", rest))
56+}
57+
58+fn render_elements(
59+ snippet_elements: &[SnippetElement<'_>],
60+ insert: &mut Tendril,
61+ offset: &mut usize,
62+ tabstops: &mut Vec<(usize, (usize, usize))>,
63+ newline_with_offset: &str,
64+ include_placeholder: bool,
65+) {
66+ use SnippetElement::*;
67+
68+ for element in snippet_elements {
69+ match element {
70+ Text(text) => {
71+ // small optimization to avoid calling replace when it's unnecessary
72+ let text = if text.contains('\n') {
73+ Cow::Owned(text.replace('\n', newline_with_offset))
74+ } else {
75+ Cow::Borrowed(text.as_str())
76+ };
77+ *offset += text.chars().count();
78+ insert.push_str(&text);
79+ }
80+ Variable {
81+ name: _,
82+ regex: _,
83+ r#default,
84+ } => {
85+ // TODO: variables. For now, fall back to the default, which defaults to "".
86+ render_elements(
87+ r#default.as_deref().unwrap_or_default(),
88+ insert,
89+ offset,
90+ tabstops,
91+ newline_with_offset,
92+ include_placeholder,
93+ );
94+ }
95+ &Tabstop { tabstop } => {
96+ tabstops.push((tabstop, (*offset, *offset)));
97+ }
98+ Placeholder {
99+ tabstop,
100+ value: inner_snippet_elements,
101+ } => {
102+ let start_offset = *offset;
103+ if include_placeholder {
104+ render_elements(
105+ inner_snippet_elements,
106+ insert,
107+ offset,
108+ tabstops,
109+ newline_with_offset,
110+ include_placeholder,
111+ );
112+ }
113+ tabstops.push((*tabstop, (start_offset, *offset)));
114+ }
115+ &Choice {
116+ tabstop,
117+ choices: _,
118+ } => {
119+ // TODO: choices
120+ tabstops.push((tabstop, (*offset, *offset)));
121+ }
122+ }
123+ }
124+}
125+
126+#[allow(clippy::type_complexity)] // only used one time
127+pub fn render(
128+ snippet: &Snippet<'_>,
129+ newline_with_offset: &str,
130+ include_placeholder: bool,
131+) -> (Tendril, Vec<SmallVec<[(usize, usize); 1]>>) {
132+ let mut insert = Tendril::new();
133+ let mut tabstops = Vec::new();
134+ let mut offset = 0;
135+
136+ render_elements(
137+ &snippet.elements,
138+ &mut insert,
139+ &mut offset,
140+ &mut tabstops,
141+ newline_with_offset,
142+ include_placeholder,
143+ );
144+
145+ // sort in ascending order (except for 0, which should always be the last one (per lsp doc))
146+ tabstops.sort_unstable_by_key(|(n, _)| if *n == 0 { usize::MAX } else { *n });
147+
148+ // merge tabstops with the same index (we take advantage of the fact that we just sorted them
149+ // above to simply look backwards)
150+ let mut ntabstops = Vec::<SmallVec<[(usize, usize); 1]>>::new();
151+ {
152+ let mut prev = None;
153+ for (tabstop, r) in tabstops {
154+ if prev == Some(tabstop) {
155+ let len_1 = ntabstops.len() - 1;
156+ ntabstops[len_1].push(r);
157+ } else {
158+ prev = Some(tabstop);
159+ ntabstops.push(smallvec![r]);
160+ }
161+ }
162+ }
163+
164+ (insert, ntabstops)
165+}
166+
167+mod parser {
168+ use helix_core::Tendril;
169+ use helix_parsec::*;
170+
171+ use super::{CaseChange, FormatItem, Regex, Snippet, SnippetElement};
172+
173+ /*
174+ https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#snippet_syntax
175+
176+ any ::= tabstop | placeholder | choice | variable | text
177+ tabstop ::= '$' int | '${' int '}'
178+ placeholder ::= '${' int ':' any '}'
179+ choice ::= '${' int '|' text (',' text)* '|}'
180+ variable ::= '$' var | '${' var }'
181+ | '${' var ':' any '}'
182+ | '${' var '/' regex '/' (format | text)+ '/' options '}'
183+ format ::= '$' int | '${' int '}'
184+ | '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}'
185+ | '${' int ':+' if '}'
186+ | '${' int ':?' if ':' else '}'
187+ | '${' int ':-' else '}' | '${' int ':' else '}'
188+ regex ::= Regular Expression value (ctor-string)
189+ options ::= Regular Expression option (ctor-options)
190+ var ::= [_a-zA-Z] [_a-zA-Z0-9]*
191+ int ::= [0-9]+
192+ text ::= .*
193+ if ::= text
194+ else ::= text
195+ */
196+
197+ fn var<'a>() -> impl Parser<'a, Output = &'a str> {
198+ // var = [_a-zA-Z][_a-zA-Z0-9]*
199+ move |input: &'a str| {
200+ input
201+ .char_indices()
202+ .take_while(|(p, c)| {
203+ *c == '_'
204+ || if *p == 0 {
205+ c.is_ascii_alphabetic()
206+ } else {
207+ c.is_ascii_alphanumeric()
208+ }
209+ })
210+ .last()
211+ .map(|(index, c)| {
212+ let index = index + c.len_utf8();
213+ (&input[index..], &input[0..index])
214+ })
215+ .ok_or(input)
216+ }
217+ }
218+
219+ const TEXT_ESCAPE_CHARS: &[char] = &['\\', '}', '$'];
220+ const CHOICE_TEXT_ESCAPE_CHARS: &[char] = &['\\', '|', ','];
221+
222+ fn text<'a>(
223+ escape_chars: &'static [char],
224+ term_chars: &'static [char],
225+ ) -> impl Parser<'a, Output = Tendril> {
226+ move |input: &'a str| {
227+ let mut chars = input.char_indices().peekable();
228+ let mut res = Tendril::new();
229+ while let Some((i, c)) = chars.next() {
230+ match c {
231+ '\\' => {
232+ if let Some(&(_, c)) = chars.peek() {
233+ if escape_chars.contains(&c) {
234+ chars.next();
235+ res.push(c);
236+ continue;
237+ }
238+ }
239+ res.push('\\');
240+ }
241+ c if term_chars.contains(&c) => return Ok((&input[i..], res)),
242+ c => res.push(c),
243+ }
244+ }
245+
246+ Ok(("", res))
247+ }
248+ }
249+
250+ fn digit<'a>() -> impl Parser<'a, Output = usize> {
251+ filter_map(take_while(|c| c.is_ascii_digit()), |s| s.parse().ok())
252+ }
253+
254+ fn case_change<'a>() -> impl Parser<'a, Output = CaseChange> {
255+ use CaseChange::*;
256+
257+ choice!(
258+ map("upcase", |_| Upcase),
259+ map("downcase", |_| Downcase),
260+ map("capitalize", |_| Capitalize),
261+ )
262+ }
263+
264+ fn format<'a>() -> impl Parser<'a, Output = FormatItem> {
265+ use FormatItem::*;
266+
267+ choice!(
268+ // '$' int
269+ map(right("$", digit()), Capture),
270+ // '${' int '}'
271+ map(seq!("${", digit(), "}"), |seq| Capture(seq.1)),
272+ // '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}'
273+ map(seq!("${", digit(), ":/", case_change(), "}"), |seq| {
274+ CaseChange(seq.1, seq.3)
275+ }),
276+ // '${' int ':+' if '}'
277+ map(
278+ seq!("${", digit(), ":+", text(TEXT_ESCAPE_CHARS, &['}']), "}"),
279+ |seq| { Conditional(seq.1, Some(seq.3), None) }
280+ ),
281+ // '${' int ':?' if ':' else '}'
282+ map(
283+ seq!(
284+ "${",
285+ digit(),
286+ ":?",
287+ text(TEXT_ESCAPE_CHARS, &[':']),
288+ ":",
289+ text(TEXT_ESCAPE_CHARS, &['}']),
290+ "}"
291+ ),
292+ |seq| { Conditional(seq.1, Some(seq.3), Some(seq.5)) }
293+ ),
294+ // '${' int ':-' else '}' | '${' int ':' else '}'
295+ map(
296+ seq!(
297+ "${",
298+ digit(),
299+ ":",
300+ optional("-"),
301+ text(TEXT_ESCAPE_CHARS, &['}']),
302+ "}"
303+ ),
304+ |seq| { Conditional(seq.1, None, Some(seq.4)) }
305+ ),
306+ )
307+ }
308+
309+ fn regex<'a>() -> impl Parser<'a, Output = Regex> {
310+ map(
311+ seq!(
312+ "/",
313+ // TODO parse as ECMAScript and convert to rust regex
314+ text(&['/'], &['/']),
315+ "/",
316+ zero_or_more(choice!(
317+ format(),
318+ // text doesn't parse $, if format fails we just accept the $ as text
319+ map("$", |_| FormatItem::Text("$".into())),
320+ map(text(&['\\', '/'], &['/', '$']), FormatItem::Text),
321+ )),
322+ "/",
323+ // vscode really doesn't allow escaping } here
324+ // so it's impossible to write a regex escape containing a }
325+ // we can consider deviating here and allowing the escape
326+ text(&[], &['}']),
327+ ),
328+ |(_, value, _, replacement, _, options)| Regex {
329+ value,
330+ replacement,
331+ options,
332+ },
333+ )
334+ }
335+
336+ fn tabstop<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
337+ map(
338+ or(
339+ right("$", digit()),
340+ map(seq!("${", digit(), "}"), |values| values.1),
341+ ),
342+ |digit| SnippetElement::Tabstop { tabstop: digit },
343+ )
344+ }
345+
346+ fn placeholder<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
347+ map(
348+ seq!(
349+ "${",
350+ digit(),
351+ ":",
352+ // according to the grammar there is just a single anything here.
353+ // However in the prose it is explained that placeholders can be nested.
354+ // The example there contains both a placeholder text and a nested placeholder
355+ // which indicates a list. Looking at the VSCode sourcecode, the placeholder
356+ // is indeed parsed as zero_or_more so the grammar is simply incorrect here
357+ zero_or_more(anything(TEXT_ESCAPE_CHARS, true)),
358+ "}"
359+ ),
360+ |seq| SnippetElement::Placeholder {
361+ tabstop: seq.1,
362+ value: seq.3,
363+ },
364+ )
365+ }
366+
367+ fn choice<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
368+ map(
369+ seq!(
370+ "${",
371+ digit(),
372+ "|",
373+ sep(text(CHOICE_TEXT_ESCAPE_CHARS, &['|', ',']), ","),
374+ "|}",
375+ ),
376+ |seq| SnippetElement::Choice {
377+ tabstop: seq.1,
378+ choices: seq.3,
379+ },
380+ )
381+ }
382+
383+ fn variable<'a>() -> impl Parser<'a, Output = SnippetElement<'a>> {
384+ choice!(
385+ // $var
386+ map(right("$", var()), |name| SnippetElement::Variable {
387+ name,
388+ default: None,
389+ regex: None,
390+ }),
391+ // ${var}
392+ map(seq!("${", var(), "}",), |values| SnippetElement::Variable {
393+ name: values.1,
394+ default: None,
395+ regex: None,
396+ }),
397+ // ${var:default}
398+ map(
399+ seq!(
400+ "${",
401+ var(),
402+ ":",
403+ zero_or_more(anything(TEXT_ESCAPE_CHARS, true)),
404+ "}",
405+ ),
406+ |values| SnippetElement::Variable {
407+ name: values.1,
408+ default: Some(values.3),
409+ regex: None,
410+ }
411+ ),
412+ // ${var/value/format/options}
413+ map(seq!("${", var(), regex(), "}"), |values| {
414+ SnippetElement::Variable {
415+ name: values.1,
416+ default: None,
417+ regex: Some(values.2),
418+ }
419+ }),
420+ )
421+ }
422+
423+ fn anything<'a>(
424+ escape_chars: &'static [char],
425+ end_at_brace: bool,
426+ ) -> impl Parser<'a, Output = SnippetElement<'a>> {
427+ let term_chars: &[_] = if end_at_brace { &['$', '}'] } else { &['$'] };
428+ move |input: &'a str| {
429+ let parser = choice!(
430+ tabstop(),
431+ placeholder(),
432+ choice(),
433+ variable(),
434+ map("$", |_| SnippetElement::Text("$".into())),
435+ map(text(escape_chars, term_chars), SnippetElement::Text),
436+ );
437+ parser.parse(input)
438+ }
439+ }
440+
441+ fn snippet<'a>() -> impl Parser<'a, Output = Snippet<'a>> {
442+ map(one_or_more(anything(TEXT_ESCAPE_CHARS, false)), |parts| {
443+ Snippet { elements: parts }
444+ })
445+ }
446+
447+ pub fn parse(s: &str) -> Result<Snippet, &str> {
448+ snippet().parse(s).and_then(|(remainder, snippet)| {
449+ if remainder.is_empty() {
450+ Ok(snippet)
451+ } else {
452+ Err(remainder)
453+ }
454+ })
455+ }
456+
457+ #[cfg(test)]
458+ mod test {
459+ use super::SnippetElement::*;
460+ use super::*;
461+
462+ #[test]
463+ fn empty_string_is_error() {
464+ assert_eq!(Err(""), parse(""));
465+ }
466+
467+ #[test]
468+ fn parse_placeholders_in_function_call() {
469+ assert_eq!(
470+ Ok(Snippet {
471+ elements: vec![
472+ Text("match(".into()),
473+ Placeholder {
474+ tabstop: 1,
475+ value: vec!(Text("Arg1".into())),
476+ },
477+ Text(")".into())
478+ ]
479+ }),
480+ parse("match(${1:Arg1})")
481+ )
482+ }
483+
484+ #[test]
485+ fn unterminated_placeholder() {
486+ assert_eq!(
487+ Ok(Snippet {
488+ elements: vec![Text("match(".into()), Text("$".into()), Text("{1:)".into())]
489+ }),
490+ parse("match(${1:)")
491+ )
492+ }
493+
494+ #[test]
495+ fn parse_empty_placeholder() {
496+ assert_eq!(
497+ Ok(Snippet {
498+ elements: vec![
499+ Text("match(".into()),
500+ Placeholder {
501+ tabstop: 1,
502+ value: vec![],
503+ },
504+ Text(")".into())
505+ ]
506+ }),
507+ parse("match(${1:})")
508+ )
509+ }
510+
511+ #[test]
512+ fn parse_placeholders_in_statement() {
513+ assert_eq!(
514+ Ok(Snippet {
515+ elements: vec![
516+ Text("local ".into()),
517+ Placeholder {
518+ tabstop: 1,
519+ value: vec!(Text("var".into())),
520+ },
521+ Text(" = ".into()),
522+ Placeholder {
523+ tabstop: 1,
524+ value: vec!(Text("value".into())),
525+ },
526+ ]
527+ }),
528+ parse("local ${1:var} = ${1:value}")
529+ )
530+ }
531+
532+ #[test]
533+ fn parse_tabstop_nested_in_placeholder() {
534+ assert_eq!(
535+ Ok(Snippet {
536+ elements: vec![Placeholder {
537+ tabstop: 1,
538+ value: vec!(Text("var, ".into()), Tabstop { tabstop: 2 },),
539+ },]
540+ }),
541+ parse("${1:var, $2}")
542+ )
543+ }
544+
545+ #[test]
546+ fn parse_placeholder_nested_in_placeholder() {
547+ assert_eq!(
548+ Ok(Snippet {
549+ elements: vec![Placeholder {
550+ tabstop: 1,
551+ value: vec!(
552+ Text("foo ".into()),
553+ Placeholder {
554+ tabstop: 2,
555+ value: vec!(Text("bar".into())),
556+ },
557+ ),
558+ },]
559+ }),
560+ parse("${1:foo ${2:bar}}")
561+ )
562+ }
563+
564+ #[test]
565+ fn parse_all() {
566+ assert_eq!(
567+ Ok(Snippet {
568+ elements: vec![
569+ Text("hello ".into()),
570+ Tabstop { tabstop: 1 },
571+ Tabstop { tabstop: 2 },
572+ Text(" ".into()),
573+ Choice {
574+ tabstop: 1,
575+ choices: vec!["one".into(), "two".into(), "three".into()]
576+ },
577+ Text(" ".into()),
578+ Variable {
579+ name: "name",
580+ default: Some(vec![Text("foo".into())]),
581+ regex: None
582+ },
583+ Text(" ".into()),
584+ Variable {
585+ name: "var",
586+ default: None,
587+ regex: None
588+ },
589+ Text(" ".into()),
590+ Variable {
591+ name: "TM",
592+ default: None,
593+ regex: None
594+ },
595+ ]
596+ }),
597+ parse("hello $1${2} ${1|one,two,three|} ${name:foo} $var $TM")
598+ );
599+ }
600+
601+ #[test]
602+ fn regex_capture_replace() {
603+ assert_eq!(
604+ Ok(Snippet {
605+ elements: vec![Variable {
606+ name: "TM_FILENAME",
607+ default: None,
608+ regex: Some(Regex {
609+ value: "(.*).+$".into(),
610+ replacement: vec![FormatItem::Capture(1), FormatItem::Text("$".into())],
611+ options: Tendril::new(),
612+ }),
613+ }]
614+ }),
615+ parse("${TM_FILENAME/(.*).+$/$1$/}")
616+ );
617+ }
618+
619+ #[test]
620+ fn rust_macro() {
621+ assert_eq!(
622+ Ok(Snippet {
623+ elements: vec![
624+ Text("macro_rules! ".into()),
625+ Tabstop { tabstop: 1 },
626+ Text(" {\n (".into()),
627+ Tabstop { tabstop: 2 },
628+ Text(") => {\n ".into()),
629+ Tabstop { tabstop: 0 },
630+ Text("\n };\n}".into())
631+ ]
632+ }),
633+ parse("macro_rules! $1 {\n ($2) => {\n $0\n };\n}")
634+ );
635+ }
636+
637+ fn assert_text(snippet: &str, parsed_text: &str) {
638+ let res = parse(snippet).unwrap();
639+ let text = crate::snippet::render(&res, "\n", true).0;
640+ assert_eq!(text, parsed_text)
641+ }
642+
643+ #[test]
644+ fn robust_parsing() {
645+ assert_text("$", "$");
646+ assert_text("\\\\$", "\\$");
647+ assert_text("{", "{");
648+ assert_text("\\}", "}");
649+ assert_text("\\abc", "\\abc");
650+ assert_text("foo${f:\\}}bar", "foo}bar");
651+ assert_text("\\{", "\\{");
652+ assert_text("I need \\\\\\$", "I need \\$");
653+ assert_text("\\", "\\");
654+ assert_text("\\{{", "\\{{");
655+ assert_text("{{", "{{");
656+ assert_text("{{dd", "{{dd");
657+ assert_text("}}", "}}");
658+ assert_text("ff}}", "ff}}");
659+ assert_text("farboo", "farboo");
660+ assert_text("far{{}}boo", "far{{}}boo");
661+ assert_text("far{{123}}boo", "far{{123}}boo");
662+ assert_text("far\\{{123}}boo", "far\\{{123}}boo");
663+ assert_text("far{{id:bern}}boo", "far{{id:bern}}boo");
664+ assert_text("far{{id:bern {{basel}}}}boo", "far{{id:bern {{basel}}}}boo");
665+ assert_text(
666+ "far{{id:bern {{id:basel}}}}boo",
667+ "far{{id:bern {{id:basel}}}}boo",
668+ );
669+ assert_text(
670+ "far{{id:bern {{id2:basel}}}}boo",
671+ "far{{id:bern {{id2:basel}}}}boo",
672+ );
673+ assert_text("${}$\\a\\$\\}\\\\", "${}$\\a$}\\");
674+ assert_text("farboo", "farboo");
675+ assert_text("far{{}}boo", "far{{}}boo");
676+ assert_text("far{{123}}boo", "far{{123}}boo");
677+ assert_text("far\\{{123}}boo", "far\\{{123}}boo");
678+ assert_text("far`123`boo", "far`123`boo");
679+ assert_text("far\\`123\\`boo", "far\\`123\\`boo");
680+ assert_text("\\$far-boo", "$far-boo");
681+ }
682+
683+ fn assert_snippet(snippet: &str, expect: &[SnippetElement]) {
684+ let parsed_snippet = parse(snippet).unwrap();
685+ assert_eq!(parsed_snippet.elements, expect.to_owned())
686+ }
687+
688+ #[test]
689+ fn parse_variable() {
690+ use SnippetElement::*;
691+ assert_snippet(
692+ "$far-boo",
693+ &[
694+ Variable {
695+ name: "far",
696+ default: None,
697+ regex: None,
698+ },
699+ Text("-boo".into()),
700+ ],
701+ );
702+ assert_snippet(
703+ "far$farboo",
704+ &[
705+ Text("far".into()),
706+ Variable {
707+ name: "farboo",
708+ regex: None,
709+ default: None,
710+ },
711+ ],
712+ );
713+ assert_snippet(
714+ "far${farboo}",
715+ &[
716+ Text("far".into()),
717+ Variable {
718+ name: "farboo",
719+ regex: None,
720+ default: None,
721+ },
722+ ],
723+ );
724+ assert_snippet("$123", &[Tabstop { tabstop: 123 }]);
725+ assert_snippet(
726+ "$farboo",
727+ &[Variable {
728+ name: "farboo",
729+ regex: None,
730+ default: None,
731+ }],
732+ );
733+ assert_snippet(
734+ "$far12boo",
735+ &[Variable {
736+ name: "far12boo",
737+ regex: None,
738+ default: None,
739+ }],
740+ );
741+ assert_snippet(
742+ "000_${far}_000",
743+ &[
744+ Text("000_".into()),
745+ Variable {
746+ name: "far",
747+ regex: None,
748+ default: None,
749+ },
750+ Text("_000".into()),
751+ ],
752+ );
753+ }
754+
755+ #[test]
756+ fn parse_variable_transform() {
757+ assert_snippet(
758+ "${foo///}",
759+ &[Variable {
760+ name: "foo",
761+ regex: Some(Regex {
762+ value: Tendril::new(),
763+ replacement: Vec::new(),
764+ options: Tendril::new(),
765+ }),
766+ default: None,
767+ }],
768+ );
769+ assert_snippet(
770+ "${foo/regex/format/gmi}",
771+ &[Variable {
772+ name: "foo",
773+ regex: Some(Regex {
774+ value: "regex".into(),
775+ replacement: vec![FormatItem::Text("format".into())],
776+ options: "gmi".into(),
777+ }),
778+ default: None,
779+ }],
780+ );
781+ assert_snippet(
782+ "${foo/([A-Z][a-z])/format/}",
783+ &[Variable {
784+ name: "foo",
785+ regex: Some(Regex {
786+ value: "([A-Z][a-z])".into(),
787+ replacement: vec![FormatItem::Text("format".into())],
788+ options: Tendril::new(),
789+ }),
790+ default: None,
791+ }],
792+ );
793+
794+ // invalid regex TODO: reneable tests once we actually parse this regex flavour
795+ // assert_text(
796+ // "${foo/([A-Z][a-z])/format/GMI}",
797+ // "${foo/([A-Z][a-z])/format/GMI}",
798+ // );
799+ // assert_text(
800+ // "${foo/([A-Z][a-z])/format/funky}",
801+ // "${foo/([A-Z][a-z])/format/funky}",
802+ // );
803+ // assert_text("${foo/([A-Z][a-z]/format/}", "${foo/([A-Z][a-z]/format/}");
804+ assert_text(
805+ "${foo/regex\\/format/options}",
806+ "${foo/regex\\/format/options}",
807+ );
808+
809+ // tricky regex
810+ assert_snippet(
811+ "${foo/m\\/atch/$1/i}",
812+ &[Variable {
813+ name: "foo",
814+ regex: Some(Regex {
815+ value: "m/atch".into(),
816+ replacement: vec![FormatItem::Capture(1)],
817+ options: "i".into(),
818+ }),
819+ default: None,
820+ }],
821+ );
822+
823+ // incomplete
824+ assert_text("${foo///", "${foo///");
825+ assert_text("${foo/regex/format/options", "${foo/regex/format/options");
826+
827+ // format string
828+ assert_snippet(
829+ "${foo/.*/${0:fooo}/i}",
830+ &[Variable {
831+ name: "foo",
832+ regex: Some(Regex {
833+ value: ".*".into(),
834+ replacement: vec![FormatItem::Conditional(0, None, Some("fooo".into()))],
835+ options: "i".into(),
836+ }),
837+ default: None,
838+ }],
839+ );
840+ assert_snippet(
841+ "${foo/.*/${1}/i}",
842+ &[Variable {
843+ name: "foo",
844+ regex: Some(Regex {
845+ value: ".*".into(),
846+ replacement: vec![FormatItem::Capture(1)],
847+ options: "i".into(),
848+ }),
849+ default: None,
850+ }],
851+ );
852+ assert_snippet(
853+ "${foo/.*/$1/i}",
854+ &[Variable {
855+ name: "foo",
856+ regex: Some(Regex {
857+ value: ".*".into(),
858+ replacement: vec![FormatItem::Capture(1)],
859+ options: "i".into(),
860+ }),
861+ default: None,
862+ }],
863+ );
864+ assert_snippet(
865+ "${foo/.*/This-$1-encloses/i}",
866+ &[Variable {
867+ name: "foo",
868+ regex: Some(Regex {
869+ value: ".*".into(),
870+ replacement: vec![
871+ FormatItem::Text("This-".into()),
872+ FormatItem::Capture(1),
873+ FormatItem::Text("-encloses".into()),
874+ ],
875+ options: "i".into(),
876+ }),
877+ default: None,
878+ }],
879+ );
880+ assert_snippet(
881+ "${foo/.*/complex${1:else}/i}",
882+ &[Variable {
883+ name: "foo",
884+ regex: Some(Regex {
885+ value: ".*".into(),
886+ replacement: vec![
887+ FormatItem::Text("complex".into()),
888+ FormatItem::Conditional(1, None, Some("else".into())),
889+ ],
890+ options: "i".into(),
891+ }),
892+ default: None,
893+ }],
894+ );
895+ assert_snippet(
896+ "${foo/.*/complex${1:-else}/i}",
897+ &[Variable {
898+ name: "foo",
899+ regex: Some(Regex {
900+ value: ".*".into(),
901+ replacement: vec![
902+ FormatItem::Text("complex".into()),
903+ FormatItem::Conditional(1, None, Some("else".into())),
904+ ],
905+ options: "i".into(),
906+ }),
907+ default: None,
908+ }],
909+ );
910+ assert_snippet(
911+ "${foo/.*/complex${1:+if}/i}",
912+ &[Variable {
913+ name: "foo",
914+ regex: Some(Regex {
915+ value: ".*".into(),
916+ replacement: vec![
917+ FormatItem::Text("complex".into()),
918+ FormatItem::Conditional(1, Some("if".into()), None),
919+ ],
920+ options: "i".into(),
921+ }),
922+ default: None,
923+ }],
924+ );
925+ assert_snippet(
926+ "${foo/.*/complex${1:?if:else}/i}",
927+ &[Variable {
928+ name: "foo",
929+ regex: Some(Regex {
930+ value: ".*".into(),
931+ replacement: vec![
932+ FormatItem::Text("complex".into()),
933+ FormatItem::Conditional(1, Some("if".into()), Some("else".into())),
934+ ],
935+ options: "i".into(),
936+ }),
937+ default: None,
938+ }],
939+ );
940+ assert_snippet(
941+ "${foo/.*/complex${1:/upcase}/i}",
942+ &[Variable {
943+ name: "foo",
944+ regex: Some(Regex {
945+ value: ".*".into(),
946+ replacement: vec![
947+ FormatItem::Text("complex".into()),
948+ FormatItem::CaseChange(1, CaseChange::Upcase),
949+ ],
950+ options: "i".into(),
951+ }),
952+ default: None,
953+ }],
954+ );
955+ assert_snippet(
956+ "${TM_DIRECTORY/src\\//$1/}",
957+ &[Variable {
958+ name: "TM_DIRECTORY",
959+ regex: Some(Regex {
960+ value: "src/".into(),
961+ replacement: vec![FormatItem::Capture(1)],
962+ options: Tendril::new(),
963+ }),
964+ default: None,
965+ }],
966+ );
967+ assert_snippet(
968+ "${TM_SELECTED_TEXT/a/\\/$1/g}",
969+ &[Variable {
970+ name: "TM_SELECTED_TEXT",
971+ regex: Some(Regex {
972+ value: "a".into(),
973+ replacement: vec![FormatItem::Text("/".into()), FormatItem::Capture(1)],
974+ options: "g".into(),
975+ }),
976+ default: None,
977+ }],
978+ );
979+ assert_snippet(
980+ "${TM_SELECTED_TEXT/a/in\\/$1ner/g}",
981+ &[Variable {
982+ name: "TM_SELECTED_TEXT",
983+ regex: Some(Regex {
984+ value: "a".into(),
985+ replacement: vec![
986+ FormatItem::Text("in/".into()),
987+ FormatItem::Capture(1),
988+ FormatItem::Text("ner".into()),
989+ ],
990+ options: "g".into(),
991+ }),
992+ default: None,
993+ }],
994+ );
995+ assert_snippet(
996+ "${TM_SELECTED_TEXT/a/end\\//g}",
997+ &[Variable {
998+ name: "TM_SELECTED_TEXT",
999+ regex: Some(Regex {
1000+ value: "a".into(),
1001+ replacement: vec![FormatItem::Text("end/".into())],
1002+ options: "g".into(),
1003+ }),
1004+ default: None,
1005+ }],
1006+ );
1007+ }
1008+ // TODO port more tests from https://github.com/microsoft/vscode/blob/dce493cb6e36346ef2714e82c42ce14fc461b15c/src/vs/editor/contrib/snippet/test/browser/snippetParser.test.ts
1009+ }
1010+}
helix-lsp/src/transport.rs+52−21
@@ -38,6 +38,7 @@ enum ServerMessage {
38#[derive(Debug)]
38#[derive(Debug)]
39pub struct Transport {
39pub struct Transport {
40 id: usize,
40 id: usize,
41+ name: String,
41 pending_requests: Mutex<HashMap<jsonrpc::Id, Sender<Result<Value>>>>,
42 pending_requests: Mutex<HashMap<jsonrpc::Id, Sender<Result<Value>>>>,
42}
43}
43
44
@@ -47,6 +48,7 @@ impl Transport {
47 server_stdin: BufWriter<ChildStdin>,
48 server_stdin: BufWriter<ChildStdin>,
48 server_stderr: BufReader<ChildStderr>,
49 server_stderr: BufReader<ChildStderr>,
49 id: usize,
50 id: usize,
51+ name: String,
50 ) -> (
52 ) -> (
51 UnboundedReceiver<(usize, jsonrpc::Call)>,
53 UnboundedReceiver<(usize, jsonrpc::Call)>,
52 UnboundedSender<Payload>,
54 UnboundedSender<Payload>,
@@ -58,6 +60,7 @@ impl Transport {
58
60
59 let transport = Self {
61 let transport = Self {
60 id,
62 id,
63+ name,
61 pending_requests: Mutex::new(HashMap::default()),
64 pending_requests: Mutex::new(HashMap::default()),
62 };
65 };
63
66
@@ -83,6 +86,7 @@ impl Transport {
83 async fn recv_server_message(
86 async fn recv_server_message(
84 reader: &mut (impl AsyncBufRead + Unpin + Send),
87 reader: &mut (impl AsyncBufRead + Unpin + Send),
85 buffer: &mut String,
88 buffer: &mut String,
89+ language_server_name: &str,
86 ) -> Result<ServerMessage> {
90 ) -> Result<ServerMessage> {
87 let mut content_length = None;
91 let mut content_length = None;
88 loop {
92 loop {
@@ -124,7 +128,7 @@ impl Transport {
124 reader.read_exact(&mut content).await?;
128 reader.read_exact(&mut content).await?;
125 let msg = std::str::from_utf8(&content).context("invalid utf8 from server")?;
129 let msg = std::str::from_utf8(&content).context("invalid utf8 from server")?;
126
130
127− info!("<- {}", msg);
131+ info!("{language_server_name} <- {msg}");
128
132
129 // try parsing as output (server response) or call (server request)
133 // try parsing as output (server response) or call (server request)
130 let output: serde_json::Result<ServerMessage> = serde_json::from_str(msg);
134 let output: serde_json::Result<ServerMessage> = serde_json::from_str(msg);
@@ -135,12 +139,13 @@ impl Transport {
135 async fn recv_server_error(
139 async fn recv_server_error(
136 err: &mut (impl AsyncBufRead + Unpin + Send),
140 err: &mut (impl AsyncBufRead + Unpin + Send),
137 buffer: &mut String,
141 buffer: &mut String,
142+ language_server_name: &str,
138 ) -> Result<()> {
143 ) -> Result<()> {
139 buffer.truncate(0);
144 buffer.truncate(0);
140 if err.read_line(buffer).await? == 0 {
145 if err.read_line(buffer).await? == 0 {
141 return Err(Error::StreamClosed);
146 return Err(Error::StreamClosed);
142 };
147 };
143− error!("err <- {:?}", buffer);
148+ error!("{language_server_name} err <- {buffer:?}");
144
149
145 Ok(())
150 Ok(())
146 }
151 }
@@ -162,15 +167,17 @@ impl Transport {
162 Payload::Notification(value) => serde_json::to_string(&value)?,
167 Payload::Notification(value) => serde_json::to_string(&value)?,
163 Payload::Response(error) => serde_json::to_string(&error)?,
168 Payload::Response(error) => serde_json::to_string(&error)?,
164 };
169 };
165− self.send_string_to_server(server_stdin, json).await
170+ self.send_string_to_server(server_stdin, json, &self.name)
171+ .await
166 }
172 }
167
173
168 async fn send_string_to_server(
174 async fn send_string_to_server(
169 &self,
175 &self,
170 server_stdin: &mut BufWriter<ChildStdin>,
176 server_stdin: &mut BufWriter<ChildStdin>,
171 request: String,
177 request: String,
178+ language_server_name: &str,
172 ) -> Result<()> {
179 ) -> Result<()> {
173− info!("-> {}", request);
180+ info!("{language_server_name} -> {request}");
174
181
175 // send the headers
182 // send the headers
176 server_stdin
183 server_stdin
@@ -189,9 +196,13 @@ impl Transport {
189 &self,
196 &self,
190 client_tx: &UnboundedSender<(usize, jsonrpc::Call)>,
197 client_tx: &UnboundedSender<(usize, jsonrpc::Call)>,
191 msg: ServerMessage,
198 msg: ServerMessage,
199+ language_server_name: &str,
192 ) -> Result<()> {
200 ) -> Result<()> {
193 match msg {
201 match msg {
202+ ServerMessage::Output(output) => {
194− ServerMessage::Output(output) => self.process_request_response(output).await?,
203+ self.process_request_response(output, language_server_name)
204+ .await?
205+ }
195 ServerMessage::Call(call) => {
206 ServerMessage::Call(call) => {
196 client_tx
207 client_tx
197 .send((self.id, call))
208 .send((self.id, call))
@@ -202,14 +213,18 @@ impl Transport {
202 Ok(())
213 Ok(())
203 }
214 }
204
215
205− async fn process_request_response(&self, output: jsonrpc::Output) -> Result<()> {
216+ async fn process_request_response(
217+ &self,
218+ output: jsonrpc::Output,
219+ language_server_name: &str,
220+ ) -> Result<()> {
206 let (id, result) = match output {
221 let (id, result) = match output {
207 jsonrpc::Output::Success(jsonrpc::Success { id, result, .. }) => {
222 jsonrpc::Output::Success(jsonrpc::Success { id, result, .. }) => {
208− info!("<- {}", result);
223+ info!("{language_server_name} <- {}", result);
209 (id, Ok(result))
224 (id, Ok(result))
210 }
225 }
211 jsonrpc::Output::Failure(jsonrpc::Failure { id, error, .. }) => {
226 jsonrpc::Output::Failure(jsonrpc::Failure { id, error, .. }) => {
212− error!("<- {}", error);
227+ error!("{language_server_name} <- {error}");
213 (id, Err(error.into()))
228 (id, Err(error.into()))
214 }
229 }
215 };
230 };
@@ -240,12 +255,17 @@ impl Transport {
240 ) {
255 ) {
241 let mut recv_buffer = String::new();
256 let mut recv_buffer = String::new();
242 loop {
257 loop {
243− match Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await {
258+ match Self::recv_server_message(&mut server_stdout, &mut recv_buffer, &transport.name)
259+ .await
260+ {
244 Ok(msg) => {
261 Ok(msg) => {
262+ match transport
245− match transport.process_server_message(&client_tx, msg).await {
263+ .process_server_message(&client_tx, msg, &transport.name)
264+ .await
265+ {
246 Ok(_) => {}
266 Ok(_) => {}
247 Err(err) => {
267 Err(err) => {
248− error!("err: <- {:?}", err);
268+ error!("{} err: <- {err:?}", transport.name);
249 break;
269 break;
250 }
270 }
251 };
271 };
@@ -270,7 +290,7 @@ impl Transport {
270 params: jsonrpc::Params::None,
290 params: jsonrpc::Params::None,
271 }));
291 }));
272 match transport
292 match transport
273− .process_server_message(&client_tx, notification)
293+ .process_server_message(&client_tx, notification, &transport.name)
274 .await
294 .await
275 {
295 {
276 Ok(_) => {}
296 Ok(_) => {}
@@ -281,20 +301,22 @@ impl Transport {
281 break;
301 break;
282 }
302 }
283 Err(err) => {
303 Err(err) => {
284− error!("err: <- {:?}", err);
304+ error!("{} err: <- {err:?}", transport.name);
285 break;
305 break;
286 }
306 }
287 }
307 }
288 }
308 }
289 }
309 }
290
310
291− async fn err(_transport: Arc<Self>, mut server_stderr: BufReader<ChildStderr>) {
311+ async fn err(transport: Arc<Self>, mut server_stderr: BufReader<ChildStderr>) {
292 let mut recv_buffer = String::new();
312 let mut recv_buffer = String::new();
293 loop {
313 loop {
294− match Self::recv_server_error(&mut server_stderr, &mut recv_buffer).await {
314+ match Self::recv_server_error(&mut server_stderr, &mut recv_buffer, &transport.name)
315+ .await
316+ {
295 Ok(_) => {}
317 Ok(_) => {}
296 Err(err) => {
318 Err(err) => {
297− error!("err: <- {:?}", err);
319+ error!("{} err: <- {err:?}", transport.name);
298 break;
320 break;
299 }
321 }
300 }
322 }
@@ -331,6 +353,11 @@ impl Transport {
331 }
353 }
332 }
354 }
333
355
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+
334 // TODO: events that use capabilities need to do the right thing
361 // TODO: events that use capabilities need to do the right thing
335
362
336 loop {
363 loop {
@@ -348,10 +375,11 @@ impl Transport {
348 method: lsp_types::notification::Initialized::METHOD.to_string(),
375 method: lsp_types::notification::Initialized::METHOD.to_string(),
349 params: jsonrpc::Params::None,
376 params: jsonrpc::Params::None,
350 }));
377 }));
378+ let language_server_name = &transport.name;
351− match transport.process_server_message(&client_tx, notification).await {
379+ match transport.process_server_message(&client_tx, notification, language_server_name).await {
352 Ok(_) => {}
380 Ok(_) => {}
353 Err(err) => {
381 Err(err) => {
354− error!("err: <- {:?}", err);
382+ error!("{language_server_name} err: <- {err:?}");
355 }
383 }
356 }
384 }
357
385
@@ -361,14 +389,17 @@ impl Transport {
361 match transport.send_payload_to_server(&mut server_stdin, msg).await {
389 match transport.send_payload_to_server(&mut server_stdin, msg).await {
362 Ok(_) => {}
390 Ok(_) => {}
363 Err(err) => {
391 Err(err) => {
364− error!("err: <- {:?}", err);
392+ error!("{language_server_name} err: <- {err:?}");
365 }
393 }
366 }
394 }
367 }
395 }
368 }
396 }
369 msg = client_rx.recv() => {
397 msg = client_rx.recv() => {
370 if let Some(msg) = msg {
398 if let Some(msg) = msg {
399+ if is_pending && is_shutdown(&msg) {
400+ log::info!("Language server not initialized, shutting down");
401+ break;
371− if is_pending && !is_initialize(&msg) {
402+ } else if is_pending && !is_initialize(&msg) {
372 // ignore notifications
403 // ignore notifications
373 if let Payload::Notification(_) = msg {
404 if let Payload::Notification(_) = msg {
374 continue;
405 continue;
@@ -380,7 +411,7 @@ impl Transport {
380 match transport.send_payload_to_server(&mut server_stdin, msg).await {
411 match transport.send_payload_to_server(&mut server_stdin, msg).await {
381 Ok(_) => {}
412 Ok(_) => {}
382 Err(err) => {
413 Err(err) => {
383− error!("err: <- {:?}", err);
414+ error!("{} err: <- {err:?}", transport.name);
384 }
415 }
385 }
416 }
386 }
417 }
helix-parsec/Cargo.tomlnew file+14
@@ -0,0 +1,14 @@
1+[package]
2+name = "helix-parsec"
3+description = "Parser combinators for Helix"
4+include = ["src/**/*", "README.md"]
5+version.workspace = true
6+authors.workspace = true
7+edition.workspace = true
8+license.workspace = true
9+rust-version.workspace = true
10+categories.workspace = true
11+repository.workspace = true
12+homepage.workspace = true
13+
14+[dependencies]
helix-parsec/src/lib.rsnew file+574
@@ -0,0 +1,574 @@
1+//! Parser-combinator functions
2+//!
3+//! This module provides parsers and parser combinators which can be used
4+//! together to build parsers by functional composition.
5+
6+// This module implements parser combinators following https://bodil.lol/parser-combinators/.
7+// `sym` (trait implementation for `&'static str`), `map`, `pred` (filter), `one_or_more`,
8+// `zero_or_more`, as well as the `Parser` trait originate mostly from that post.
9+// The remaining parsers and parser combinators are either based on
10+// https://github.com/archseer/snippets.nvim/blob/a583da6ef130d2a4888510afd8c4e5ffd62d0dce/lua/snippet/parser.lua#L5-L138
11+// or are novel.
12+
13+// When a parser matches the input successfully, it returns `Ok((next_input, some_value))`
14+// where the type of the returned value depends on the parser. If the parser fails to match,
15+// it returns `Err(input)`.
16+type ParseResult<'a, Output> = Result<(&'a str, Output), &'a str>;
17+
18+/// A parser or parser-combinator.
19+///
20+/// Parser-combinators compose multiple parsers together to parse input.
21+/// For example, two basic parsers (`&'static str`s) may be combined with
22+/// a parser-combinator like [or] to produce a new parser.
23+///
24+/// ```
25+/// use helix_parsec::{or, Parser};
26+/// let foo = "foo"; // matches "foo" literally
27+/// let bar = "bar"; // matches "bar" literally
28+/// let foo_or_bar = or(foo, bar); // matches either "foo" or "bar"
29+/// assert_eq!(Ok(("", "foo")), foo_or_bar.parse("foo"));
30+/// assert_eq!(Ok(("", "bar")), foo_or_bar.parse("bar"));
31+/// assert_eq!(Err("baz"), foo_or_bar.parse("baz"));
32+/// ```
33+pub trait Parser<'a> {
34+ type Output;
35+
36+ fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output>;
37+}
38+
39+// Most parser-combinators are written as higher-order functions which take some
40+// parser(s) as input and return a new parser: a function that takes input and returns
41+// a parse result. The underlying implementation of [Parser::parse] for these functions
42+// is simply application.
43+#[doc(hidden)]
44+impl<'a, F, T> Parser<'a> for F
45+where
46+ F: Fn(&'a str) -> ParseResult<T>,
47+{
48+ type Output = T;
49+
50+ fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output> {
51+ self(input)
52+ }
53+}
54+
55+/// A parser which matches the string literal exactly.
56+///
57+/// This parser succeeds if the next characters in the input are equal to the given
58+/// string literal.
59+///
60+/// Note that [str::parse] interferes with calling [Parser::parse] on string literals
61+/// directly; this trait implementation works when used within any parser combinator
62+/// but does not work on its own. To call [Parser::parse] on a parser for a string
63+/// literal, use the [token] parser.
64+///
65+/// # Examples
66+///
67+/// ```
68+/// use helix_parsec::{or, Parser};
69+/// let parser = or("foo", "bar");
70+/// assert_eq!(Ok(("", "foo")), parser.parse("foo"));
71+/// assert_eq!(Ok(("", "bar")), parser.parse("bar"));
72+/// assert_eq!(Err("baz"), parser.parse("baz"));
73+/// ```
74+impl<'a> Parser<'a> for &'static str {
75+ type Output = &'a str;
76+
77+ fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output> {
78+ match input.get(0..self.len()) {
79+ Some(actual) if actual == *self => Ok((&input[self.len()..], &input[0..self.len()])),
80+ _ => Err(input),
81+ }
82+ }
83+}
84+
85+// Parsers
86+
87+/// A parser which matches the given string literally.
88+///
89+/// This function is a convenience for interpreting string literals as parsers
90+/// and is only necessary to avoid conflict with [str::parse]. See the documentation
91+/// for the `&'static str` implementation of [Parser].
92+///
93+/// # Examples
94+///
95+/// ```
96+/// use helix_parsec::{token, Parser};
97+/// let parser = token("foo");
98+/// assert_eq!(Ok(("", "foo")), parser.parse("foo"));
99+/// assert_eq!(Err("bar"), parser.parse("bar"));
100+/// ```
101+pub fn token<'a>(literal: &'static str) -> impl Parser<'a, Output = &'a str> {
102+ literal
103+}
104+
105+/// A parser which matches all values until the specified pattern is found.
106+///
107+/// If the pattern is not found, this parser does not match. The input up to the
108+/// character which returns `true` is returned but not that character itself.
109+///
110+/// If the pattern function returns true on the first input character, this
111+/// parser fails.
112+///
113+/// # Examples
114+///
115+/// ```
116+/// use helix_parsec::{take_until, Parser};
117+/// let parser = take_until(|c| c == '.');
118+/// assert_eq!(Ok((".bar", "foo")), parser.parse("foo.bar"));
119+/// assert_eq!(Err(".foo"), parser.parse(".foo"));
120+/// assert_eq!(Err("foo"), parser.parse("foo"));
121+/// ```
122+pub fn take_until<'a, F>(pattern: F) -> impl Parser<'a, Output = &'a str>
123+where
124+ F: Fn(char) -> bool,
125+{
126+ move |input: &'a str| match input.find(&pattern) {
127+ Some(index) if index != 0 => Ok((&input[index..], &input[0..index])),
128+ _ => Err(input),
129+ }
130+}
131+
132+/// A parser which matches all values until the specified pattern no longer match.
133+///
134+/// This parser only ever fails if the input has a length of zero.
135+///
136+/// # Examples
137+///
138+/// ```
139+/// use helix_parsec::{take_while, Parser};
140+/// let parser = take_while(|c| c == '1');
141+/// assert_eq!(Ok(("2", "11")), parser.parse("112"));
142+/// assert_eq!(Err("22"), parser.parse("22"));
143+/// ```
144+pub fn take_while<'a, F>(pattern: F) -> impl Parser<'a, Output = &'a str>
145+where
146+ F: Fn(char) -> bool,
147+{
148+ move |input: &'a str| match input
149+ .char_indices()
150+ .take_while(|(_p, c)| pattern(*c))
151+ .last()
152+ {
153+ Some((index, c)) => {
154+ let index = index + c.len_utf8();
155+ Ok((&input[index..], &input[0..index]))
156+ }
157+ _ => Err(input),
158+ }
159+}
160+
161+// Variadic parser combinators
162+
163+/// A parser combinator which matches a sequence of parsers in an all-or-nothing fashion.
164+///
165+/// The returned value is a tuple containing the outputs of all parsers in order. Each
166+/// parser in the sequence may be typed differently.
167+///
168+/// # Examples
169+///
170+/// ```
171+/// use helix_parsec::{seq, Parser};
172+/// let parser = seq!("<", "a", ">");
173+/// assert_eq!(Ok(("", ("<", "a", ">"))), parser.parse("<a>"));
174+/// assert_eq!(Err("<b>"), parser.parse("<b>"));
175+/// ```
176+#[macro_export]
177+macro_rules! seq {
178+ ($($parsers: expr),+ $(,)?) => {
179+ ($($parsers),+)
180+ }
181+}
182+
183+// Seq is implemented using trait-implementations of Parser for various size tuples.
184+// This allows sequences to be typed heterogeneously.
185+macro_rules! seq_impl {
186+ ($($parser:ident),+) => {
187+ #[allow(non_snake_case)]
188+ impl<'a, $($parser),+> Parser<'a> for ($($parser),+)
189+ where
190+ $($parser: Parser<'a>),+
191+ {
192+ type Output = ($($parser::Output),+);
193+
194+ fn parse(&self, input: &'a str) -> ParseResult<'a, Self::Output> {
195+ let ($($parser),+) = self;
196+ seq_body_impl!(input, input, $($parser),+ ; )
197+ }
198+ }
199+ }
200+}
201+
202+macro_rules! seq_body_impl {
203+ ($input:expr, $next_input:expr, $head:ident, $($tail:ident),+ ; $(,)? $($acc:ident),*) => {
204+ match $head.parse($next_input) {
205+ Ok((next_input, $head)) => seq_body_impl!($input, next_input, $($tail),+ ; $($acc),*, $head),
206+ Err(_) => Err($input),
207+ }
208+ };
209+ ($input:expr, $next_input:expr, $last:ident ; $(,)? $($acc:ident),*) => {
210+ match $last.parse($next_input) {
211+ Ok((next_input, last)) => Ok((next_input, ($($acc),+, last))),
212+ Err(_) => Err($input),
213+ }
214+ }
215+}
216+
217+seq_impl!(A, B);
218+seq_impl!(A, B, C);
219+seq_impl!(A, B, C, D);
220+seq_impl!(A, B, C, D, E);
221+seq_impl!(A, B, C, D, E, F);
222+seq_impl!(A, B, C, D, E, F, G);
223+seq_impl!(A, B, C, D, E, F, G, H);
224+seq_impl!(A, B, C, D, E, F, G, H, I);
225+seq_impl!(A, B, C, D, E, F, G, H, I, J);
226+
227+/// A parser combinator which chooses the first of the input parsers which matches
228+/// successfully.
229+///
230+/// All input parsers must have the same output type. This is a variadic form for [or].
231+///
232+/// # Examples
233+///
234+/// ```
235+/// use helix_parsec::{choice, or, Parser};
236+/// let parser = choice!("foo", "bar", "baz");
237+/// assert_eq!(Ok(("", "foo")), parser.parse("foo"));
238+/// assert_eq!(Ok(("", "bar")), parser.parse("bar"));
239+/// assert_eq!(Err("quiz"), parser.parse("quiz"));
240+/// ```
241+#[macro_export]
242+macro_rules! choice {
243+ ($parser: expr $(,)?) => {
244+ $parser
245+ };
246+ ($parser: expr, $($rest: expr),+ $(,)?) => {
247+ or($parser, choice!($($rest),+))
248+ }
249+}
250+
251+// Ordinary parser combinators
252+
253+/// A parser combinator which takes a parser as input and maps the output using the
254+/// given transformation function.
255+///
256+/// This corresponds to [Result::map]. The value is only mapped if the input parser
257+/// matches against input.
258+///
259+/// # Examples
260+///
261+/// ```
262+/// use helix_parsec::{map, Parser};
263+/// let parser = map("123", |s| s.parse::<i32>().unwrap());
264+/// assert_eq!(Ok(("", 123)), parser.parse("123"));
265+/// assert_eq!(Err("abc"), parser.parse("abc"));
266+/// ```
267+pub fn map<'a, P, F, T>(parser: P, map_fn: F) -> impl Parser<'a, Output = T>
268+where
269+ P: Parser<'a>,
270+ F: Fn(P::Output) -> T,
271+{
272+ move |input| {
273+ parser
274+ .parse(input)
275+ .map(|(next_input, result)| (next_input, map_fn(result)))
276+ }
277+}
278+
279+/// A parser combinator which succeeds if the given parser matches the input and
280+/// the given `filter_map_fn` returns `Some`.
281+///
282+/// # Examples
283+///
284+/// ```
285+/// use helix_parsec::{filter_map, take_until, Parser};
286+/// let parser = filter_map(take_until(|c| c == '.'), |s| s.parse::<i32>().ok());
287+/// assert_eq!(Ok((".456", 123)), parser.parse("123.456"));
288+/// assert_eq!(Err("abc.def"), parser.parse("abc.def"));
289+/// ```
290+pub fn filter_map<'a, P, F, T>(parser: P, filter_map_fn: F) -> impl Parser<'a, Output = T>
291+where
292+ P: Parser<'a>,
293+ F: Fn(P::Output) -> Option<T>,
294+{
295+ move |input| match parser.parse(input) {
296+ Ok((next_input, value)) => match filter_map_fn(value) {
297+ Some(value) => Ok((next_input, value)),
298+ None => Err(input),
299+ },
300+ Err(_) => Err(input),
301+ }
302+}
303+
304+/// A parser combinator which succeeds if the first given parser matches the input and
305+/// the second given parse also matches.
306+///
307+/// # Examples
308+///
309+/// ```
310+/// use helix_parsec::{reparse_as, take_until, one_or_more, Parser};
311+/// let parser = reparse_as(take_until(|c| c == '/'), one_or_more("a"));
312+/// assert_eq!(Ok(("/bb", vec!["a", "a"])), parser.parse("aa/bb"));
313+/// ```
314+pub fn reparse_as<'a, P1, P2, T>(parser1: P1, parser2: P2) -> impl Parser<'a, Output = T>
315+where
316+ P1: Parser<'a, Output = &'a str>,
317+ P2: Parser<'a, Output = T>,
318+{
319+ filter_map(parser1, move |str| {
320+ parser2.parse(str).map(|(_, value)| value).ok()
321+ })
322+}
323+
324+/// A parser combinator which only matches the input when the predicate function
325+/// returns true.
326+///
327+/// # Examples
328+///
329+/// ```
330+/// use helix_parsec::{filter, take_until, Parser};
331+/// let parser = filter(take_until(|c| c == '.'), |s| s == &"123");
332+/// assert_eq!(Ok((".456", "123")), parser.parse("123.456"));
333+/// assert_eq!(Err("456.123"), parser.parse("456.123"));
334+/// ```
335+pub fn filter<'a, P, F, T>(parser: P, pred_fn: F) -> impl Parser<'a, Output = T>
336+where
337+ P: Parser<'a, Output = T>,
338+ F: Fn(&P::Output) -> bool,
339+{
340+ move |input| {
341+ if let Ok((next_input, value)) = parser.parse(input) {
342+ if pred_fn(&value) {
343+ return Ok((next_input, value));
344+ }
345+ }
346+ Err(input)
347+ }
348+}
349+
350+/// A parser combinator which matches either of the input parsers.
351+///
352+/// Both parsers must have the same output type. For a variadic form which
353+/// can take any number of parsers, use `choice!`.
354+///
355+/// # Examples
356+///
357+/// ```
358+/// use helix_parsec::{or, Parser};
359+/// let parser = or("foo", "bar");
360+/// assert_eq!(Ok(("", "foo")), parser.parse("foo"));
361+/// assert_eq!(Ok(("", "bar")), parser.parse("bar"));
362+/// assert_eq!(Err("baz"), parser.parse("baz"));
363+/// ```
364+pub fn or<'a, P1, P2, T>(parser1: P1, parser2: P2) -> impl Parser<'a, Output = T>
365+where
366+ P1: Parser<'a, Output = T>,
367+ P2: Parser<'a, Output = T>,
368+{
369+ move |input| match parser1.parse(input) {
370+ ok @ Ok(_) => ok,
371+ Err(_) => parser2.parse(input),
372+ }
373+}
374+
375+/// A parser combinator which attempts to match the given parser, returning a
376+/// `None` output value if the parser does not match.
377+///
378+/// The parser produced with this combinator always succeeds. If the given parser
379+/// succeeds, `Some(value)` is returned where `value` is the output of the given
380+/// parser. Otherwise, `None`.
381+///
382+/// # Examples
383+///
384+/// ```
385+/// use helix_parsec::{optional, Parser};
386+/// let parser = optional("foo");
387+/// assert_eq!(Ok(("bar", Some("foo"))), parser.parse("foobar"));
388+/// assert_eq!(Ok(("bar", None)), parser.parse("bar"));
389+/// ```
390+pub fn optional<'a, P, T>(parser: P) -> impl Parser<'a, Output = Option<T>>
391+where
392+ P: Parser<'a, Output = T>,
393+{
394+ move |input| match parser.parse(input) {
395+ Ok((next_input, value)) => Ok((next_input, Some(value))),
396+ Err(_) => Ok((input, None)),
397+ }
398+}
399+
400+/// A parser combinator which runs the given parsers in sequence and returns the
401+/// value of `left` if both are matched.
402+///
403+/// This is useful for two-element sequences in which you only want the output
404+/// value of the `left` parser.
405+///
406+/// # Examples
407+///
408+/// ```
409+/// use helix_parsec::{left, Parser};
410+/// let parser = left("foo", "bar");
411+/// assert_eq!(Ok(("", "foo")), parser.parse("foobar"));
412+/// ```
413+pub fn left<'a, L, R, T>(left: L, right: R) -> impl Parser<'a, Output = T>
414+where
415+ L: Parser<'a, Output = T>,
416+ R: Parser<'a>,
417+{
418+ map(seq!(left, right), |(left_value, _)| left_value)
419+}
420+
421+/// A parser combinator which runs the given parsers in sequence and returns the
422+/// value of `right` if both are matched.
423+///
424+/// This is useful for two-element sequences in which you only want the output
425+/// value of the `right` parser.
426+///
427+/// # Examples
428+///
429+/// ```
430+/// use helix_parsec::{right, Parser};
431+/// let parser = right("foo", "bar");
432+/// assert_eq!(Ok(("", "bar")), parser.parse("foobar"));
433+/// ```
434+pub fn right<'a, L, R, T>(left: L, right: R) -> impl Parser<'a, Output = T>
435+where
436+ L: Parser<'a>,
437+ R: Parser<'a, Output = T>,
438+{
439+ map(seq!(left, right), |(_, right_value)| right_value)
440+}
441+
442+/// A parser combinator which matches the given parser against the input zero or
443+/// more times.
444+///
445+/// This parser always succeeds and returns the empty Vec when it matched zero
446+/// times.
447+///
448+/// # Examples
449+///
450+/// ```
451+/// use helix_parsec::{zero_or_more, Parser};
452+/// let parser = zero_or_more("a");
453+/// assert_eq!(Ok(("", vec![])), parser.parse(""));
454+/// assert_eq!(Ok(("", vec!["a"])), parser.parse("a"));
455+/// assert_eq!(Ok(("", vec!["a", "a"])), parser.parse("aa"));
456+/// assert_eq!(Ok(("bb", vec![])), parser.parse("bb"));
457+/// ```
458+pub fn zero_or_more<'a, P, T>(parser: P) -> impl Parser<'a, Output = Vec<T>>
459+where
460+ P: Parser<'a, Output = T>,
461+{
462+ let parser = non_empty(parser);
463+ move |mut input| {
464+ let mut values = Vec::new();
465+
466+ while let Ok((next_input, value)) = parser.parse(input) {
467+ input = next_input;
468+ values.push(value);
469+ }
470+
471+ Ok((input, values))
472+ }
473+}
474+
475+/// A parser combinator which matches the given parser against the input one or
476+/// more times.
477+///
478+/// This parser combinator acts the same as [zero_or_more] but must match at
479+/// least once.
480+///
481+/// # Examples
482+///
483+/// ```
484+/// use helix_parsec::{one_or_more, Parser};
485+/// let parser = one_or_more("a");
486+/// assert_eq!(Err(""), parser.parse(""));
487+/// assert_eq!(Ok(("", vec!["a"])), parser.parse("a"));
488+/// assert_eq!(Ok(("", vec!["a", "a"])), parser.parse("aa"));
489+/// assert_eq!(Err("bb"), parser.parse("bb"));
490+/// ```
491+pub fn one_or_more<'a, P, T>(parser: P) -> impl Parser<'a, Output = Vec<T>>
492+where
493+ P: Parser<'a, Output = T>,
494+{
495+ let parser = non_empty(parser);
496+ move |mut input| {
497+ let mut values = Vec::new();
498+
499+ match parser.parse(input) {
500+ Ok((next_input, value)) => {
501+ input = next_input;
502+ values.push(value);
503+ }
504+ Err(err) => return Err(err),
505+ }
506+
507+ while let Ok((next_input, value)) = parser.parse(input) {
508+ input = next_input;
509+ values.push(value);
510+ }
511+
512+ Ok((input, values))
513+ }
514+}
515+
516+/// A parser combinator which matches one or more instances of the given parser
517+/// interspersed with the separator parser.
518+///
519+/// Output values of the separator parser are discarded.
520+///
521+/// This is typically used to parse function arguments or list items.
522+///
523+/// # Examples
524+///
525+/// ```rust
526+/// use helix_parsec::{sep, Parser};
527+/// let parser = sep("a", ",");
528+/// assert_eq!(Ok(("", vec!["a", "a", "a"])), parser.parse("a,a,a"));
529+/// ```
530+pub fn sep<'a, P, S, T>(parser: P, separator: S) -> impl Parser<'a, Output = Vec<T>>
531+where
532+ P: Parser<'a, Output = T>,
533+ S: Parser<'a>,
534+{
535+ move |mut input| {
536+ let mut values = Vec::new();
537+
538+ match parser.parse(input) {
539+ Ok((next_input, value)) => {
540+ input = next_input;
541+ values.push(value);
542+ }
543+ Err(err) => return Err(err),
544+ }
545+
546+ loop {
547+ match separator.parse(input) {
548+ Ok((next_input, _)) => input = next_input,
549+ Err(_) => break,
550+ }
551+
552+ match parser.parse(input) {
553+ Ok((next_input, value)) => {
554+ input = next_input;
555+ values.push(value);
556+ }
557+ Err(_) => break,
558+ }
559+ }
560+
561+ Ok((input, values))
562+ }
563+}
564+
565+pub fn non_empty<'a, T>(p: impl Parser<'a, Output = T>) -> impl Parser<'a, Output = T> {
566+ move |input| {
567+ let (new_input, res) = p.parse(input)?;
568+ if new_input.len() == input.len() {
569+ Err(input)
570+ } else {
571+ Ok((new_input, res))
572+ }
573+ }
574+}
helix-term/Cargo.toml+33−24
@@ -1,16 +1,16 @@
1[package]
1[package]
2name = "helix-term"
2name = "helix-term"
3−version = "0.6.0"
4description = "A post-modern text editor."
3description = "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"
11include = ["src/**/*", "README.md"]
4include = ["src/**/*", "README.md"]
12default-run = "hx"
5default-run = "hx"
6+version.workspace = true
7+authors.workspace = true
8+edition.workspace = true
9+license.workspace = true
13−rust-version = "1.57"
10+rust-version.workspace = true
11+categories.workspace = true
12+repository.workspace = true
13+homepage.workspace = true
14
14
15[features]
15[features]
16default = ["git"]
16default = ["git"]
@@ -23,21 +23,22 @@ name = "hx"
23path = "src/main.rs"
23path = "src/main.rs"
24
24
25[dependencies]
25[dependencies]
26−helix-core = { version = "0.6", path = "../helix-core" }
26+helix-core = { path = "../helix-core" }
27+helix-event = { path = "../helix-event" }
27−helix-view = { version = "0.6", path = "../helix-view" }
28+helix-view = { path = "../helix-view" }
28−helix-lsp = { version = "0.6", path = "../helix-lsp" }
29+helix-lsp = { path = "../helix-lsp" }
29−helix-dap = { version = "0.6", path = "../helix-dap" }
30+helix-dap = { path = "../helix-dap" }
30−helix-vcs = { version = "0.6", path = "../helix-vcs" }
31+helix-vcs = { path = "../helix-vcs" }
31−helix-loader = { version = "0.6", path = "../helix-loader" }
32+helix-loader = { path = "../helix-loader" }
32
33
33anyhow = "1"
34anyhow = "1"
34−once_cell = "1.17"
35+once_cell = "1.19"
35
36
36−which = "4.4"
37+which = "5.0.0"
37
38
38tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
39tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
39tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
40tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
40−crossterm = { version = "0.25", features = ["event-stream"] }
41+crossterm = { version = "0.27", features = ["event-stream"] }
41signal-hook = "0.3"
42signal-hook = "0.3"
42tokio-stream = "0.1"
43tokio-stream = "0.1"
43futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
44futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
@@ -49,13 +50,17 @@ chrono = { version = "0.4", default-features = false, features = ["clock"] }
49log = "0.4"
50log = "0.4"
50
51
51# File picker
52# File picker
52−fuzzy-matcher = "0.3"
53+nucleo.workspace = true
53ignore = "0.4"
54ignore = "0.4"
54# markdown doc rendering
55# markdown doc rendering
55pulldown-cmark = { version = "0.9", default-features = false }
56pulldown-cmark = { version = "0.9", default-features = false }
56# file type detection
57# file type detection
57content_inspector = "0.2.4"
58content_inspector = "0.2.4"
58
59
60+# opening URLs
61+open = "5.0.1"
62+url = "2.5.0"
63+
59# config
64# config
60toml = "0.7"
65toml = "0.7"
61
66
@@ -63,16 +68,20 @@ serde_json = "1.0"
63serde = { version = "1.0", features = ["derive"] }
68serde = { version = "1.0", features = ["derive"] }
64
69
65# ripgrep for global search
70# ripgrep for global search
66−grep-regex = "0.1.10"
71+grep-regex = "0.1.12"
67−grep-searcher = "0.1.10"
72+grep-searcher = "0.1.13"
68
73
69[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
74[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
70signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
75signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
76+libc = "0.2.151"
77+
78+[target.'cfg(target_os = "macos")'.dependencies]
79+crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] }
71
80
72[build-dependencies]
81[build-dependencies]
73−helix-loader = { version = "0.6", path = "../helix-loader" }
82+helix-loader = { path = "../helix-loader" }
74
83
75[dev-dependencies]
84[dev-dependencies]
76−smallvec = "1.10"
85+smallvec = "1.11"
77−indoc = "2.0.0"
86+indoc = "2.0.4"
78−tempfile = "3.3.0"
87+tempfile = "3.8.1"
helix-term/src/application.rs+402−274

This diff is too big to show here.

helix-term/src/args.rs+29

This diff is too big to show here.

helix-term/src/commands.rs+1200−678

This diff is too big to show here.

helix-term/src/commands/dap.rs+90−57

This diff is too big to show here.

helix-term/src/commands/lsp.rs+828−490

This diff is too big to show here.

helix-term/src/commands/typed.rs+1316−746

This diff is too big to show here.

helix-term/src/compositor.rs+12

This diff is too big to show here.

helix-term/src/config.rs+110−30

This diff is too big to show here.

helix-term/src/health.rs+74−24

This diff is too big to show here.

helix-term/src/keymap.rs+119−166

This diff is too big to show here.

helix-term/src/keymap/default.rs+16−8

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/src/main.rs+36−31

This diff is too big to show here.

helix-term/src/ui/completion.rs+214−112

This diff is too big to show here.

helix-term/src/ui/document.rs+91−49

This diff is too big to show here.

helix-term/src/ui/editor.rs+233−134

This diff is too big to show here.

helix-term/src/ui/fuzzy_match.rsdeleted−239

This diff is too big to show here.

helix-term/src/ui/fuzzy_match/test.rsdeleted−47

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/src/ui/markdown.rs+24−18

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/src/ui/mod.rs+112−183

This diff is too big to show here.

helix-term/src/ui/overlay.rs+1−1

This diff is too big to show here.

helix-term/src/ui/picker.rs+630−575

This diff is too big to show here.

helix-term/src/ui/popup.rs+44−18

This diff is too big to show here.

helix-term/src/ui/prompt.rs+70−53

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/src/ui/text.rs+1−1

This diff is too big to show here.

helix-term/tests/integration.rs+3−5

This diff is too big to show here.

helix-term/tests/test/auto_indent.rs+3−9

This diff is too big to show here.

helix-term/tests/test/auto_pairs.rs+5−11

This diff is too big to show here.

helix-term/tests/test/commands.rs+212−162

This diff is too big to show here.

helix-term/tests/test/commands/movement.rsnew file+452

This diff is too big to show here.

helix-term/tests/test/write.rs → helix-term/tests/test/commands/write.rsrenamed+254−7

This diff is too big to show here.

helix-term/tests/test/helpers.rs+53−26

This diff is too big to show here.

helix-term/tests/test/languages/go.rsnew file+41

This diff is too big to show here.

helix-term/tests/test/languages/mod.rsnew file+4

This diff is too big to show here.

helix-term/tests/test/languages/yaml.rsnew file+819

This diff is too big to show here.

helix-term/tests/test/movement.rs+54−43

This diff is too big to show here.

helix-term/tests/test/splits.rs+8−6

This diff is too big to show here.

helix-tui/Cargo.toml+17−15

This diff is too big to show here.

helix-tui/src/backend/crossterm.rs+168−45

This diff is too big to show here.

helix-tui/src/backend/mod.rs+5−1

This diff is too big to show here.

helix-tui/src/backend/test.rs+17

This diff is too big to show here.

helix-tui/src/buffer.rs+43−2

This diff is too big to show here.

helix-tui/src/lib.rs+9−3

This diff is too big to show here.

helix-tui/src/terminal.rs+26−14

This diff is too big to show here.

helix-tui/src/text.rs+3−1

This diff is too big to show here.

helix-tui/src/widgets/block.rs+2−7

This diff is too big to show here.

helix-tui/src/widgets/mod.rs+2−2

This diff is too big to show here.

helix-tui/src/widgets/table.rs+19−7

This diff is too big to show here.

helix-vcs/Cargo.toml+15−11

This diff is too big to show here.

helix-vcs/src/diff.rs+48−35

This diff is too big to show here.

helix-vcs/src/diff/line_cache.rs+15−7

This diff is too big to show here.

helix-vcs/src/diff/worker.rs+12−15

This diff is too big to show here.

helix-vcs/src/diff/worker/test.rs+4−8

This diff is too big to show here.

helix-vcs/src/git.rs+67−33

This diff is too big to show here.

helix-vcs/src/git/test.rs+6−6

This diff is too big to show here.

helix-vcs/src/lib.rs+32−5

This diff is too big to show here.

helix-view/Cargo.toml+23−18

This diff is too big to show here.

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

This diff is too big to show here.

helix-view/src/document.rs+771−174

This diff is too big to show here.

helix-view/src/editor.rs+336−134

This diff is too big to show here.

helix-view/src/graphics.rs+3−2

This diff is too big to show here.

helix-view/src/gutter.rs+64−43

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-view/src/keyboard.rs+1

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.rsnew file+333

This diff is too big to show here.

helix-view/src/theme.rs+79−47

This diff is too big to show here.

helix-view/src/tree.rs+6−8

This diff is too big to show here.

helix-view/src/view.rs+88−41

This diff is too big to show here.

languages.toml+1105−303

This diff is too big to show here.

logo.svg+1−1

This diff is too big to show here.

runtime/queries/_javascript/highlights.scmnew file+36

This diff is too big to show here.

runtime/queries/_javascript/locals.scmnew file+14

This diff is too big to show here.

runtime/queries/_javascript/tags.scmnew file+88

This diff is too big to show here.

runtime/queries/_jsx/highlights.scmnew file+43

This diff is too big to show here.

runtime/queries/_jsx/indents.scmnew file+6

This diff is too big to show here.

runtime/queries/_typescript/highlights.scmnew file+142

This diff is too big to show here.

runtime/queries/_typescript/indents.scmnew file+5

This diff is too big to show here.

runtime/queries/_typescript/locals.scmnew file+33

This diff is too big to show here.

runtime/queries/_typescript/tags.scmnew file+23

This diff is too big to show here.

runtime/queries/_typescript/textobjects.scmnew file+6

This diff is too big to show here.

runtime/queries/agda/highlights.scmnew file+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.scmnew file+9

This diff is too big to show here.

runtime/queries/blueprint/highlights.scmnew file+61

This diff is too big to show here.

runtime/queries/c/highlights.scm+1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/cairo/indents.scmnew file+1

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/cairo/locals.scmnew file+1

This diff is too big to show here.

runtime/queries/cairo/textobjects.scmnew file+1

This diff is too big to show here.

runtime/queries/capnp/folds.scmnew file+14

This diff is too big to show here.

runtime/queries/capnp/highlights.scmnew file+141

This diff is too big to show here.

runtime/queries/capnp/indents.scmnew file+19

This diff is too big to show here.

runtime/queries/capnp/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/capnp/locals.scmnew file+96

This diff is too big to show here.

runtime/queries/comment/highlights.scm+20−4

This diff is too big to show here.

runtime/queries/common-lisp/indents.scmnew file+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+3−1

This diff is too big to show here.

runtime/queries/dbml/highlights.scmnew file+44

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/dtd/highlights.scmnew file+39

This diff is too big to show here.

runtime/queries/dtd/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/ecma/README.mdnew file+14

This diff is too big to show here.

runtime/queries/ecma/highlights.scm+49−21

This diff is too big to show here.

runtime/queries/ecma/indents.scm+6

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/elm/textobjects.scmnew file+63

This diff is too big to show here.

runtime/queries/erlang/highlights.scm+10−10

This diff is too big to show here.

runtime/queries/forth/highlights.scmnew file+7

This diff is too big to show here.

runtime/queries/fortran/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/fsharp/highlights.scmnew file+196

This diff is too big to show here.

runtime/queries/fsharp/locals.scmnew file+25

This diff is too big to show here.

runtime/queries/gas/highlights.scmnew file+21

This diff is too big to show here.

runtime/queries/gas/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/gas/textobjects.scmnew file+2

This diff is too big to show here.

runtime/queries/gdscript/highlights.scm+86−29

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/gemini/highlights.scmnew file+26

This diff is too big to show here.

runtime/queries/git-commit/highlights.scm+5−1

This diff is too big to show here.

runtime/queries/git-commit/textobjects.scmnew file+2

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/gn/highlights.scmnew file+42

This diff is too big to show here.

runtime/queries/gn/injections.scmnew file+15

This diff is too big to show here.

runtime/queries/gn/locals.scmnew file+16

This diff is too big to show here.

runtime/queries/go/highlights.scm+67−26

This diff is too big to show here.

runtime/queries/go/indents.scm+16−4

This diff is too big to show here.

runtime/queries/go/locals.scm+11−3

This diff is too big to show here.

runtime/queries/godot-resource/injections.scm+16

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/haskell-persistent/folds.scmnew file+3

This diff is too big to show here.

runtime/queries/haskell-persistent/highlights.scmnew file+37

This diff is too big to show here.

runtime/queries/haskell-persistent/locals.scmnew file+1

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/haskell/injections.scm+12

This diff is too big to show here.

runtime/queries/haskell/textobjects.scm+1−1

This diff is too big to show here.

runtime/queries/hurl/highlights.scmnew file+127

This diff is too big to show here.

runtime/queries/hurl/indents.scmnew file+11

This diff is too big to show here.

runtime/queries/hurl/injections.scmnew file+14

This diff is too big to show here.

runtime/queries/janet/highlights.scmnew file+1

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/java/indents.scmnew file+35

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/jinja/highlights.scmnew file+7

This diff is too big to show here.

runtime/queries/jinja/injections.scmnew file+4

This diff is too big to show here.

runtime/queries/json5/highlights.scmnew file+11

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/jsx/tags.scmnew file+3

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/julia/indents.scmnew file+16

This diff is too big to show here.

runtime/queries/julia/injections.scm+6

This diff is too big to show here.

runtime/queries/julia/textobjects.scmnew file+46

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/just/highlights.scmnew file+33

This diff is too big to show here.

runtime/queries/just/indents.scmnew file+3

This diff is too big to show here.

runtime/queries/just/injections.scmnew file+16

This diff is too big to show here.

runtime/queries/just/locals.scmnew file+10

This diff is too big to show here.

runtime/queries/just/textobjects.scmnew file+48

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.scmnew file+3

This diff is too big to show here.

runtime/queries/kdl/textobjects.scmnew file+27

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/kotlin/locals.scmnew file+15

This diff is too big to show here.

runtime/queries/log/highlights.scmnew file+10

This diff is too big to show here.

runtime/queries/lpf/highlights.scmnew file+19

This diff is too big to show here.

runtime/queries/make/highlights.scm+6−1

This diff is too big to show here.

runtime/queries/make/injections.scm+3

This diff is too big to show here.

runtime/queries/markdoc/highlights.scmnew file+16

This diff is too big to show here.

runtime/queries/markdoc/injections.scmnew file+2

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/markdown/highlights.scm+10−1

This diff is too big to show here.

runtime/queries/markdown/injections.scm+4

This diff is too big to show here.

runtime/queries/matlab/context.scmnew file+41

This diff is too big to show here.

runtime/queries/matlab/folds.scmnew file+11

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/matlab/indents.scmnew file+23

This diff is too big to show here.

runtime/queries/matlab/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/matlab/locals.scmnew file+19

This diff is too big to show here.

runtime/queries/matlab/textobjects.scmnew file+9

This diff is too big to show here.

runtime/queries/nasm/highlights.scmnew file+126

This diff is too big to show here.

runtime/queries/nasm/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/nasm/textobjects.scmnew file+15

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.scmnew file+3

This diff is too big to show here.

runtime/queries/nim/highlights.scmnew file+315

This diff is too big to show here.

runtime/queries/nim/indents.scmnew file+54

This diff is too big to show here.

runtime/queries/nim/textobjects.scmnew file+19

This diff is too big to show here.

runtime/queries/nix/highlights.scm+6−1

This diff is too big to show here.

runtime/queries/nix/injections.scm+29−3

This diff is too big to show here.

runtime/queries/nu/folds.scmdeleted−4

This diff is too big to show here.

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

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

This diff is too big to show here.

runtime/queries/nunjucks/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/nunjucks/injections.scmnew file+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/highlights.scm+18−133

This diff is too big to show here.

runtime/queries/odin/indents.scmnew file+16

This diff is too big to show here.

runtime/queries/odin/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/opencl/highlights.scmnew file+150

This diff is too big to show here.

runtime/queries/opencl/indents.scmnew file+1

This diff is too big to show here.

runtime/queries/opencl/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/opencl/textobjects.scmnew file+1

This diff is too big to show here.

runtime/queries/perl/fold.scmnew file+19

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/php/highlights.scm+229−40

This diff is too big to show here.

runtime/queries/php/injections.scm+19

This diff is too big to show here.

runtime/queries/pod/highlights.scmnew file+61

This diff is too big to show here.

runtime/queries/prql/highlights.scmnew file+143

This diff is too big to show here.

runtime/queries/prql/injections.scmnew file+8

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/purescript/textobjects.scmnew file+13

This diff is too big to show here.

runtime/queries/python/highlights.scm+7−1

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.scmnew file+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/rego/highlights.scmnew file+68

This diff is too big to show here.

runtime/queries/rego/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/robot/highlights.scmnew file+59

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/rst/highlights.scmnew file+38

This diff is too big to show here.

runtime/queries/ruby/highlights.scm+82−38

This diff is too big to show here.

runtime/queries/ruby/injections.scm+6

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+69−2

This diff is too big to show here.

runtime/queries/rust/injections.scm+10−4

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/scala/locals.scmnew file+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.scmnew file+43

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/slint/injections.scmnew file+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.scmnew file+12

This diff is too big to show here.

runtime/queries/smali/highlights.scmnew file+216

This diff is too big to show here.

runtime/queries/smali/indents.scmnew file+31

This diff is too big to show here.

runtime/queries/smali/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/smali/locals.scmnew file+42

This diff is too big to show here.

runtime/queries/smithy/highlights.scmnew file+102

This diff is too big to show here.

runtime/queries/sql/highlights.scm+326−176

This diff is too big to show here.

runtime/queries/strace/highlights.scmnew file+8

This diff is too big to show here.

runtime/queries/svelte/indents.scmnew file+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/sway/highlights.scmnew file+336

This diff is too big to show here.

runtime/queries/sway/indents.scmnew file+71

This diff is too big to show here.

runtime/queries/sway/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/sway/locals.scmnew file+17

This diff is too big to show here.

runtime/queries/sway/textobjects.scmnew file+52

This diff is too big to show here.

runtime/queries/t32/highlights.scmnew file+227

This diff is too big to show here.

runtime/queries/t32/injections.scmnew file+2

This diff is too big to show here.

runtime/queries/templ/highlights.scmnew file+99

This diff is too big to show here.

runtime/queries/templ/injections.scmnew file+4

This diff is too big to show here.

runtime/queries/todotxt/highlights.scmnew file+6

This diff is too big to show here.

runtime/queries/toml/highlights.scm+15−2

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/tsx/locals.scmnew file+3

This diff is too big to show here.

runtime/queries/tsx/tags.scmnew file+3

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/typst/highlights.scmnew file+77

This diff is too big to show here.

runtime/queries/typst/injections.scmnew file+6

This diff is too big to show here.

runtime/queries/unison/highlights.scmnew file+87

This diff is too big to show here.

runtime/queries/unison/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/v/highlights.scm+158−67

This diff is too big to show here.

runtime/queries/v/indents.scmnew file+16

This diff is too big to show here.

runtime/queries/v/injections.scmnew file+6

This diff is too big to show here.

runtime/queries/v/textobjects.scmnew file+27

This diff is too big to show here.

runtime/queries/vhdl/highlights.scmnew file+338

This diff is too big to show here.

runtime/queries/webc/highlights.scmnew file+1

This diff is too big to show here.

runtime/queries/webc/injections.scmnew file+1

This diff is too big to show here.

runtime/queries/wren/highlights.scmnew file+82

This diff is too big to show here.

runtime/queries/wren/indents.scmnew file+29

This diff is too big to show here.

runtime/queries/wren/injections.scmnew file+3

This diff is too big to show here.

runtime/queries/wren/locals.scmnew file+21

This diff is too big to show here.

runtime/queries/wren/textobjects.scmnew file+16

This diff is too big to show here.

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

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+88−47

This diff is too big to show here.

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

This diff is too big to show here.

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

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/acme.toml+2

This diff is too big to show here.

runtime/themes/adwaita-dark.tomlnew file+172

This diff is too big to show here.

runtime/themes/amberwood.tomlnew file+138

This diff is too big to show here.

runtime/themes/autumn.toml+72−64

This diff is too big to show here.

runtime/themes/autumn_night.toml+13−11

This diff is too big to show here.

runtime/themes/ayu_dark.toml+12−2

This diff is too big to show here.

runtime/themes/ayu_evolve.toml+2

This diff is too big to show here.

runtime/themes/ayu_light.toml+13−3

This diff is too big to show here.

runtime/themes/ayu_mirage.toml+12−2

This diff is too big to show here.

runtime/themes/base16_transparent.toml+7−1

This diff is too big to show here.

runtime/themes/boo_berry.toml+1

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+24−14

This diff is too big to show here.

runtime/themes/cyan_light.tomlnew file+154

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/darcula.toml+11−4

This diff is too big to show here.

runtime/themes/dark_high_contrast.toml+6−1

This diff is too big to show here.

runtime/themes/dark_plus.toml+2

This diff is too big to show here.

runtime/themes/dracula.toml+128−70

This diff is too big to show here.

runtime/themes/dracula_at_night.toml+2

This diff is too big to show here.

runtime/themes/emacs.toml+1

This diff is too big to show here.

runtime/themes/everblush.tomlnew file+115

This diff is too big to show here.

runtime/themes/everforest_dark.toml+75−54

This diff is too big to show here.

runtime/themes/everforest_light.toml+75−54

This diff is too big to show here.

runtime/themes/ferra.tomlnew file+83

This diff is too big to show here.

runtime/themes/flatwhite.toml+3

This diff is too big to show here.

runtime/themes/fleet_dark.toml+8−6

This diff is too big to show here.

runtime/themes/gruber-darker.tomlnew file+114

This diff is too big to show here.

runtime/themes/gruvbox.toml+2

This diff is too big to show here.

runtime/themes/gruvbox_dark_hard.toml+2−96

This diff is too big to show here.

runtime/themes/gruvbox_dark_soft.tomlnew file+7

This diff is too big to show here.

runtime/themes/gruvbox_light.toml+1−77

This diff is too big to show here.

runtime/themes/horizon-dark.tomlnew file+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/kanagawa.toml+32−24

This diff is too big to show here.

runtime/themes/kaolin-dark.tomlnew file+108

This diff is too big to show here.

runtime/themes/kaolin-light.tomlnew file+60

This diff is too big to show here.

runtime/themes/kaolin-valley-dark.tomlnew file+73

This diff is too big to show here.

runtime/themes/licenses/everforest.LICENSEnew file+21

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi.LICENSEnew file+5

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi_deuteranopia.LICENSEnew file+5

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi_tinted.LICENSEnew file+5

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi_tritanopia.LICENSEnew file+5

This diff is too big to show here.

runtime/themes/licenses/poimandres.LICENSEnew file+21

This diff is too big to show here.

runtime/themes/licenses/serika-syntax.LICENSEnew file+21

This diff is too big to show here.

runtime/themes/licenses/sonokai.LICENSEnew file+21

This diff is too big to show here.

runtime/themes/licenses/starlight.LICENSEnew file+24

This diff is too big to show here.

runtime/themes/material_darker.tomlnew file+20

This diff is too big to show here.

runtime/themes/material_deep_ocean.tomlnew file+123

This diff is too big to show here.

runtime/themes/material_oceanic.tomlnew file+21

This diff is too big to show here.

runtime/themes/material_palenight.tomlnew file+19

This diff is too big to show here.

runtime/themes/mellow.toml+1

This diff is too big to show here.

runtime/themes/merionette.tomlnew file+98

This diff is too big to show here.

runtime/themes/modus_operandi.tomlnew file+194

This diff is too big to show here.

runtime/themes/modus_operandi_deuteranopia.tomlnew file+46

This diff is too big to show here.

runtime/themes/modus_operandi_tinted.tomlnew file+45

This diff is too big to show here.

runtime/themes/modus_operandi_tritanopia.tomlnew file+73

This diff is too big to show here.

runtime/themes/modus_vivendi.tomlnew file+242

This diff is too big to show here.

runtime/themes/modus_vivendi_deuteranopia.tomlnew file+158

This diff is too big to show here.

runtime/themes/modus_vivendi_tinted.tomlnew file+144

This diff is too big to show here.

runtime/themes/modus_vivendi_tritanopia.tomlnew file+186

This diff is too big to show here.

runtime/themes/molokai.tomlnew file+48

This diff is too big to show here.

runtime/themes/monokai.toml+7−2

This diff is too big to show here.

runtime/themes/naysayer.tomlnew file+91

This diff is too big to show here.

runtime/themes/new_moon.tomlnew file+124

This diff is too big to show here.

runtime/themes/nightfox.toml+9−4

This diff is too big to show here.

runtime/themes/noctis.toml+34−32

This diff is too big to show here.

runtime/themes/nord-night.tomlnew file+23

This diff is too big to show here.

runtime/themes/nord.toml+162−102

This diff is too big to show here.

runtime/themes/onedark.toml+5

This diff is too big to show here.

runtime/themes/onedarker.toml+3

This diff is too big to show here.

runtime/themes/onelight.toml+150−89

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/poimandres.tomlnew file+113

This diff is too big to show here.

runtime/themes/poimandres_storm.tomlnew file+19

This diff is too big to show here.

runtime/themes/pop-dark.toml+9−5

This diff is too big to show here.

runtime/themes/rasmus.toml+1

This diff is too big to show here.

runtime/themes/rose_pine.toml+36−32

This diff is too big to show here.

runtime/themes/rose_pine_dawn.toml+1−1

This diff is too big to show here.

runtime/themes/rose_pine_moon.toml+1−1

This diff is too big to show here.

runtime/themes/serika-dark.toml+2−1

This diff is too big to show here.

runtime/themes/serika-light.toml+1

This diff is too big to show here.

runtime/themes/solarized_dark.toml+1

This diff is too big to show here.

runtime/themes/solarized_light.toml+1

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.tomlnew file+113

This diff is too big to show here.

runtime/themes/tokyonight.toml+1

This diff is too big to show here.

runtime/themes/varua.toml+2

This diff is too big to show here.

runtime/themes/vim_dark_high_contrast.tomlnew file+72

This diff is too big to show here.

runtime/themes/yellowed.tomlnew file+135

This diff is too big to show here.

runtime/themes/zed_onedark.tomlnew file+110

This diff is too big to show here.

runtime/themes/zed_onelight.tomlnew file+55

This diff is too big to show here.

runtime/themes/zenburn.toml+3−1

This diff is too big to show here.

runtime/tutor+165−11

This diff is too big to show here.

rust-toolchain.toml+2−2

This diff is too big to show here.

theme.toml+3−1

This diff is too big to show here.

xtask/Cargo.toml+12−6

This diff is too big to show here.

xtask/src/docgen.rs+6−5

This diff is too big to show here.

xtask/src/themelint.rs+2

This diff is too big to show here.