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.65
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.65
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.65
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@v20
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+15−15
@@ -23,7 +23,7 @@ jobs:
23 runs-on: ubuntu-latest
23 runs-on: ubuntu-latest
24 steps:
24 steps:
25 - name: Checkout sources
25 - name: Checkout sources
26− uses: actions/checkout@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
@@ -103,10 +103,10 @@ jobs:
103
103
104 steps:
104 steps:
105 - name: Checkout sources
105 - name: Checkout sources
106− uses: actions/checkout@v3
106+ uses: actions/checkout@v4
107
107
108 - name: Download grammars
108 - name: Download grammars
109− uses: actions/download-artifact@v3
109+ uses: actions/download-artifact@v4
110
110
111 - name: Move grammars under runtime
111 - name: Move grammars under runtime
112 if: "!startsWith(matrix.os, 'windows')"
112 if: "!startsWith(matrix.os, 'windows')"
@@ -160,7 +160,7 @@ jobs:
160
160
161 - name: Build AppImage
161 - name: Build AppImage
162 shell: bash
162 shell: bash
163− if: matrix.build == 'aarch64-linux' || matrix.build == 'x86_64-linux'
163+ if: matrix.build == 'x86_64-linux'
164 run: |
164 run: |
165 # Required as of 22.x https://github.com/AppImage/AppImageKit/wiki/FUSE
165 # Required as of 22.x https://github.com/AppImage/AppImageKit/wiki/FUSE
166 sudo add-apt-repository universe
166 sudo add-apt-repository universe
@@ -220,7 +220,7 @@ jobs:
220 fi
220 fi
221 cp -r runtime dist
221 cp -r runtime dist
222
222
223− - uses: actions/upload-artifact@v3
223+ - uses: actions/upload-artifact@v4
224 with:
224 with:
225 name: bins-${{ matrix.build }}
225 name: bins-${{ matrix.build }}
226 path: dist
226 path: dist
@@ -231,9 +231,9 @@ jobs:
231 runs-on: ubuntu-latest
231 runs-on: ubuntu-latest
232 steps:
232 steps:
233 - name: Checkout sources
233 - name: Checkout sources
234− uses: actions/checkout@v3
234+ uses: actions/checkout@v4
235
235
236− - uses: actions/download-artifact@v3
236+ - uses: actions/download-artifact@v4
237
237
238 - name: Build archive
238 - name: Build archive
239 shell: bash
239 shell: bash
@@ -263,7 +263,7 @@ jobs:
263 mv bins-$platform/hx$exe $pkgname
263 mv bins-$platform/hx$exe $pkgname
264 chmod +x $pkgname/hx$exe
264 chmod +x $pkgname/hx$exe
265
265
266− if [[ "$platform" = "aarch64-linux" || "$platform" = "x86_64-linux" ]]; then
266+ if [[ "$platform" = "x86_64-linux" ]]; then
267 mv bins-$platform/helix-*.AppImage* dist/
267 mv bins-$platform/helix-*.AppImage* dist/
268 fi
268 fi
269
269
@@ -288,7 +288,7 @@ jobs:
288 overwrite: true
288 overwrite: true
289
289
290 - name: Upload binaries as artifact
290 - name: Upload binaries as artifact
291− uses: actions/upload-artifact@v3
291+ uses: actions/upload-artifact@v4
292 if: env.preview == 'true'
292 if: env.preview == 'true'
293 with:
293 with:
294 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+261−1
@@ -1,3 +1,263 @@
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+
1# 23.05 (2023-05-18)
261# 23.05 (2023-05-18)
2
262
323.05 is a smaller release focusing on fixes. There were 88 contributors in this release. Thank you all!
26323.05 is a smaller release focusing on fixes. There were 88 contributors in this release. Thank you all!
@@ -1596,7 +1856,7 @@ to distinguish it in bug reports..
1596
1856
1597- The `runtime/` directory is now properly detected on binary releases and
1857- The `runtime/` directory is now properly detected on binary releases and
1598 on cargo run. `~/.config/helix/runtime` can also be used.
1858 on cargo run. `~/.config/helix/runtime` can also be used.
1599−- Registers can now be selected via " (for example `"ay`)
1859+- Registers can now be selected via " (for example, `"ay`)
1600- Support for Nix files was added
1860- Support for Nix files was added
1601- Movement is now fully tested and matches Kakoune implementation
1861- Movement is now fully tested and matches Kakoune implementation
1602- 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+662−570
@@ -3,51 +3,53 @@
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
43[[package]]
42[[package]]
44−name = "aho-corasick"
43+name = "allocator-api2"
45−version = "1.0.1"
44+version = "0.2.14"
46source = "registry+https://github.com/rust-lang/crates.io-index"
45source = "registry+https://github.com/rust-lang/crates.io-index"
47−checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
46+checksum = "c4f263788a35611fba42eb41ff811c5d0360c58b97402570312a350736e2542e"
48−dependencies = [
47+
49− "memchr",
48+[[package]]
50−]
49+name = "android-tzdata"
50+version = "0.1.1"
51+source = "registry+https://github.com/rust-lang/crates.io-index"
52+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
51
53
52[[package]]
54[[package]]
53name = "android_system_properties"
55name = "android_system_properties"
@@ -60,9 +62,9 @@ dependencies = [
60
62
61[[package]]
63[[package]]
62name = "anyhow"
64name = "anyhow"
63−version = "1.0.71"
65+version = "1.0.76"
64source = "registry+https://github.com/rust-lang/crates.io-index"
66source = "registry+https://github.com/rust-lang/crates.io-index"
65−checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
67+checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
66
68
67[[package]]
69[[package]]
68name = "arc-swap"
70name = "arc-swap"
@@ -76,6 +78,21 @@ version = "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"
@@ -84,18 +101,17 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
84
101
85[[package]]
102[[package]]
86name = "bitflags"
103name = "bitflags"
87−version = "2.2.1"
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 = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813"
106+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
90
107
91[[package]]
108[[package]]
92name = "bstr"
109name = "bstr"
93−version = "1.4.0"
110+version = "1.8.0"
94source = "registry+https://github.com/rust-lang/crates.io-index"
111source = "registry+https://github.com/rust-lang/crates.io-index"
95−checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
112+checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
96dependencies = [
113dependencies = [
97 "memchr",
114 "memchr",
98− "once_cell",
99 "regex-automata",
115 "regex-automata",
100 "serde",
116 "serde",
101]
117]
@@ -115,12 +131,6 @@ version = "3.12.0"
115source = "registry+https://github.com/rust-lang/crates.io-index"
131source = "registry+https://github.com/rust-lang/crates.io-index"
116checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
132checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
117
133
118−[[package]]
119−name = "bytecount"
120−version = "0.6.3"
121−source = "registry+https://github.com/rust-lang/crates.io-index"
122−checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
123−
124[[package]]
134[[package]]
125name = "bytes"
135name = "bytes"
126version = "1.4.0"
136version = "1.4.0"
@@ -135,9 +145,12 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
135
145
136[[package]]
146[[package]]
137name = "cc"
147name = "cc"
138−version = "1.0.79"
148+version = "1.0.84"
139source = "registry+https://github.com/rust-lang/crates.io-index"
149source = "registry+https://github.com/rust-lang/crates.io-index"
140−checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
150+checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
151+dependencies = [
152+ "libc",
153+]
141
154
142[[package]]
155[[package]]
143name = "cfg-if"
156name = "cfg-if"
@@ -158,25 +171,23 @@ dependencies = [
158
171
159[[package]]
172[[package]]
160name = "chrono"
173name = "chrono"
161−version = "0.4.24"
174+version = "0.4.31"
162source = "registry+https://github.com/rust-lang/crates.io-index"
175source = "registry+https://github.com/rust-lang/crates.io-index"
163−checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
176+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
164dependencies = [
177dependencies = [
178+ "android-tzdata",
165 "iana-time-zone",
179 "iana-time-zone",
166− "num-integer",
167 "num-traits",
180 "num-traits",
168− "winapi",
181+ "windows-targets 0.48.0",
169]
182]
170
183
171[[package]]
184[[package]]
172name = "clipboard-win"
185name = "clipboard-win"
173−version = "4.5.0"
186+version = "5.0.0"
174source = "registry+https://github.com/rust-lang/crates.io-index"
187source = "registry+https://github.com/rust-lang/crates.io-index"
175−checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
188+checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
176dependencies = [
189dependencies = [
177 "error-code",
190 "error-code",
178− "str-buf",
179− "winapi",
180]
191]
181
192
182[[package]]
193[[package]]
@@ -210,6 +221,12 @@ version = "0.8.4"
210source = "registry+https://github.com/rust-lang/crates.io-index"
221source = "registry+https://github.com/rust-lang/crates.io-index"
211checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
222checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
212
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"
229+
213[[package]]
230[[package]]
214name = "crc32fast"
231name = "crc32fast"
215version = "1.3.2"
232version = "1.3.2"
@@ -219,14 +236,58 @@ dependencies = [
219 "cfg-if",
236 "cfg-if",
220]
237]
221
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+
273+[[package]]
274+name = "crossbeam-utils"
275+version = "0.8.16"
276+source = "registry+https://github.com/rust-lang/crates.io-index"
277+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
278+dependencies = [
279+ "cfg-if",
280+]
281+
222[[package]]
282[[package]]
223name = "crossterm"
283name = "crossterm"
224−version = "0.26.1"
284+version = "0.27.0"
225source = "registry+https://github.com/rust-lang/crates.io-index"
285source = "registry+https://github.com/rust-lang/crates.io-index"
226−checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
286+checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
227dependencies = [
287dependencies = [
228− "bitflags 1.3.2",
288+ "bitflags 2.4.1",
229 "crossterm_winapi",
289 "crossterm_winapi",
290+ "filedescriptor",
230 "futures-core",
291 "futures-core",
231 "libc",
292 "libc",
232 "mio",
293 "mio",
@@ -238,9 +299,9 @@ dependencies = [
238
299
239[[package]]
300[[package]]
240name = "crossterm_winapi"
301name = "crossterm_winapi"
241−version = "0.9.0"
302+version = "0.9.1"
242source = "registry+https://github.com/rust-lang/crates.io-index"
303source = "registry+https://github.com/rust-lang/crates.io-index"
243−checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
304+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
244dependencies = [
305dependencies = [
245 "winapi",
306 "winapi",
246]
307]
@@ -269,7 +330,7 @@ dependencies = [
269 "proc-macro2",
330 "proc-macro2",
270 "quote",
331 "quote",
271 "scratch",
332 "scratch",
272− "syn 2.0.15",
333+ "syn 2.0.38",
273]
334]
274
335
275[[package]]
336[[package]]
@@ -286,7 +347,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
286dependencies = [
347dependencies = [
287 "proc-macro2",
348 "proc-macro2",
288 "quote",
349 "quote",
289− "syn 2.0.15",
350+ "syn 2.0.38",
290]
351]
291
352
292[[package]]
353[[package]]
@@ -303,9 +364,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
303
364
304[[package]]
365[[package]]
305name = "encoding_rs"
366name = "encoding_rs"
306−version = "0.8.32"
367+version = "0.8.33"
307source = "registry+https://github.com/rust-lang/crates.io-index"
368source = "registry+https://github.com/rust-lang/crates.io-index"
308−checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
369+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
309dependencies = [
370dependencies = [
310 "cfg-if",
371 "cfg-if",
311]
372]
@@ -320,35 +381,26 @@ dependencies = [
320]
381]
321
382
322[[package]]
383[[package]]
323−name = "errno"
384+name = "equivalent"
324−version = "0.3.1"
385+version = "1.0.0"
325source = "registry+https://github.com/rust-lang/crates.io-index"
386source = "registry+https://github.com/rust-lang/crates.io-index"
326−checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
387+checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
327−dependencies = [
328− "errno-dragonfly",
329− "libc",
330− "windows-sys 0.48.0",
331−]
332
388
333[[package]]
389[[package]]
334−name = "errno-dragonfly"
390+name = "errno"
335−version = "0.1.2"
391+version = "0.3.8"
336source = "registry+https://github.com/rust-lang/crates.io-index"
392source = "registry+https://github.com/rust-lang/crates.io-index"
337−checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
393+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
338dependencies = [
394dependencies = [
339− "cc",
340 "libc",
395 "libc",
396+ "windows-sys 0.52.0",
341]
397]
342
398
343[[package]]
399[[package]]
344name = "error-code"
400name = "error-code"
345−version = "2.3.1"
401+version = "3.0.0"
346source = "registry+https://github.com/rust-lang/crates.io-index"
402source = "registry+https://github.com/rust-lang/crates.io-index"
347−checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
403+checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
348−dependencies = [
349− "libc",
350− "str-buf",
351−]
352
404
353[[package]]
405[[package]]
354name = "etcetera"
406name = "etcetera"
@@ -362,14 +414,20 @@ dependencies = [
362]
414]
363
415
364[[package]]
416[[package]]
365−name = "fastrand"
417+name = "faster-hex"
366−version = "1.9.0"
418+version = "0.9.0"
367source = "registry+https://github.com/rust-lang/crates.io-index"
419source = "registry+https://github.com/rust-lang/crates.io-index"
368−checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
420+checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
369dependencies = [
421dependencies = [
370− "instant",
422+ "serde",
371]
423]
372
424
425+[[package]]
426+name = "fastrand"
427+version = "2.0.0"
428+source = "registry+https://github.com/rust-lang/crates.io-index"
429+checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
430+
373[[package]]
431[[package]]
374name = "fern"
432name = "fern"
375version = "0.6.2"
433version = "0.6.2"
@@ -380,53 +438,46 @@ dependencies = [
380]
438]
381
439
382[[package]]
440[[package]]
383−name = "filetime"
441+name = "filedescriptor"
384−version = "0.2.21"
442+version = "0.8.2"
385source = "registry+https://github.com/rust-lang/crates.io-index"
443source = "registry+https://github.com/rust-lang/crates.io-index"
386−checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
444+checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
387dependencies = [
445dependencies = [
388− "cfg-if",
389 "libc",
446 "libc",
390− "redox_syscall 0.2.16",
447+ "thiserror",
391− "windows-sys 0.48.0",
448+ "winapi",
392]
449]
393
450
394[[package]]
451[[package]]
395name = "flate2"
452name = "flate2"
396−version = "1.0.25"
453+version = "1.0.27"
397source = "registry+https://github.com/rust-lang/crates.io-index"
454source = "registry+https://github.com/rust-lang/crates.io-index"
398−checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
455+checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
399dependencies = [
456dependencies = [
400 "crc32fast",
457 "crc32fast",
401 "miniz_oxide",
458 "miniz_oxide",
402]
459]
403
460
404−[[package]]
405−name = "fnv"
406−version = "1.0.7"
407−source = "registry+https://github.com/rust-lang/crates.io-index"
408−checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
409−
410[[package]]
461[[package]]
411name = "form_urlencoded"
462name = "form_urlencoded"
412−version = "1.1.0"
463+version = "1.2.1"
413source = "registry+https://github.com/rust-lang/crates.io-index"
464source = "registry+https://github.com/rust-lang/crates.io-index"
414−checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
465+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
415dependencies = [
466dependencies = [
416 "percent-encoding",
467 "percent-encoding",
417]
468]
418
469
419[[package]]
470[[package]]
420name = "futures-core"
471name = "futures-core"
421−version = "0.3.28"
472+version = "0.3.30"
422source = "registry+https://github.com/rust-lang/crates.io-index"
473source = "registry+https://github.com/rust-lang/crates.io-index"
423−checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
474+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
424
475
425[[package]]
476[[package]]
426name = "futures-executor"
477name = "futures-executor"
427−version = "0.3.28"
478+version = "0.3.29"
428source = "registry+https://github.com/rust-lang/crates.io-index"
479source = "registry+https://github.com/rust-lang/crates.io-index"
429−checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
480+checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
430dependencies = [
481dependencies = [
431 "futures-core",
482 "futures-core",
432 "futures-task",
483 "futures-task",
@@ -435,15 +486,15 @@ dependencies = [
435
486
436[[package]]
487[[package]]
437name = "futures-task"
488name = "futures-task"
438−version = "0.3.28"
489+version = "0.3.30"
439source = "registry+https://github.com/rust-lang/crates.io-index"
490source = "registry+https://github.com/rust-lang/crates.io-index"
440−checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
491+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
441
492
442[[package]]
493[[package]]
443name = "futures-util"
494name = "futures-util"
444−version = "0.3.28"
495+version = "0.3.30"
445source = "registry+https://github.com/rust-lang/crates.io-index"
496source = "registry+https://github.com/rust-lang/crates.io-index"
446−checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
497+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
447dependencies = [
498dependencies = [
448 "futures-core",
499 "futures-core",
449 "futures-task",
500 "futures-task",
@@ -452,15 +503,6 @@ dependencies = [
452 "slab",
503 "slab",
453]
504]
454
505
455−[[package]]
456−name = "fuzzy-matcher"
457−version = "0.3.7"
458−source = "registry+https://github.com/rust-lang/crates.io-index"
459−checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
460−dependencies = [
461− "thread_local",
462−]
463−
464[[package]]
506[[package]]
465name = "getrandom"
507name = "getrandom"
466version = "0.2.9"
508version = "0.2.9"
@@ -472,16 +514,21 @@ dependencies = [
472 "wasi",
514 "wasi",
473]
515]
474
516
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+
475[[package]]
523[[package]]
476name = "gix"
524name = "gix"
477−version = "0.44.1"
525+version = "0.57.0"
478source = "registry+https://github.com/rust-lang/crates.io-index"
526source = "registry+https://github.com/rust-lang/crates.io-index"
479−checksum = "6bf41b61f7df395284f7a579c0fa1a7e012c5aede655174d4e91299ef1cac643"
527+checksum = "721c7497ab24b665ed5a1eb2b3526936aa60068e61ba260651f7e77c52feec69"
480dependencies = [
528dependencies = [
481 "gix-actor",
529 "gix-actor",
482− "gix-attributes",
530+ "gix-commitgraph",
483 "gix-config",
531 "gix-config",
484− "gix-credentials",
485 "gix-date",
532 "gix-date",
486 "gix-diff",
533 "gix-diff",
487 "gix-discover",
534 "gix-discover",
@@ -490,28 +537,25 @@ dependencies = [
490 "gix-glob",
537 "gix-glob",
491 "gix-hash",
538 "gix-hash",
492 "gix-hashtable",
539 "gix-hashtable",
493− "gix-ignore",
494− "gix-index",
495 "gix-lock",
540 "gix-lock",
496− "gix-mailmap",
541+ "gix-macros",
497 "gix-object",
542 "gix-object",
498 "gix-odb",
543 "gix-odb",
499 "gix-pack",
544 "gix-pack",
500 "gix-path",
545 "gix-path",
501− "gix-prompt",
502 "gix-ref",
546 "gix-ref",
503 "gix-refspec",
547 "gix-refspec",
504 "gix-revision",
548 "gix-revision",
549+ "gix-revwalk",
505 "gix-sec",
550 "gix-sec",
506 "gix-tempfile",
551 "gix-tempfile",
552+ "gix-trace",
507 "gix-traverse",
553 "gix-traverse",
508 "gix-url",
554 "gix-url",
509 "gix-utils",
555 "gix-utils",
510 "gix-validate",
556 "gix-validate",
511− "gix-worktree",
512− "log",
513 "once_cell",
557 "once_cell",
514− "signal-hook",
558+ "parking_lot",
515 "smallvec",
559 "smallvec",
516 "thiserror",
560 "thiserror",
517 "unicode-normalization",
561 "unicode-normalization",
@@ -519,67 +563,46 @@ dependencies = [
519
563
520[[package]]
564[[package]]
521name = "gix-actor"
565name = "gix-actor"
522−version = "0.20.0"
566+version = "0.29.0"
523source = "registry+https://github.com/rust-lang/crates.io-index"
567source = "registry+https://github.com/rust-lang/crates.io-index"
524−checksum = "848efa0f1210cea8638f95691c82a46f98a74b9e3524f01d4955ebc25a8f84f3"
568+checksum = "886014c4865b93ce268f1d3eddd4fd3261242c3f3ee61eb36009f913016a9059"
525dependencies = [
569dependencies = [
526 "bstr",
570 "bstr",
527 "btoi",
571 "btoi",
528 "gix-date",
572 "gix-date",
529 "itoa",
573 "itoa",
530− "nom",
531− "thiserror",
532−]
533−
534−[[package]]
535−name = "gix-attributes"
536−version = "0.12.0"
537−source = "registry+https://github.com/rust-lang/crates.io-index"
538−checksum = "3015baa01ad2122fbcaab7863c857a603eb7b7ec12ac8141207c42c6439805e2"
539−dependencies = [
540− "bstr",
541− "gix-glob",
542− "gix-path",
543− "gix-quote",
544− "kstring",
545− "log",
546− "smallvec",
547− "thiserror",
548− "unicode-bom",
549−]
550−
551−[[package]]
552−name = "gix-bitmap"
553−version = "0.2.3"
554−source = "registry+https://github.com/rust-lang/crates.io-index"
555−checksum = "55a95f4942360766c3880bdb2b4b57f1ef73b190fc424755e7fdf480430af618"
556−dependencies = [
557 "thiserror",
574 "thiserror",
575+ "winnow 0.5.28",
558]
576]
559
577
560[[package]]
578[[package]]
561name = "gix-chunk"
579name = "gix-chunk"
562−version = "0.4.1"
580+version = "0.4.6"
563source = "registry+https://github.com/rust-lang/crates.io-index"
581source = "registry+https://github.com/rust-lang/crates.io-index"
564−checksum = "b0d39583cab06464b8bf73b3f1707458270f0e7383cb24c3c9c1a16e6f792978"
582+checksum = "2ade37ef69870de0ed966b97c57a3a947a22ff3482a52c3b99b205f77bcb08fb"
565dependencies = [
583dependencies = [
566 "thiserror",
584 "thiserror",
567]
585]
568
586
569[[package]]
587[[package]]
570−name = "gix-command"
588+name = "gix-commitgraph"
571−version = "0.2.4"
589+version = "0.23.0"
572source = "registry+https://github.com/rust-lang/crates.io-index"
590source = "registry+https://github.com/rust-lang/crates.io-index"
573−checksum = "b2c6f75c1e0f924de39e750880a6e21307194bb1ab773efe3c7d2d787277f8ab"
591+checksum = "e7559ea9cefee188cd88b05afcc8e3ef7a3cb4a5c647bccf06b981e591b02b77"
574dependencies = [
592dependencies = [
575 "bstr",
593 "bstr",
594+ "gix-chunk",
595+ "gix-features",
596+ "gix-hash",
597+ "memmap2",
598+ "thiserror",
576]
599]
577
600
578[[package]]
601[[package]]
579name = "gix-config"
602name = "gix-config"
580−version = "0.22.0"
603+version = "0.33.0"
581source = "registry+https://github.com/rust-lang/crates.io-index"
604source = "registry+https://github.com/rust-lang/crates.io-index"
582−checksum = "1d252a0eddb6df74600d3d8872dc9fe98835a7da43110411d705b682f49d4ac1"
605+checksum = "9c35dc7f9c00a42bbc9cfa1ca2ec0a78ad1b76ff0736d3d35dfd612962244467"
583dependencies = [
606dependencies = [
584 "bstr",
607 "bstr",
585 "gix-config-value",
608 "gix-config-value",
@@ -588,49 +611,32 @@ dependencies = [
588 "gix-path",
611 "gix-path",
589 "gix-ref",
612 "gix-ref",
590 "gix-sec",
613 "gix-sec",
591− "log",
592 "memchr",
614 "memchr",
593− "nom",
594 "once_cell",
615 "once_cell",
595 "smallvec",
616 "smallvec",
596 "thiserror",
617 "thiserror",
597 "unicode-bom",
618 "unicode-bom",
619+ "winnow 0.5.28",
598]
620]
599
621
600[[package]]
622[[package]]
601name = "gix-config-value"
623name = "gix-config-value"
602−version = "0.12.0"
624+version = "0.14.2"
603source = "registry+https://github.com/rust-lang/crates.io-index"
625source = "registry+https://github.com/rust-lang/crates.io-index"
604−checksum = "786861e84a5793ad5f863d846de5eb064cd23b87e61ad708c8c402608202e7be"
626+checksum = "39f388cb2396aee82d6f460a2e7770659bdf8854e9e5478f7d2b1324a9698284"
605dependencies = [
627dependencies = [
606− "bitflags 2.2.1",
628+ "bitflags 2.4.1",
607 "bstr",
629 "bstr",
608 "gix-path",
630 "gix-path",
609 "libc",
631 "libc",
610 "thiserror",
632 "thiserror",
611]
633]
612
634
613−[[package]]
614−name = "gix-credentials"
615−version = "0.14.0"
616−source = "registry+https://github.com/rust-lang/crates.io-index"
617−checksum = "4874a4fc11ffa844a3c2b87a66957bda30a73b577ef1acf15ac34df5745de5ff"
618−dependencies = [
619− "bstr",
620− "gix-command",
621− "gix-config-value",
622− "gix-path",
623− "gix-prompt",
624− "gix-sec",
625− "gix-url",
626− "thiserror",
627−]
628−
629[[package]]
635[[package]]
630name = "gix-date"
636name = "gix-date"
631−version = "0.5.0"
637+version = "0.8.2"
632source = "registry+https://github.com/rust-lang/crates.io-index"
638source = "registry+https://github.com/rust-lang/crates.io-index"
633−checksum = "99056f37270715f5c7584fd8b46899a2296af9cae92463bf58b8bd1f5a78e553"
639+checksum = "9d85f4a01e0d05c3de585e28bae514d4baf01655e3fc3f14ce6f30bf62405345"
634dependencies = [
640dependencies = [
635 "bstr",
641 "bstr",
636 "itoa",
642 "itoa",
@@ -640,21 +646,21 @@ dependencies = [
640
646
641[[package]]
647[[package]]
642name = "gix-diff"
648name = "gix-diff"
643−version = "0.29.0"
649+version = "0.39.0"
644source = "registry+https://github.com/rust-lang/crates.io-index"
650source = "registry+https://github.com/rust-lang/crates.io-index"
645−checksum = "644a0f2768bc42d7a69289ada80c9e15c589caefc6a315d2307202df83ed1186"
651+checksum = "ac0e75f5afd2f6c47c800b6b0a000a08045739d0450d20482e8faa42543f62d1"
646dependencies = [
652dependencies = [
653+ "bstr",
647 "gix-hash",
654 "gix-hash",
648 "gix-object",
655 "gix-object",
649− "imara-diff",
650 "thiserror",
656 "thiserror",
651]
657]
652
658
653[[package]]
659[[package]]
654name = "gix-discover"
660name = "gix-discover"
655−version = "0.18.1"
661+version = "0.28.0"
656source = "registry+https://github.com/rust-lang/crates.io-index"
662source = "registry+https://github.com/rust-lang/crates.io-index"
657−checksum = "1a6b61363e63e7cdaa3e6f96acb0257ebdb3d8883e21eba5930c99f07f0a5fc0"
663+checksum = "f0b63ce2ad81632ac1a84ac370f85a5e580c3261580bd85ea17ff35d3a0bba18"
658dependencies = [
664dependencies = [
659 "bstr",
665 "bstr",
660 "dunce",
666 "dunce",
@@ -667,13 +673,14 @@ dependencies = [
667
673
668[[package]]
674[[package]]
669name = "gix-features"
675name = "gix-features"
670−version = "0.29.0"
676+version = "0.37.0"
671source = "registry+https://github.com/rust-lang/crates.io-index"
677source = "registry+https://github.com/rust-lang/crates.io-index"
672−checksum = "cf69b0f5c701cc3ae22d3204b671907668f6437ca88862d355eaf9bc47a4f897"
678+checksum = "befe7edea299a824504b5acc96d7a3a538125b38c42f3a8379f6912a29c90c81"
673dependencies = [
679dependencies = [
674 "crc32fast",
680 "crc32fast",
675 "flate2",
681 "flate2",
676 "gix-hash",
682 "gix-hash",
683+ "gix-trace",
677 "libc",
684 "libc",
678 "once_cell",
685 "once_cell",
679 "prodash",
686 "prodash",
@@ -684,20 +691,20 @@ dependencies = [
684
691
685[[package]]
692[[package]]
686name = "gix-fs"
693name = "gix-fs"
687−version = "0.1.1"
694+version = "0.9.0"
688source = "registry+https://github.com/rust-lang/crates.io-index"
695source = "registry+https://github.com/rust-lang/crates.io-index"
689−checksum = "9b37a1832f691fdc09910bd267f9a2e413737c1f9ec68c6e31f9e802616278a9"
696+checksum = "007017ce93b819ea52c0ec68306f7d72212a2d307c3d70f1548c7141c015d0a1"
690dependencies = [
697dependencies = [
691 "gix-features",
698 "gix-features",
692]
699]
693
700
694[[package]]
701[[package]]
695name = "gix-glob"
702name = "gix-glob"
696−version = "0.7.0"
703+version = "0.15.0"
697source = "registry+https://github.com/rust-lang/crates.io-index"
704source = "registry+https://github.com/rust-lang/crates.io-index"
698−checksum = "c07c98204529ac3f24b34754540a852593d2a4c7349008df389240266627a72a"
705+checksum = "61fb116c2516d3a1170e010118f639944a6389872c875a008960cdab2a44ac72"
699dependencies = [
706dependencies = [
700− "bitflags 2.2.1",
707+ "bitflags 2.4.1",
701 "bstr",
708 "bstr",
702 "gix-features",
709 "gix-features",
703 "gix-path",
710 "gix-path",
@@ -705,107 +712,74 @@ dependencies = [
705
712
706[[package]]
713[[package]]
707name = "gix-hash"
714name = "gix-hash"
708−version = "0.11.1"
715+version = "0.14.0"
709source = "registry+https://github.com/rust-lang/crates.io-index"
716source = "registry+https://github.com/rust-lang/crates.io-index"
710−checksum = "078eec3ac2808cc03f0bddd2704cb661da5c5dc33b41a9d7947b141d499c7c42"
717+checksum = "c52c3170a17b6031833cd2eb4ad7bc23f2755d06e6e70f78dec21d42e8fe1b30"
711dependencies = [
718dependencies = [
712− "hex",
719+ "faster-hex",
713 "thiserror",
720 "thiserror",
714]
721]
715
722
716[[package]]
723[[package]]
717name = "gix-hashtable"
724name = "gix-hashtable"
718−version = "0.2.0"
725+version = "0.5.0"
719source = "registry+https://github.com/rust-lang/crates.io-index"
726source = "registry+https://github.com/rust-lang/crates.io-index"
720−checksum = "afebb85691c6a085b114e01a27f4a61364519298c5826cb87a45c304802299bc"
727+checksum = "40a838e6366a5e5b84668b6997ce0981b833136468e8ba949f424c0ef2927eba"
721dependencies = [
728dependencies = [
722 "gix-hash",
729 "gix-hash",
723− "hashbrown 0.13.2",
730+ "hashbrown 0.14.3",
724 "parking_lot",
731 "parking_lot",
725]
732]
726
733
727−[[package]]
728−name = "gix-ignore"
729−version = "0.2.0"
730−source = "registry+https://github.com/rust-lang/crates.io-index"
731−checksum = "ba205b6df563e2906768bb22834c82eb46c5fdfcd86ba2c347270bc8309a05b2"
732−dependencies = [
733− "bstr",
734− "gix-glob",
735− "gix-path",
736− "unicode-bom",
737−]
738−
739−[[package]]
740−name = "gix-index"
741−version = "0.16.0"
742−source = "registry+https://github.com/rust-lang/crates.io-index"
743−checksum = "fa282756760f79c401d4f4f42588fbb4aa27bbb4b0830f3b4d3480c21a4ac5a7"
744−dependencies = [
745− "bitflags 2.2.1",
746− "bstr",
747− "btoi",
748− "filetime",
749− "gix-bitmap",
750− "gix-features",
751− "gix-hash",
752− "gix-lock",
753− "gix-object",
754− "gix-traverse",
755− "itoa",
756− "memmap2",
757− "smallvec",
758− "thiserror",
759−]
760−
761[[package]]
734[[package]]
762name = "gix-lock"
735name = "gix-lock"
763−version = "5.0.0"
736+version = "12.0.0"
764source = "registry+https://github.com/rust-lang/crates.io-index"
737source = "registry+https://github.com/rust-lang/crates.io-index"
765−checksum = "41b80172055c5d8017a48ddac5cc7a95421c00211047db0165c97853c4f05194"
738+checksum = "6cf112ddee94223c119a8534dad027740dc3aba3365ac5edeef8a7f6660c74db"
766dependencies = [
739dependencies = [
767− "fastrand",
768 "gix-tempfile",
740 "gix-tempfile",
741+ "gix-utils",
769 "thiserror",
742 "thiserror",
770]
743]
771
744
772[[package]]
745[[package]]
773−name = "gix-mailmap"
746+name = "gix-macros"
774−version = "0.12.0"
747+version = "0.1.2"
775source = "registry+https://github.com/rust-lang/crates.io-index"
748source = "registry+https://github.com/rust-lang/crates.io-index"
776−checksum = "e8856cec3bdc3610c06970d28b6cb20a0c6621621cf9a8ec48cbd23f2630f362"
749+checksum = "fc207b64189cf71bcb17fc841ab99a5d63d0845546b611e2703ce467f659323a"
777dependencies = [
750dependencies = [
778− "bstr",
751+ "proc-macro2",
779− "gix-actor",
752+ "quote",
780− "thiserror",
753+ "syn 2.0.38",
781]
754]
782
755
783[[package]]
756[[package]]
784name = "gix-object"
757name = "gix-object"
785−version = "0.29.1"
758+version = "0.40.0"
786source = "registry+https://github.com/rust-lang/crates.io-index"
759source = "registry+https://github.com/rust-lang/crates.io-index"
787−checksum = "c9bb30ce0818d37096daa29efe361a4bc6dd0b51a5726598898be7e9a40a01e1"
760+checksum = "e4c77e47ffba92127faf632a841fce23d19547e269bd8b88e68961a70eab4e93"
788dependencies = [
761dependencies = [
789 "bstr",
762 "bstr",
790 "btoi",
763 "btoi",
791 "gix-actor",
764 "gix-actor",
765+ "gix-date",
792 "gix-features",
766 "gix-features",
793 "gix-hash",
767 "gix-hash",
794 "gix-validate",
768 "gix-validate",
795− "hex",
796 "itoa",
769 "itoa",
797− "nom",
798 "smallvec",
770 "smallvec",
799 "thiserror",
771 "thiserror",
772+ "winnow 0.5.28",
800]
773]
801
774
802[[package]]
775[[package]]
803name = "gix-odb"
776name = "gix-odb"
804−version = "0.45.0"
777+version = "0.56.0"
805source = "registry+https://github.com/rust-lang/crates.io-index"
778source = "registry+https://github.com/rust-lang/crates.io-index"
806−checksum = "bca2f324aa67672b6d0f2c0fa93f96eb6a7029d260e4c1df5dce3c015f5e5add"
779+checksum = "a3254f2005cc7553ea78e85e816a09150c6f7a64e6b7627b8d1fdc56721bea73"
807dependencies = [
780dependencies = [
808 "arc-swap",
781 "arc-swap",
782+ "gix-date",
809 "gix-features",
783 "gix-features",
810 "gix-hash",
784 "gix-hash",
811 "gix-object",
785 "gix-object",
@@ -819,20 +793,18 @@ dependencies = [
819
793
820[[package]]
794[[package]]
821name = "gix-pack"
795name = "gix-pack"
822−version = "0.35.0"
796+version = "0.46.0"
823source = "registry+https://github.com/rust-lang/crates.io-index"
797source = "registry+https://github.com/rust-lang/crates.io-index"
824−checksum = "164a515900a83257ae4aa80e741655bee7a2e39113fb535d7a5ac623b445ff20"
798+checksum = "02ebc8cd657eec207d82d8f876ca402361308ecd3c87a47935b0299506257b4f"
825dependencies = [
799dependencies = [
826 "clru",
800 "clru",
827 "gix-chunk",
801 "gix-chunk",
828− "gix-diff",
829 "gix-features",
802 "gix-features",
830 "gix-hash",
803 "gix-hash",
831 "gix-hashtable",
804 "gix-hashtable",
832 "gix-object",
805 "gix-object",
833 "gix-path",
806 "gix-path",
834 "gix-tempfile",
807 "gix-tempfile",
835− "gix-traverse",
836 "memmap2",
808 "memmap2",
837 "parking_lot",
809 "parking_lot",
838 "smallvec",
810 "smallvec",
@@ -841,34 +813,22 @@ dependencies = [
841
813
842[[package]]
814[[package]]
843name = "gix-path"
815name = "gix-path"
844−version = "0.8.0"
816+version = "0.10.2"
845source = "registry+https://github.com/rust-lang/crates.io-index"
817source = "registry+https://github.com/rust-lang/crates.io-index"
846−checksum = "4fc78f47095a0c15aea0e66103838f0748f4494bf7a9555dfe0f00425400396c"
818+checksum = "10931652a3da126990ac93bce1b1c600cc99d7c268d712b6360ed52174ce1b68"
847dependencies = [
819dependencies = [
848 "bstr",
820 "bstr",
821+ "gix-trace",
849 "home",
822 "home",
850 "once_cell",
823 "once_cell",
851 "thiserror",
824 "thiserror",
852]
825]
853
826
854−[[package]]
855−name = "gix-prompt"
856−version = "0.5.0"
857−source = "registry+https://github.com/rust-lang/crates.io-index"
858−checksum = "330d11fdf88fff3366c2491efde2f3e454958efe7d5ddf60272e8fb1d944bb01"
859−dependencies = [
860− "gix-command",
861− "gix-config-value",
862− "parking_lot",
863− "rustix",
864− "thiserror",
865−]
866−
867[[package]]
827[[package]]
868name = "gix-quote"
828name = "gix-quote"
869−version = "0.4.3"
829+version = "0.4.9"
870source = "registry+https://github.com/rust-lang/crates.io-index"
830source = "registry+https://github.com/rust-lang/crates.io-index"
871−checksum = "a282f5a8d9ee0b09ec47390ac727350c48f2f5c76d803cd8da6b3e7ad56e0bcb"
831+checksum = "6c43530cb94a7759807e6f8d180e17ac9c65673b891645c6c433831dc0cf4342"
872dependencies = [
832dependencies = [
873 "bstr",
833 "bstr",
874 "btoi",
834 "btoi",
@@ -877,11 +837,12 @@ dependencies = [
877
837
878[[package]]
838[[package]]
879name = "gix-ref"
839name = "gix-ref"
880−version = "0.29.1"
840+version = "0.40.0"
881source = "registry+https://github.com/rust-lang/crates.io-index"
841source = "registry+https://github.com/rust-lang/crates.io-index"
882−checksum = "1e03989e9d49954368e1b526578230fc7189d1634acdfbe79e9ba1de717e15d5"
842+checksum = "baa951b3b850d6d1be4f900768e49af20b76bf9505beac22af723d57249a2f1d"
883dependencies = [
843dependencies = [
884 "gix-actor",
844 "gix-actor",
845+ "gix-date",
885 "gix-features",
846 "gix-features",
886 "gix-fs",
847 "gix-fs",
887 "gix-hash",
848 "gix-hash",
@@ -891,15 +852,15 @@ dependencies = [
891 "gix-tempfile",
852 "gix-tempfile",
892 "gix-validate",
853 "gix-validate",
893 "memmap2",
854 "memmap2",
894− "nom",
895 "thiserror",
855 "thiserror",
856+ "winnow 0.5.28",
896]
857]
897
858
898[[package]]
859[[package]]
899name = "gix-refspec"
860name = "gix-refspec"
900−version = "0.10.1"
861+version = "0.21.0"
901source = "registry+https://github.com/rust-lang/crates.io-index"
862source = "registry+https://github.com/rust-lang/crates.io-index"
902−checksum = "0a6ea733820df67e4cd7797deb12727905824d8f5b7c59d943c456d314475892"
863+checksum = "88c61f849d58c06e3068a0b601cf10127d2a07cdad00a725ed66cf303f76f6b3"
903dependencies = [
864dependencies = [
904 "bstr",
865 "bstr",
905 "gix-hash",
866 "gix-hash",
@@ -911,61 +872,87 @@ dependencies = [
911
872
912[[package]]
873[[package]]
913name = "gix-revision"
874name = "gix-revision"
914−version = "0.13.0"
875+version = "0.25.0"
915source = "registry+https://github.com/rust-lang/crates.io-index"
876source = "registry+https://github.com/rust-lang/crates.io-index"
916−checksum = "810f35e9afeccca999d5d348b239f9c162353127d2e13ff3240e31b919e35476"
877+checksum = "bcbbf91f4200c5c76802ef5f057b96f5a336827508881fe55a780be71a794d22"
917dependencies = [
878dependencies = [
918 "bstr",
879 "bstr",
919 "gix-date",
880 "gix-date",
920 "gix-hash",
881 "gix-hash",
921 "gix-hashtable",
882 "gix-hashtable",
922 "gix-object",
883 "gix-object",
884+ "gix-revwalk",
885+ "gix-trace",
886+ "thiserror",
887+]
888+
889+[[package]]
890+name = "gix-revwalk"
891+version = "0.11.0"
892+source = "registry+https://github.com/rust-lang/crates.io-index"
893+checksum = "33ab66354d83f70f2730391747d0c75f94ef3c3dd40ebde2e206db9faaf7a0a7"
894+dependencies = [
895+ "gix-commitgraph",
896+ "gix-date",
897+ "gix-hash",
898+ "gix-hashtable",
899+ "gix-object",
900+ "smallvec",
923 "thiserror",
901 "thiserror",
924]
902]
925
903
926[[package]]
904[[package]]
927name = "gix-sec"
905name = "gix-sec"
928−version = "0.8.0"
906+version = "0.10.2"
929source = "registry+https://github.com/rust-lang/crates.io-index"
907source = "registry+https://github.com/rust-lang/crates.io-index"
930−checksum = "794520043d5a024dfeac335c6e520cb616f6963e30dab995892382e998c12897"
908+checksum = "a9963f38a42144253ed4d571882d7db5ef644c12e6726e4b75135597cc9d0e1a"
931dependencies = [
909dependencies = [
932− "bitflags 2.2.1",
910+ "bitflags 2.4.1",
933 "gix-path",
911 "gix-path",
934 "libc",
912 "libc",
935− "windows",
913+ "windows 0.52.0",
936]
914]
937
915
938[[package]]
916[[package]]
939name = "gix-tempfile"
917name = "gix-tempfile"
940−version = "5.0.2"
918+version = "12.0.0"
941source = "registry+https://github.com/rust-lang/crates.io-index"
919source = "registry+https://github.com/rust-lang/crates.io-index"
942−checksum = "c2ceb30a610e3f5f2d5f9a5114689fde507ba9417705a8cf3429604275b2153c"
920+checksum = "e76a494bd530e1a1309188ff971825a24f159c76c2db0bf71fa5dfb469a2c915"
943dependencies = [
921dependencies = [
922+ "gix-fs",
944 "libc",
923 "libc",
945 "once_cell",
924 "once_cell",
946 "parking_lot",
925 "parking_lot",
947− "signal-hook",
948− "signal-hook-registry",
949 "tempfile",
926 "tempfile",
950]
927]
951
928
929+[[package]]
930+name = "gix-trace"
931+version = "0.1.5"
932+source = "registry+https://github.com/rust-lang/crates.io-index"
933+checksum = "bda62acb44dd86a40c7c3762a5403cfc1ac789ea559df54085cedf79864f809e"
934+
952[[package]]
935[[package]]
953name = "gix-traverse"
936name = "gix-traverse"
954−version = "0.25.0"
937+version = "0.36.0"
955source = "registry+https://github.com/rust-lang/crates.io-index"
938source = "registry+https://github.com/rust-lang/crates.io-index"
956−checksum = "a5be1e807f288c33bb005075111886cceb43ed8a167b3182a0f62c186e2a0dd1"
939+checksum = "8661fab39985c9214e56d81a63ceb5886ac948cec2fba76c39494d1e0e307ea8"
957dependencies = [
940dependencies = [
941+ "gix-commitgraph",
942+ "gix-date",
958 "gix-hash",
943 "gix-hash",
959 "gix-hashtable",
944 "gix-hashtable",
960 "gix-object",
945 "gix-object",
946+ "gix-revwalk",
947+ "smallvec",
961 "thiserror",
948 "thiserror",
962]
949]
963
950
964[[package]]
951[[package]]
965name = "gix-url"
952name = "gix-url"
966−version = "0.18.0"
953+version = "0.26.0"
967source = "registry+https://github.com/rust-lang/crates.io-index"
954source = "registry+https://github.com/rust-lang/crates.io-index"
968−checksum = "dfc77f89054297cc81491e31f1bab4027e554b5ef742a44bd7035db9a0f78b76"
955+checksum = "10a0129c1e8b52736d7c5128300a4485dbc85863001371e2771ac1754bd89fd7"
969dependencies = [
956dependencies = [
970 "bstr",
957 "bstr",
971 "gix-features",
958 "gix-features",
@@ -977,93 +964,70 @@ dependencies = [
977
964
978[[package]]
965[[package]]
979name = "gix-utils"
966name = "gix-utils"
980−version = "0.1.1"
967+version = "0.1.7"
981source = "registry+https://github.com/rust-lang/crates.io-index"
968source = "registry+https://github.com/rust-lang/crates.io-index"
982−checksum = "c10b69beac219acb8df673187a1f07dde2d74092f974fb3f9eb385aeb667c909"
969+checksum = "ab9277a5e32e85d53f738096d872a4a9b76067ad471894ad31bd99c8fa2da1dc"
983dependencies = [
970dependencies = [
984 "fastrand",
971 "fastrand",
985]
972]
986
973
987[[package]]
974[[package]]
988name = "gix-validate"
975name = "gix-validate"
989−version = "0.7.4"
976+version = "0.8.2"
990source = "registry+https://github.com/rust-lang/crates.io-index"
977source = "registry+https://github.com/rust-lang/crates.io-index"
991−checksum = "7bd629d3680773e1785e585d76fd4295b740b559cad9141517300d99a0c8c049"
978+checksum = "f805ebbdbaa4bfd98e2ee43e6d14099b8a4d9141f5d7b8202fea4d48e44263e7"
992dependencies = [
979dependencies = [
993 "bstr",
980 "bstr",
994 "thiserror",
981 "thiserror",
995]
982]
996
983
997−[[package]]
998−name = "gix-worktree"
999−version = "0.17.0"
1000−source = "registry+https://github.com/rust-lang/crates.io-index"
1001−checksum = "10bf56a1f5037d84293ea6cece61d9f27c4866b1e13c1c95f37cf56b7da7af25"
1002−dependencies = [
1003− "bstr",
1004− "filetime",
1005− "gix-attributes",
1006− "gix-features",
1007− "gix-fs",
1008− "gix-glob",
1009− "gix-hash",
1010− "gix-ignore",
1011− "gix-index",
1012− "gix-object",
1013− "gix-path",
1014− "io-close",
1015− "thiserror",
1016−]
1017−
1018[[package]]
984[[package]]
1019name = "globset"
985name = "globset"
1020−version = "0.4.10"
986+version = "0.4.14"
1021source = "registry+https://github.com/rust-lang/crates.io-index"
987source = "registry+https://github.com/rust-lang/crates.io-index"
1022−checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
988+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
1023dependencies = [
989dependencies = [
1024− "aho-corasick 0.7.20",
990+ "aho-corasick",
1025 "bstr",
991 "bstr",
1026− "fnv",
1027 "log",
992 "log",
1028− "regex",
993+ "regex-automata",
994+ "regex-syntax",
1029]
995]
1030
996
1031[[package]]
997[[package]]
1032name = "grep-matcher"
998name = "grep-matcher"
1033−version = "0.1.6"
999+version = "0.1.7"
1034source = "registry+https://github.com/rust-lang/crates.io-index"
1000source = "registry+https://github.com/rust-lang/crates.io-index"
1035−checksum = "3902ca28f26945fe35cad349d776f163981d777fee382ccd6ef451126f51b319"
1001+checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02"
1036dependencies = [
1002dependencies = [
1037 "memchr",
1003 "memchr",
1038]
1004]
1039
1005
1040[[package]]
1006[[package]]
1041name = "grep-regex"
1007name = "grep-regex"
1042−version = "0.1.11"
1008+version = "0.1.12"
1043source = "registry+https://github.com/rust-lang/crates.io-index"
1009source = "registry+https://github.com/rust-lang/crates.io-index"
1044−checksum = "997598b41d53a37a2e3fc5300d5c11d825368c054420a9c65125b8fe1078463f"
1010+checksum = "f748bb135ca835da5cbc67ca0e6955f968db9c5df74ca4f56b18e1ddbc68230d"
1045dependencies = [
1011dependencies = [
1046− "aho-corasick 0.7.20",
1047 "bstr",
1012 "bstr",
1048 "grep-matcher",
1013 "grep-matcher",
1049 "log",
1014 "log",
1050− "regex",
1015+ "regex-automata",
1051− "regex-syntax 0.6.29",
1016+ "regex-syntax",
1052− "thread_local",
1053]
1017]
1054
1018
1055[[package]]
1019[[package]]
1056name = "grep-searcher"
1020name = "grep-searcher"
1057−version = "0.1.11"
1021+version = "0.1.13"
1058source = "registry+https://github.com/rust-lang/crates.io-index"
1022source = "registry+https://github.com/rust-lang/crates.io-index"
1059−checksum = "5601c4b9f480f0c9ebb40b1f6cbf447b8a50c5369223937a6c5214368c58779f"
1023+checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54"
1060dependencies = [
1024dependencies = [
1061 "bstr",
1025 "bstr",
1062− "bytecount",
1063 "encoding_rs",
1026 "encoding_rs",
1064 "encoding_rs_io",
1027 "encoding_rs_io",
1065 "grep-matcher",
1028 "grep-matcher",
1066 "log",
1029 "log",
1030+ "memchr",
1067 "memmap2",
1031 "memmap2",
1068]
1032]
1069
1033
@@ -1072,36 +1036,36 @@ name = "hashbrown"
1072version = "0.12.3"
1036version = "0.12.3"
1073source = "registry+https://github.com/rust-lang/crates.io-index"
1037source = "registry+https://github.com/rust-lang/crates.io-index"
1074checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1038checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1075−dependencies = [
1076− "ahash 0.7.6",
1077−]
1078
1039
1079[[package]]
1040[[package]]
1080name = "hashbrown"
1041name = "hashbrown"
1081−version = "0.13.2"
1042+version = "0.14.3"
1082source = "registry+https://github.com/rust-lang/crates.io-index"
1043source = "registry+https://github.com/rust-lang/crates.io-index"
1083−checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
1044+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
1084dependencies = [
1045dependencies = [
1085− "ahash 0.8.3",
1046+ "ahash",
1047+ "allocator-api2",
1086]
1048]
1087
1049
1088[[package]]
1050[[package]]
1089name = "helix-core"
1051name = "helix-core"
1090−version = "0.6.0"
1052+version = "23.10.0"
1091dependencies = [
1053dependencies = [
1092− "ahash 0.8.3",
1054+ "ahash",
1093 "arc-swap",
1055 "arc-swap",
1094− "bitflags 2.2.1",
1056+ "bitflags 2.4.1",
1095 "chrono",
1057 "chrono",
1096 "dunce",
1058 "dunce",
1097 "encoding_rs",
1059 "encoding_rs",
1098 "etcetera",
1060 "etcetera",
1099− "hashbrown 0.13.2",
1061+ "hashbrown 0.14.3",
1100 "helix-loader",
1062 "helix-loader",
1101 "imara-diff",
1063 "imara-diff",
1102 "indoc",
1064 "indoc",
1103 "log",
1065 "log",
1066+ "nucleo",
1104 "once_cell",
1067 "once_cell",
1068+ "parking_lot",
1105 "quickcheck",
1069 "quickcheck",
1106 "regex",
1070 "regex",
1107 "ropey",
1071 "ropey",
@@ -1120,7 +1084,7 @@ dependencies = [
1120
1084
1121[[package]]
1085[[package]]
1122name = "helix-dap"
1086name = "helix-dap"
1123−version = "0.6.0"
1087+version = "23.10.0"
1124dependencies = [
1088dependencies = [
1125 "anyhow",
1089 "anyhow",
1126 "fern",
1090 "fern",
@@ -1133,12 +1097,21 @@ dependencies = [
1133 "which",
1097 "which",
1134]
1098]
1135
1099
1100+[[package]]
1101+name = "helix-event"
1102+version = "23.10.0"
1103+dependencies = [
1104+ "parking_lot",
1105+ "tokio",
1106+]
1107+
1136[[package]]
1108[[package]]
1137name = "helix-loader"
1109name = "helix-loader"
1138−version = "0.6.0"
1110+version = "23.10.0"
1139dependencies = [
1111dependencies = [
1140 "anyhow",
1112 "anyhow",
1141 "cc",
1113 "cc",
1114+ "dunce",
1142 "etcetera",
1115 "etcetera",
1143 "libloading",
1116 "libloading",
1144 "log",
1117 "log",
@@ -1148,15 +1121,17 @@ dependencies = [
1148 "threadpool",
1121 "threadpool",
1149 "toml",
1122 "toml",
1150 "tree-sitter",
1123 "tree-sitter",
1124+ "which",
1151]
1125]
1152
1126
1153[[package]]
1127[[package]]
1154name = "helix-lsp"
1128name = "helix-lsp"
1155−version = "0.6.0"
1129+version = "23.10.0"
1156dependencies = [
1130dependencies = [
1157 "anyhow",
1131 "anyhow",
1158 "futures-executor",
1132 "futures-executor",
1159 "futures-util",
1133 "futures-util",
1134+ "globset",
1160 "helix-core",
1135 "helix-core",
1161 "helix-loader",
1136 "helix-loader",
1162 "helix-parsec",
1137 "helix-parsec",
@@ -1173,11 +1148,11 @@ dependencies = [
1173
1148
1174[[package]]
1149[[package]]
1175name = "helix-parsec"
1150name = "helix-parsec"
1176−version = "0.6.0"
1151+version = "23.10.0"
1177
1152
1178[[package]]
1153[[package]]
1179name = "helix-term"
1154name = "helix-term"
1180−version = "0.6.0"
1155+version = "23.10.0"
1181dependencies = [
1156dependencies = [
1182 "anyhow",
1157 "anyhow",
1183 "arc-swap",
1158 "arc-swap",
@@ -1186,11 +1161,11 @@ dependencies = [
1186 "crossterm",
1161 "crossterm",
1187 "fern",
1162 "fern",
1188 "futures-util",
1163 "futures-util",
1189− "fuzzy-matcher",
1190 "grep-regex",
1164 "grep-regex",
1191 "grep-searcher",
1165 "grep-searcher",
1192 "helix-core",
1166 "helix-core",
1193 "helix-dap",
1167 "helix-dap",
1168+ "helix-event",
1194 "helix-loader",
1169 "helix-loader",
1195 "helix-lsp",
1170 "helix-lsp",
1196 "helix-tui",
1171 "helix-tui",
@@ -1200,7 +1175,9 @@ dependencies = [
1200 "indoc",
1175 "indoc",
1201 "libc",
1176 "libc",
1202 "log",
1177 "log",
1178+ "nucleo",
1203 "once_cell",
1179 "once_cell",
1180+ "open",
1204 "pulldown-cmark",
1181 "pulldown-cmark",
1205 "serde",
1182 "serde",
1206 "serde_json",
1183 "serde_json",
@@ -1211,14 +1188,15 @@ dependencies = [
1211 "tokio",
1188 "tokio",
1212 "tokio-stream",
1189 "tokio-stream",
1213 "toml",
1190 "toml",
1191+ "url",
1214 "which",
1192 "which",
1215]
1193]
1216
1194
1217[[package]]
1195[[package]]
1218name = "helix-tui"
1196name = "helix-tui"
1219−version = "0.6.0"
1197+version = "23.10.0"
1220dependencies = [
1198dependencies = [
1221− "bitflags 2.2.1",
1199+ "bitflags 2.4.1",
1222 "cassowary",
1200 "cassowary",
1223 "crossterm",
1201 "crossterm",
1224 "helix-core",
1202 "helix-core",
@@ -1232,12 +1210,13 @@ dependencies = [
1232
1210
1233[[package]]
1211[[package]]
1234name = "helix-vcs"
1212name = "helix-vcs"
1235−version = "0.6.0"
1213+version = "23.10.0"
1236dependencies = [
1214dependencies = [
1237 "anyhow",
1215 "anyhow",
1238 "arc-swap",
1216 "arc-swap",
1239 "gix",
1217 "gix",
1240 "helix-core",
1218 "helix-core",
1219+ "helix-event",
1241 "imara-diff",
1220 "imara-diff",
1242 "log",
1221 "log",
1243 "parking_lot",
1222 "parking_lot",
@@ -1247,17 +1226,18 @@ dependencies = [
1247
1226
1248[[package]]
1227[[package]]
1249name = "helix-view"
1228name = "helix-view"
1250−version = "0.6.0"
1229+version = "23.10.0"
1251dependencies = [
1230dependencies = [
1252 "anyhow",
1231 "anyhow",
1253 "arc-swap",
1232 "arc-swap",
1254− "bitflags 2.2.1",
1233+ "bitflags 2.4.1",
1255 "chardetng",
1234 "chardetng",
1256 "clipboard-win",
1235 "clipboard-win",
1257 "crossterm",
1236 "crossterm",
1258 "futures-util",
1237 "futures-util",
1259 "helix-core",
1238 "helix-core",
1260 "helix-dap",
1239 "helix-dap",
1240+ "helix-event",
1261 "helix-loader",
1241 "helix-loader",
1262 "helix-lsp",
1242 "helix-lsp",
1263 "helix-tui",
1243 "helix-tui",
@@ -1266,6 +1246,7 @@ dependencies = [
1266 "log",
1246 "log",
1267 "once_cell",
1247 "once_cell",
1268 "parking_lot",
1248 "parking_lot",
1249+ "rustix",
1269 "serde",
1250 "serde",
1270 "serde_json",
1251 "serde_json",
1271 "slotmap",
1252 "slotmap",
@@ -1285,25 +1266,13 @@ dependencies = [
1285 "libc",
1266 "libc",
1286]
1267]
1287
1268
1288−[[package]]
1289−name = "hermit-abi"
1290−version = "0.3.1"
1291−source = "registry+https://github.com/rust-lang/crates.io-index"
1292−checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
1293−
1294−[[package]]
1295−name = "hex"
1296−version = "0.4.3"
1297−source = "registry+https://github.com/rust-lang/crates.io-index"
1298−checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1299−
1300[[package]]
1269[[package]]
1301name = "home"
1270name = "home"
1302−version = "0.5.4"
1271+version = "0.5.5"
1303source = "registry+https://github.com/rust-lang/crates.io-index"
1272source = "registry+https://github.com/rust-lang/crates.io-index"
1304−checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
1273+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
1305dependencies = [
1274dependencies = [
1306− "winapi",
1275+ "windows-sys 0.48.0",
1307]
1276]
1308
1277
1309[[package]]
1278[[package]]
@@ -1317,7 +1286,7 @@ dependencies = [
1317 "iana-time-zone-haiku",
1286 "iana-time-zone-haiku",
1318 "js-sys",
1287 "js-sys",
1319 "wasm-bindgen",
1288 "wasm-bindgen",
1320− "windows",
1289+ "windows 0.48.0",
1321]
1290]
1322
1291
1323[[package]]
1292[[package]]
@@ -1332,9 +1301,9 @@ dependencies = [
1332
1301
1333[[package]]
1302[[package]]
1334name = "idna"
1303name = "idna"
1335−version = "0.3.0"
1304+version = "0.5.0"
1336source = "registry+https://github.com/rust-lang/crates.io-index"
1305source = "registry+https://github.com/rust-lang/crates.io-index"
1337−checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
1306+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
1338dependencies = [
1307dependencies = [
1339 "unicode-bidi",
1308 "unicode-bidi",
1340 "unicode-normalization",
1309 "unicode-normalization",
@@ -1342,17 +1311,16 @@ dependencies = [
1342
1311
1343[[package]]
1312[[package]]
1344name = "ignore"
1313name = "ignore"
1345−version = "0.4.20"
1314+version = "0.4.21"
1346source = "registry+https://github.com/rust-lang/crates.io-index"
1315source = "registry+https://github.com/rust-lang/crates.io-index"
1347−checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
1316+checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
1348dependencies = [
1317dependencies = [
1318+ "crossbeam-deque",
1349 "globset",
1319 "globset",
1350− "lazy_static",
1351 "log",
1320 "log",
1352 "memchr",
1321 "memchr",
1353− "regex",
1322+ "regex-automata",
1354 "same-file",
1323 "same-file",
1355− "thread_local",
1356 "walkdir",
1324 "walkdir",
1357 "winapi-util",
1325 "winapi-util",
1358]
1326]
@@ -1363,54 +1331,43 @@ version = "0.1.5"
1363source = "registry+https://github.com/rust-lang/crates.io-index"
1331source = "registry+https://github.com/rust-lang/crates.io-index"
1364checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
1332checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
1365dependencies = [
1333dependencies = [
1366− "ahash 0.8.3",
1334+ "ahash",
1367 "hashbrown 0.12.3",
1335 "hashbrown 0.12.3",
1368]
1336]
1369
1337
1370[[package]]
1338[[package]]
1371name = "indexmap"
1339name = "indexmap"
1372−version = "1.9.3"
1340+version = "2.0.0"
1373source = "registry+https://github.com/rust-lang/crates.io-index"
1341source = "registry+https://github.com/rust-lang/crates.io-index"
1374−checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
1342+checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
1375dependencies = [
1343dependencies = [
1376− "autocfg",
1344+ "equivalent",
1377− "hashbrown 0.12.3",
1345+ "hashbrown 0.14.3",
1378]
1346]
1379
1347
1380[[package]]
1348[[package]]
1381name = "indoc"
1349name = "indoc"
1382−version = "2.0.1"
1350+version = "2.0.4"
1383−source = "registry+https://github.com/rust-lang/crates.io-index"
1384−checksum = "9f2cb48b81b1dc9f39676bf99f5499babfec7cd8fe14307f7b3d747208fb5690"
1385−
1386−[[package]]
1387−name = "instant"
1388−version = "0.1.12"
1389source = "registry+https://github.com/rust-lang/crates.io-index"
1351source = "registry+https://github.com/rust-lang/crates.io-index"
1390−checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
1352+checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
1391−dependencies = [
1392− "cfg-if",
1393−]
1394
1353
1395[[package]]
1354[[package]]
1396−name = "io-close"
1355+name = "is-docker"
1397−version = "0.3.7"
1356+version = "0.2.0"
1398source = "registry+https://github.com/rust-lang/crates.io-index"
1357source = "registry+https://github.com/rust-lang/crates.io-index"
1399−checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc"
1358+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
1400dependencies = [
1359dependencies = [
1401− "libc",
1360+ "once_cell",
1402− "winapi",
1403]
1361]
1404
1362
1405[[package]]
1363[[package]]
1406−name = "io-lifetimes"
1364+name = "is-wsl"
1407−version = "1.0.10"
1365+version = "0.4.0"
1408source = "registry+https://github.com/rust-lang/crates.io-index"
1366source = "registry+https://github.com/rust-lang/crates.io-index"
1409−checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
1367+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
1410dependencies = [
1368dependencies = [
1411− "hermit-abi 0.3.1",
1369+ "is-docker",
1412− "libc",
1370+ "once_cell",
1413− "windows-sys 0.48.0",
1414]
1371]
1415
1372
1416[[package]]
1373[[package]]
@@ -1428,32 +1385,17 @@ dependencies = [
1428 "wasm-bindgen",
1385 "wasm-bindgen",
1429]
1386]
1430
1387
1431−[[package]]
1432−name = "kstring"
1433−version = "2.0.0"
1434−source = "registry+https://github.com/rust-lang/crates.io-index"
1435−checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747"
1436−dependencies = [
1437− "static_assertions",
1438−]
1439−
1440−[[package]]
1441−name = "lazy_static"
1442−version = "1.4.0"
1443−source = "registry+https://github.com/rust-lang/crates.io-index"
1444−checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
1445−
1446[[package]]
1388[[package]]
1447name = "libc"
1389name = "libc"
1448−version = "0.2.144"
1390+version = "0.2.151"
1449source = "registry+https://github.com/rust-lang/crates.io-index"
1391source = "registry+https://github.com/rust-lang/crates.io-index"
1450−checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
1392+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
1451
1393
1452[[package]]
1394[[package]]
1453name = "libloading"
1395name = "libloading"
1454−version = "0.8.0"
1396+version = "0.8.1"
1455source = "registry+https://github.com/rust-lang/crates.io-index"
1397source = "registry+https://github.com/rust-lang/crates.io-index"
1456−checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb"
1398+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
1457dependencies = [
1399dependencies = [
1458 "cfg-if",
1400 "cfg-if",
1459 "windows-sys 0.48.0",
1401 "windows-sys 0.48.0",
@@ -1470,9 +1412,9 @@ dependencies = [
1470
1412
1471[[package]]
1413[[package]]
1472name = "linux-raw-sys"
1414name = "linux-raw-sys"
1473−version = "0.3.4"
1415+version = "0.4.11"
1474source = "registry+https://github.com/rust-lang/crates.io-index"
1416source = "registry+https://github.com/rust-lang/crates.io-index"
1475−checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
1417+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
1476
1418
1477[[package]]
1419[[package]]
1478name = "lock_api"
1420name = "lock_api"
@@ -1486,18 +1428,15 @@ dependencies = [
1486
1428
1487[[package]]
1429[[package]]
1488name = "log"
1430name = "log"
1489−version = "0.4.17"
1431+version = "0.4.20"
1490source = "registry+https://github.com/rust-lang/crates.io-index"
1432source = "registry+https://github.com/rust-lang/crates.io-index"
1491−checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
1433+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
1492−dependencies = [
1493− "cfg-if",
1494−]
1495
1434
1496[[package]]
1435[[package]]
1497name = "lsp-types"
1436name = "lsp-types"
1498−version = "0.94.0"
1437+version = "0.95.0"
1499source = "registry+https://github.com/rust-lang/crates.io-index"
1438source = "registry+https://github.com/rust-lang/crates.io-index"
1500−checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
1439+checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
1501dependencies = [
1440dependencies = [
1502 "bitflags 1.3.2",
1441 "bitflags 1.3.2",
1503 "serde",
1442 "serde",
@@ -1508,64 +1447,69 @@ dependencies = [
1508
1447
1509[[package]]
1448[[package]]
1510name = "memchr"
1449name = "memchr"
1511−version = "2.5.0"
1450+version = "2.6.3"
1512source = "registry+https://github.com/rust-lang/crates.io-index"
1451source = "registry+https://github.com/rust-lang/crates.io-index"
1513−checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
1452+checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
1514
1453
1515[[package]]
1454[[package]]
1516name = "memmap2"
1455name = "memmap2"
1517−version = "0.5.10"
1456+version = "0.9.0"
1518source = "registry+https://github.com/rust-lang/crates.io-index"
1457source = "registry+https://github.com/rust-lang/crates.io-index"
1519−checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
1458+checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
1520dependencies = [
1459dependencies = [
1521 "libc",
1460 "libc",
1522]
1461]
1523
1462
1524[[package]]
1463[[package]]
1525−name = "minimal-lexical"
1464+name = "memoffset"
1526−version = "0.2.1"
1465+version = "0.9.0"
1527source = "registry+https://github.com/rust-lang/crates.io-index"
1466source = "registry+https://github.com/rust-lang/crates.io-index"
1528−checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
1467+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
1468+dependencies = [
1469+ "autocfg",
1470+]
1529
1471
1530[[package]]
1472[[package]]
1531name = "miniz_oxide"
1473name = "miniz_oxide"
1532−version = "0.6.2"
1474+version = "0.7.1"
1533source = "registry+https://github.com/rust-lang/crates.io-index"
1475source = "registry+https://github.com/rust-lang/crates.io-index"
1534−checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
1476+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
1535dependencies = [
1477dependencies = [
1536 "adler",
1478 "adler",
1537]
1479]
1538
1480
1539[[package]]
1481[[package]]
1540name = "mio"
1482name = "mio"
1541−version = "0.8.6"
1483+version = "0.8.9"
1542source = "registry+https://github.com/rust-lang/crates.io-index"
1484source = "registry+https://github.com/rust-lang/crates.io-index"
1543−checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
1485+checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
1544dependencies = [
1486dependencies = [
1545 "libc",
1487 "libc",
1546 "log",
1488 "log",
1547 "wasi",
1489 "wasi",
1548− "windows-sys 0.45.0",
1490+ "windows-sys 0.48.0",
1549]
1491]
1550
1492
1551[[package]]
1493[[package]]
1552−name = "nom"
1494+name = "nucleo"
1553−version = "7.1.3"
1495+version = "0.2.1"
1554source = "registry+https://github.com/rust-lang/crates.io-index"
1496source = "registry+https://github.com/rust-lang/crates.io-index"
1555−checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
1497+checksum = "ae5331f4bcce475cf28cb29c95366c3091af4b0aa7703f1a6bc858f29718fdf3"
1556dependencies = [
1498dependencies = [
1557− "memchr",
1499+ "nucleo-matcher",
1558− "minimal-lexical",
1500+ "parking_lot",
1501+ "rayon",
1559]
1502]
1560
1503
1561[[package]]
1504[[package]]
1562−name = "num-integer"
1505+name = "nucleo-matcher"
1563−version = "0.1.45"
1506+version = "0.2.0"
1564source = "registry+https://github.com/rust-lang/crates.io-index"
1507source = "registry+https://github.com/rust-lang/crates.io-index"
1565−checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
1508+checksum = "1b702b402fe286162d1f00b552a046ce74365d2ac473a2607ff36ba650f9bd57"
1566dependencies = [
1509dependencies = [
1567− "autocfg",
1510+ "cov-mark",
1568− "num-traits",
1511+ "memchr",
1512+ "unicode-segmentation",
1569]
1513]
1570
1514
1571[[package]]
1515[[package]]
@@ -1583,7 +1527,7 @@ version = "1.15.0"
1583source = "registry+https://github.com/rust-lang/crates.io-index"
1527source = "registry+https://github.com/rust-lang/crates.io-index"
1584checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
1528checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
1585dependencies = [
1529dependencies = [
1586− "hermit-abi 0.2.6",
1530+ "hermit-abi",
1587 "libc",
1531 "libc",
1588]
1532]
1589
1533
@@ -1596,11 +1540,31 @@ dependencies = [
1596 "libc",
1540 "libc",
1597]
1541]
1598
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+
1599[[package]]
1552[[package]]
1600name = "once_cell"
1553name = "once_cell"
1601−version = "1.17.1"
1554+version = "1.19.0"
1602source = "registry+https://github.com/rust-lang/crates.io-index"
1555source = "registry+https://github.com/rust-lang/crates.io-index"
1603−checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
1556+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
1557+
1558+[[package]]
1559+name = "open"
1560+version = "5.0.1"
1561+source = "registry+https://github.com/rust-lang/crates.io-index"
1562+checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349"
1563+dependencies = [
1564+ "is-wsl",
1565+ "libc",
1566+ "pathdiff",
1567+]
1604
1568
1605[[package]]
1569[[package]]
1606name = "parking_lot"
1570name = "parking_lot"
@@ -1625,17 +1589,23 @@ dependencies = [
1625 "windows-sys 0.45.0",
1589 "windows-sys 0.45.0",
1626]
1590]
1627
1591
1592+[[package]]
1593+name = "pathdiff"
1594+version = "0.2.1"
1595+source = "registry+https://github.com/rust-lang/crates.io-index"
1596+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
1597+
1628[[package]]
1598[[package]]
1629name = "percent-encoding"
1599name = "percent-encoding"
1630−version = "2.2.0"
1600+version = "2.3.1"
1631source = "registry+https://github.com/rust-lang/crates.io-index"
1601source = "registry+https://github.com/rust-lang/crates.io-index"
1632−checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
1602+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
1633
1603
1634[[package]]
1604[[package]]
1635name = "pin-project-lite"
1605name = "pin-project-lite"
1636−version = "0.2.9"
1606+version = "0.2.12"
1637source = "registry+https://github.com/rust-lang/crates.io-index"
1607source = "registry+https://github.com/rust-lang/crates.io-index"
1638−checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
1608+checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
1639
1609
1640[[package]]
1610[[package]]
1641name = "pin-utils"
1611name = "pin-utils"
@@ -1645,24 +1615,24 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1645
1615
1646[[package]]
1616[[package]]
1647name = "proc-macro2"
1617name = "proc-macro2"
1648−version = "1.0.56"
1618+version = "1.0.69"
1649source = "registry+https://github.com/rust-lang/crates.io-index"
1619source = "registry+https://github.com/rust-lang/crates.io-index"
1650−checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
1620+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
1651dependencies = [
1621dependencies = [
1652 "unicode-ident",
1622 "unicode-ident",
1653]
1623]
1654
1624
1655[[package]]
1625[[package]]
1656name = "prodash"
1626name = "prodash"
1657−version = "23.1.2"
1627+version = "28.0.0"
1658source = "registry+https://github.com/rust-lang/crates.io-index"
1628source = "registry+https://github.com/rust-lang/crates.io-index"
1659−checksum = "9516b775656bc3e8985e19cd4b8c0c0de045095074e453d2c0a513b5f978392d"
1629+checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
1660
1630
1661[[package]]
1631[[package]]
1662name = "pulldown-cmark"
1632name = "pulldown-cmark"
1663−version = "0.9.2"
1633+version = "0.9.3"
1664source = "registry+https://github.com/rust-lang/crates.io-index"
1634source = "registry+https://github.com/rust-lang/crates.io-index"
1665−checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
1635+checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
1666dependencies = [
1636dependencies = [
1667 "bitflags 1.3.2",
1637 "bitflags 1.3.2",
1668 "memchr",
1638 "memchr",
@@ -1680,9 +1650,9 @@ dependencies = [
1680
1650
1681[[package]]
1651[[package]]
1682name = "quote"
1652name = "quote"
1683−version = "1.0.26"
1653+version = "1.0.29"
1684source = "registry+https://github.com/rust-lang/crates.io-index"
1654source = "registry+https://github.com/rust-lang/crates.io-index"
1685−checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
1655+checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
1686dependencies = [
1656dependencies = [
1687 "proc-macro2",
1657 "proc-macro2",
1688]
1658]
@@ -1705,6 +1675,28 @@ dependencies = [
1705 "getrandom",
1675 "getrandom",
1706]
1676]
1707
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+
1708[[package]]
1700[[package]]
1709name = "redox_syscall"
1701name = "redox_syscall"
1710version = "0.2.16"
1702version = "0.2.16"
@@ -1716,64 +1708,69 @@ dependencies = [
1716
1708
1717[[package]]
1709[[package]]
1718name = "redox_syscall"
1710name = "redox_syscall"
1719−version = "0.3.5"
1711+version = "0.4.1"
1720source = "registry+https://github.com/rust-lang/crates.io-index"
1712source = "registry+https://github.com/rust-lang/crates.io-index"
1721−checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
1713+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
1722dependencies = [
1714dependencies = [
1723 "bitflags 1.3.2",
1715 "bitflags 1.3.2",
1724]
1716]
1725
1717
1726[[package]]
1718[[package]]
1727name = "regex"
1719name = "regex"
1728−version = "1.8.1"
1720+version = "1.10.2"
1729source = "registry+https://github.com/rust-lang/crates.io-index"
1721source = "registry+https://github.com/rust-lang/crates.io-index"
1730−checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
1722+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
1731dependencies = [
1723dependencies = [
1732− "aho-corasick 1.0.1",
1724+ "aho-corasick",
1733 "memchr",
1725 "memchr",
1726+ "regex-automata",
1734− "regex-syntax 0.7.1",
1727+ "regex-syntax",
1735]
1728]
1736
1729
1737[[package]]
1730[[package]]
1738name = "regex-automata"
1731name = "regex-automata"
1739−version = "0.1.10"
1732+version = "0.4.3"
1740−source = "registry+https://github.com/rust-lang/crates.io-index"
1741−checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
1742−
1743−[[package]]
1744−name = "regex-syntax"
1745−version = "0.6.29"
1746source = "registry+https://github.com/rust-lang/crates.io-index"
1733source = "registry+https://github.com/rust-lang/crates.io-index"
1747−checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
1734+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
1735+dependencies = [
1736+ "aho-corasick",
1737+ "memchr",
1738+ "regex-syntax",
1739+]
1748
1740
1749[[package]]
1741[[package]]
1750name = "regex-syntax"
1742name = "regex-syntax"
1751−version = "0.7.1"
1743+version = "0.8.2"
1752source = "registry+https://github.com/rust-lang/crates.io-index"
1744source = "registry+https://github.com/rust-lang/crates.io-index"
1753−checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
1745+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
1754
1746
1755[[package]]
1747[[package]]
1756name = "ropey"
1748name = "ropey"
1757−version = "1.6.0"
1749+version = "1.6.1"
1758source = "registry+https://github.com/rust-lang/crates.io-index"
1750source = "registry+https://github.com/rust-lang/crates.io-index"
1759−checksum = "53ce7a2c43a32e50d666e33c5a80251b31147bb4b49024bcab11fb6f20c671ed"
1751+checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
1760dependencies = [
1752dependencies = [
1761 "smallvec",
1753 "smallvec",
1762 "str_indices",
1754 "str_indices",
1763]
1755]
1764
1756
1757+[[package]]
1758+name = "rustc-demangle"
1759+version = "0.1.23"
1760+source = "registry+https://github.com/rust-lang/crates.io-index"
1761+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
1762+
1765[[package]]
1763[[package]]
1766name = "rustix"
1764name = "rustix"
1767−version = "0.37.15"
1765+version = "0.38.28"
1768source = "registry+https://github.com/rust-lang/crates.io-index"
1766source = "registry+https://github.com/rust-lang/crates.io-index"
1769−checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece"
1767+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
1770dependencies = [
1768dependencies = [
1771− "bitflags 1.3.2",
1769+ "bitflags 2.4.1",
1772 "errno",
1770 "errno",
1773− "io-lifetimes",
1774 "libc",
1771 "libc",
1775 "linux-raw-sys",
1772 "linux-raw-sys",
1776− "windows-sys 0.48.0",
1773+ "windows-sys 0.52.0",
1777]
1774]
1778
1775
1779[[package]]
1776[[package]]
@@ -1805,29 +1802,29 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
1805
1802
1806[[package]]
1803[[package]]
1807name = "serde"
1804name = "serde"
1808−version = "1.0.163"
1805+version = "1.0.193"
1809source = "registry+https://github.com/rust-lang/crates.io-index"
1806source = "registry+https://github.com/rust-lang/crates.io-index"
1810−checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
1807+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
1811dependencies = [
1808dependencies = [
1812 "serde_derive",
1809 "serde_derive",
1813]
1810]
1814
1811
1815[[package]]
1812[[package]]
1816name = "serde_derive"
1813name = "serde_derive"
1817−version = "1.0.163"
1814+version = "1.0.193"
1818source = "registry+https://github.com/rust-lang/crates.io-index"
1815source = "registry+https://github.com/rust-lang/crates.io-index"
1819−checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
1816+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
1820dependencies = [
1817dependencies = [
1821 "proc-macro2",
1818 "proc-macro2",
1822 "quote",
1819 "quote",
1823− "syn 2.0.15",
1820+ "syn 2.0.38",
1824]
1821]
1825
1822
1826[[package]]
1823[[package]]
1827name = "serde_json"
1824name = "serde_json"
1828−version = "1.0.96"
1825+version = "1.0.108"
1829source = "registry+https://github.com/rust-lang/crates.io-index"
1826source = "registry+https://github.com/rust-lang/crates.io-index"
1830−checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
1827+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
1831dependencies = [
1828dependencies = [
1832 "itoa",
1829 "itoa",
1833 "ryu",
1830 "ryu",
@@ -1842,14 +1839,14 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
1842dependencies = [
1839dependencies = [
1843 "proc-macro2",
1840 "proc-macro2",
1844 "quote",
1841 "quote",
1845− "syn 2.0.15",
1842+ "syn 2.0.38",
1846]
1843]
1847
1844
1848[[package]]
1845[[package]]
1849name = "serde_spanned"
1846name = "serde_spanned"
1850−version = "0.6.1"
1847+version = "0.6.3"
1851source = "registry+https://github.com/rust-lang/crates.io-index"
1848source = "registry+https://github.com/rust-lang/crates.io-index"
1852−checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
1849+checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
1853dependencies = [
1850dependencies = [
1854 "serde",
1851 "serde",
1855]
1852]
@@ -1862,9 +1859,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
1862
1859
1863[[package]]
1860[[package]]
1864name = "signal-hook"
1861name = "signal-hook"
1865−version = "0.3.15"
1862+version = "0.3.17"
1866source = "registry+https://github.com/rust-lang/crates.io-index"
1863source = "registry+https://github.com/rust-lang/crates.io-index"
1867−checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
1864+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
1868dependencies = [
1865dependencies = [
1869 "libc",
1866 "libc",
1870 "signal-hook-registry",
1867 "signal-hook-registry",
@@ -1913,18 +1910,18 @@ dependencies = [
1913
1910
1914[[package]]
1911[[package]]
1915name = "slotmap"
1912name = "slotmap"
1916−version = "1.0.6"
1913+version = "1.0.7"
1917source = "registry+https://github.com/rust-lang/crates.io-index"
1914source = "registry+https://github.com/rust-lang/crates.io-index"
1918−checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
1915+checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
1919dependencies = [
1916dependencies = [
1920 "version_check",
1917 "version_check",
1921]
1918]
1922
1919
1923[[package]]
1920[[package]]
1924name = "smallvec"
1921name = "smallvec"
1925−version = "1.10.0"
1922+version = "1.11.2"
1926source = "registry+https://github.com/rust-lang/crates.io-index"
1923source = "registry+https://github.com/rust-lang/crates.io-index"
1927−checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
1924+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
1928
1925
1929[[package]]
1926[[package]]
1930name = "smartstring"
1927name = "smartstring"
@@ -1945,12 +1942,12 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
1945
1942
1946[[package]]
1943[[package]]
1947name = "socket2"
1944name = "socket2"
1948−version = "0.4.9"
1945+version = "0.5.5"
1949source = "registry+https://github.com/rust-lang/crates.io-index"
1946source = "registry+https://github.com/rust-lang/crates.io-index"
1950−checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
1947+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
1951dependencies = [
1948dependencies = [
1952 "libc",
1949 "libc",
1953− "winapi",
1950+ "windows-sys 0.48.0",
1954]
1951]
1955
1952
1956[[package]]
1953[[package]]
@@ -1959,12 +1956,6 @@ version = "1.1.0"
1959source = "registry+https://github.com/rust-lang/crates.io-index"
1956source = "registry+https://github.com/rust-lang/crates.io-index"
1960checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1957checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1961
1958
1962−[[package]]
1963−name = "str-buf"
1964−version = "1.0.6"
1965−source = "registry+https://github.com/rust-lang/crates.io-index"
1966−checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
1967−
1968[[package]]
1959[[package]]
1969name = "str_indices"
1960name = "str_indices"
1970version = "0.4.1"
1961version = "0.4.1"
@@ -1984,9 +1975,9 @@ dependencies = [
1984
1975
1985[[package]]
1976[[package]]
1986name = "syn"
1977name = "syn"
1987−version = "2.0.15"
1978+version = "2.0.38"
1988source = "registry+https://github.com/rust-lang/crates.io-index"
1979source = "registry+https://github.com/rust-lang/crates.io-index"
1989−checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
1980+checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
1990dependencies = [
1981dependencies = [
1991 "proc-macro2",
1982 "proc-macro2",
1992 "quote",
1983 "quote",
@@ -1995,15 +1986,15 @@ dependencies = [
1995
1986
1996[[package]]
1987[[package]]
1997name = "tempfile"
1988name = "tempfile"
1998−version = "3.5.0"
1989+version = "3.8.1"
1999source = "registry+https://github.com/rust-lang/crates.io-index"
1990source = "registry+https://github.com/rust-lang/crates.io-index"
2000−checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
1991+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
2001dependencies = [
1992dependencies = [
2002 "cfg-if",
1993 "cfg-if",
2003 "fastrand",
1994 "fastrand",
2004− "redox_syscall 0.3.5",
1995+ "redox_syscall 0.4.1",
2005 "rustix",
1996 "rustix",
2006− "windows-sys 0.45.0",
1997+ "windows-sys 0.48.0",
2007]
1998]
2008
1999
2009[[package]]
2000[[package]]
@@ -2037,32 +2028,22 @@ dependencies = [
2037
2028
2038[[package]]
2029[[package]]
2039name = "thiserror"
2030name = "thiserror"
2040−version = "1.0.40"
2031+version = "1.0.52"
2041source = "registry+https://github.com/rust-lang/crates.io-index"
2032source = "registry+https://github.com/rust-lang/crates.io-index"
2042−checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
2033+checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
2043dependencies = [
2034dependencies = [
2044 "thiserror-impl",
2035 "thiserror-impl",
2045]
2036]
2046
2037
2047[[package]]
2038[[package]]
2048name = "thiserror-impl"
2039name = "thiserror-impl"
2049−version = "1.0.40"
2040+version = "1.0.52"
2050source = "registry+https://github.com/rust-lang/crates.io-index"
2041source = "registry+https://github.com/rust-lang/crates.io-index"
2051−checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
2042+checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
2052dependencies = [
2043dependencies = [
2053 "proc-macro2",
2044 "proc-macro2",
2054 "quote",
2045 "quote",
2055− "syn 2.0.15",
2046+ "syn 2.0.38",
2056−]
2057−
2058−[[package]]
2059−name = "thread_local"
2060−version = "1.1.7"
2061−source = "registry+https://github.com/rust-lang/crates.io-index"
2062−checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
2063−dependencies = [
2064− "cfg-if",
2065− "once_cell",
2066]
2047]
2067
2048
2068[[package]]
2049[[package]]
@@ -2076,9 +2057,9 @@ dependencies = [
2076
2057
2077[[package]]
2058[[package]]
2078name = "time"
2059name = "time"
2079−version = "0.3.20"
2060+version = "0.3.23"
2080source = "registry+https://github.com/rust-lang/crates.io-index"
2061source = "registry+https://github.com/rust-lang/crates.io-index"
2081−checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
2062+checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
2082dependencies = [
2063dependencies = [
2083 "itoa",
2064 "itoa",
2084 "libc",
2065 "libc",
@@ -2090,15 +2071,15 @@ dependencies = [
2090
2071
2091[[package]]
2072[[package]]
2092name = "time-core"
2073name = "time-core"
2093−version = "0.1.0"
2074+version = "0.1.1"
2094source = "registry+https://github.com/rust-lang/crates.io-index"
2075source = "registry+https://github.com/rust-lang/crates.io-index"
2095−checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
2076+checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
2096
2077
2097[[package]]
2078[[package]]
2098name = "time-macros"
2079name = "time-macros"
2099−version = "0.2.8"
2080+version = "0.2.10"
2100source = "registry+https://github.com/rust-lang/crates.io-index"
2081source = "registry+https://github.com/rust-lang/crates.io-index"
2101−checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
2082+checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
2102dependencies = [
2083dependencies = [
2103 "time-core",
2084 "time-core",
2104]
2085]
@@ -2120,11 +2101,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
2120
2101
2121[[package]]
2102[[package]]
2122name = "tokio"
2103name = "tokio"
2123−version = "1.28.1"
2104+version = "1.35.1"
2124source = "registry+https://github.com/rust-lang/crates.io-index"
2105source = "registry+https://github.com/rust-lang/crates.io-index"
2125−checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105"
2106+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
2126dependencies = [
2107dependencies = [
2127− "autocfg",
2108+ "backtrace",
2128 "bytes",
2109 "bytes",
2129 "libc",
2110 "libc",
2130 "mio",
2111 "mio",
@@ -2139,13 +2120,13 @@ dependencies = [
2139
2120
2140[[package]]
2121[[package]]
2141name = "tokio-macros"
2122name = "tokio-macros"
2142−version = "2.1.0"
2123+version = "2.2.0"
2143source = "registry+https://github.com/rust-lang/crates.io-index"
2124source = "registry+https://github.com/rust-lang/crates.io-index"
2144−checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
2125+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
2145dependencies = [
2126dependencies = [
2146 "proc-macro2",
2127 "proc-macro2",
2147 "quote",
2128 "quote",
2148− "syn 2.0.15",
2129+ "syn 2.0.38",
2149]
2130]
2150
2131
2151[[package]]
2132[[package]]
@@ -2161,9 +2142,9 @@ dependencies = [
2161
2142
2162[[package]]
2143[[package]]
2163name = "toml"
2144name = "toml"
2164−version = "0.7.3"
2145+version = "0.7.6"
2165source = "registry+https://github.com/rust-lang/crates.io-index"
2146source = "registry+https://github.com/rust-lang/crates.io-index"
2166−checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
2147+checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
2167dependencies = [
2148dependencies = [
2168 "serde",
2149 "serde",
2169 "serde_spanned",
2150 "serde_spanned",
@@ -2173,31 +2154,30 @@ dependencies = [
2173
2154
2174[[package]]
2155[[package]]
2175name = "toml_datetime"
2156name = "toml_datetime"
2176−version = "0.6.1"
2157+version = "0.6.3"
2177source = "registry+https://github.com/rust-lang/crates.io-index"
2158source = "registry+https://github.com/rust-lang/crates.io-index"
2178−checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
2159+checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
2179dependencies = [
2160dependencies = [
2180 "serde",
2161 "serde",
2181]
2162]
2182
2163
2183[[package]]
2164[[package]]
2184name = "toml_edit"
2165name = "toml_edit"
2185−version = "0.19.8"
2166+version = "0.19.12"
2186source = "registry+https://github.com/rust-lang/crates.io-index"
2167source = "registry+https://github.com/rust-lang/crates.io-index"
2187−checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
2168+checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
2188dependencies = [
2169dependencies = [
2189 "indexmap",
2170 "indexmap",
2190 "serde",
2171 "serde",
2191 "serde_spanned",
2172 "serde_spanned",
2192 "toml_datetime",
2173 "toml_datetime",
2193− "winnow",
2174+ "winnow 0.4.6",
2194]
2175]
2195
2176
2196[[package]]
2177[[package]]
2197name = "tree-sitter"
2178name = "tree-sitter"
2198version = "0.20.10"
2179version = "0.20.10"
2199−source = "registry+https://github.com/rust-lang/crates.io-index"
2180+source = "git+https://github.com/tree-sitter/tree-sitter?rev=ab09ae20d640711174b8da8a654f6b3dec93da1a#ab09ae20d640711174b8da8a654f6b3dec93da1a"
2200−checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d"
2201dependencies = [
2181dependencies = [
2202 "cc",
2182 "cc",
2203 "regex",
2183 "regex",
@@ -2238,13 +2218,9 @@ checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
2238
2218
2239[[package]]
2219[[package]]
2240name = "unicode-linebreak"
2220name = "unicode-linebreak"
2241−version = "0.1.4"
2221+version = "0.1.5"
2242source = "registry+https://github.com/rust-lang/crates.io-index"
2222source = "registry+https://github.com/rust-lang/crates.io-index"
2243−checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
2223+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
2244−dependencies = [
2245− "hashbrown 0.12.3",
2246− "regex",
2247−]
2248
2224
2249[[package]]
2225[[package]]
2250name = "unicode-normalization"
2226name = "unicode-normalization"
@@ -2263,15 +2239,15 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
2263
2239
2264[[package]]
2240[[package]]
2265name = "unicode-width"
2241name = "unicode-width"
2266−version = "0.1.10"
2242+version = "0.1.11"
2267source = "registry+https://github.com/rust-lang/crates.io-index"
2243source = "registry+https://github.com/rust-lang/crates.io-index"
2268−checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
2244+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
2269
2245
2270[[package]]
2246[[package]]
2271name = "url"
2247name = "url"
2272−version = "2.3.1"
2248+version = "2.5.0"
2273source = "registry+https://github.com/rust-lang/crates.io-index"
2249source = "registry+https://github.com/rust-lang/crates.io-index"
2274−checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
2250+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
2275dependencies = [
2251dependencies = [
2276 "form_urlencoded",
2252 "form_urlencoded",
2277 "idna",
2253 "idna",
@@ -2287,9 +2263,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
2287
2263
2288[[package]]
2264[[package]]
2289name = "walkdir"
2265name = "walkdir"
2290−version = "2.3.3"
2266+version = "2.4.0"
2291source = "registry+https://github.com/rust-lang/crates.io-index"
2267source = "registry+https://github.com/rust-lang/crates.io-index"
2292−checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
2268+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
2293dependencies = [
2269dependencies = [
2294 "same-file",
2270 "same-file",
2295 "winapi-util",
2271 "winapi-util",
@@ -2357,13 +2333,15 @@ checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
2357
2333
2358[[package]]
2334[[package]]
2359name = "which"
2335name = "which"
2360−version = "4.4.0"
2336+version = "5.0.0"
2361source = "registry+https://github.com/rust-lang/crates.io-index"
2337source = "registry+https://github.com/rust-lang/crates.io-index"
2362−checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
2338+checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14"
2363dependencies = [
2339dependencies = [
2364 "either",
2340 "either",
2365− "libc",
2341+ "home",
2366 "once_cell",
2342 "once_cell",
2343+ "rustix",
2344+ "windows-sys 0.48.0",
2367]
2345]
2368
2346
2369[[package]]
2347[[package]]
@@ -2406,6 +2384,25 @@ dependencies = [
2406 "windows-targets 0.48.0",
2384 "windows-targets 0.48.0",
2407]
2385]
2408
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+
2409[[package]]
2406[[package]]
2410name = "windows-sys"
2407name = "windows-sys"
2411version = "0.45.0"
2408version = "0.45.0"
@@ -2424,6 +2421,15 @@ dependencies = [
2424 "windows-targets 0.48.0",
2421 "windows-targets 0.48.0",
2425]
2422]
2426
2423
2424+[[package]]
2425+name = "windows-sys"
2426+version = "0.52.0"
2427+source = "registry+https://github.com/rust-lang/crates.io-index"
2428+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
2429+dependencies = [
2430+ "windows-targets 0.52.0",
2431+]
2432+
2427[[package]]
2433[[package]]
2428name = "windows-targets"
2434name = "windows-targets"
2429version = "0.42.2"
2435version = "0.42.2"
@@ -2454,6 +2460,21 @@ dependencies = [
2454 "windows_x86_64_msvc 0.48.0",
2460 "windows_x86_64_msvc 0.48.0",
2455]
2461]
2456
2462
2463+[[package]]
2464+name = "windows-targets"
2465+version = "0.52.0"
2466+source = "registry+https://github.com/rust-lang/crates.io-index"
2467+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
2468+dependencies = [
2469+ "windows_aarch64_gnullvm 0.52.0",
2470+ "windows_aarch64_msvc 0.52.0",
2471+ "windows_i686_gnu 0.52.0",
2472+ "windows_i686_msvc 0.52.0",
2473+ "windows_x86_64_gnu 0.52.0",
2474+ "windows_x86_64_gnullvm 0.52.0",
2475+ "windows_x86_64_msvc 0.52.0",
2476+]
2477+
2457[[package]]
2478[[package]]
2458name = "windows_aarch64_gnullvm"
2479name = "windows_aarch64_gnullvm"
2459version = "0.42.2"
2480version = "0.42.2"
@@ -2466,6 +2487,12 @@ version = "0.48.0"
2466source = "registry+https://github.com/rust-lang/crates.io-index"
2487source = "registry+https://github.com/rust-lang/crates.io-index"
2467checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
2488checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
2468
2489
2490+[[package]]
2491+name = "windows_aarch64_gnullvm"
2492+version = "0.52.0"
2493+source = "registry+https://github.com/rust-lang/crates.io-index"
2494+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
2495+
2469[[package]]
2496[[package]]
2470name = "windows_aarch64_msvc"
2497name = "windows_aarch64_msvc"
2471version = "0.42.2"
2498version = "0.42.2"
@@ -2478,6 +2505,12 @@ version = "0.48.0"
2478source = "registry+https://github.com/rust-lang/crates.io-index"
2505source = "registry+https://github.com/rust-lang/crates.io-index"
2479checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
2506checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
2480
2507
2508+[[package]]
2509+name = "windows_aarch64_msvc"
2510+version = "0.52.0"
2511+source = "registry+https://github.com/rust-lang/crates.io-index"
2512+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
2513+
2481[[package]]
2514[[package]]
2482name = "windows_i686_gnu"
2515name = "windows_i686_gnu"
2483version = "0.42.2"
2516version = "0.42.2"
@@ -2490,6 +2523,12 @@ version = "0.48.0"
2490source = "registry+https://github.com/rust-lang/crates.io-index"
2523source = "registry+https://github.com/rust-lang/crates.io-index"
2491checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
2524checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
2492
2525
2526+[[package]]
2527+name = "windows_i686_gnu"
2528+version = "0.52.0"
2529+source = "registry+https://github.com/rust-lang/crates.io-index"
2530+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
2531+
2493[[package]]
2532[[package]]
2494name = "windows_i686_msvc"
2533name = "windows_i686_msvc"
2495version = "0.42.2"
2534version = "0.42.2"
@@ -2502,6 +2541,12 @@ version = "0.48.0"
2502source = "registry+https://github.com/rust-lang/crates.io-index"
2541source = "registry+https://github.com/rust-lang/crates.io-index"
2503checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
2542checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
2504
2543
2544+[[package]]
2545+name = "windows_i686_msvc"
2546+version = "0.52.0"
2547+source = "registry+https://github.com/rust-lang/crates.io-index"
2548+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
2549+
2505[[package]]
2550[[package]]
2506name = "windows_x86_64_gnu"
2551name = "windows_x86_64_gnu"
2507version = "0.42.2"
2552version = "0.42.2"
@@ -2514,6 +2559,12 @@ version = "0.48.0"
2514source = "registry+https://github.com/rust-lang/crates.io-index"
2559source = "registry+https://github.com/rust-lang/crates.io-index"
2515checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
2560checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
2516
2561
2562+[[package]]
2563+name = "windows_x86_64_gnu"
2564+version = "0.52.0"
2565+source = "registry+https://github.com/rust-lang/crates.io-index"
2566+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
2567+
2517[[package]]
2568[[package]]
2518name = "windows_x86_64_gnullvm"
2569name = "windows_x86_64_gnullvm"
2519version = "0.42.2"
2570version = "0.42.2"
@@ -2526,6 +2577,12 @@ version = "0.48.0"
2526source = "registry+https://github.com/rust-lang/crates.io-index"
2577source = "registry+https://github.com/rust-lang/crates.io-index"
2527checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
2578checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
2528
2579
2580+[[package]]
2581+name = "windows_x86_64_gnullvm"
2582+version = "0.52.0"
2583+source = "registry+https://github.com/rust-lang/crates.io-index"
2584+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
2585+
2529[[package]]
2586[[package]]
2530name = "windows_x86_64_msvc"
2587name = "windows_x86_64_msvc"
2531version = "0.42.2"
2588version = "0.42.2"
@@ -2538,18 +2595,33 @@ version = "0.48.0"
2538source = "registry+https://github.com/rust-lang/crates.io-index"
2595source = "registry+https://github.com/rust-lang/crates.io-index"
2539checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
2596checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
2540
2597
2598+[[package]]
2599+name = "windows_x86_64_msvc"
2600+version = "0.52.0"
2601+source = "registry+https://github.com/rust-lang/crates.io-index"
2602+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
2603+
2541[[package]]
2604[[package]]
2542name = "winnow"
2605name = "winnow"
2543−version = "0.4.1"
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"
2544source = "registry+https://github.com/rust-lang/crates.io-index"
2616source = "registry+https://github.com/rust-lang/crates.io-index"
2545−checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
2617+checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
2546dependencies = [
2618dependencies = [
2547 "memchr",
2619 "memchr",
2548]
2620]
2549
2621
2550[[package]]
2622[[package]]
2551name = "xtask"
2623name = "xtask"
2552−version = "0.6.0"
2624+version = "23.10.0"
2553dependencies = [
2625dependencies = [
2554 "helix-core",
2626 "helix-core",
2555 "helix-loader",
2627 "helix-loader",
@@ -2557,3 +2629,23 @@ dependencies = [
2557 "helix-view",
2629 "helix-view",
2558 "toml",
2630 "toml",
2559]
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+16
@@ -1,10 +1,12 @@
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",
@@ -32,3 +34,17 @@ inherits = "test"
32package.helix-core.opt-level = 2
34package.helix-core.opt-level = 2
33package.helix-tui.opt-level = 2
35package.helix-tui.opt-level = 2
34package.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+2−2
@@ -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.
@@ -61,4 +61,4 @@ Discuss the project on the community [Matrix Space](https://matrix.to/#/#helix-c
61
61
62# Credits
62# Credits
63
63
64−Thanks to [@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−23.05
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/configuration.md+43−13
@@ -51,7 +51,8 @@ Its settings will be merged with the configuration directory `config.toml` and t
51| `auto-completion` | Enable automatic pop up of auto-completion | `true` |
51| `auto-completion` | Enable automatic pop up of auto-completion | `true` |
52| `auto-format` | Enable automatic formatting on save | `true` |
52| `auto-format` | Enable automatic formatting on save | `true` |
53| `auto-save` | Enable automatic saving on the focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal | `false` |
53| `auto-save` | Enable automatic saving on the focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal | `false` |
54−| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant | `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` |
55| `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` |
56| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
57| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
57| `auto-info` | Whether to display info boxes | `true` |
58| `auto-info` | Whether to display info boxes | `true` |
@@ -62,6 +63,10 @@ Its settings will be merged with the configuration directory `config.toml` and t
62| `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` |
63| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
64| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
64| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
65| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
66+| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
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`
65
70
66### `[editor.statusline]` Section
71### `[editor.statusline]` Section
67
72
@@ -87,9 +92,9 @@ The `[editor.statusline]` key takes the following sub-keys:
87
92
88| Key | Description | Default |
93| Key | Description | Default |
89| --- | --- | --- |
94| --- | --- | --- |
90−| `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"]` |
91| `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 | `[]` |
92−| `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"]` |
93| `separator` | The character used to separate elements in the statusline | `"│"` |
98| `separator` | The character used to separate elements in the statusline | `"│"` |
94| `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"` |
95| `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"` |
@@ -106,6 +111,7 @@ The following statusline elements can be configured:
106| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
111| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
107| `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 |
108| `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 |
109| `total-line-numbers` | The total line numbers of the opened file |
115| `total-line-numbers` | The total line numbers of the opened file |
110| `file-type` | The type of the opened file |
116| `file-type` | The type of the opened file |
111| `diagnostics` | The number of warnings and/or errors |
117| `diagnostics` | The number of warnings and/or errors |
@@ -117,6 +123,7 @@ The following statusline elements can be configured:
117| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
123| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
118| `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) |
119| `version-control` | The current branch name or detached commit hash of the opened workspace |
125| `version-control` | The current branch name or detached commit hash of the opened workspace |
126+| `register` | The current selected register |
120
127
121### `[editor.lsp]` Section
128### `[editor.lsp]` Section
122
129
@@ -131,8 +138,8 @@ The following statusline elements can be configured:
131| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
138| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
132
139
133[^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.
134−[^2]: You may also have to activate them in the LSP config for them to appear, not just in Helix.
141+
135− Inlay hints in Helix are still being improved on and may be a little bit laggy/janky under some circumstances, please report any bugs you see so we can fix them!
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!
136
143
137### `[editor.cursor-shape]` Section
144### `[editor.cursor-shape]` Section
138
145
@@ -161,16 +168,31 @@ All git related options are only enabled in a git repository.
161
168
162| Key | Description | Default |
169| Key | Description | Default |
163|--|--|---------|
170|--|--|---------|
164−|`hidden` | Enables ignoring hidden files | true
171+|`hidden` | Enables ignoring hidden files | `true`
165−|`follow-symlinks` | Follow symlinks instead of ignoring them | true
172+|`follow-symlinks` | Follow symlinks instead of ignoring them | `true`
166−|`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`
167−|`parents` | Enables reading ignore files from parent directories | true
174+|`parents` | Enables reading ignore files from parent directories | `true`
168−|`ignore` | Enables reading `.ignore` files | true
175+|`ignore` | Enables reading `.ignore` files | `true`
169−|`git-ignore` | Enables reading `.gitignore` files | true
176+|`git-ignore` | Enables reading `.gitignore` files | `true`
170−|`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`
171−|`git-exclude` | Enables reading `.git/info/exclude` files | true
178+|`git-exclude` | Enables reading `.git/info/exclude` files | `true`
172|`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`.
173
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+```
195+
174### `[editor.auto-pairs]` Section
196### `[editor.auto-pairs]` Section
175
197
176Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a
198Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a
@@ -344,3 +366,11 @@ max-wrap = 25 # increase value to reduce forced mid-word wrapping
344max-indent-retain = 0
366max-indent-retain = 0
345wrap-indicator = "" # set wrap-indicator to "" to hide it
367wrap-indicator = "" # set wrap-indicator to "" to hide it
346```
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/generated/lang-support.md+43−18
@@ -1,21 +1,23 @@
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−| cabal | | | | |
14+| cabal | | | | `haskell-language-server-wrapper` |
13−| cairo | ✓ | | | |
15+| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
14| capnp | ✓ | | ✓ | |
16| capnp | ✓ | | ✓ | |
15| clojure | ✓ | | | `clojure-lsp` |
17| clojure | ✓ | | | `clojure-lsp` |
16| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
18| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
17| comment | ✓ | | | |
19| comment | ✓ | | | |
18−| common-lisp | ✓ | | | `cl-lsp` |
20+| common-lisp | ✓ | | ✓ | `cl-lsp` |
19| cpon | ✓ | | ✓ | |
21| cpon | ✓ | | ✓ | |
20| cpp | ✓ | ✓ | ✓ | `clangd` |
22| cpp | ✓ | ✓ | ✓ | `clangd` |
21| crystal | ✓ | ✓ | | `crystalline` |
23| crystal | ✓ | ✓ | | `crystalline` |
@@ -23,6 +25,7 @@
23| cue | ✓ | | | `cuelsp` |
25| cue | ✓ | | | `cuelsp` |
24| d | ✓ | ✓ | ✓ | `serve-d` |
26| d | ✓ | ✓ | ✓ | `serve-d` |
25| dart | ✓ | | ✓ | `dart` |
27| dart | ✓ | | ✓ | `dart` |
28+| dbml | ✓ | | | |
26| devicetree | ✓ | | | |
29| devicetree | ✓ | | | |
27| dhall | ✓ | ✓ | | `dhall-lsp-server` |
30| dhall | ✓ | ✓ | | `dhall-lsp-server` |
28| diff | ✓ | | | |
31| diff | ✓ | | | |
@@ -40,8 +43,12 @@
40| erlang | ✓ | ✓ | | `erlang_ls` |
43| erlang | ✓ | ✓ | | `erlang_ls` |
41| esdl | ✓ | | | |
44| esdl | ✓ | | | |
42| fish | ✓ | ✓ | ✓ | |
45| fish | ✓ | ✓ | ✓ | |
46+| forth | ✓ | | | `forth-lsp` |
43| fortran | ✓ | | ✓ | `fortls` |
47| fortran | ✓ | | ✓ | `fortls` |
48+| fsharp | ✓ | | | `fsautocomplete` |
49+| gas | ✓ | ✓ | | |
44| gdscript | ✓ | ✓ | ✓ | |
50| gdscript | ✓ | ✓ | ✓ | |
51+| gemini | ✓ | | | |
45| git-attributes | ✓ | | | |
52| git-attributes | ✓ | | | |
46| git-commit | ✓ | ✓ | | |
53| git-commit | ✓ | ✓ | | |
47| git-config | ✓ | | | |
54| git-config | ✓ | | | |
@@ -49,14 +56,16 @@
49| git-rebase | ✓ | | | |
56| git-rebase | ✓ | | | |
50| gleam | ✓ | ✓ | | `gleam` |
57| gleam | ✓ | ✓ | | `gleam` |
51| glsl | ✓ | ✓ | ✓ | |
58| glsl | ✓ | ✓ | ✓ | |
52−| go | ✓ | ✓ | ✓ | `gopls` |
59+| gn | ✓ | | | |
60+| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
53| godot-resource | ✓ | | | |
61| godot-resource | ✓ | | | |
54| gomod | ✓ | | | `gopls` |
62| gomod | ✓ | | | `gopls` |
55| gotmpl | ✓ | | | `gopls` |
63| gotmpl | ✓ | | | `gopls` |
56| gowork | ✓ | | | `gopls` |
64| gowork | ✓ | | | `gopls` |
57−| graphql | ✓ | | | |
65+| graphql | ✓ | | | `graphql-lsp` |
58| hare | ✓ | | | |
66| hare | ✓ | | | |
59| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
67| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
68+| haskell-persistent | ✓ | | | |
60| hcl | ✓ | | ✓ | `terraform-ls` |
69| hcl | ✓ | | ✓ | `terraform-ls` |
61| heex | ✓ | ✓ | | `elixir-ls` |
70| heex | ✓ | ✓ | | `elixir-ls` |
62| hosts | ✓ | | | |
71| hosts | ✓ | | | |
@@ -65,15 +74,18 @@
65| idris | | | | `idris2-lsp` |
74| idris | | | | `idris2-lsp` |
66| iex | ✓ | | | |
75| iex | ✓ | | | |
67| ini | ✓ | | | |
76| ini | ✓ | | | |
77+| janet | ✓ | | | |
68−| java | ✓ | ✓ | | `jdtls` |
78+| java | ✓ | ✓ | ✓ | `jdtls` |
69| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
79| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
80+| jinja | ✓ | | | |
70| jsdoc | ✓ | | | |
81| jsdoc | ✓ | | | |
71| json | ✓ | | ✓ | `vscode-json-language-server` |
82| json | ✓ | | ✓ | `vscode-json-language-server` |
83+| json5 | ✓ | | | |
72| jsonnet | ✓ | | | `jsonnet-language-server` |
84| jsonnet | ✓ | | | `jsonnet-language-server` |
73| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
85| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
74| julia | ✓ | ✓ | ✓ | `julia` |
86| julia | ✓ | ✓ | ✓ | `julia` |
75| just | ✓ | ✓ | ✓ | |
87| just | ✓ | ✓ | ✓ | |
76−| kdl | ✓ | | | |
88+| kdl | ✓ | ✓ | ✓ | |
77| kotlin | ✓ | | | `kotlin-language-server` |
89| kotlin | ✓ | | | `kotlin-language-server` |
78| latex | ✓ | ✓ | | `texlab` |
90| latex | ✓ | ✓ | | `texlab` |
79| lean | ✓ | | | `lean` |
91| lean | ✓ | | | `lean` |
@@ -81,12 +93,14 @@
81| llvm | ✓ | ✓ | ✓ | |
93| llvm | ✓ | ✓ | ✓ | |
82| llvm-mir | ✓ | ✓ | ✓ | |
94| llvm-mir | ✓ | ✓ | ✓ | |
83| llvm-mir-yaml | ✓ | | ✓ | |
95| llvm-mir-yaml | ✓ | | ✓ | |
96+| log | ✓ | | | |
97+| lpf | ✓ | | | |
84| lua | ✓ | ✓ | ✓ | `lua-language-server` |
98| lua | ✓ | ✓ | ✓ | `lua-language-server` |
85| make | ✓ | | | |
99| make | ✓ | | | |
86| markdoc | ✓ | | | `markdoc-ls` |
100| markdoc | ✓ | | | `markdoc-ls` |
87| markdown | ✓ | | | `marksman` |
101| markdown | ✓ | | | `marksman` |
88| markdown.inline | ✓ | | | |
102| markdown.inline | ✓ | | | |
89−| matlab | ✓ | | | |
103+| matlab | ✓ | ✓ | ✓ | |
90| mermaid | ✓ | | | |
104| mermaid | ✓ | | | |
91| meson | ✓ | | ✓ | |
105| meson | ✓ | | ✓ | |
92| mint | | | | `mint` |
106| mint | | | | `mint` |
@@ -95,7 +109,8 @@
95| nickel | ✓ | | ✓ | `nls` |
109| nickel | ✓ | | ✓ | `nls` |
96| nim | ✓ | ✓ | ✓ | `nimlangserver` |
110| nim | ✓ | ✓ | ✓ | `nimlangserver` |
97| nix | ✓ | | | `nil` |
111| nix | ✓ | | | `nil` |
98−| nu | ✓ | | | |
112+| nu | ✓ | | | `nu` |
113+| nunjucks | ✓ | | | |
99| ocaml | ✓ | | ✓ | `ocamllsp` |
114| ocaml | ✓ | | ✓ | `ocamllsp` |
100| ocaml-interface | ✓ | | | `ocamllsp` |
115| ocaml-interface | ✓ | | | `ocamllsp` |
101| odin | ✓ | | ✓ | `ols` |
116| odin | ✓ | | ✓ | `ols` |
@@ -108,16 +123,17 @@
108| perl | ✓ | ✓ | ✓ | `perlnavigator` |
123| perl | ✓ | ✓ | ✓ | `perlnavigator` |
109| php | ✓ | ✓ | ✓ | `intelephense` |
124| php | ✓ | ✓ | ✓ | `intelephense` |
110| po | ✓ | ✓ | | |
125| po | ✓ | ✓ | | |
126+| pod | ✓ | | | |
111| ponylang | ✓ | ✓ | ✓ | |
127| ponylang | ✓ | ✓ | ✓ | |
112| prisma | ✓ | | | `prisma-language-server` |
128| prisma | ✓ | | | `prisma-language-server` |
113| prolog | | | | `swipl` |
129| prolog | | | | `swipl` |
114−| protobuf | ✓ | | ✓ | |
130+| protobuf | ✓ | | ✓ | `bufls`, `pb` |
115| prql | ✓ | | | |
131| prql | ✓ | | | |
116−| purescript | ✓ | | | `purescript-language-server` |
132+| purescript | ✓ | ✓ | | `purescript-language-server` |
117| python | ✓ | ✓ | ✓ | `pylsp` |
133| python | ✓ | ✓ | ✓ | `pylsp` |
118| qml | ✓ | | ✓ | `qmlls` |
134| qml | ✓ | | ✓ | `qmlls` |
119| r | ✓ | | | `R` |
135| r | ✓ | | | `R` |
120−| racket | ✓ | | | `racket` |
136+| racket | ✓ | | ✓ | `racket` |
121| regex | ✓ | | | |
137| regex | ✓ | | | |
122| rego | ✓ | | | `regols` |
138| rego | ✓ | | | `regols` |
123| rescript | ✓ | ✓ | | `rescript-language-server` |
139| rescript | ✓ | ✓ | | `rescript-language-server` |
@@ -129,40 +145,49 @@
129| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
145| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
130| sage | ✓ | ✓ | | |
146| sage | ✓ | ✓ | | |
131| scala | ✓ | | ✓ | `metals` |
147| scala | ✓ | | ✓ | `metals` |
132−| scheme | ✓ | | | |
148+| scheme | ✓ | | ✓ | |
133| scss | ✓ | | | `vscode-css-language-server` |
149| scss | ✓ | | | `vscode-css-language-server` |
134| slint | ✓ | | ✓ | `slint-lsp` |
150| slint | ✓ | | ✓ | `slint-lsp` |
151+| smali | ✓ | | ✓ | |
135| smithy | ✓ | | | `cs` |
152| smithy | ✓ | | | `cs` |
136| sml | ✓ | | | |
153| sml | ✓ | | | |
137| solidity | ✓ | | | `solc` |
154| solidity | ✓ | | | `solc` |
138| sql | ✓ | | | |
155| sql | ✓ | | | |
139| sshclientconfig | ✓ | | | |
156| sshclientconfig | ✓ | | | |
140| starlark | ✓ | ✓ | | |
157| starlark | ✓ | ✓ | | |
158+| strace | ✓ | | | |
141−| svelte | ✓ | | | `svelteserver` |
159+| svelte | ✓ | | ✓ | `svelteserver` |
142| sway | ✓ | ✓ | ✓ | `forc` |
160| sway | ✓ | ✓ | ✓ | `forc` |
143| swift | ✓ | | | `sourcekit-lsp` |
161| swift | ✓ | | | `sourcekit-lsp` |
162+| t32 | ✓ | | | |
144| tablegen | ✓ | ✓ | ✓ | |
163| tablegen | ✓ | ✓ | ✓ | |
145| task | ✓ | | | |
164| task | ✓ | | | |
165+| templ | ✓ | | | `templ` |
146| tfvars | ✓ | | ✓ | `terraform-ls` |
166| tfvars | ✓ | | ✓ | `terraform-ls` |
167+| todotxt | ✓ | | | |
147| toml | ✓ | | | `taplo` |
168| toml | ✓ | | | `taplo` |
148| tsq | ✓ | | | |
169| tsq | ✓ | | | |
149| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
170| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
150| twig | ✓ | | | |
171| twig | ✓ | | | |
151| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
172| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
173+| typst | ✓ | | | `typst-lsp` |
152| ungrammar | ✓ | | | |
174| ungrammar | ✓ | | | |
175+| unison | ✓ | | | |
153| uxntal | ✓ | | | |
176| uxntal | ✓ | | | |
154−| v | ✓ | ✓ | ✓ | `v` |
177+| v | ✓ | ✓ | ✓ | `v-analyzer` |
155| vala | ✓ | | | `vala-language-server` |
178| vala | ✓ | | | `vala-language-server` |
156| verilog | ✓ | ✓ | | `svlangserver` |
179| verilog | ✓ | ✓ | | `svlangserver` |
157| vhdl | ✓ | | | `vhdl_ls` |
180| vhdl | ✓ | | | `vhdl_ls` |
158| vhs | ✓ | | | |
181| vhs | ✓ | | | |
159−| vue | ✓ | | | `vls` |
182+| vue | ✓ | | | `vue-language-server` |
160| wast | ✓ | | | |
183| wast | ✓ | | | |
161| wat | ✓ | | | |
184| wat | ✓ | | | |
185+| webc | ✓ | | | |
162| wgsl | ✓ | | | `wgsl_analyzer` |
186| wgsl | ✓ | | | `wgsl_analyzer` |
163| wit | ✓ | | ✓ | |
187| wit | ✓ | | ✓ | |
188+| wren | ✓ | ✓ | ✓ | |
164| xit | ✓ | | | |
189| xit | ✓ | | | |
165| xml | ✓ | | ✓ | |
190| xml | ✓ | | ✓ | |
166−| yaml | ✓ | | ✓ | `yaml-language-server` |
191+| yaml | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
167| yuck | ✓ | | | |
192| yuck | ✓ | | | |
168| zig | ✓ | ✓ | ✓ | `zls` |
193| zig | ✓ | ✓ | ✓ | `zls` |
book/src/generated/typable-cmd.md+8−3
@@ -17,13 +17,14 @@
17| `:write-buffer-close!`, `:wbc!` | Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt) |
17| `:write-buffer-close!`, `:wbc!` | Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt) |
18| `:new`, `:n` | Create a new scratch buffer. |
18| `:new`, `:n` | Create a new scratch buffer. |
19| `:format`, `:fmt` | Format the file using the LSP formatter. |
19| `:format`, `:fmt` | Format the file using the LSP formatter. |
20−| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
20+| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.) |
21| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
21| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
22| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
22| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
23| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
23| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
24| `: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) |
25| `: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) |
26| `: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. |
27| `: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. |
28| `: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). |
29| `:quit-all`, `:qa` | Close all views. |
30| `:quit-all`, `:qa` | Close all views. |
@@ -31,6 +32,7 @@
31| `: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). |
32| `: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). |
33| `: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. |
34| `:clipboard-yank` | Yank main selection into system clipboard. |
36| `:clipboard-yank` | Yank main selection into system clipboard. |
35| `: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. |
36| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
38| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
@@ -46,13 +48,14 @@
46| `:show-directory`, `:pwd` | Show the current working directory. |
48| `:show-directory`, `:pwd` | Show the current working directory. |
47| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
49| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
48| `: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. |
49−| `:reload` | Discard changes and reload from the source file. |
51+| `:reload`, `:rl` | Discard changes and reload from the source file. |
50−| `: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. |
51| `:update`, `:u` | Write changes only if the file has been modified. |
53| `:update`, `:u` | Write changes only if the file has been modified. |
52| `:lsp-workspace-command` | Open workspace command picker |
54| `:lsp-workspace-command` | Open workspace command picker |
53| `:lsp-restart` | Restarts the language servers used by the current doc |
55| `:lsp-restart` | Restarts the language servers used by the current doc |
54| `:lsp-stop` | Stops the language servers that are used by the current doc |
56| `:lsp-stop` | Stops the language servers that are used by the current doc |
55| `: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. |
56| `: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. |
57| `: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. |
58| `:debug-eval` | Evaluate expression in current debug context. |
61| `:debug-eval` | Evaluate expression in current debug context. |
@@ -81,3 +84,5 @@
81| `:run-shell-command`, `:sh` | Run a shell command |
84| `:run-shell-command`, `:sh` | Run a shell command |
82| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
85| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
83| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
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/adding_languages.md+2
@@ -36,6 +36,7 @@ below.
363. Refer to the
363. Refer to the
37 [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
37 [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
38 for more information on writing queries.
38 for more information on writing queries.
39+4. A list of highlight captures can be found [on the themes page](https://docs.helix-editor.com/themes.html#scopes).
39
40
40> 💡 In Helix, the first matching query takes precedence when evaluating
41> 💡 In Helix, the first matching query takes precedence when evaluating
41> queries, which is different from other editors such as Neovim where the last
42> queries, which is different from other editors such as Neovim where the last
@@ -51,3 +52,4 @@ below.
51 grammars.
52 grammars.
52- If a parser is causing a segfault, or you want to remove it, make sure to
53- If a parser is causing a segfault, or you want to remove it, make sure to
53 remove the compiled parser located at `runtime/grammars/<name>.so`.
54 remove the compiled parser located at `runtime/grammars/<name>.so`.
55+- If you are attempting to add queries and Helix is unable to locate them, ensure that the environment variable `HELIX_RUNTIME` is set to the location of the `runtime` folder you're developing in.
book/src/guides/indent.md+319−43
@@ -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.md+1−1
@@ -1,7 +1,7 @@
1# Adding Injection Queries
1# Adding Injection Queries
2
2
3Writing language injection queries allows one to highlight a specific node as a different language.
3Writing language injection queries allows one to highlight a specific node as a different language.
4−In addition to the [standard](upstream-docs) language injection options used by tree-sitter, there
4+In addition to the [standard][upstream-docs] language injection options used by tree-sitter, there
5are a few Helix specific extensions that allow for more control.
5are a few Helix specific extensions that allow for more control.
6
6
7And example of a simple query that would highlight all strings as bash in Nix:
7And example of a simple query that would highlight all strings as bash in Nix:
book/src/install.md+69−23
@@ -6,12 +6,14 @@
6- [Linux](#linux)
6- [Linux](#linux)
7 - [Ubuntu](#ubuntu)
7 - [Ubuntu](#ubuntu)
8 - [Fedora/RHEL](#fedorarhel)
8 - [Fedora/RHEL](#fedorarhel)
9− - [Arch Linux community](#arch-linux-community)
9+ - [Arch Linux extra](#arch-linux-extra)
10 - [NixOS](#nixos)
10 - [NixOS](#nixos)
11 - [Flatpak](#flatpak)
11 - [Flatpak](#flatpak)
12+ - [Snap](#snap)
12 - [AppImage](#appimage)
13 - [AppImage](#appimage)
13- [macOS](#macos)
14- [macOS](#macos)
14 - [Homebrew Core](#homebrew-core)
15 - [Homebrew Core](#homebrew-core)
16+ - [MacPorts](#macports)
15- [Windows](#windows)
17- [Windows](#windows)
16 - [Winget](#winget)
18 - [Winget](#winget)
17 - [Scoop](#scoop)
19 - [Scoop](#scoop)
@@ -63,16 +65,13 @@ sudo apt install helix
63
65
64### Fedora/RHEL
66### Fedora/RHEL
65
67
66−Enable the `COPR` repository for Helix:
67−
68```sh
68```sh
69−sudo dnf copr enable varlad/helix
70sudo dnf install helix
69sudo dnf install helix
71```
70```
72
71
73−### Arch Linux community
72+### Arch Linux extra
74
73
75−Releases are available in the `community` repository:
74+Releases are available in the `extra` repository:
76
75
77```sh
76```sh
78sudo pacman -S helix
77sudo pacman -S helix
@@ -82,7 +81,10 @@ in the AUR, which builds the master branch.
82
81
83### NixOS
82### NixOS
84
83
84+Helix is available in [nixpkgs](https://github.com/nixos/nixpkgs) through the `helix` attribute,
85+the unstable channel usually carries the latest release.
86+
85−Helix is available as a [flake](https://nixos.wiki/wiki/Flakes) in the project
87+Helix is also available as a [flake](https://nixos.wiki/wiki/Flakes) in the project
86root. Use `nix develop` to spin up a reproducible development shell. Outputs are
88root. Use `nix develop` to spin up a reproducible development shell. Outputs are
87cached for each push to master using [Cachix](https://www.cachix.org/). The
89cached for each push to master using [Cachix](https://www.cachix.org/). The
88flake is configured to automatically make use of this cache assuming the user
90flake is configured to automatically make use of this cache assuming the user
@@ -101,6 +103,16 @@ flatpak install flathub com.helix_editor.Helix
101flatpak run com.helix_editor.Helix
103flatpak run com.helix_editor.Helix
102```
104```
103
105
106+### Snap
107+
108+Helix is available on [Snapcraft](https://snapcraft.io/helix) and can be installed with:
109+
110+```sh
111+snap install --classic helix
112+```
113+
114+This will install Helix as both `/snap/bin/helix` and `/snap/bin/hx`, so make sure `/snap/bin` is in your `PATH`.
115+
104### AppImage
116### AppImage
105
117
106Install Helix using the Linux [AppImage](https://appimage.org/) format.
118Install Helix using the Linux [AppImage](https://appimage.org/) format.
@@ -119,6 +131,12 @@ chmod +x helix-*.AppImage # change permission for executable mode
119brew install helix
131brew install helix
120```
132```
121
133
134+### MacPorts
135+
136+```sh
137+port install helix
138+```
139+
122## Windows
140## Windows
123
141
124Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
142Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
@@ -156,9 +174,13 @@ pacman -S mingw-w64-ucrt-x86_64-helix
156
174
157Requirements:
175Requirements:
158
176
177+Clone the Helix GitHub repository into a directory of your choice. The
178+examples in this documentation assume installation into either `~/src/` on
179+Linux and macOS, or `%userprofile%\src\` on Windows.
180+
159- The [Rust toolchain](https://www.rust-lang.org/tools/install)
181- The [Rust toolchain](https://www.rust-lang.org/tools/install)
160- The [Git version control system](https://git-scm.com/)
182- The [Git version control system](https://git-scm.com/)
161−- A c++14 compatible compiler to build the tree-sitter grammars, for example GCC or Clang
183+- A C++14 compatible compiler to build the tree-sitter grammars, for example GCC or Clang
162
184
163If 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:
185If you are using the `musl-libc` standard library instead of `glibc` the following environment variable must be set during the build to ensure tree-sitter grammars can be loaded correctly:
164
186
@@ -168,19 +190,19 @@ RUSTFLAGS="-C target-feature=-crt-static"
168
190
1691. Clone the repository:
1911. Clone the repository:
170
192
171−```sh
193+ ```sh
172−git clone https://github.com/helix-editor/helix
194+ git clone https://github.com/helix-editor/helix
173−cd helix
195+ cd helix
174−```
196+ ```
175
197
1762. Compile from source:
1982. Compile from source:
177
199
178−```sh
200+ ```sh
179−cargo install --path helix-term --locked
201+ cargo install --path helix-term --locked
180−```
202+ ```
181
203
182−This command will create the `hx` executable and construct the tree-sitter
204+ This command will create the `hx` executable and construct the tree-sitter
183−grammars in the local `runtime` folder.
205+ grammars in the local `runtime` folder.
184
206
185> 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch
207> 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch
186> grammars with `hx --grammar fetch` and compile them with
208> grammars with `hx --grammar fetch` and compile them with
@@ -192,18 +214,22 @@ grammars in the local `runtime` folder.
192
214
193#### Linux and macOS
215#### Linux and macOS
194
216
217+The **runtime** directory is one below the Helix source, so either set a
195−Either set the `HELIX_RUNTIME` environment variable to point to the runtime files and add it to your `~/.bashrc` or equivalent:
218+`HELIX_RUNTIME` environment variable to point to that directory and add it to
219+your `~/.bashrc` or equivalent:
196
220
197```sh
221```sh
198−HELIX_RUNTIME=/home/user-name/src/helix/runtime
222+HELIX_RUNTIME=~/src/helix/runtime
199```
223```
200
224
201−Or, create a symlink in `~/.config/helix` that links to the source code directory:
225+Or, create a symbolic link:
202
226
203```sh
227```sh
204−ln -s $PWD/runtime ~/.config/helix/runtime
228+ln -Ts $PWD/runtime ~/.config/helix/runtime
205```
229```
206
230
231+If the above command fails to create a symbolic link because the file exists either move `~/.config/helix/runtime` to a new location or delete it, then run the symlink command above again.
232+
207#### Windows
233#### Windows
208
234
209Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
235Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
@@ -235,12 +261,32 @@ following order:
2351. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
2611. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
236 developing and testing helix only).
262 developing and testing helix only).
2372. `runtime/` subdirectory of OS-dependent helix user config directory.
2632. `runtime/` subdirectory of OS-dependent helix user config directory.
238−3. `$HELIX_RUNTIME`.
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)
239−4. `runtime/` subdirectory of path to Helix executable.
267+5. `runtime/` subdirectory of path to Helix executable.
240
268
241This order also sets the priority for selecting which file will be used if multiple runtime
269This order also sets the priority for selecting which file will be used if multiple runtime
242directories have files with the same name.
270directories have files with the same name.
243
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+
244### Validating the installation
290### Validating the installation
245
291
246To make sure everything is set up as expected you should run the Helix health
292To make sure everything is set up as expected you should run the Helix health
book/src/keymap.md+24−16
@@ -25,6 +25,8 @@
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.
@@ -111,7 +113,8 @@
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 sub selections 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` |
@@ -288,7 +291,7 @@ This layer is a kludge of mappings, mostly pickers.
288| `w` | Enter [window mode](#window-mode) | N/A |
291| `w` | Enter [window mode](#window-mode) | N/A |
289| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
292| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
290| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
293| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
291−| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` |
294+| `y` | Yank selections to clipboard | `yank_to_clipboard` |
292| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
295| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
293| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
296| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
294| `/` | Global search in workspace folder | `global_search` |
297| `/` | Global search in workspace folder | `global_search` |
@@ -336,6 +339,8 @@ These mappings are in the style of [vim-unimpaired](https://github.com/tpope/vim
336
339
337## Insert mode
340## Insert mode
338
341
342+Accessed by typing `i` in [normal mode](#normal-mode).
343+
339Insert mode bindings are minimal by default. Helix is designed to
344Insert mode bindings are minimal by default. Helix is designed to
340be 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
341mechanics. Changes to the text are only saved for undos when
346mechanics. Changes to the text are only saved for undos when
@@ -389,9 +394,11 @@ end = "no_op"
389
394
390## Select / extend mode
395## Select / extend mode
391
396
397+Accessed by typing `v` in [normal mode](#normal-mode).
398+
392Select mode echoes Normal mode, but changes any movements to extend
399Select mode echoes Normal mode, but changes any movements to extend
393selections rather than replace them. Goto motions are also changed to
400selections rather than replace them. Goto motions are also changed to
394−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
395the line.
402the line.
396
403
397Search 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
@@ -404,19 +411,20 @@ you to selectively add search terms to your selections.
404
411
405Keys to use within picker. Remapping currently not supported.
412Keys to use within picker. Remapping currently not supported.
406
413
407−| Key | Description |
414+| Key | Description |
408−| ----- | ------------- |
415+| ----- | ------------- |
409−| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
416+| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
410−| `Tab`, `Down`, `Ctrl-n` | Next entry |
417+| `Tab`, `Down`, `Ctrl-n` | Next entry |
411−| `PageUp`, `Ctrl-u` | Page up |
418+| `PageUp`, `Ctrl-u` | Page up |
412−| `PageDown`, `Ctrl-d` | Page down |
419+| `PageDown`, `Ctrl-d` | Page down |
413−| `Home` | Go to first entry |
420+| `Home` | Go to first entry |
414−| `End` | Go to last entry |
421+| `End` | Go to last entry |
415−| `Enter` | Open selected |
422+| `Enter` | Open selected |
423+| `Alt-Enter` | Open selected in the background without closing the picker |
416−| `Ctrl-s` | Open horizontally |
424+| `Ctrl-s` | Open horizontally |
417−| `Ctrl-v` | Open vertically |
425+| `Ctrl-v` | Open vertically |
418−| `Ctrl-t` | Toggle preview |
426+| `Ctrl-t` | Toggle preview |
419−| `Escape`, `Ctrl-c` | Close picker |
427+| `Escape`, `Ctrl-c` | Close picker |
420
428
421## Prompt
429## Prompt
422
430
book/src/languages.md+20−18
@@ -7,24 +7,24 @@ in `languages.toml` files.
7
7
8There are three possible locations for a `languages.toml` file:
8There are three possible locations for a `languages.toml` file:
9
9
10−1. In the Helix source code, this lives in the
10+1. In the Helix source code, which lives in the
11 [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml).
11 [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml).
12 It provides the default configurations for languages and language servers.
12 It provides the default configurations for languages and language servers.
13
13
142. In your [configuration directory](./configuration.md). This overrides values
142. In your [configuration directory](./configuration.md). This overrides values
15− from the built-in language configuration. For example to disable
15+ from the built-in language configuration. For example, to disable
16 auto-LSP-formatting in Rust:
16 auto-LSP-formatting in Rust:
17
17
18−```toml
18+ ```toml
19−# in <config_dir>/helix/languages.toml
19+ # in <config_dir>/helix/languages.toml
20
20
21−[language-server.mylang-lsp]
21+ [language-server.mylang-lsp]
22−command = "mylang-lsp"
22+ command = "mylang-lsp"
23
23
24−[[language]]
24+ [[language]]
25−name = "rust"
25+ name = "rust"
26−auto-format = false
26+ auto-format = false
27−```
27+ ```
28
28
293. In a `.helix` folder in your project. Language configuration may also be
293. In a `.helix` folder in your project. Language configuration may also be
30 overridden local to a project by creating a `languages.toml` file in a
30 overridden local to a project by creating a `languages.toml` file in a
@@ -64,11 +64,12 @@ These configuration keys are available:
64| `comment-token` | The token to use as a comment-token |
64| `comment-token` | The token to use as a comment-token |
65| `indent` | The indent to use. Has sub keys `unit` (the text inserted into the document when indenting; usually set to N spaces or `"\t"` for tabs) and `tab-width` (the number of spaces rendered for a tab) |
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) |
66| `language-servers` | The Language Servers used for this language. See below for more information in the section [Configuring Language Servers for a language](#configuring-language-servers-for-a-language) |
66| `language-servers` | The Language Servers used for this language. See below for more information in the section [Configuring Language Servers for a language](#configuring-language-servers-for-a-language) |
67−| `config` | Language Server configuration |
68| `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`) |
69| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout |
68| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout |
69+| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
70| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
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. |
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.
72
73
73### File-type detection and the `file-types` key
74### File-type detection and the `file-types` key
74
75
@@ -128,8 +129,8 @@ These are the available options for a language server.
128| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
129| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
129
130
130A `format` sub-table within `config` can be used to pass extra formatting options to
131A `format` sub-table within `config` can be used to pass extra formatting options to
131−[Document Formatting Requests](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-17.md#document-formatting-request--leftwards_arrow_with_hook).
132+[Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting).
132−For example with typescript:
133+For example, with typescript:
133
134
134```toml
135```toml
135[language-server.typescript-language-server]
136[language-server.typescript-language-server]
@@ -148,8 +149,8 @@ Different languages can use the same language server instance, e.g. `typescript-
148In case multiple language servers are specified in the `language-servers` attribute of a `language`,
149In case multiple language servers are specified in the `language-servers` attribute of a `language`,
149it's often useful to only enable/disable certain language-server features for these language servers.
150it's often useful to only enable/disable certain language-server features for these language servers.
150
151
151−For example `efm-lsp-prettier` of the previous example is used only with a formatting command `prettier`,
152+As an example, `efm-lsp-prettier` of the previous example is used only with a formatting command `prettier`,
152−so everything else should be handled by the `typescript-language-server` (which is configured by default)
153+so everything else should be handled by the `typescript-language-server` (which is configured by default).
153The language configuration for typescript could look like this:
154The language configuration for typescript could look like this:
154
155
155```toml
156```toml
@@ -167,9 +168,10 @@ language-servers = [ { name = "typescript-language-server", except-features = [
167```
168```
168
169
169Each requested LSP feature is prioritized in the order of the `language-servers` array.
170Each requested LSP feature is prioritized in the order of the `language-servers` array.
170−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`).
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.
171−If no `except-features` or `only-features` is given all features for the language server are enabled.
173+If no `except-features` or `only-features` is given, all features for the language server are enabled.
172−If a language server itself doesn't support a feature the next language server array entry will be tried (and so on).
174+If a language server itself doesn't support a feature, the next language server array entry will be tried (and so on).
173
175
174The list of supported features is:
176The list of supported features is:
175
177
book/src/themes.md+10−2
@@ -70,6 +70,7 @@ over it and is merged into the default palette.
70
70
71| Color Name |
71| Color Name |
72| --- |
72| --- |
73+| `default` |
73| `black` |
74| `black` |
74| `red` |
75| `red` |
75| `green` |
76| `green` |
@@ -154,6 +155,7 @@ We use a similar set of scopes as
154
155
155- `type` - Types
156- `type` - Types
156 - `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`)
157 - `enum`
159 - `enum`
158 - `variant`
160 - `variant`
159- `constructor`
161- `constructor`
@@ -244,9 +246,12 @@ We use a similar set of scopes as
244
246
245- `diff` - version control changes
247- `diff` - version control changes
246 - `plus` - additions
248 - `plus` - additions
249+ - `gutter` - gutter indicator
247 - `minus` - deletions
250 - `minus` - deletions
251+ - `gutter` - gutter indicator
248 - `delta` - modifications
252 - `delta` - modifications
249 - `moved` - renamed or moved files/changes
253 - `moved` - renamed or moved files/changes
254+ - `gutter` - gutter indicator
250
255
251#### Interface
256#### Interface
252
257
@@ -291,12 +296,15 @@ These scopes are used for theming the editor interface:
291| `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]) |
292| `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]) |
293| `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 |
294| `ui.popup` | Documentation popups (e.g. Space + k) |
302| `ui.popup` | Documentation popups (e.g. Space + k) |
295| `ui.popup.info` | Prompt for multiple key options |
303| `ui.popup.info` | Prompt for multiple key options |
296| `ui.window` | Borderlines separating splits |
304| `ui.window` | Borderlines separating splits |
297| `ui.help` | Description box for commands |
305| `ui.help` | Description box for commands |
298−| `ui.text` | Command prompts, popup text, etc. |
306+| `ui.text` | Default text style, command prompts, popup text, etc. |
299−| `ui.text.focus` | |
307+| `ui.text.focus` | The currently selected line in the picker |
300| `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) |
301| `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 |
302| `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]) |
book/src/usage.md+26−10
@@ -37,19 +37,35 @@ If a register is selected before invoking a change or delete command, the select
37- `"hc` - Store the selection in register `h` and then change it (delete and enter insert mode).
37- `"hc` - Store the selection in register `h` and then change it (delete and enter insert mode).
38- `"md` - Store the selection in register `m` and delete it.
38- `"md` - Store the selection in register `m` and delete it.
39
39
40−### 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:
41
44
42| Register character | Contains |
45| Register character | Contains |
43| --- | --- |
46| --- | --- |
44| `/` | Last search |
47| `/` | Last search |
45| `:` | Last executed command |
48| `:` | Last executed command |
46| `"` | Last yanked text |
49| `"` | Last yanked text |
47−| `_` | Black hole |
50+| `@` | Last recorded macro |
51+
52+### Special registers
53+
54+Some registers have special behavior when read from and written to.
48
55
49−The system clipboard is not directly supported by a special register. Instead, special commands and keybindings are provided. Refer to the
56+| Register character | When read | When written |
50−[key map](keymap.md#space-mode) for more details.
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 |
51
64
52−The black hole register is a no-op register, meaning that no data will be read or written to it.
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.
53
69
54## Surround
70## Surround
55
71
@@ -96,13 +112,13 @@ function or block of code.
96| `(`, `[`, `'`, etc. | Specified surround pairs |
112| `(`, `[`, `'`, etc. | Specified surround pairs |
97| `m` | The closest surround pair |
113| `m` | The closest surround pair |
98| `f` | Function |
114| `f` | Function |
99−| `c` | Class |
115+| `t` | Type (or Class) |
100| `a` | Argument/parameter |
116| `a` | Argument/parameter |
101−| `o` | Comment |
117+| `c` | Comment |
102−| `t` | Test |
118+| `T` | Test |
103| `g` | Change |
119| `g` | Change |
104
120
105−> 💡 `f`, `c`, etc. need a tree-sitter grammar active for the current
121+> 💡 `f`, `t`, etc. need a tree-sitter grammar active for the current
106document and a special tree-sitter query file to work properly. [Only
122document and a special tree-sitter query file to work properly. [Only
107some grammars][lang-support] currently have the query file implemented.
123some grammars][lang-support] currently have the query file implemented.
108Contributions are welcome!
124Contributions are welcome!
@@ -112,7 +128,7 @@ Contributions are welcome!
112Navigating between functions, classes, parameters, and other elements is
128Navigating between functions, classes, parameters, and other elements is
113possible using tree-sitter and textobject queries. For
129possible using tree-sitter and textobject queries. For
114example to move to the next function use `]f`, to move to previous
130example to move to the next function use `]f`, to move to previous
115−class use `[c`, and so on.
131+type use `[t`, and so on.
116
132
117![Tree-sitter-nav-demo][tree-sitter-nav-demo]
133![Tree-sitter-nav-demo][tree-sitter-nav-demo]
118
134
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+15
@@ -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,9 @@
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>
39 <release version="23.05" date="2023-05-18">
54 <release version="23.05" date="2023-05-18">
40 <url>https://github.com/helix-editor/helix/releases/tag/23.05</url>
55 <url>https://github.com/helix-editor/helix/releases/tag/23.05</url>
41 </release>
56 </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+10−3
@@ -29,9 +29,15 @@ 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
33+
34−to `cargo install` anything either).
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).
35
41
36# Testing
42# Testing
37
43
@@ -58,4 +64,5 @@ The current MSRV and future changes to the MSRV are listed in the [Firefox docum
58[architecture.md]: ./architecture.md
64[architecture.md]: ./architecture.md
59[docs]: https://docs.helix-editor.com/
65[docs]: https://docs.helix-editor.com/
60[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
61[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+21−237
@@ -1,126 +1,22 @@
1{
1{
2 "nodes": {
2 "nodes": {
3 "crane": {
3 "crane": {
4− "flake": false,
5− "locked": {
6− "lastModified": 1681175776,
7− "narHash": "sha256-7SsUy9114fryHAZ8p1L6G6YSu7jjz55FddEwa2U8XZc=",
8− "owner": "ipetkov",
9− "repo": "crane",
10− "rev": "445a3d222947632b5593112bb817850e8a9cf737",
11− "type": "github"
12− },
13− "original": {
14− "owner": "ipetkov",
15− "ref": "v0.12.1",
16− "repo": "crane",
17− "type": "github"
18− }
19− },
20− "dream2nix": {
21 "inputs": {
4 "inputs": {
22− "all-cabal-json": [
23− "nci"
24− ],
25− "crane": "crane",
26− "devshell": [
27− "nci"
28− ],
29− "drv-parts": "drv-parts",
30− "flake-compat": "flake-compat",
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− "nixpkgsV1": "nixpkgsV1",
55− "poetry2nix": [
56− "nci"
57− ],
58− "pre-commit-hooks": [
59− "nci"
60− ],
61− "pruned-racket-catalog": [
62− "nci"
63 ]
7 ]
64 },
8 },
65 "locked": {
9 "locked": {
66− "lastModified": 1683212002,
10+ "lastModified": 1701025348,
67− "narHash": "sha256-EObtqyQsv9v+inieRY5cvyCMCUI5zuU5qu+1axlJCPM=",
11+ "narHash": "sha256-42GHmYH+GF7VjwGSt+fVT1CQuNpGanJbNgVHTAZppUM=",
68− "owner": "nix-community",
69− "repo": "dream2nix",
70− "rev": "fbfb09d2ab5ff761d822dd40b4a1def81651d096",
71− "type": "github"
72− },
73− "original": {
74− "owner": "nix-community",
75− "repo": "dream2nix",
76− "type": "github"
77− }
78− },
79− "drv-parts": {
80− "inputs": {
81− "flake-compat": [
82− "nci",
83− "dream2nix",
84− "flake-compat"
85− ],
86− "flake-parts": [
87− "nci",
88− "dream2nix",
89− "flake-parts"
90− ],
91− "nixpkgs": [
92− "nci",
93− "dream2nix",
94− "nixpkgs"
95− ]
96− },
97− "locked": {
98− "lastModified": 1680698112,
99− "narHash": "sha256-FgnobN/DvCjEsc0UAZEAdPLkL4IZi2ZMnu2K2bUaElc=",
100− "owner": "davhau",
101− "repo": "drv-parts",
102− "rev": "e8c2ec1157dc1edb002989669a0dbd935f430201",
103− "type": "github"
104− },
105− "original": {
106− "owner": "davhau",
107− "repo": "drv-parts",
108− "type": "github"
109− }
110− },
111− "flake-compat": {
112− "flake": false,
113− "locked": {
114− "lastModified": 1673956053,
115− "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
116− "owner": "edolstra",
12+ "owner": "ipetkov",
117− "repo": "flake-compat",
13+ "repo": "crane",
118− "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
14+ "rev": "42afaeb1a0325194a7cdb526332d2cb92fddd07b",
119 "type": "github"
15 "type": "github"
120 },
16 },
121 "original": {
17 "original": {
122− "owner": "edolstra",
18+ "owner": "ipetkov",
123− "repo": "flake-compat",
19+ "repo": "crane",
124 "type": "github"
20 "type": "github"
125 }
21 }
126 },
22 },
@@ -129,11 +25,11 @@
129 "systems": "systems"
25 "systems": "systems"
130 },
26 },
131 "locked": {
27 "locked": {
132− "lastModified": 1681202837,
28+ "lastModified": 1694529238,
133− "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
29+ "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
134 "owner": "numtide",
30 "owner": "numtide",
135 "repo": "flake-utils",
31 "repo": "flake-utils",
136− "rev": "cfacdce06f30d2b68473a46042957675eebb3401",
32+ "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
137 "type": "github"
33 "type": "github"
138 },
34 },
139 "original": {
35 "original": {
@@ -142,55 +38,13 @@
142 "type": "github"
38 "type": "github"
143 }
39 }
144 },
40 },
145− "mk-naked-shell": {
146− "flake": false,
147− "locked": {
148− "lastModified": 1681286841,
149− "narHash": "sha256-3XlJrwlR0nBiREnuogoa5i1b4+w/XPe0z8bbrJASw0g=",
150− "owner": "yusdacra",
151− "repo": "mk-naked-shell",
152− "rev": "7612f828dd6f22b7fb332cc69440e839d7ffe6bd",
153− "type": "github"
154− },
155− "original": {
156− "owner": "yusdacra",
157− "repo": "mk-naked-shell",
158− "type": "github"
159− }
160− },
161− "nci": {
162− "inputs": {
163− "dream2nix": "dream2nix",
164− "mk-naked-shell": "mk-naked-shell",
165− "nixpkgs": [
166− "nixpkgs"
167− ],
168− "parts": "parts",
169− "rust-overlay": [
170− "rust-overlay"
171− ]
172− },
173− "locked": {
174− "lastModified": 1683699050,
175− "narHash": "sha256-UWKQpzVcSshB+sU2O8CCHjOSTQrNS7Kk9V3+UeBsJpg=",
176− "owner": "yusdacra",
177− "repo": "nix-cargo-integration",
178− "rev": "ed27173cd1b223f598343ea3c15aacb1d140feac",
179− "type": "github"
180− },
181− "original": {
182− "owner": "yusdacra",
183− "repo": "nix-cargo-integration",
184− "type": "github"
185− }
186− },
187 "nixpkgs": {
41 "nixpkgs": {
188 "locked": {
42 "locked": {
189− "lastModified": 1683408522,
43+ "lastModified": 1700794826,
190− "narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=",
44+ "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=",
191 "owner": "nixos",
45 "owner": "nixos",
192 "repo": "nixpkgs",
46 "repo": "nixpkgs",
193− "rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7",
47+ "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8",
194 "type": "github"
48 "type": "github"
195 },
49 },
196 "original": {
50 "original": {
@@ -200,99 +54,29 @@
200 "type": "github"
54 "type": "github"
201 }
55 }
202 },
56 },
203− "nixpkgs-lib": {
204− "locked": {
205− "dir": "lib",
206− "lastModified": 1682879489,
207− "narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=",
208− "owner": "NixOS",
209− "repo": "nixpkgs",
210− "rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0",
211− "type": "github"
212− },
213− "original": {
214− "dir": "lib",
215− "owner": "NixOS",
216− "ref": "nixos-unstable",
217− "repo": "nixpkgs",
218− "type": "github"
219− }
220− },
221− "nixpkgsV1": {
222− "locked": {
223− "lastModified": 1678500271,
224− "narHash": "sha256-tRBLElf6f02HJGG0ZR7znMNFv/Uf7b2fFInpTHiHaSE=",
225− "owner": "NixOS",
226− "repo": "nixpkgs",
227− "rev": "5eb98948b66de29f899c7fe27ae112a47964baf8",
228− "type": "github"
229− },
230− "original": {
231− "id": "nixpkgs",
232− "ref": "nixos-22.11",
233− "type": "indirect"
234− }
235− },
236− "parts": {
237− "inputs": {
238− "nixpkgs-lib": [
239− "nci",
240− "nixpkgs"
241− ]
242− },
243− "locked": {
244− "lastModified": 1683560683,
245− "narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=",
246− "owner": "hercules-ci",
247− "repo": "flake-parts",
248− "rev": "006c75898cf814ef9497252b022e91c946ba8e17",
249− "type": "github"
250− },
251− "original": {
252− "owner": "hercules-ci",
253− "repo": "flake-parts",
254− "type": "github"
255− }
256− },
257− "parts_2": {
258− "inputs": {
259− "nixpkgs-lib": "nixpkgs-lib"
260− },
261− "locked": {
262− "lastModified": 1683560683,
263− "narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=",
264− "owner": "hercules-ci",
265− "repo": "flake-parts",
266− "rev": "006c75898cf814ef9497252b022e91c946ba8e17",
267− "type": "github"
268− },
269− "original": {
270− "owner": "hercules-ci",
271− "repo": "flake-parts",
272− "type": "github"
273− }
274− },
275 "root": {
57 "root": {
276 "inputs": {
58 "inputs": {
277− "nci": "nci",
59+ "crane": "crane",
60+ "flake-utils": "flake-utils",
278 "nixpkgs": "nixpkgs",
61 "nixpkgs": "nixpkgs",
279− "parts": "parts_2",
280 "rust-overlay": "rust-overlay"
62 "rust-overlay": "rust-overlay"
281 }
63 }
282 },
64 },
283 "rust-overlay": {
65 "rust-overlay": {
284 "inputs": {
66 "inputs": {
285− "flake-utils": "flake-utils",
67+ "flake-utils": [
68+ "flake-utils"
69+ ],
286 "nixpkgs": [
70 "nixpkgs": [
287 "nixpkgs"
71 "nixpkgs"
288 ]
72 ]
289 },
73 },
290 "locked": {
74 "locked": {
291− "lastModified": 1683771545,
75+ "lastModified": 1701137803,
292− "narHash": "sha256-we0GYcKTo2jRQGmUGrzQ9VH0OYAUsJMCsK8UkF+vZUA=",
76+ "narHash": "sha256-0LcPAdql5IhQSUXJx3Zna0dYTgdIoYO7zUrsKgiBd04=",
293 "owner": "oxalica",
77 "owner": "oxalica",
294 "repo": "rust-overlay",
78 "repo": "rust-overlay",
295− "rev": "c57e210faf68e5d5386f18f1b17ad8365d25e4ed",
79+ "rev": "9dd940c967502f844eacea52a61e9596268d4f70",
296 "type": "github"
80 "type": "github"
297 },
81 },
298 "original": {
82 "original": {
flake.nix+171−140
@@ -3,162 +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− nci.projects."helix-project".relPath = "";
104+ helix-wrapped
127− nci.crates."helix-term" = {
105+ // {
128− overrides = {
106+ override = makeOverridableHelix old;
129− add-meta.override = _: {meta.mainProgram = "hx";};
107+ passthru =
130− add-inputs.overrideAttrs = prev: {
108+ helix-wrapped.passthru
131− buildInputs = (prev.buildInputs or []) ++ [stdenv.cc.cc.lib];
109+ // {
132− };
110+ wrapper = old: makeOverridableHelix old config;
133− disable-grammar-builds = {
134− # disable fetching and building of tree-sitter grammars in the helix-term build.rs
135− HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
136 };
111 };
137− disable-tests = {checkPhase = ":";};
138− set-stdenv.override = _: {inherit stdenv;};
139− set-filtered-src.override = _: {src = filteredSource;};
140− };
141 };
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+ };
142
152
143− packages.helix-unwrapped = config.nci.outputs."helix-term".packages.release;
153+ checks = {
144− packages.helix-unwrapped-dev = config.nci.outputs."helix-term".packages.dev;
154+ # Build the crate itself
145− packages.helix = makeOverridableHelix config.packages.helix-unwrapped {};
155+ inherit (self.packages.${system}) helix;
146− packages.helix-dev = makeOverridableHelix config.packages.helix-unwrapped-dev {};
147− packages.default = config.packages.helix;
148
156
149− devShells.default = config.nci.outputs."helix-project".devShell.overrideAttrs (old: {
157+ clippy = craneLibMSRV.cargoClippy (commonArgs
150− 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};
151− (old.nativeBuildInputs or [])
178+ nativeBuildInputs = with pkgs;
152− ++ (with pkgs; [lld_13 cargo-flamegraph rust-analyzer])
179+ [lld_13 cargo-flamegraph rust-analyzer]
153− ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin)
180+ ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin)
154− ++ (lib.optional stdenv.isLinux pkgs.lldb)
181+ ++ (lib.optional stdenv.isLinux pkgs.lldb)
155− ++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation);
182+ ++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation);
156− shellHook = ''
183+ shellHook = ''
157− export HELIX_RUNTIME="$PWD/runtime"
184+ export HELIX_RUNTIME="$PWD/runtime"
158− export RUST_BACKTRACE="1"
185+ export RUST_BACKTRACE="1"
159− export RUSTFLAGS="${rustFlagsEnv}"
186+ export RUSTFLAGS="${rustFlagsEnv}"
160− '';
187+ '';
161− });
188+ };
189+ })
190+ // {
191+ overlays.default = final: prev: {
192+ inherit (self.packages.${final.system}) helix;
162 };
193 };
163 };
194 };
164
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+20−16
@@ -1,37 +1,38 @@
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 = "2.2"
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"] }
35dunce = "1.0"
36dunce = "1.0"
36
37
37log = "0.4"
38log = "0.4"
@@ -48,6 +49,9 @@ chrono = { version = "0.4", default-features = false, features = ["alloc", "std"
48etcetera = "0.8"
49etcetera = "0.8"
49textwrap = "0.16.0"
50textwrap = "0.16.0"
50
51
52+nucleo.workspace = true
53+parking_lot = "0.12"
54+
51[dev-dependencies]
55[dev-dependencies]
52quickcheck = { version = "1", default-features = false }
56quickcheck = { version = "1", default-features = false }
53−indoc = "2.0.1"
57+indoc = "2.0.4"
helix-core/src/diagnostic.rs+4
@@ -39,6 +39,10 @@ pub enum DiagnosticTag {
39#[derive(Debug, Clone)]
39#[derive(Debug, Clone)]
40pub struct Diagnostic {
40pub struct Diagnostic {
41 pub range: Range,
41 pub range: Range,
42+ // whether this diagnostic ends at the end of(or inside) a word
43+ pub ends_at_word: bool,
44+ pub starts_at_word: bool,
45+ pub zero_width: bool,
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>,
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+617−145
@@ -1,13 +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,
7− graphemes::tab_width_at,
8+ graphemes::{grapheme_width, tab_width_at},
8− syntax::{LanguageConfiguration, RopeProvider, Syntax},
9+ syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
9 tree_sitter::Node,
10 tree_sitter::Node,
10− Rope, RopeSlice,
11+ Position, Rope, RopeGraphemes, RopeSlice,
11};
12};
12
13
13/// Enum representing indentation style.
14/// Enum representing indentation style.
@@ -19,6 +20,10 @@ pub enum IndentStyle {
19 Spaces(u8),
20 Spaces(u8),
20}
21}
21
22
23+// 16 spaces
24+const INDENTS: &str = " ";
25+pub const MAX_INDENT: u8 = 16;
26+
22impl IndentStyle {
27impl IndentStyle {
23 /// Creates an `IndentStyle` from an indentation string.
28 /// Creates an `IndentStyle` from an indentation string.
24 ///
29 ///
@@ -27,10 +32,10 @@ impl IndentStyle {
27 #[inline]
32 #[inline]
28 pub fn from_str(indent: &str) -> Self {
33 pub fn from_str(indent: &str) -> Self {
29 // 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...?
30− debug_assert!(!indent.is_empty() && indent.len() <= 8);
35+ debug_assert!(!indent.is_empty() && indent.len() <= MAX_INDENT as usize);
31
36
32 if indent.starts_with(' ') {
37 if indent.starts_with(' ') {
33− IndentStyle::Spaces(indent.len() as u8)
38+ IndentStyle::Spaces(indent.len().clamp(1, MAX_INDENT as usize) as u8)
34 } else {
39 } else {
35 IndentStyle::Tabs
40 IndentStyle::Tabs
36 }
41 }
@@ -40,20 +45,13 @@ impl IndentStyle {
40 pub fn as_str(&self) -> &'static str {
45 pub fn as_str(&self) -> &'static str {
41 match *self {
46 match *self {
42 IndentStyle::Tabs => "\t",
47 IndentStyle::Tabs => "\t",
43− IndentStyle::Spaces(1) => " ",
44− IndentStyle::Spaces(2) => " ",
45− IndentStyle::Spaces(3) => " ",
46− IndentStyle::Spaces(4) => " ",
47− IndentStyle::Spaces(5) => " ",
48− IndentStyle::Spaces(6) => " ",
49− IndentStyle::Spaces(7) => " ",
50− IndentStyle::Spaces(8) => " ",
51−
52− // Unsupported indentation style. This should never happen,
53− // but just in case fall back to two spaces.
54 IndentStyle::Spaces(n) => {
48 IndentStyle::Spaces(n) => {
49+ // Unsupported indentation style. This should never happen,
55− debug_assert!(n > 0 && n <= 8); // Always triggers. `debug_panic!()` wanted.
50+ debug_assert!(n > 0 && n <= MAX_INDENT);
56− " "
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]
57 }
55 }
58 }
56 }
59 }
57 }
@@ -75,9 +73,9 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
75 // Build a histogram of the indentation *increases* between
73 // Build a histogram of the indentation *increases* between
76 // subsequent lines, ignoring lines that are all whitespace.
74 // subsequent lines, ignoring lines that are all whitespace.
77 //
75 //
78− // Index 0 is for tabs, the rest are 1-8 spaces.
76+ // Index 0 is for tabs, the rest are 1-MAX_INDENT spaces.
79− let histogram: [usize; 9] = {
77+ let histogram: [usize; MAX_INDENT as usize + 1] = {
80− let mut histogram = [0; 9];
78+ let mut histogram = [0; MAX_INDENT as usize + 1];
81 let mut prev_line_is_tabs = false;
79 let mut prev_line_is_tabs = false;
82 let mut prev_line_leading_count = 0usize;
80 let mut prev_line_leading_count = 0usize;
83
81
@@ -136,7 +134,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
136 histogram[0] += 1;
134 histogram[0] += 1;
137 } else {
135 } else {
138 let amount = leading_count - prev_line_leading_count;
136 let amount = leading_count - prev_line_leading_count;
139− if amount <= 8 {
137+ if amount <= MAX_INDENT as usize {
140 histogram[amount] += 1;
138 histogram[amount] += 1;
141 }
139 }
142 }
140 }
@@ -199,6 +197,56 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: us
199 len / indent_width
197 len / indent_width
200}
198}
201
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+
202/// 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.
203/// 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
204fn 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> {
@@ -240,68 +288,140 @@ fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bo
240/// This is usually constructed in one of 2 ways:
288/// This is usually constructed in one of 2 ways:
241/// - 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
242/// - 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
243−#[derive(Default)]
294+#[derive(Default, Debug, PartialEq, Eq, Clone)]
244−pub struct Indentation {
295+pub struct Indentation<'a> {
245− /// The total indent (the number of indent levels) is defined as max(0, indent-outdent).
246− /// The string that this results in depends on the indent style (spaces or tabs, etc.)
247 indent: usize,
296 indent: usize,
297+ indent_always: usize,
248 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>>,
249}
303}
304+
250−impl Indentation {
305+impl<'a> Indentation<'a> {
251 /// Add some other [Indentation] to this.
306 /// Add some other [Indentation] to this.
252− /// 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).
253− fn add_line(&mut self, added: &Indentation) {
309+ fn add_line(&mut self, added: Indentation<'a>) {
254− if added.indent > 0 && added.outdent == 0 {
310+ // Align overrides the indent from outer scopes.
255− self.indent += 1;
311+ if self.align.is_some() {
256− } else if added.outdent > 0 && added.indent == 0 {
312+ return;
257− self.outdent += 1;
258 }
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;
259 }
322 }
323+
260 /// Add an indent capture to this indent.
324 /// Add an indent capture to this indent.
261− /// 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).
262− fn add_capture(&mut self, added: IndentCaptureType) {
328+ fn add_capture(&mut self, added: IndentCaptureType<'a>) {
263 match added {
329 match added {
264 IndentCaptureType::Indent => {
330 IndentCaptureType::Indent => {
331+ if self.indent_always == 0 {
265− 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;
266 }
340 }
267 IndentCaptureType::Outdent => {
341 IndentCaptureType::Outdent => {
342+ if self.outdent_always == 0 {
268− 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+ }
269 }
354 }
270 }
355 }
271 }
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,
272− fn as_string(&self, indent_style: &IndentStyle) -> String {
366+ indent_style: &IndentStyle,
273− let indent_level = if self.indent >= self.outdent {
367+ tab_width: usize,
274− 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+ ))
275 } else {
380 } else {
276− 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
277− 0
382+ None
278− };
383+ }
279− 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+ )
280 }
393 }
281}
394}
282
395
283/// 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)]
284−struct IndentCapture {
398+struct IndentCapture<'a> {
285− capture_type: IndentCaptureType,
399+ capture_type: IndentCaptureType<'a>,
286 scope: IndentScope,
400 scope: IndentScope,
287}
401}
288−#[derive(Clone, Copy)]
402+#[derive(Debug, Clone, PartialEq)]
289−enum IndentCaptureType {
403+enum IndentCaptureType<'a> {
290 Indent,
404 Indent,
405+ IndentAlways,
291 Outdent,
406 Outdent,
407+ OutdentAlways,
408+ /// Alignment given as a string of whitespace
409+ Align(RopeSlice<'a>),
292}
410}
411+
293−impl IndentCaptureType {
412+impl<'a> IndentCaptureType<'a> {
294 fn default_scope(&self) -> IndentScope {
413 fn default_scope(&self) -> IndentScope {
295 match self {
414 match self {
296− IndentCaptureType::Indent => IndentScope::Tail,
415+ IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
297− IndentCaptureType::Outdent => IndentScope::All,
416+ IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All,
417+ IndentCaptureType::Align(_) => IndentScope::All,
298 }
418 }
299 }
419 }
300}
420}
301/// This defines which part of a node an [IndentCapture] applies to.
421/// This defines which part of a node an [IndentCapture] applies to.
302/// 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
303/// with `#set!` property declarations.
423/// with `#set!` property declarations.
304−#[derive(Clone, Copy)]
424+#[derive(Debug, Clone, Copy)]
305enum IndentScope {
425enum IndentScope {
306 /// The indent applies to the whole node
426 /// The indent applies to the whole node
307 All,
427 All,
@@ -311,6 +431,7 @@ enum IndentScope {
311
431
312/// 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
313/// 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)]
314enum ExtendCapture {
435enum ExtendCapture {
315 Extend,
436 Extend,
316 PreventOnce,
437 PreventOnce,
@@ -319,24 +440,41 @@ enum ExtendCapture {
319/// 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
320/// each node (identified by its ID) the relevant captures (already filtered
441/// each node (identified by its ID) the relevant captures (already filtered
321/// by predicates).
442/// by predicates).
443+#[derive(Debug)]
322−struct IndentQueryResult {
444+struct IndentQueryResult<'a> {
323− indent_captures: HashMap<usize, Vec<IndentCapture>>,
445+ indent_captures: HashMap<usize, Vec<IndentCapture<'a>>>,
324 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
446 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
325}
447}
326
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+
327−fn query_indents(
466+fn query_indents<'a>(
328 query: &Query,
467 query: &Query,
329 syntax: &Syntax,
468 syntax: &Syntax,
330 cursor: &mut QueryCursor,
469 cursor: &mut QueryCursor,
331− text: RopeSlice,
470+ text: RopeSlice<'a>,
332 range: std::ops::Range<usize>,
471 range: std::ops::Range<usize>,
333− // Position of the (optional) newly inserted line break.
472+ new_line_byte_pos: Option<usize>,
334− // Given as (line, byte_pos)
335− new_line_break: Option<(usize, usize)>,
336−) -> IndentQueryResult {
473+) -> IndentQueryResult<'a> {
337 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
474 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
338 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
475 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
339 cursor.set_byte_range(range);
476 cursor.set_byte_range(range);
477+
340 // Iterate over all captures from the query
478 // Iterate over all captures from the query
341 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)) {
342 // Skip matches where not all custom predicates are fulfilled
480 // Skip matches where not all custom predicates are fulfilled
@@ -363,21 +501,13 @@ fn query_indents(
363 Some(QueryPredicateArg::Capture(capt1)),
501 Some(QueryPredicateArg::Capture(capt1)),
364 Some(QueryPredicateArg::Capture(capt2))
502 Some(QueryPredicateArg::Capture(capt2))
365 ) => {
503 ) => {
366− let get_line_num = |node: Node| {
367− let mut node_line = node.start_position().row;
368− // Adjust for the new line that will be inserted
369− if let Some((line, byte)) = new_line_break {
370− if node_line==line && node.start_byte()>=byte {
371− node_line += 1;
372− }
373− }
374− node_line
375− };
376 let n1 = m.nodes_for_capture_index(*capt1).next();
504 let n1 = m.nodes_for_capture_index(*capt1).next();
377 let n2 = m.nodes_for_capture_index(*capt2).next();
505 let n2 = m.nodes_for_capture_index(*capt2).next();
378 match (n1, n2) {
506 match (n1, n2) {
379 (Some(n1), Some(n2)) => {
507 (Some(n1), Some(n2)) => {
380− 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;
381 same_line==(pred.operator.as_ref()=="same-line?")
511 same_line==(pred.operator.as_ref()=="same-line?")
382 }
512 }
383 _ => true,
513 _ => true,
@@ -388,6 +518,23 @@ fn query_indents(
388 }
518 }
389 }
519 }
390 }
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+ },
391 _ => {
538 _ => {
392 panic!(
539 panic!(
393 "Invalid indent query: Unknown predicate (\"{}\")",
540 "Invalid indent query: Unknown predicate (\"{}\")",
@@ -398,11 +545,28 @@ fn query_indents(
398 }) {
545 }) {
399 continue;
546 continue;
400 }
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;
401 for capture in m.captures {
553 for capture in m.captures {
402 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();
403 let capture_type = match capture_name {
555 let capture_type = match capture_name {
404 "indent" => IndentCaptureType::Indent,
556 "indent" => IndentCaptureType::Indent,
557+ "indent.always" => IndentCaptureType::IndentAlways,
405 "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+ }
406 "extend" => {
570 "extend" => {
407 extend_captures
571 extend_captures
408 .entry(capture.node.id())
572 .entry(capture.node.id())
@@ -452,17 +616,40 @@ fn query_indents(
452 }
616 }
453 }
617 }
454 }
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+ }
455 indent_captures
638 indent_captures
456− .entry(capture.node.id())
639+ .entry(node_id)
457− // Most entries only need to contain a single IndentCapture
458 .or_insert_with(|| Vec::with_capacity(1))
640 .or_insert_with(|| Vec::with_capacity(1))
459− .push(indent_capture);
641+ .push(capture);
460 }
642 }
461 }
643 }
644+
462− IndentQueryResult {
645+ let result = IndentQueryResult {
463 indent_captures,
646 indent_captures,
464 extend_captures,
647 extend_captures,
465− }
648+ };
649+
650+ log::trace!("indent result = {:?}", result);
651+
652+ result
466}
653}
467
654
468/// 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.
@@ -532,61 +719,27 @@ fn extend_nodes<'a>(
532 }
719 }
533}
720}
534
721
535−/// Use the syntax tree to determine the indentation for a given position.
722+/// Prepare an indent query by computing:
536−/// This can be used in 2 ways:
723+/// - The node from which to start the query (this is non-trivial due to `@extend` captures)
537−///
724+/// - The indent captures for all relevant nodes.
538−/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
539−/// - In this case, pos should be inside the first tree-sitter node on that line.
540−/// In most cases, this can just be the first non-whitespace on that line.
541−/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
542−/// after pos were moved to a new line.
543−///
544−/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
545−/// Each of these nodes produces some [Indentation] for:
546−///
547−/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
548−/// - The line after the node. This is defined by:
549−/// - The scope `tail`.
550−/// - The scope `all` if this node is not the first node on its line.
551−/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
552−/// The indents from different nodes for the same line are then combined.
553−/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
554−///
555−/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
556−/// ```ignore
557−/// some_function(|| {
558−/// // Both the function parameters as well as the contained block should be indented.
559−/// // Because they are on the same line, this only yields one indent level
560−/// });
561−/// ```
562−///
563−/// ```ignore
564−/// some_function(
565−/// param1,
566−/// || {
567−/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
568−/// },
569−/// );
570−/// ```
571#[allow(clippy::too_many_arguments)]
725#[allow(clippy::too_many_arguments)]
572−pub fn treesitter_indent_for_pos(
726+fn init_indent_query<'a, 'b>(
573 query: &Query,
727 query: &Query,
574− syntax: &Syntax,
728+ syntax: &'a Syntax,
575− indent_style: &IndentStyle,
729+ text: RopeSlice<'b>,
576 tab_width: usize,
730 tab_width: usize,
577 indent_width: usize,
731 indent_width: usize,
578− text: RopeSlice,
579 line: usize,
732 line: usize,
580− pos: usize,
733+ byte_pos: usize,
581− new_line: bool,
734+ new_line_byte_pos: Option<usize>,
582−) -> Option<String> {
735+) -> Option<(Node<'a>, HashMap<usize, Vec<IndentCapture<'b>>>)> {
583− let byte_pos = text.char_to_byte(pos);
584 // The innermost tree-sitter node which is considered for the indent
736 // The innermost tree-sitter node which is considered for the indent
585 // computation. It may change if some predeceding node is extended
737 // computation. It may change if some predeceding node is extended
586 let mut node = syntax
738 let mut node = syntax
587 .tree()
739 .tree()
588 .root_node()
740 .root_node()
589 .descendant_for_byte_range(byte_pos, byte_pos)?;
741 .descendant_for_byte_range(byte_pos, byte_pos)?;
742+
590 let (query_result, deepest_preceding) = {
743 let (query_result, deepest_preceding) = {
591 // The query range should intersect with all nodes directly preceding
744 // The query range should intersect with all nodes directly preceding
592 // 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.
@@ -617,13 +770,12 @@ pub fn treesitter_indent_for_pos(
617 &mut cursor,
770 &mut cursor,
618 text,
771 text,
619 query_range,
772 query_range,
620− new_line.then_some((line, byte_pos)),
773+ new_line_byte_pos,
621 );
774 );
622 ts_parser.cursors.push(cursor);
775 ts_parser.cursors.push(cursor);
623 (query_result, deepest_preceding)
776 (query_result, deepest_preceding)
624 })
777 })
625 };
778 };
626− let indent_captures = query_result.indent_captures;
627 let extend_captures = query_result.extend_captures;
779 let extend_captures = query_result.extend_captures;
628
780
629 // 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
@@ -638,6 +790,68 @@ pub fn treesitter_indent_for_pos(
638 indent_width,
790 indent_width,
639 );
791 );
640 }
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+ )?;
641 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));
642
856
643 let mut result = Indentation::default();
857 let mut result = Indentation::default();
@@ -645,12 +859,16 @@ pub fn treesitter_indent_for_pos(
645 // even if there are multiple "indent" nodes on the same line
859 // even if there are multiple "indent" nodes on the same line
646 let mut indent_for_line = Indentation::default();
860 let mut indent_for_line = Indentation::default();
647 let mut indent_for_line_below = Indentation::default();
861 let mut indent_for_line_below = Indentation::default();
862+
648 loop {
863 loop {
649 // This can safely be unwrapped because `first_in_line` contains
864 // This can safely be unwrapped because `first_in_line` contains
650 // 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)
651 let is_first = *first_in_line.last().unwrap();
866 let is_first = *first_in_line.last().unwrap();
867+
652− // 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.
653− if let Some(definitions) = indent_captures.get(&node.id()) {
871+ if let Some(definitions) = indent_captures.remove(&node.id()) {
654 for definition in definitions {
872 for definition in definitions {
655 match definition.scope {
873 match definition.scope {
656 IndentScope::All => {
874 IndentScope::All => {
@@ -668,28 +886,22 @@ pub fn treesitter_indent_for_pos(
668 }
886 }
669
887
670 if let Some(parent) = node.parent() {
888 if let Some(parent) = node.parent() {
671− let mut node_line = node.start_position().row;
889+ let node_line = get_node_start_line(node, new_line_byte_pos);
672− let mut parent_line = parent.start_position().row;
890+ let parent_line = get_node_start_line(parent, new_line_byte_pos);
673− if node_line == line && new_line {
891+
674− // Also consider the line that will be inserted
675− if node.start_byte() >= byte_pos {
676− node_line += 1;
677− }
678− if parent.start_byte() >= byte_pos {
679− parent_line += 1;
680− }
681− };
682 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
683 if node_line < line + (new_line as usize) {
894 if node_line < line + (new_line as usize) {
684− // Don't add indent for the line below the line of the query
685− result.add_line(&indent_for_line_below);
895+ result.add_line(indent_for_line_below);
686 }
896 }
897+
687 if node_line == parent_line + 1 {
898 if node_line == parent_line + 1 {
688 indent_for_line_below = indent_for_line;
899 indent_for_line_below = indent_for_line;
689 } else {
900 } else {
690− result.add_line(&indent_for_line);
901+ result.add_line(indent_for_line);
691 indent_for_line_below = Indentation::default();
902 indent_for_line_below = Indentation::default();
692 }
903 }
904+
693 indent_for_line = Indentation::default();
905 indent_for_line = Indentation::default();
694 }
906 }
695
907
@@ -701,13 +913,13 @@ pub fn treesitter_indent_for_pos(
701 if (node.start_position().row < line)
913 if (node.start_position().row < line)
702 || (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)
703 {
915 {
704− result.add_line(&indent_for_line_below);
916+ result.add_line(indent_for_line_below);
705 }
917 }
706− result.add_line(&indent_for_line);
918+ result.add_line(indent_for_line);
707 break;
919 break;
708 }
920 }
709 }
921 }
710− Some(result.as_string(indent_style))
922+ Some(result)
711}
923}
712
924
713/// Returns the indentation for a new line.
925/// Returns the indentation for a new line.
@@ -716,6 +928,7 @@ pub fn treesitter_indent_for_pos(
716pub fn indent_for_newline(
928pub fn indent_for_newline(
717 language_config: Option<&LanguageConfiguration>,
929 language_config: Option<&LanguageConfiguration>,
718 syntax: Option<&Syntax>,
930 syntax: Option<&Syntax>,
931+ indent_heuristic: &IndentationHeuristic,
719 indent_style: &IndentStyle,
932 indent_style: &IndentStyle,
720 tab_width: usize,
933 tab_width: usize,
721 text: RopeSlice,
934 text: RopeSlice,
@@ -724,14 +937,18 @@ pub fn indent_for_newline(
724 current_line: usize,
937 current_line: usize,
725) -> String {
938) -> String {
726 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),
727− if let (Some(query), Some(syntax)) = (
943+ Some(syntax),
944+ ) = (
945+ indent_heuristic,
728 language_config.and_then(|config| config.indent_query()),
946 language_config.and_then(|config| config.indent_query()),
729 syntax,
947 syntax,
730 ) {
948 ) {
731 if let Some(indent) = treesitter_indent_for_pos(
949 if let Some(indent) = treesitter_indent_for_pos(
732 query,
950 query,
733 syntax,
951 syntax,
734− indent_style,
735 tab_width,
952 tab_width,
736 indent_width,
953 indent_width,
737 text,
954 text,
@@ -739,9 +956,57 @@ pub fn indent_for_newline(
739 line_before_end_pos,
956 line_before_end_pos,
740 true,
957 true,
741 ) {
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+ })() {
742− 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);
743 };
1007 };
744 }
1008 }
1009+ // Fallback in case we either don't have indent queries or they failed for some reason
745 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);
746 indent_style.as_str().repeat(indent_level)
1011 indent_style.as_str().repeat(indent_level)
747}
1012}
@@ -797,4 +1062,211 @@ mod test {
797 3
1062 3
798 );
1063 );
799 }
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+ }
800}
1272}
helix-core/src/lib.rs+4−2
@@ -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;
@@ -41,7 +41,9 @@ pub use helix_loader::find_workspace;
41pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
41pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
42 line.chars().position(|ch| !ch.is_whitespace())
42 line.chars().position(|ch| !ch.is_whitespace())
43}
43}
44+mod rope_reader;
44
45
46+pub use rope_reader::RopeReader;
45pub use ropey::{self, str_utils, Rope, RopeBuilder, RopeSlice};
47pub use ropey::{self, str_utils, Rope, RopeBuilder, RopeSlice};
46
48
47// pub use tendril::StrTendril as Tendril;
49// pub use tendril::StrTendril as Tendril;
@@ -66,5 +68,5 @@ pub use syntax::Syntax;
66
68
67pub use diagnostic::Diagnostic;
69pub use diagnostic::Diagnostic;
68
70
69−pub use line_ending::{LineEnding, DEFAULT_LINE_ENDING};
71+pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
70pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction};
72pub 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+187−7
@@ -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)]
@@ -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 = [
helix-core/src/path.rs+5−7
@@ -85,23 +85,21 @@ pub fn get_normalized_path(path: &Path) -> PathBuf {
85///
85///
86/// This function is used instead of `std::fs::canonicalize` because we don't want to verify
86/// This function is used instead of `std::fs::canonicalize` because we don't want to verify
87/// here if the path exists, just normalize it's components.
87/// here if the path exists, just normalize it's components.
88−pub fn get_canonicalized_path(path: &Path) -> std::io::Result<PathBuf> {
88+pub fn get_canonicalized_path(path: &Path) -> PathBuf {
89 let path = expand_tilde(path);
89 let path = expand_tilde(path);
90 let path = if path.is_relative() {
90 let path = if path.is_relative() {
91− std::env::current_dir().map(|current_dir| current_dir.join(path))?
91+ helix_loader::current_working_dir().join(path)
92 } else {
92 } else {
93 path
93 path
94 };
94 };
95
95
96− Ok(get_normalized_path(path.as_path()))
96+ get_normalized_path(path.as_path())
97}
97}
98
98
99pub fn get_relative_path(path: &Path) -> PathBuf {
99pub fn get_relative_path(path: &Path) -> PathBuf {
100 let path = PathBuf::from(path);
100 let path = PathBuf::from(path);
101 let path = if path.is_absolute() {
101 let path = if path.is_absolute() {
102− let cwdir = std::env::current_dir()
103− .map(|path| get_normalized_path(&path))
102+ let cwdir = get_normalized_path(&helix_loader::current_working_dir());
104− .expect("couldn't determine current directory");
105 get_normalized_path(&path)
103 get_normalized_path(&path)
106 .strip_prefix(cwdir)
104 .strip_prefix(cwdir)
107 .map(PathBuf::from)
105 .map(PathBuf::from)
@@ -142,7 +140,7 @@ pub fn get_relative_path(path: &Path) -> PathBuf {
142/// ```
140/// ```
143///
141///
144pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
142pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
145− let cwd = std::env::current_dir().unwrap_or_default();
143+ let cwd = helix_loader::current_working_dir();
146 let path = path
144 let path = path
147 .as_ref()
145 .as_ref()
148 .strip_prefix(cwd)
146 .strip_prefix(cwd)
helix-core/src/register.rsdeleted−89
@@ -1,89 +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−
82− pub fn clear(&mut self) {
83− self.inner.clear();
84− }
85−
86− pub fn remove(&mut self, name: char) -> Option<Register> {
87− self.inner.remove(&name)
88− }
89−}
helix-core/src/rope_reader.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+76−43
@@ -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.
@@ -605,11 +630,19 @@ impl Selection {
605 self.transform(|range| Range::point(range.cursor(text)))
630 self.transform(|range| Range::point(range.cursor(text)))
606 }
631 }
607
632
608− 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+ {
609 self.ranges.iter().map(move |range| range.fragment(text))
638 self.ranges.iter().map(move |range| range.fragment(text))
610 }
639 }
611
640
612− 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+ {
613 self.ranges.iter().map(move |range| range.slice(text))
646 self.ranges.iter().map(move |range| range.slice(text))
614 }
647 }
615
648
helix-core/src/syntax.rs+105−81
@@ -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;
@@ -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>,
@@ -86,7 +101,8 @@ pub struct LanguageConfiguration {
86 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>
87 #[serde(default)]
102 #[serde(default)]
88 pub shebangs: Vec<String>, // interpreter(s) associated with language
103 pub shebangs: Vec<String>, // interpreter(s) associated with language
104+ #[serde(default)]
89− pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
105+ pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
90 pub comment_token: Option<String>,
106 pub comment_token: Option<String>,
91 pub text_width: Option<usize>,
107 pub text_width: Option<usize>,
92 pub soft_wrap: Option<SoftWrap>,
108 pub soft_wrap: Option<SoftWrap>,
@@ -139,6 +155,8 @@ pub struct LanguageConfiguration {
139 /// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
155 /// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
140 /// Falling back to the current working directory if none are configured.
156 /// Falling back to the current working directory if none are configured.
141 pub workspace_lsp_roots: Option<Vec<PathBuf>>,
157 pub workspace_lsp_roots: Option<Vec<PathBuf>>,
158+ #[serde(default)]
159+ pub persistent_diagnostic_sources: Vec<String>,
142}
160}
143
161
144#[derive(Debug, PartialEq, Eq, Hash)]
162#[derive(Debug, PartialEq, Eq, Hash)]
@@ -196,10 +214,7 @@ impl<'de> Deserialize<'de> for FileType {
196 {
214 {
197 match map.next_entry::<String, String>()? {
215 match map.next_entry::<String, String>()? {
198 Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
216 Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
199− // FIXME: use `suffix.replace('/', std::path::MAIN_SEPARATOR_STR)`
217+ suffix.replace('/', std::path::MAIN_SEPARATOR_STR)
200− // if MSRV is updated to 1.68
201− let mut seperator = [0; 1];
202− suffix.replace('/', std::path::MAIN_SEPARATOR.encode_utf8(&mut seperator))
203 })),
218 })),
204 Some((key, _value)) => Err(serde::de::Error::custom(format!(
219 Some((key, _value)) => Err(serde::de::Error::custom(format!(
205 "unknown key in `file-types` list: {}",
220 "unknown key in `file-types` list: {}",
@@ -424,10 +439,27 @@ pub struct DebuggerQuirks {
424#[derive(Debug, Serialize, Deserialize)]
439#[derive(Debug, Serialize, Deserialize)]
425#[serde(rename_all = "kebab-case")]
440#[serde(rename_all = "kebab-case")]
426pub struct IndentationConfiguration {
441pub struct IndentationConfiguration {
442+ #[serde(deserialize_with = "deserialize_tab_width")]
427 pub tab_width: usize,
443 pub tab_width: usize,
428 pub unit: String,
444 pub unit: String,
429}
445}
430
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+
431/// Configuration for auto pairs
463/// Configuration for auto pairs
432#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
464#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
433#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
465#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
@@ -802,7 +834,10 @@ impl Loader {
802 // TODO: content_regex handling conflict resolution
834 // TODO: content_regex handling conflict resolution
803 }
835 }
804
836
805− 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>> {
806 let line = Cow::from(source.line(0));
841 let line = Cow::from(source.line(0));
807 static SHEBANG_REGEX: Lazy<Regex> =
842 static SHEBANG_REGEX: Lazy<Regex> =
808 Lazy::new(|| Regex::new(&["^", SHEBANG].concat()).unwrap());
843 Lazy::new(|| Regex::new(&["^", SHEBANG].concat()).unwrap());
@@ -912,7 +947,7 @@ fn byte_range_to_str(range: std::ops::Range<usize>, source: RopeSlice) -> Cow<st
912
947
913impl Syntax {
948impl Syntax {
914 pub fn new(
949 pub fn new(
915− source: &Rope,
950+ source: RopeSlice,
916 config: Arc<HighlightConfiguration>,
951 config: Arc<HighlightConfiguration>,
917 loader: Arc<Loader>,
952 loader: Arc<Loader>,
918 ) -> Option<Self> {
953 ) -> Option<Self> {
@@ -943,7 +978,7 @@ impl Syntax {
943 let res = syntax.update(source, source, &ChangeSet::new(source));
978 let res = syntax.update(source, source, &ChangeSet::new(source));
944
979
945 if res.is_err() {
980 if res.is_err() {
946− log::error!("TS parser failed, disabeling TS for the current buffer: {res:?}");
981+ log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
947 return None;
982 return None;
948 }
983 }
949 Some(syntax)
984 Some(syntax)
@@ -951,8 +986,8 @@ impl Syntax {
951
986
952 pub fn update(
987 pub fn update(
953 &mut self,
988 &mut self,
954− old_source: &Rope,
989+ old_source: RopeSlice,
955− source: &Rope,
990+ source: RopeSlice,
956 changeset: &ChangeSet,
991 changeset: &ChangeSet,
957 ) -> Result<(), Error> {
992 ) -> Result<(), Error> {
958 let mut queue = VecDeque::new();
993 let mut queue = VecDeque::new();
@@ -1119,12 +1154,38 @@ impl Syntax {
1119 layer.tree().root_node(),
1154 layer.tree().root_node(),
1120 RopeProvider(source_slice),
1155 RopeProvider(source_slice),
1121 );
1156 );
1157+ let mut combined_injections = vec![
1158+ (None, Vec::new(), IncludedChildren::default());
1159+ layer.config.combined_injections_patterns.len()
1160+ ];
1122 let mut injections = Vec::new();
1161 let mut injections = Vec::new();
1162+ let mut last_injection_end = 0;
1123 for mat in matches {
1163 for mat in matches {
1124 let (injection_capture, content_node, included_children) = layer
1164 let (injection_capture, content_node, included_children) = layer
1125 .config
1165 .config
1126 .injection_for_match(&layer.config.injections_query, &mat, source_slice);
1166 .injection_for_match(&layer.config.injections_query, &mat, source_slice);
1127
1167
1168+ // in case this is a combined injection save it for more processing later
1169+ if let Some(combined_injection_idx) = layer
1170+ .config
1171+ .combined_injections_patterns
1172+ .iter()
1173+ .position(|&pattern| pattern == mat.pattern_index)
1174+ {
1175+ let entry = &mut combined_injections[combined_injection_idx];
1176+ if injection_capture.is_some() {
1177+ entry.0 = injection_capture;
1178+ }
1179+ if let Some(content_node) = content_node {
1180+ if content_node.start_byte() >= last_injection_end {
1181+ entry.1.push(content_node);
1182+ last_injection_end = content_node.end_byte();
1183+ }
1184+ }
1185+ entry.2 = included_children;
1186+ continue;
1187+ }
1188+
1128 // 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
1129 // in the stream of captures.
1190 // in the stream of captures.
1130 mat.remove();
1191 mat.remove();
@@ -1139,49 +1200,23 @@ impl Syntax {
1139 intersect_ranges(&layer.ranges, &[content_node], included_children);
1200 intersect_ranges(&layer.ranges, &[content_node], included_children);
1140
1201
1141 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();
1142 injections.push((config, ranges));
1207 injections.push((config, ranges));
1143 }
1208 }
1144 }
1209 }
1145 }
1210 }
1146 }
1211 }
1147
1212
1148− // Process combined injections.
1149− if let Some(combined_injections_query) = &layer.config.combined_injections_query {
1150− let mut injections_by_pattern_index =
1151− vec![
1152− (None, Vec::new(), IncludedChildren::default());
1153− combined_injections_query.pattern_count()
1154− ];
1155− let matches = cursor.matches(
1156− combined_injections_query,
1157− layer.tree().root_node(),
1158− RopeProvider(source_slice),
1159− );
1160− for mat in matches {
1161− let entry = &mut injections_by_pattern_index[mat.pattern_index];
1162− let (injection_capture, content_node, included_children) = layer
1163− .config
1164− .injection_for_match(combined_injections_query, &mat, source_slice);
1165− if injection_capture.is_some() {
1166− entry.0 = injection_capture;
1167− }
1168− if let Some(content_node) = content_node {
1169− entry.1.push(content_node);
1170− }
1171− entry.2 = included_children;
1172− }
1173− for (lang_name, content_nodes, included_children) in injections_by_pattern_index
1213+ for (lang_name, content_nodes, included_children) in combined_injections {
1174− {
1175− 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()) {
1176− if let Some(config) = (injection_callback)(&lang_name) {
1215+ if let Some(config) = (injection_callback)(&lang_name) {
1177− let ranges = intersect_ranges(
1216+ let ranges =
1178− &layer.ranges,
1179− &content_nodes,
1180− included_children,
1217+ intersect_ranges(&layer.ranges, &content_nodes, included_children);
1181− );
1182− if !ranges.is_empty() {
1218+ if !ranges.is_empty() {
1183− injections.push((config, ranges));
1219+ injections.push((config, ranges));
1184− }
1185 }
1220 }
1186 }
1221 }
1187 }
1222 }
@@ -1371,7 +1406,7 @@ impl LanguageLayer {
1371 self.tree.as_ref().unwrap()
1406 self.tree.as_ref().unwrap()
1372 }
1407 }
1373
1408
1374− fn parse(&mut self, parser: &mut Parser, source: &Rope) -> Result<(), Error> {
1409+ fn parse(&mut self, parser: &mut Parser, source: RopeSlice) -> Result<(), Error> {
1375 parser
1410 parser
1376 .set_included_ranges(&self.ranges)
1411 .set_included_ranges(&self.ranges)
1377 .map_err(|_| Error::InvalidRanges)?;
1412 .map_err(|_| Error::InvalidRanges)?;
@@ -1386,7 +1421,7 @@ impl LanguageLayer {
1386 &mut |byte, _| {
1421 &mut |byte, _| {
1387 if byte <= source.len_bytes() {
1422 if byte <= source.len_bytes() {
1388 let (chunk, start_byte, _, _) = source.chunk_at_byte(byte);
1423 let (chunk, start_byte, _, _) = source.chunk_at_byte(byte);
1389− chunk[byte - start_byte..].as_bytes()
1424+ &chunk.as_bytes()[byte - start_byte..]
1390 } else {
1425 } else {
1391 // out of range
1426 // out of range
1392 &[]
1427 &[]
@@ -1402,7 +1437,7 @@ impl LanguageLayer {
1402}
1437}
1403
1438
1404pub(crate) fn generate_edits(
1439pub(crate) fn generate_edits(
1405− old_text: &Rope,
1440+ old_text: RopeSlice,
1406 changeset: &ChangeSet,
1441 changeset: &ChangeSet,
1407) -> Vec<tree_sitter::InputEdit> {
1442) -> Vec<tree_sitter::InputEdit> {
1408 use Operation::*;
1443 use Operation::*;
@@ -1418,7 +1453,7 @@ pub(crate) fn generate_edits(
1418
1453
1419 // TODO; this is a lot easier with Change instead of Operation.
1454 // TODO; this is a lot easier with Change instead of Operation.
1420
1455
1421− fn point_at_pos(text: &Rope, pos: usize) -> (usize, Point) {
1456+ fn point_at_pos(text: RopeSlice, pos: usize) -> (usize, Point) {
1422 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
1423 let line = text.char_to_line(pos);
1458 let line = text.char_to_line(pos);
1424 let line_start_byte = text.line_to_byte(line);
1459 let line_start_byte = text.line_to_byte(line);
@@ -1544,7 +1579,7 @@ pub struct HighlightConfiguration {
1544 pub language: Grammar,
1579 pub language: Grammar,
1545 pub query: Query,
1580 pub query: Query,
1546 injections_query: Query,
1581 injections_query: Query,
1547− combined_injections_query: Option<Query>,
1582+ combined_injections_patterns: Vec<usize>,
1548 highlights_pattern_index: usize,
1583 highlights_pattern_index: usize,
1549 highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
1584 highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
1550 non_local_variable_patterns: Vec<bool>,
1585 non_local_variable_patterns: Vec<bool>,
@@ -1595,7 +1630,7 @@ impl<'a> Iterator for ChunksBytes<'a> {
1595}
1630}
1596
1631
1597pub struct RopeProvider<'a>(pub RopeSlice<'a>);
1632pub struct RopeProvider<'a>(pub RopeSlice<'a>);
1598−impl<'a> TextProvider<'a> for RopeProvider<'a> {
1633+impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> {
1599 type I = ChunksBytes<'a>;
1634 type I = ChunksBytes<'a>;
1600
1635
1601 fn text(&mut self, node: Node) -> Self::I {
1636 fn text(&mut self, node: Node) -> Self::I {
@@ -1609,7 +1644,7 @@ impl<'a> TextProvider<'a> for RopeProvider<'a> {
1609struct HighlightIterLayer<'a> {
1644struct HighlightIterLayer<'a> {
1610 _tree: Option<Tree>,
1645 _tree: Option<Tree>,
1611 cursor: QueryCursor,
1646 cursor: QueryCursor,
1612− captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>>>>,
1647+ captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>, &'a [u8]>>>,
1613 config: &'a HighlightConfiguration,
1648 config: &'a HighlightConfiguration,
1614 highlight_end_stack: Vec<usize>,
1649 highlight_end_stack: Vec<usize>,
1615 scope_stack: Vec<LocalScope<'a>>,
1650 scope_stack: Vec<LocalScope<'a>>,
@@ -1660,26 +1695,15 @@ impl HighlightConfiguration {
1660 }
1695 }
1661 }
1696 }
1662
1697
1663− let mut injections_query = Query::new(language, injection_query)?;
1698+ let injections_query = Query::new(language, injection_query)?;
1664−
1665− // Construct a separate query just for dealing with the 'combined injections'.
1666− // Disable the combined injection patterns in the main query.
1667− let mut combined_injections_query = Query::new(language, injection_query)?;
1668− let mut has_combined_queries = false;
1669− 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
1670− let settings = injections_query.property_settings(pattern_index);
1702+ .property_settings(i)
1703+ .iter()
1671− if settings.iter().any(|s| &*s.key == "injection.combined") {
1704+ .any(|s| &*s.key == "injection.combined")
1672− has_combined_queries = true;
1705+ })
1673− injections_query.disable_pattern(pattern_index);
1706+ .collect();
1674− } else {
1675− combined_injections_query.disable_pattern(pattern_index);
1676− }
1677− }
1678− let combined_injections_query = if has_combined_queries {
1679− Some(combined_injections_query)
1680− } else {
1681− None
1682− };
1683
1707
1684 // 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
1685 // have been identified as local variables.
1709 // have been identified as local variables.
@@ -1728,7 +1752,7 @@ impl HighlightConfiguration {
1728 language,
1752 language,
1729 query,
1753 query,
1730 injections_query,
1754 injections_query,
1731− combined_injections_query,
1755+ combined_injections_patterns,
1732 highlights_pattern_index,
1756 highlights_pattern_index,
1733 highlight_indices,
1757 highlight_indices,
1734 non_local_variable_patterns,
1758 non_local_variable_patterns,
@@ -2524,7 +2548,7 @@ mod test {
2524 let mut cursor = QueryCursor::new();
2548 let mut cursor = QueryCursor::new();
2525
2549
2526 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2550 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2527− let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader)).unwrap();
2551+ let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
2528
2552
2529 let root = syntax.tree().root_node();
2553 let root = syntax.tree().root_node();
2530 let mut test = |capture, range| {
2554 let mut test = |capture, range| {
@@ -2598,7 +2622,7 @@ mod test {
2598 fn main() {}
2622 fn main() {}
2599 ",
2623 ",
2600 );
2624 );
2601− let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader)).unwrap();
2625+ let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
2602 let tree = syntax.tree();
2626 let tree = syntax.tree();
2603 let root = tree.root_node();
2627 let root = tree.root_node();
2604 assert_eq!(root.kind(), "source_file");
2628 assert_eq!(root.kind(), "source_file");
@@ -2625,7 +2649,7 @@ mod test {
2625 &doc,
2649 &doc,
2626 vec![(6, 11, Some("test".into())), (12, 17, None)].into_iter(),
2650 vec![(6, 11, Some("test".into())), (12, 17, None)].into_iter(),
2627 );
2651 );
2628− let edits = generate_edits(&doc, transaction.changes());
2652+ let edits = generate_edits(doc.slice(..), transaction.changes());
2629 // transaction.apply(&mut state);
2653 // transaction.apply(&mut state);
2630
2654
2631 assert_eq!(
2655 assert_eq!(
@@ -2654,7 +2678,7 @@ mod test {
2654 let mut doc = Rope::from("fn test() {}");
2678 let mut doc = Rope::from("fn test() {}");
2655 let transaction =
2679 let transaction =
2656 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());
2657− let edits = generate_edits(&doc, transaction.changes());
2681+ let edits = generate_edits(doc.slice(..), transaction.changes());
2658 transaction.apply(&mut doc);
2682 transaction.apply(&mut doc);
2659
2683
2660 assert_eq!(doc, "fn test(a: u32) {}");
2684 assert_eq!(doc, "fn test(a: u32) {}");
@@ -2688,7 +2712,7 @@ mod test {
2688 let language = get_language(language_name).unwrap();
2712 let language = get_language(language_name).unwrap();
2689
2713
2690 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2714 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2691− let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader)).unwrap();
2715+ let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
2692
2716
2693 let root = syntax
2717 let root = syntax
2694 .tree()
2718 .tree()
helix-core/src/transaction.rs+191−51
@@ -1,7 +1,8 @@
1+use ropey::RopeSlice;
1use smallvec::SmallVec;
2use smallvec::SmallVec;
2
3
3−use crate::{Range, Rope, Selection, Tendril};
4+use crate::{chars::char_is_word, Range, Rope, Selection, Tendril};
4−use std::borrow::Cow;
5+use std::{borrow::Cow, iter::once};
5
6
6/// (from, to, replacement)
7/// (from, to, replacement)
7pub type Change = (usize, usize, Option<Tendril>);
8pub type Change = (usize, usize, Option<Tendril>);
@@ -22,6 +23,30 @@ pub enum Operation {
22pub enum Assoc {
23pub enum Assoc {
23 Before,
24 Before,
24 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+ }
25}
50}
26
51
27#[derive(Debug, Default, Clone, PartialEq, Eq)]
52#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -42,7 +67,7 @@ impl ChangeSet {
42 }
67 }
43
68
44 #[must_use]
69 #[must_use]
45− pub fn new(doc: &Rope) -> Self {
70+ pub fn new(doc: RopeSlice) -> Self {
46 let len = doc.len_chars();
71 let len = doc.len_chars();
47 Self {
72 Self {
48 changes: Vec::new(),
73 changes: Vec::new(),
@@ -326,20 +351,75 @@ impl ChangeSet {
326 self.changes.is_empty() || self.changes == [Operation::Retain(self.len)]
351 self.changes.is_empty() || self.changes == [Operation::Retain(self.len)]
327 }
352 }
328
353
329− /// Map a position through the changes.
354+ /// Map a (mostly) *sorted* list of positions through the changes.
330 ///
355 ///
331− /// `assoc` indicates which size to associate the position with. `Before` will keep the
356+ /// This is equivalent to updating each position with `map_pos`:
332− /// position close to the character before, and will place it before insertions over that
357+ ///
333− /// range, or at that point. `After` will move it forward, placing it at the end of such
358+ /// ``` no-compile
334− /// insertions.
359+ /// for (pos, assoc) in positions {
335− 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)>) {
336 use Operation::*;
369 use Operation::*;
370+
371+ let mut positions = positions.peekable();
372+
337 let mut old_pos = 0;
373 let mut old_pos = 0;
338 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+ }
339
414
415+ let Some((i, change)) = iter.next() else {
416+ map!(
417+ |pos, _| (old_pos == pos).then_some(new_pos),
340− let mut iter = self.changes.iter().peekable();
418+ self.changes.len()
419+ );
420+ break;
421+ };
341
422
342− while let Some(change) = iter.next() {
343 let len = match change {
423 let len = match change {
344 Delete(i) | Retain(i) => *i,
424 Delete(i) | Retain(i) => *i,
345 Insert(_) => 0,
425 Insert(_) => 0,
@@ -348,61 +428,65 @@ impl ChangeSet {
348
428
349 match change {
429 match change {
350 Retain(_) => {
430 Retain(_) => {
351− if old_end > pos {
431+ map!(
352− return new_pos + (pos - old_pos);
432+ |pos, _| (old_end > pos).then_some(new_pos + (pos - old_pos)),
353− }
433+ i
434+ );
354 new_pos += len;
435 new_pos += len;
355 }
436 }
356 Delete(_) => {
437 Delete(_) => {
357 // in range
438 // in range
358− if old_end > pos {
439+ map!(|pos, _| (old_end > pos).then_some(new_pos), i);
359− return new_pos;
360− }
361 }
440 }
362 Insert(s) => {
441 Insert(s) => {
363− let ins = s.chars().count();
364−
365 // a subsequent delete means a replace, consume it
442 // a subsequent delete means a replace, consume it
366− if let Some(Delete(len)) = iter.peek() {
443+ if let Some((_, Delete(len))) = iter.peek() {
367 iter.next();
444 iter.next();
368
445
369 old_end = old_pos + len;
446 old_end = old_pos + len;
370 // in range of replaced text
447 // in range of replaced text
448+ map!(
371− if old_end > pos {
449+ |pos, assoc: Assoc| (old_end > pos).then(|| {
372− // at point or tracking before
450+ // at point or tracking before
373− if pos == old_pos || assoc == Assoc::Before {
451+ if pos == old_pos && assoc.stay_at_gaps() {
374− return new_pos;
452+ new_pos
375− } else {
453+ } else {
376− // place to end of insert
454+ // place to end of insert
377− return new_pos + ins;
455+ new_pos + assoc.insert_offset(s)
456+ }
378− }
457+ }),
379− }
458+ i
459+ );
380 } else {
460 } else {
381 // at insert point
461 // at insert point
462+ map!(
382− if old_pos == pos {
463+ |pos, assoc: Assoc| (old_pos == pos).then(|| {
383− // return position before inserted text
464+ // return position before inserted text
384− if assoc == Assoc::Before {
465+ new_pos + assoc.insert_offset(s)
385− return new_pos;
386− } else {
387− // after text
388− return new_pos + ins;
389− }
466+ }),
390− }
467+ i
468+ );
391 }
469 }
392
470
393− new_pos += ins;
471+ new_pos += s.chars().count();
394 }
472 }
395 }
473 }
396 old_pos = old_end;
474 old_pos = old_end;
397 }
475 }
476+ let out_of_bounds: Vec<_> = positions.collect();
398
477
399− if pos > old_pos {
400− panic!(
401− "Position {} is out of range for changeset len {}!",
478+ panic!("Positions {out_of_bounds:?} are out of range for changeset len {old_pos}!",)
402− pos, old_pos
479+ }
403− )
480+
404− }
481+ /// Map a position through the changes.
405− 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
406 }
490 }
407
491
408 pub fn changes_iter(&self) -> ChangeIterator {
492 pub fn changes_iter(&self) -> ChangeIterator {
@@ -422,7 +506,7 @@ impl Transaction {
422 /// Create a new, empty transaction.
506 /// Create a new, empty transaction.
423 pub fn new(doc: &Rope) -> Self {
507 pub fn new(doc: &Rope) -> Self {
424 Self {
508 Self {
425− changes: ChangeSet::new(doc),
509+ changes: ChangeSet::new(doc.slice(..)),
426 selection: None,
510 selection: None,
427 }
511 }
428 }
512 }
@@ -803,6 +887,62 @@ mod test {
803 };
887 };
804 assert_eq!(cs.map_pos(2, Assoc::Before), 2);
888 assert_eq!(cs.map_pos(2, Assoc::Before), 2);
805 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);
806 }
946 }
807
947
808 #[test]
948 #[test]
@@ -869,9 +1009,9 @@ mod test {
869 #[test]
1009 #[test]
870 fn combine_with_empty() {
1010 fn combine_with_empty() {
871 let empty = Rope::from("");
1011 let empty = Rope::from("");
872− let a = ChangeSet::new(&empty);
1012+ let a = ChangeSet::new(empty.slice(..));
873
1013
874− let mut b = ChangeSet::new(&empty);
1014+ let mut b = ChangeSet::new(empty.slice(..));
875 b.insert("a".into());
1015 b.insert("a".into());
876
1016
877 let changes = a.compose(b);
1017 let changes = a.compose(b);
@@ -885,9 +1025,9 @@ mod test {
885 const TEST_CASE: &str = "Hello, これはヘリックスエディターです!";
1025 const TEST_CASE: &str = "Hello, これはヘリックスエディターです!";
886
1026
887 let empty = Rope::from("");
1027 let empty = Rope::from("");
888− let a = ChangeSet::new(&empty);
1028+ let a = ChangeSet::new(empty.slice(..));
889
1029
890− let mut b = ChangeSet::new(&empty);
1030+ let mut b = ChangeSet::new(empty.slice(..));
891 b.insert(TEST_CASE.into());
1031 b.insert(TEST_CASE.into());
892
1032
893 let changes = a.compose(b);
1033 let changes = a.compose(b);
helix-core/src/wrap.rs+3−1
@@ -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.
5pub fn reflow_hard_wrap(text: &str, text_width: usize) -> SmartString<LazyCompact> {
6pub 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, text_width).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+153−25
@@ -1,20 +1,122 @@
1use helix_core::{
1use helix_core::{
2 indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle},
2 indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle},
3− syntax::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+
12#[test]
14#[test]
13−fn test_treesitter_indent_rust_2() {
15+fn test_treesitter_indent_cpp() {
14− test_treesitter_indent("indent.rs", "source.rust");
16+ standard_treesitter_test("cpp.cpp", "source.cpp");
15− // TODO Use commands.rs as indentation test.
17+}
16− // Currently this fails because we can't align the parameters of a closure yet
18+
17− // test_treesitter_indent("commands.rs", "source.rust");
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+ }
18}
120}
19
121
20#[test]
122#[test]
@@ -50,20 +152,41 @@ fn test_indent_level_for_line_with_spaces_and_tabs() {
50 assert_eq!(indent_level, 2)
152 assert_eq!(indent_level, 2)
51}
153}
52
154
53−fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
155+fn indent_tests_dir() -> PathBuf {
54 let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
156 let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
55 test_dir.push("tests/data/indent");
157 test_dir.push("tests/data/indent");
158+ test_dir
159+}
56
160
57− let mut test_file = test_dir.clone();
161+fn indent_test_path(name: &str) -> PathBuf {
58− 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);
59− let test_file = std::fs::File::open(test_file).unwrap();
176+ let test_file = std::fs::File::open(test_path).unwrap();
60 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+}
61
180
62− let mut config_file = test_dir;
181+/// Test that all the lines in the given file are indented as expected.
63− config_file.push("languages.toml");
182+/// ignored_lines is a list of (1-indexed) line ranges that are excluded from this test.
64− let config = std::fs::read_to_string(config_file).unwrap();
183+fn test_treesitter_indent(
65− 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+) {
66− let loader = Loader::new(config);
189+ let loader = Loader::new(indent_tests_config());
67
190
68 // set runtime path so we can find the queries
191 // set runtime path so we can find the queries
69 let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
192 let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
@@ -71,30 +194,35 @@ fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
71 std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
194 std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
72
195
73 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);
74 let highlight_config = language_config.highlight_config(&[]).unwrap();
198 let highlight_config = language_config.highlight_config(&[]).unwrap();
75− let syntax = Syntax::new(&doc, highlight_config, std::sync::Arc::new(loader)).unwrap();
76− let indent_query = language_config.indent_query().unwrap();
77 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();
78
202
79 for i in 0..doc.len_lines() {
203 for i in 0..doc.len_lines() {
80 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+ }
81 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) {
82− let tab_and_indent_width: usize = 4;
209+ let tab_width: usize = 4;
83 let suggested_indent = treesitter_indent_for_pos(
210 let suggested_indent = treesitter_indent_for_pos(
84 indent_query,
211 indent_query,
85 &syntax,
212 &syntax,
86− &IndentStyle::Spaces(tab_and_indent_width as u8),
213+ tab_width,
87− tab_and_indent_width,
214+ indent_style.indent_width(tab_width),
88− tab_and_indent_width,
89 text,
215 text,
90 i,
216 i,
91 text.line_to_char(i) + pos,
217 text.line_to_char(i) + pos,
92 false,
218 false,
93 )
219 )
94− .unwrap();
220+ .unwrap()
221+ .to_string(&indent_style, tab_width);
95 assert!(
222 assert!(
96 line.get_slice(..pos).map_or(false, |s| s == suggested_indent),
223 line.get_slice(..pos).map_or(false, |s| s == suggested_indent),
97− "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,
98 i+1,
226 i+1,
99 line.slice(..line.len_chars()-1),
227 line.slice(..line.len_chars()-1),
100 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-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+14−11
@@ -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"
@@ -18,16 +19,18 @@ anyhow = "1"
18serde = { version = "1.0", features = ["derive"] }
19serde = { version = "1.0", features = ["derive"] }
19toml = "0.7"
20toml = "0.7"
20etcetera = "0.8"
21etcetera = "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" }
30−tempfile = "3.5.0"
32+tempfile = "3.8.1"
33+dunce = "1.0.4"
31
34
32[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
35[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
33libloading = "0.8"
36libloading = "0.8"
helix-loader/build.rs+20−6
@@ -2,7 +2,17 @@ use std::borrow::Cow;
2use std::path::Path;
2use std::path::Path;
3use std::process::Command;
3use std::process::Command;
4
4
5−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+}
6
16
7fn main() {
17fn main() {
8 let git_hash = Command::new("git")
18 let git_hash = Command::new("git")
@@ -12,9 +22,10 @@ fn main() {
12 .filter(|output| output.status.success())
22 .filter(|output| output.status.success())
13 .and_then(|x| String::from_utf8(x.stdout).ok());
23 .and_then(|x| String::from_utf8(x.stdout).ok());
14
24
25+ let calver = get_calver();
15 let version: Cow<_> = match &git_hash {
26 let version: Cow<_> = match &git_hash {
16− Some(git_hash) => format!("{} ({})", VERSION, &git_hash[..8]).into(),
27+ Some(git_hash) => format!("{} ({})", calver, &git_hash[..8]).into(),
17− None => VERSION.into(),
28+ None => calver.into(),
18 };
29 };
19
30
20 println!(
31 println!(
@@ -22,7 +33,6 @@ fn main() {
22 std::env::var("TARGET").unwrap()
33 std::env::var("TARGET").unwrap()
23 );
34 );
24
35
25− println!("cargo:rerun-if-changed=../VERSION");
26 println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
36 println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
27
37
28 if git_hash.is_none() {
38 if git_hash.is_none() {
@@ -40,7 +50,9 @@ fn main() {
40 .ok()
50 .ok()
41 .filter(|output| output.status.success())
51 .filter(|output| output.status.success())
42 .and_then(|x| String::from_utf8(x.stdout).ok())
52 .and_then(|x| String::from_utf8(x.stdout).ok())
53+ else {
43− else{ return; };
54+ return;
55+ };
44 // If heads starts pointing at something else (different branch)
56 // If heads starts pointing at something else (different branch)
45 // we need to return
57 // we need to return
46 let head = Path::new(&git_dir).join("HEAD");
58 let head = Path::new(&git_dir).join("HEAD");
@@ -55,7 +67,9 @@ fn main() {
55 .ok()
67 .ok()
56 .filter(|output| output.status.success())
68 .filter(|output| output.status.success())
57 .and_then(|x| String::from_utf8(x.stdout).ok())
69 .and_then(|x| String::from_utf8(x.stdout).ok())
70+ else {
58− else{ return; };
71+ return;
72+ };
59 let head_ref = Path::new(&git_dir).join(head_ref);
73 let head_ref = Path::new(&git_dir).join(head_ref);
60 if head_ref.exists() {
74 if head_ref.exists() {
61 println!("cargo:rerun-if-changed={}", head_ref.display());
75 println!("cargo:rerun-if-changed={}", head_ref.display());
helix-loader/src/grammar.rs+11
@@ -85,7 +85,16 @@ pub fn get_language(name: &str) -> Result<Language> {
85 Ok(language)
85 Ok(language)
86}
86}
87
87
88+fn ensure_git_is_available() -> Result<()> {
89+ match which::which("git") {
90+ Ok(_cmd) => Ok(()),
91+ Err(err) => Err(anyhow::anyhow!("'git' could not be found ({err})")),
92+ }
93+}
94+
88pub fn fetch_grammars() -> Result<()> {
95pub fn fetch_grammars() -> Result<()> {
96+ ensure_git_is_available()?;
97+
89 // We do not need to fetch local grammars.
98 // We do not need to fetch local grammars.
90 let mut grammars = get_grammar_configs()?;
99 let mut grammars = get_grammar_configs()?;
91 grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. }));
100 grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. }));
@@ -145,6 +154,8 @@ pub fn fetch_grammars() -> Result<()> {
145}
154}
146
155
147pub fn build_grammars(target: Option<String>) -> Result<()> {
156pub fn build_grammars(target: Option<String>) -> Result<()> {
157+ ensure_git_is_available()?;
158+
148 let grammars = get_grammar_configs()?;
159 let grammars = get_grammar_configs()?;
149 println!("Building {} grammars", grammars.len());
160 println!("Building {} grammars", grammars.len());
150 let results = run_parallel(grammars, move |grammar| {
161 let results = run_parallel(grammars, move |grammar| {
helix-loader/src/lib.rs+78−17
@@ -3,29 +3,56 @@ pub mod grammar;
3
3
4use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
4use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
5use std::path::{Path, PathBuf};
5use std::path::{Path, PathBuf};
6+use std::sync::RwLock;
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+
9static RUNTIME_DIRS: once_cell::sync::Lazy<Vec<PathBuf>> =
12static RUNTIME_DIRS: once_cell::sync::Lazy<Vec<PathBuf>> =
10 once_cell::sync::Lazy::new(prioritize_runtime_dirs);
13 once_cell::sync::Lazy::new(prioritize_runtime_dirs);
11
14
12static 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();
13
16
14−pub fn initialize_config_file(specified_file: Option<PathBuf>) {
17+static LOG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
15− let config_file = specified_file.unwrap_or_else(|| {
16− let config_dir = config_dir();
17
18
18− if !config_dir.exists() {
19+// Get the current working directory.
19− 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();
20− }
25+ }
21
26
22− config_dir.join("config.toml")
27+ let path = std::env::current_dir()
23− });
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());
24
32
25− // We should only initialize this value once.
33+ path
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+}
43+
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);
26 CONFIG_FILE.set(config_file).ok();
47 CONFIG_FILE.set(config_file).ok();
27}
48}
28
49
50+pub fn initialize_log_file(specified_file: Option<PathBuf>) {
51+ let log_file = specified_file.unwrap_or_else(default_log_file);
52+ ensure_parent_dir(&log_file);
53+ LOG_FILE.set(log_file).ok();
54+}
55+
29/// A list of runtime directories from highest to lowest priority
56/// A list of runtime directories from highest to lowest priority
30///
57///
31/// The priority is:
58/// The priority is:
@@ -33,7 +60,8 @@ pub fn initialize_config_file(specified_file: Option<PathBuf>) {
33/// 1. sibling directory to `CARGO_MANIFEST_DIR` (if environment variable is set)
60/// 1. sibling directory to `CARGO_MANIFEST_DIR` (if environment variable is set)
34/// 2. subdirectory of user config directory (always included)
61/// 2. subdirectory of user config directory (always included)
35/// 3. `HELIX_RUNTIME` (if environment variable is set)
62/// 3. `HELIX_RUNTIME` (if environment variable is set)
63+/// 4. `HELIX_DEFAULT_RUNTIME` (if environment variable is set *at build time*)
36−/// 4. subdirectory of path to helix executable (always included)
64+/// 5. subdirectory of path to helix executable (always included)
37///
65///
38/// Postcondition: returns at least two paths (they might not exist).
66/// Postcondition: returns at least two paths (they might not exist).
39fn prioritize_runtime_dirs() -> Vec<PathBuf> {
67fn prioritize_runtime_dirs() -> Vec<PathBuf> {
@@ -54,6 +82,14 @@ fn prioritize_runtime_dirs() -> Vec<PathBuf> {
54 rt_dirs.push(dir.into());
82 rt_dirs.push(dir.into());
55 }
83 }
56
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());
91+ }
92+
57 // fallback to location of the executable being run
93 // fallback to location of the executable being run
58 // canonicalize the path in case the executable is symlinked
94 // canonicalize the path in case the executable is symlinked
59 let exe_rt_dir = std::env::current_exe()
95 let exe_rt_dir = std::env::current_exe()
@@ -122,10 +158,11 @@ pub fn cache_dir() -> PathBuf {
122}
158}
123
159
124pub fn config_file() -> PathBuf {
160pub fn config_file() -> PathBuf {
125− CONFIG_FILE
126− .get()
127− .map(|path| path.to_path_buf())
161+ CONFIG_FILE.get().map(|path| path.to_path_buf()).unwrap()
128− .unwrap_or_else(|| config_dir().join("config.toml"))
162+}
163+
164+pub fn log_file() -> PathBuf {
165+ LOG_FILE.get().map(|path| path.to_path_buf()).unwrap()
129}
166}
130
167
131pub fn workspace_config_file() -> PathBuf {
168pub fn workspace_config_file() -> PathBuf {
@@ -136,7 +173,7 @@ pub fn lang_config_file() -> PathBuf {
136 config_dir().join("languages.toml")
173 config_dir().join("languages.toml")
137}
174}
138
175
139−pub fn log_file() -> PathBuf {
176+pub fn default_log_file() -> PathBuf {
140 cache_dir().join("helix.log")
177 cache_dir().join("helix.log")
141}
178}
142
179
@@ -217,7 +254,7 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi
217/// If no workspace was found returns (CWD, true).
254/// If no workspace was found returns (CWD, true).
218/// Otherwise (workspace, false) is returned
255/// Otherwise (workspace, false) is returned
219pub fn find_workspace() -> (PathBuf, bool) {
256pub fn find_workspace() -> (PathBuf, bool) {
220− let current_dir = std::env::current_dir().expect("unable to determine current directory");
257+ let current_dir = current_working_dir();
221 for ancestor in current_dir.ancestors() {
258 for ancestor in current_dir.ancestors() {
222 if ancestor.join(".git").exists() || ancestor.join(".helix").exists() {
259 if ancestor.join(".git").exists() || ancestor.join(".helix").exists() {
223 return (ancestor.to_owned(), false);
260 return (ancestor.to_owned(), false);
@@ -227,13 +264,37 @@ pub fn find_workspace() -> (PathBuf, bool) {
227 (current_dir, true)
264 (current_dir, true)
228}
265}
229
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+
230#[cfg(test)]
279#[cfg(test)]
231mod merge_toml_tests {
280mod merge_toml_tests {
232 use std::str;
281 use std::str;
233
282
234− use super::merge_toml_values;
283+ use super::{current_working_dir, merge_toml_values, set_current_working_dir};
235 use toml::Value;
284 use toml::Value;
236
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+
237 #[test]
298 #[test]
238 fn language_toml_map_merges() {
299 fn language_toml_map_merges() {
239 const USER: &str = r#"
300 const USER: &str = r#"
helix-lsp/Cargo.toml+15−13
@@ -1,30 +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" }
17−helix-parsec = { version = "0.6", path = "../helix-parsec" }
18+helix-parsec = { path = "../helix-parsec" }
18
19
19anyhow = "1.0"
20anyhow = "1.0"
20futures-executor = "0.3"
21futures-executor = "0.3"
21futures-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"
22log = "0.4"
24log = "0.4"
23−lsp-types = { version = "0.94" }
25+lsp-types = { version = "0.95" }
24serde = { version = "1.0", features = ["derive"] }
26serde = { version = "1.0", features = ["derive"] }
25serde_json = "1.0"
27serde_json = "1.0"
26thiserror = "1.0"
28thiserror = "1.0"
27−tokio = { version = "1.28", 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"] }
28tokio-stream = "0.1.14"
30tokio-stream = "0.1.14"
29−which = "4.4"
31+which = "5.0.0"
30parking_lot = "0.12.1"
32parking_lot = "0.12.1"
helix-lsp/src/client.rs+146−40
@@ -7,8 +7,9 @@ use crate::{
7use helix_core::{find_workspace, path, syntax::LanguageServerFeature, ChangeSet, Rope};
7use 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};
9use lsp::{
9use lsp::{
10− notification::DidChangeWorkspaceFolders, DidChangeWorkspaceFoldersParams, OneOf,
10+ notification::DidChangeWorkspaceFolders, CodeActionCapabilityResolveSupport,
11+ DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, WorkspaceFolder,
11− PositionEncodingKind, WorkspaceFolder, WorkspaceFoldersChangeEvent,
12+ WorkspaceFoldersChangeEvent,
12};
13};
13use lsp_types as lsp;
14use lsp_types as lsp;
14use parking_lot::Mutex;
15use parking_lot::Mutex;
@@ -276,65 +277,62 @@ impl Client {
276 .expect("language server not yet initialized!")
277 .expect("language server not yet initialized!")
277 }
278 }
278
279
280+ /// Client has to be initialized otherwise this function panics
279− #[inline] // TODO inline?
281+ #[inline]
280 pub fn supports_feature(&self, feature: LanguageServerFeature) -> bool {
282 pub fn supports_feature(&self, feature: LanguageServerFeature) -> bool {
281− let capabilities = match self.capabilities.get() {
283+ let capabilities = self.capabilities();
282− Some(capabilities) => capabilities,
284+
283− None => return false, // not initialized, TODO unwrap/expect instead?
285+ use lsp::*;
284− };
285 match feature {
286 match feature {
286 LanguageServerFeature::Format => matches!(
287 LanguageServerFeature::Format => matches!(
287 capabilities.document_formatting_provider,
288 capabilities.document_formatting_provider,
288− Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
289+ Some(OneOf::Left(true) | OneOf::Right(_))
289 ),
290 ),
290 LanguageServerFeature::GotoDeclaration => matches!(
291 LanguageServerFeature::GotoDeclaration => matches!(
291 capabilities.declaration_provider,
292 capabilities.declaration_provider,
292 Some(
293 Some(
293− lsp::DeclarationCapability::Simple(true)
294+ DeclarationCapability::Simple(true)
294− | lsp::DeclarationCapability::RegistrationOptions(_)
295+ | DeclarationCapability::RegistrationOptions(_)
295− | lsp::DeclarationCapability::Options(_),
296+ | DeclarationCapability::Options(_),
296 )
297 )
297 ),
298 ),
298 LanguageServerFeature::GotoDefinition => matches!(
299 LanguageServerFeature::GotoDefinition => matches!(
299 capabilities.definition_provider,
300 capabilities.definition_provider,
300− Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
301+ Some(OneOf::Left(true) | OneOf::Right(_))
301 ),
302 ),
302 LanguageServerFeature::GotoTypeDefinition => matches!(
303 LanguageServerFeature::GotoTypeDefinition => matches!(
303 capabilities.type_definition_provider,
304 capabilities.type_definition_provider,
304 Some(
305 Some(
305− lsp::TypeDefinitionProviderCapability::Simple(true)
306+ TypeDefinitionProviderCapability::Simple(true)
306− | lsp::TypeDefinitionProviderCapability::Options(_),
307+ | TypeDefinitionProviderCapability::Options(_),
307 )
308 )
308 ),
309 ),
309 LanguageServerFeature::GotoReference => matches!(
310 LanguageServerFeature::GotoReference => matches!(
310 capabilities.references_provider,
311 capabilities.references_provider,
311− Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
312+ Some(OneOf::Left(true) | OneOf::Right(_))
312 ),
313 ),
313 LanguageServerFeature::GotoImplementation => matches!(
314 LanguageServerFeature::GotoImplementation => matches!(
314 capabilities.implementation_provider,
315 capabilities.implementation_provider,
315 Some(
316 Some(
316− lsp::ImplementationProviderCapability::Simple(true)
317+ ImplementationProviderCapability::Simple(true)
317− | lsp::ImplementationProviderCapability::Options(_),
318+ | ImplementationProviderCapability::Options(_),
318 )
319 )
319 ),
320 ),
320 LanguageServerFeature::SignatureHelp => capabilities.signature_help_provider.is_some(),
321 LanguageServerFeature::SignatureHelp => capabilities.signature_help_provider.is_some(),
321 LanguageServerFeature::Hover => matches!(
322 LanguageServerFeature::Hover => matches!(
322 capabilities.hover_provider,
323 capabilities.hover_provider,
323− Some(
324− lsp::HoverProviderCapability::Simple(true)
324+ Some(HoverProviderCapability::Simple(true) | HoverProviderCapability::Options(_),)
325− | lsp::HoverProviderCapability::Options(_),
326− )
327 ),
325 ),
328 LanguageServerFeature::DocumentHighlight => matches!(
326 LanguageServerFeature::DocumentHighlight => matches!(
329 capabilities.document_highlight_provider,
327 capabilities.document_highlight_provider,
330− Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
328+ Some(OneOf::Left(true) | OneOf::Right(_))
331 ),
329 ),
332 LanguageServerFeature::Completion => capabilities.completion_provider.is_some(),
330 LanguageServerFeature::Completion => capabilities.completion_provider.is_some(),
333 LanguageServerFeature::CodeAction => matches!(
331 LanguageServerFeature::CodeAction => matches!(
334 capabilities.code_action_provider,
332 capabilities.code_action_provider,
335 Some(
333 Some(
336− lsp::CodeActionProviderCapability::Simple(true)
334+ CodeActionProviderCapability::Simple(true)
337− | lsp::CodeActionProviderCapability::Options(_),
335+ | CodeActionProviderCapability::Options(_),
338 )
336 )
339 ),
337 ),
340 LanguageServerFeature::WorkspaceCommand => {
338 LanguageServerFeature::WorkspaceCommand => {
@@ -342,23 +340,20 @@ impl Client {
342 }
340 }
343 LanguageServerFeature::DocumentSymbols => matches!(
341 LanguageServerFeature::DocumentSymbols => matches!(
344 capabilities.document_symbol_provider,
342 capabilities.document_symbol_provider,
345− Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
343+ Some(OneOf::Left(true) | OneOf::Right(_))
346 ),
344 ),
347 LanguageServerFeature::WorkspaceSymbols => matches!(
345 LanguageServerFeature::WorkspaceSymbols => matches!(
348 capabilities.workspace_symbol_provider,
346 capabilities.workspace_symbol_provider,
349− Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
347+ Some(OneOf::Left(true) | OneOf::Right(_))
350 ),
348 ),
351 LanguageServerFeature::Diagnostics => true, // there's no extra server capability
349 LanguageServerFeature::Diagnostics => true, // there's no extra server capability
352 LanguageServerFeature::RenameSymbol => matches!(
350 LanguageServerFeature::RenameSymbol => matches!(
353 capabilities.rename_provider,
351 capabilities.rename_provider,
354− Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_))
352+ Some(OneOf::Left(true)) | Some(OneOf::Right(_))
355 ),
353 ),
356 LanguageServerFeature::InlayHints => matches!(
354 LanguageServerFeature::InlayHints => matches!(
357 capabilities.inlay_hint_provider,
355 capabilities.inlay_hint_provider,
358− Some(
359− lsp::OneOf::Left(true)
360− | lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_))
356+ Some(OneOf::Left(true) | OneOf::Right(InlayHintServerCapabilities::Options(_)))
361− )
362 ),
357 ),
363 }
358 }
364 }
359 }
@@ -406,12 +401,22 @@ impl Client {
406 &self,
401 &self,
407 params: R::Params,
402 params: R::Params,
408 ) -> 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>>
409 where
415 where
410 R::Params: serde::Serialize,
416 R::Params: serde::Serialize,
411 {
417 {
412 let server_tx = self.server_tx.clone();
418 let server_tx = self.server_tx.clone();
413 let id = self.next_request_id();
419 let id = self.next_request_id();
414− let timeout_secs = self.req_timeout;
415
420
416 async move {
421 async move {
417 use std::time::Duration;
422 use std::time::Duration;
@@ -549,6 +554,15 @@ impl Client {
549 normalizes_line_endings: Some(false),
554 normalizes_line_endings: Some(false),
550 change_annotation_support: None,
555 change_annotation_support: None,
551 }),
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+ }),
552 ..Default::default()
566 ..Default::default()
553 }),
567 }),
554 text_document: Some(lsp::TextDocumentClientCapabilities {
568 text_document: Some(lsp::TextDocumentClientCapabilities {
@@ -615,6 +629,12 @@ impl Client {
615 .collect(),
629 .collect(),
616 },
630 },
617 }),
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+ }),
618 ..Default::default()
638 ..Default::default()
619 }),
639 }),
620 publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
640 publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
@@ -647,6 +667,7 @@ impl Client {
647 version: Some(String::from(VERSION_AND_GIT_HASH)),
667 version: Some(String::from(VERSION_AND_GIT_HASH)),
648 }),
668 }),
649 locale: None, // TODO
669 locale: None, // TODO
670+ work_done_progress_params: lsp::WorkDoneProgressParams::default(),
650 };
671 };
651
672
652 self.request::<lsp::request::Initialize>(params).await
673 self.request::<lsp::request::Initialize>(params).await
@@ -695,6 +716,65 @@ impl Client {
695 })
716 })
696 }
717 }
697
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+
698 // -------------------------------------------------------------------------------------------
778 // -------------------------------------------------------------------------------------------
699 // Text document
779 // Text document
700 // -------------------------------------------------------------------------------------------
780 // -------------------------------------------------------------------------------------------
@@ -890,20 +970,19 @@ impl Client {
890 ) -> Option<impl Future<Output = Result<()>>> {
970 ) -> Option<impl Future<Output = Result<()>>> {
891 let capabilities = self.capabilities.get().unwrap();
971 let capabilities = self.capabilities.get().unwrap();
892
972
893− let include_text = match &capabilities.text_document_sync {
973+ let include_text = match &capabilities.text_document_sync.as_ref()? {
894− Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
974+ lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
895− save: Some(options),
975+ save: options,
896 ..
976 ..
897− })) => match options {
977+ }) => match options.as_ref()? {
898 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
978 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
899 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
979 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
900 include_text,
980 include_text,
901 }) => include_text.unwrap_or(false),
981 }) => include_text.unwrap_or(false),
902− // Supported(false)
982+ lsp::TextDocumentSyncSaveOptions::Supported(false) => return None,
903− _ => return None,
904 },
983 },
905− // unsupported
984+ // see: https://github.com/microsoft/language-server-protocol/issues/288
906− _ => return None,
985+ lsp::TextDocumentSyncCapability::Kind(..) => false,
907 };
986 };
908
987
909 Some(self.notify::<lsp::notification::DidSaveTextDocument>(
988 Some(self.notify::<lsp::notification::DidSaveTextDocument>(
@@ -960,6 +1039,24 @@ impl Client {
960 Some(self.call::<lsp::request::ResolveCompletionItem>(completion_item))
1039 Some(self.call::<lsp::request::ResolveCompletionItem>(completion_item))
961 }
1040 }
962
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+
963 pub fn text_document_signature_help(
1060 pub fn text_document_signature_help(
964 &self,
1061 &self,
965 text_document: lsp::TextDocumentIdentifier,
1062 text_document: lsp::TextDocumentIdentifier,
@@ -1434,4 +1531,13 @@ impl Client {
1434
1531
1435 Some(self.call::<lsp::request::ExecuteCommand>(params))
1532 Some(self.call::<lsp::request::ExecuteCommand>(params))
1436 }
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+ }
1437}
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+57−41
@@ -1,4 +1,5 @@
1mod client;
1mod client;
2+pub mod file_event;
2pub mod jsonrpc;
3pub mod jsonrpc;
3pub mod snippet;
4pub mod snippet;
4mod transport;
5mod transport;
@@ -377,7 +378,7 @@ pub mod util {
377 .expect("transaction must be valid for primary selection");
378 .expect("transaction must be valid for primary selection");
378 let removed_text = text.slice(removed_start..removed_end);
379 let removed_text = text.slice(removed_start..removed_end);
379
380
380− let (transaction, selection) = Transaction::change_by_selection_ignore_overlapping(
381+ let (transaction, mut selection) = Transaction::change_by_selection_ignore_overlapping(
381 doc,
382 doc,
382 selection,
383 selection,
383 |range| {
384 |range| {
@@ -420,6 +421,11 @@ pub mod util {
420 return transaction;
421 return transaction;
421 }
422 }
422
423
424+ // Don't normalize to avoid merging/reording selections which would
425+ // break the association between tabstops and selections. Most ranges
426+ // will be replaced by tabstops anyways and the final selection will be
427+ // normalized anyways
428+ selection = selection.map_no_normalize(changes);
423 let mut mapped_selection = SmallVec::with_capacity(selection.len());
429 let mut mapped_selection = SmallVec::with_capacity(selection.len());
424 let mut mapped_primary_idx = 0;
430 let mut mapped_primary_idx = 0;
425 let primary_range = selection.primary();
431 let primary_range = selection.primary();
@@ -428,9 +434,8 @@ pub mod util {
428 mapped_primary_idx = mapped_selection.len()
434 mapped_primary_idx = mapped_selection.len()
429 }
435 }
430
436
431− let range = range.map(changes);
432 let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty());
437 let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty());
433− let Some(tabstops) = tabstops else{
438+ let Some(tabstops) = tabstops else {
434 // no tabstop normal mapping
439 // no tabstop normal mapping
435 mapped_selection.push(range);
440 mapped_selection.push(range);
436 continue;
441 continue;
@@ -440,36 +445,36 @@ pub mod util {
440 // the tabstop closest to the range simply replaces `head` while anchor remains in place
445 // the tabstop closest to the range simply replaces `head` while anchor remains in place
441 // the remaining tabstops receive their own single-width cursor
446 // the remaining tabstops receive their own single-width cursor
442 if range.head < range.anchor {
447 if range.head < range.anchor {
448+ let last_idx = tabstops.len() - 1;
443− let first_tabstop = tabstop_anchor + tabstops[0].1;
449+ let last_tabstop = tabstop_anchor + tabstops[last_idx].0;
444
450
445 // if selection is forward but was moved to the right it is
451 // if selection is forward but was moved to the right it is
446 // contained entirely in the replacement text, just do a point
452 // contained entirely in the replacement text, just do a point
447 // selection (fallback below)
453 // selection (fallback below)
448− if range.anchor >= first_tabstop {
454+ if range.anchor > last_tabstop {
449− let range = Range::new(range.anchor, first_tabstop);
455+ let range = Range::new(range.anchor, last_tabstop);
450 mapped_selection.push(range);
456 mapped_selection.push(range);
451− let rem_tabstops = tabstops[1..]
457+ let rem_tabstops = tabstops[..last_idx]
452 .iter()
458 .iter()
453− .map(|tabstop| Range::point(tabstop_anchor + tabstop.1));
459+ .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
454 mapped_selection.extend(rem_tabstops);
460 mapped_selection.extend(rem_tabstops);
455 continue;
461 continue;
456 }
462 }
457 } else {
463 } else {
458− let last_idx = tabstops.len() - 1;
459− let last_tabstop = tabstop_anchor + tabstops[last_idx].1;
464+ let first_tabstop = tabstop_anchor + tabstops[0].0;
460
465
461 // if selection is forward but was moved to the right it is
466 // if selection is forward but was moved to the right it is
462 // contained entirely in the replacement text, just do a point
467 // contained entirely in the replacement text, just do a point
463 // selection (fallback below)
468 // selection (fallback below)
464− if range.anchor <= last_tabstop {
469+ if range.anchor < first_tabstop {
465 // we can't properly compute the the next grapheme
470 // we can't properly compute the the next grapheme
466 // here because the transaction hasn't been applied yet
471 // here because the transaction hasn't been applied yet
467 // that is not a problem because the range gets grapheme aligned anyway
472 // that is not a problem because the range gets grapheme aligned anyway
468 // tough so just adding one will always cause head to be grapheme
473 // tough so just adding one will always cause head to be grapheme
469 // aligned correctly when applied to the document
474 // aligned correctly when applied to the document
470− let range = Range::new(range.anchor, last_tabstop + 1);
475+ let range = Range::new(range.anchor, first_tabstop + 1);
471 mapped_selection.push(range);
476 mapped_selection.push(range);
472− let rem_tabstops = tabstops[..last_idx]
477+ let rem_tabstops = tabstops[1..]
473 .iter()
478 .iter()
474 .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
479 .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
475 mapped_selection.extend(rem_tabstops);
480 mapped_selection.extend(rem_tabstops);
@@ -543,6 +548,7 @@ pub enum MethodCall {
543 WorkspaceFolders,
548 WorkspaceFolders,
544 WorkspaceConfiguration(lsp::ConfigurationParams),
549 WorkspaceConfiguration(lsp::ConfigurationParams),
545 RegisterCapability(lsp::RegistrationParams),
550 RegisterCapability(lsp::RegistrationParams),
551+ UnregisterCapability(lsp::UnregistrationParams),
546}
552}
547
553
548impl MethodCall {
554impl MethodCall {
@@ -566,6 +572,10 @@ impl MethodCall {
566 let params: lsp::RegistrationParams = params.parse()?;
572 let params: lsp::RegistrationParams = params.parse()?;
567 Self::RegisterCapability(params)
573 Self::RegisterCapability(params)
568 }
574 }
575+ lsp::request::UnregisterCapability::METHOD => {
576+ let params: lsp::UnregistrationParams = params.parse()?;
577+ Self::UnregisterCapability(params)
578+ }
569 _ => {
579 _ => {
570 return Err(Error::Unhandled);
580 return Err(Error::Unhandled);
571 }
581 }
@@ -625,6 +635,7 @@ pub struct Registry {
625 syn_loader: Arc<helix_core::syntax::Loader>,
635 syn_loader: Arc<helix_core::syntax::Loader>,
626 counter: usize,
636 counter: usize,
627 pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
637 pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
638+ pub file_event_handler: file_event::Handler,
628}
639}
629
640
630impl Registry {
641impl Registry {
@@ -634,6 +645,7 @@ impl Registry {
634 syn_loader,
645 syn_loader,
635 counter: 0,
646 counter: 0,
636 incoming: SelectAll::new(),
647 incoming: SelectAll::new(),
648+ file_event_handler: file_event::Handler::new(),
637 }
649 }
638 }
650 }
639
651
@@ -646,6 +658,7 @@ impl Registry {
646 }
658 }
647
659
648 pub fn remove_by_id(&mut self, id: usize) {
660 pub fn remove_by_id(&mut self, id: usize) {
661+ self.file_event_handler.remove_client(id);
649 self.inner.retain(|_, language_servers| {
662 self.inner.retain(|_, language_servers| {
650 language_servers.retain(|ls| id != ls.id());
663 language_servers.retain(|ls| id != ls.id());
651 !language_servers.is_empty()
664 !language_servers.is_empty()
@@ -665,8 +678,8 @@ impl Registry {
665 .language_server_configs()
678 .language_server_configs()
666 .get(&name)
679 .get(&name)
667 .ok_or_else(|| anyhow::anyhow!("Language server '{name}' not defined"))?;
680 .ok_or_else(|| anyhow::anyhow!("Language server '{name}' not defined"))?;
668− self.counter += 1;
669 let id = self.counter;
681 let id = self.counter;
682+ self.counter += 1;
670 let NewClient(client, incoming) = start_client(
683 let NewClient(client, incoming) = start_client(
671 id,
684 id,
672 name,
685 name,
@@ -680,6 +693,9 @@ impl Registry {
680 Ok(client)
693 Ok(client)
681 }
694 }
682
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
683 pub fn restart(
699 pub fn restart(
684 &mut self,
700 &mut self,
685 language_config: &LanguageConfiguration,
701 language_config: &LanguageConfiguration,
@@ -691,7 +707,6 @@ impl Registry {
691 .language_servers
707 .language_servers
692 .iter()
708 .iter()
693 .filter_map(|LanguageServerFeatures { name, .. }| {
709 .filter_map(|LanguageServerFeatures { name, .. }| {
694− // #[allow(clippy::map_entry)]
695 if self.inner.contains_key(name) {
710 if self.inner.contains_key(name) {
696 let client = match self.start_client(
711 let client = match self.start_client(
697 name.clone(),
712 name.clone(),
@@ -708,9 +723,8 @@ impl Registry {
708 .insert(name.clone(), vec![client.clone()])
723 .insert(name.clone(), vec![client.clone()])
709 .unwrap();
724 .unwrap();
710
725
711− // TODO what if there are different language servers for different workspaces,
712− // I think the language servers will be stopped without being restarted, which is not intended
713 for old_client in old_clients {
726 for old_client in old_clients {
727+ self.file_event_handler.remove_client(old_client.id());
714 tokio::spawn(async move {
728 tokio::spawn(async move {
715 let _ = old_client.force_shutdown().await;
729 let _ = old_client.force_shutdown().await;
716 });
730 });
@@ -727,6 +741,7 @@ impl Registry {
727 pub fn stop(&mut self, name: &str) {
741 pub fn stop(&mut self, name: &str) {
728 if let Some(clients) = self.inner.remove(name) {
742 if let Some(clients) = self.inner.remove(name) {
729 for client in clients {
743 for client in clients {
744+ self.file_event_handler.remove_client(client.id());
730 tokio::spawn(async move {
745 tokio::spawn(async move {
731 let _ = client.force_shutdown().await;
746 let _ = client.force_shutdown().await;
732 });
747 });
@@ -734,39 +749,40 @@ impl Registry {
734 }
749 }
735 }
750 }
736
751
737− pub fn get(
752+ pub fn get<'a>(
738− &mut self,
753+ &'a mut self,
739− language_config: &LanguageConfiguration,
754+ language_config: &'a LanguageConfiguration,
740− doc_path: Option<&std::path::PathBuf>,
755+ doc_path: Option<&'a std::path::PathBuf>,
741− root_dirs: &[PathBuf],
756+ root_dirs: &'a [PathBuf],
742 enable_snippets: bool,
757 enable_snippets: bool,
743− ) -> Result<HashMap<LanguageServerName, Arc<Client>>> {
758+ ) -> impl Iterator<Item = (LanguageServerName, Result<Arc<Client>>)> + 'a {
744− language_config
745− .language_servers
759+ language_config.language_servers.iter().map(
746− .iter()
747− .map(|LanguageServerFeatures { name, .. }| {
760+ move |LanguageServerFeatures { name, .. }| {
748− if let Some(clients) = self.inner.get_mut(name) {
761+ if let Some(clients) = self.inner.get(name) {
749− // clients.find(
750−
751− if let Some((_, client)) = clients.iter_mut().enumerate().find(|(i, client)| {
762+ if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| {
752 client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
763 client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
753 }) {
764 }) {
754− return Ok((name.to_owned(), client.clone()));
765+ return (name.to_owned(), Ok(client.clone()));
755 }
766 }
756− // return Ok((name.clone(), clients.clone()));
757 }
767 }
758− let client = self.start_client(
768+ match self.start_client(
759 name.clone(),
769 name.clone(),
760 language_config,
770 language_config,
761 doc_path,
771 doc_path,
762 root_dirs,
772 root_dirs,
763 enable_snippets,
773 enable_snippets,
764− )?;
774+ ) {
765− let clients = self.inner.entry(name.clone()).or_default();
775+ Ok(client) => {
776+ self.inner
777+ .entry(name.to_owned())
778+ .or_default()
766− clients.push(client.clone());
779+ .push(client.clone());
767− Ok((name.clone(), client))
780+ (name.clone(), Ok(client))
768− })
781+ }
769− .collect()
782+ Err(err) => (name.to_owned(), Err(err)),
783+ }
784+ },
785+ )
770 }
786 }
771
787
772 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
788 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
@@ -930,7 +946,7 @@ pub fn find_lsp_workspace(
930 let mut file = if file.is_absolute() {
946 let mut file = if file.is_absolute() {
931 file.to_path_buf()
947 file.to_path_buf()
932 } else {
948 } else {
933− let current_dir = std::env::current_dir().expect("unable to determine current directory");
949+ let current_dir = helix_loader::current_working_dir();
934 current_dir.join(file)
950 current_dir.join(file)
935 };
951 };
936 file = path::get_normalized_path(&file);
952 file = path::get_normalized_path(&file);
helix-lsp/src/transport.rs+9−1
@@ -353,6 +353,11 @@ impl Transport {
353 }
353 }
354 }
354 }
355
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+
356 // TODO: events that use capabilities need to do the right thing
361 // TODO: events that use capabilities need to do the right thing
357
362
358 loop {
363 loop {
@@ -391,7 +396,10 @@ impl Transport {
391 }
396 }
392 msg = client_rx.recv() => {
397 msg = client_rx.recv() => {
393 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;
394− if is_pending && !is_initialize(&msg) {
402+ } else if is_pending && !is_initialize(&msg) {
395 // ignore notifications
403 // ignore notifications
396 if let Payload::Notification(_) = msg {
404 if let Payload::Notification(_) = msg {
397 continue;
405 continue;
helix-parsec/Cargo.toml+8−7
@@ -1,13 +1,14 @@
1[package]
1[package]
2name = "helix-parsec"
2name = "helix-parsec"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
7description = "Parser combinators for Helix"
3description = "Parser combinators for Helix"
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[dependencies]
14[dependencies]
helix-term/Cargo.toml+33−25
@@ -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.65"
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.26", 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,17 +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.11"
71+grep-regex = "0.1.12"
67−grep-searcher = "0.1.11"
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"] }
71−libc = "0.2.144"
76+libc = "0.2.151"
77+
78+[target.'cfg(target_os = "macos")'.dependencies]
79+crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] }
72
80
73[build-dependencies]
81[build-dependencies]
74−helix-loader = { version = "0.6", path = "../helix-loader" }
82+helix-loader = { path = "../helix-loader" }
75
83
76[dev-dependencies]
84[dev-dependencies]
77−smallvec = "1.10"
85+smallvec = "1.11"
78−indoc = "2.0.1"
86+indoc = "2.0.4"
79−tempfile = "3.4.0"
87+tempfile = "3.8.1"
helix-term/src/application.rs+239−142
@@ -1,11 +1,16 @@
1use arc_swap::{access::Map, ArcSwap};
1use arc_swap::{access::Map, ArcSwap};
2use futures_util::Stream;
2use futures_util::Stream;
3use helix_core::{
3use helix_core::{
4+ chars::char_is_word,
4 diagnostic::{DiagnosticTag, NumberOrString},
5 diagnostic::{DiagnosticTag, NumberOrString},
5 path::get_relative_path,
6 path::get_relative_path,
6 pos_at_coords, syntax, Selection,
7 pos_at_coords, syntax, Selection,
7};
8};
9+use helix_lsp::{
10+ lsp::{self, notification::Notification},
8−use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
11+ util::lsp_pos_to_pos,
12+ LspProgressMap,
13+};
9use helix_view::{
14use helix_view::{
10 align_view,
15 align_view,
11 document::DocumentSavedEventResult,
16 document::DocumentSavedEventResult,
@@ -29,13 +34,9 @@ use crate::{
29};
34};
30
35
31use log::{debug, error, warn};
36use log::{debug, error, warn};
32−use std::{
37+#[cfg(not(feature = "integration"))]
38+use std::io::stdout;
33− collections::btree_map::Entry,
39+use std::{collections::btree_map::Entry, io::stdin, path::Path, sync::Arc};
34− io::{stdin, stdout},
35− path::Path,
36− sync::Arc,
37− time::{Duration, Instant},
38−};
39
40
40use anyhow::{Context, Error};
41use anyhow::{Context, Error};
41
42
@@ -45,8 +46,6 @@ use {signal_hook::consts::signal, signal_hook_tokio::Signals};
45#[cfg(windows)]
46#[cfg(windows)]
46type Signals = futures_util::stream::Empty<()>;
47type Signals = futures_util::stream::Empty<()>;
47
48
48−const LSP_DEADLINE: Duration = Duration::from_millis(16);
49−
50#[cfg(not(feature = "integration"))]
49#[cfg(not(feature = "integration"))]
51use tui::backend::CrosstermBackend;
50use tui::backend::CrosstermBackend;
52
51
@@ -76,7 +75,6 @@ pub struct Application {
76 signals: Signals,
75 signals: Signals,
77 jobs: Jobs,
76 jobs: Jobs,
78 lsp_progress: LspProgressMap,
77 lsp_progress: LspProgressMap,
79− last_render: Instant,
80}
78}
81
79
82#[cfg(feature = "integration")]
80#[cfg(feature = "integration")]
@@ -163,17 +161,21 @@ impl Application {
163 let path = helix_loader::runtime_file(Path::new("tutor"));
161 let path = helix_loader::runtime_file(Path::new("tutor"));
164 editor.open(&path, Action::VerticalSplit)?;
162 editor.open(&path, Action::VerticalSplit)?;
165 // Unset path to prevent accidentally saving to the original tutor file.
163 // Unset path to prevent accidentally saving to the original tutor file.
166− doc_mut!(editor).set_path(None)?;
164+ doc_mut!(editor).set_path(None);
167 } else if !args.files.is_empty() {
165 } else if !args.files.is_empty() {
168− let first = &args.files[0].0; // we know it's not empty
166+ let mut files_it = args.files.into_iter().peekable();
167+
168+ // If the first file is a directory, skip it and open a picker
169− if first.is_dir() {
169+ if let Some((first, _)) = files_it.next_if(|(p, _)| p.is_dir()) {
170− std::env::set_current_dir(first).context("set current dir")?;
171− editor.new_file(Action::VerticalSplit);
172− let picker = ui::file_picker(".".into(), &config.load().editor);
170+ let picker = ui::file_picker(first, &config.load().editor);
173 compositor.push(Box::new(overlaid(picker)));
171 compositor.push(Box::new(overlaid(picker)));
174− } else {
172+ }
173+
174+ // If there are any more files specified, open them
175+ if files_it.peek().is_some() {
175− let nr_of_files = args.files.len();
176+ let mut nr_of_files = 0;
176− for (i, (file, pos)) in args.files.into_iter().enumerate() {
177+ for (file, pos) in files_it {
178+ nr_of_files += 1;
177 if file.is_dir() {
179 if file.is_dir() {
178 return Err(anyhow::anyhow!(
180 return Err(anyhow::anyhow!(
179 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
181 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
@@ -185,7 +187,7 @@ impl Application {
185 // option. If neither of those two arguments are passed
187 // option. If neither of those two arguments are passed
186 // in, just load the files normally.
188 // in, just load the files normally.
187 let action = match args.split {
189 let action = match args.split {
188− _ if i == 0 => Action::VerticalSplit,
190+ _ if nr_of_files == 1 => Action::VerticalSplit,
189 Some(Layout::Vertical) => Action::VerticalSplit,
191 Some(Layout::Vertical) => Action::VerticalSplit,
190 Some(Layout::Horizontal) => Action::HorizontalSplit,
192 Some(Layout::Horizontal) => Action::HorizontalSplit,
191 None => Action::Load,
193 None => Action::Load,
@@ -212,14 +214,11 @@ impl Application {
212 // does not affect views without pos since it is at the top
214 // does not affect views without pos since it is at the top
213 let (view, doc) = current!(editor);
215 let (view, doc) = current!(editor);
214 align_view(doc, view, Align::Center);
216 align_view(doc, view, Align::Center);
217+ } else {
218+ editor.new_file(Action::VerticalSplit);
215 }
219 }
216 } else if stdin().is_tty() || cfg!(feature = "integration") {
220 } else if stdin().is_tty() || cfg!(feature = "integration") {
217 editor.new_file(Action::VerticalSplit);
221 editor.new_file(Action::VerticalSplit);
218− } else if cfg!(target_os = "macos") {
219− // On Linux and Windows, we allow the output of a command to be piped into the new buffer.
220− // This doesn't currently work on macOS because of the following issue:
221− // https://github.com/crossterm-rs/crossterm/issues/500
222− anyhow::bail!("Piping into helix-term is currently not supported on macOS");
223 } else {
222 } else {
224 editor
223 editor
225 .new_file_from_stdin(Action::VerticalSplit)
224 .new_file_from_stdin(Action::VerticalSplit)
@@ -231,8 +230,14 @@ impl Application {
231 #[cfg(windows)]
230 #[cfg(windows)]
232 let signals = futures_util::stream::empty();
231 let signals = futures_util::stream::empty();
233 #[cfg(not(windows))]
232 #[cfg(not(windows))]
234− let signals = Signals::new([signal::SIGTSTP, signal::SIGCONT, signal::SIGUSR1])
233+ let signals = Signals::new([
234+ signal::SIGTSTP,
235+ signal::SIGCONT,
236+ signal::SIGUSR1,
237+ signal::SIGTERM,
238+ signal::SIGINT,
239+ ])
235− .context("build signal handler")?;
240+ .context("build signal handler")?;
236
241
237 let app = Self {
242 let app = Self {
238 compositor,
243 compositor,
@@ -247,29 +252,25 @@ impl Application {
247 signals,
252 signals,
248 jobs: Jobs::new(),
253 jobs: Jobs::new(),
249 lsp_progress: LspProgressMap::new(),
254 lsp_progress: LspProgressMap::new(),
250− last_render: Instant::now(),
251 };
255 };
252
256
253 Ok(app)
257 Ok(app)
254 }
258 }
255
259
256 async fn render(&mut self) {
260 async fn render(&mut self) {
261+ if self.compositor.full_redraw {
262+ self.terminal.clear().expect("Cannot clear the terminal");
263+ self.compositor.full_redraw = false;
264+ }
265+
257 let mut cx = crate::compositor::Context {
266 let mut cx = crate::compositor::Context {
258 editor: &mut self.editor,
267 editor: &mut self.editor,
259 jobs: &mut self.jobs,
268 jobs: &mut self.jobs,
260 scroll: None,
269 scroll: None,
261 };
270 };
262
271
263− // Acquire mutable access to the redraw_handle lock
272+ helix_event::start_frame();
264− // to ensure that there are no tasks running that want to block rendering
265− drop(cx.editor.redraw_handle.1.write().await);
266 cx.editor.needs_redraw = false;
273 cx.editor.needs_redraw = false;
267− {
268− // exhaust any leftover redraw notifications
269− let notify = cx.editor.redraw_handle.0.notified();
270− tokio::pin!(notify);
271− notify.enable();
272− }
273
274
274 let area = self
275 let area = self
275 .terminal
276 .terminal
@@ -291,10 +292,9 @@ impl Application {
291
292
292 pub async fn event_loop<S>(&mut self, input_stream: &mut S)
293 pub async fn event_loop<S>(&mut self, input_stream: &mut S)
293 where
294 where
294− S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
295+ S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
295 {
296 {
296 self.render().await;
297 self.render().await;
297− self.last_render = Instant::now();
298
298
299 loop {
299 loop {
300 if !self.event_loop_until_idle(input_stream).await {
300 if !self.event_loop_until_idle(input_stream).await {
@@ -305,7 +305,7 @@ impl Application {
305
305
306 pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool
306 pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool
307 where
307 where
308− S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
308+ S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
309 {
309 {
310 loop {
310 loop {
311 if self.editor.should_close() {
311 if self.editor.should_close() {
@@ -318,7 +318,9 @@ impl Application {
318 biased;
318 biased;
319
319
320 Some(signal) = self.signals.next() => {
320 Some(signal) = self.signals.next() => {
321− self.handle_signals(signal).await;
321+ if !self.handle_signals(signal).await {
322+ return false;
323+ };
322 }
324 }
323 Some(event) = input_stream.next() => {
325 Some(event) = input_stream.next() => {
324 self.handle_terminal_events(event).await;
326 self.handle_terminal_events(event).await;
@@ -442,10 +444,12 @@ impl Application {
442
444
443 #[cfg(windows)]
445 #[cfg(windows)]
444 // no signal handling available on windows
446 // no signal handling available on windows
445− pub async fn handle_signals(&mut self, _signal: ()) {}
447+ pub async fn handle_signals(&mut self, _signal: ()) -> bool {
448+ true
449+ }
446
450
447 #[cfg(not(windows))]
451 #[cfg(not(windows))]
448− pub async fn handle_signals(&mut self, signal: i32) {
452+ pub async fn handle_signals(&mut self, signal: i32) -> bool {
449 match signal {
453 match signal {
450 signal::SIGTSTP => {
454 signal::SIGTSTP => {
451 self.restore_term().unwrap();
455 self.restore_term().unwrap();
@@ -499,8 +503,14 @@ impl Application {
499 self.refresh_config();
503 self.refresh_config();
500 self.render().await;
504 self.render().await;
501 }
505 }
506+ signal::SIGTERM | signal::SIGINT => {
507+ self.restore_term().unwrap();
508+ return false;
509+ }
502 _ => unreachable!(),
510 _ => unreachable!(),
503 }
511 }
512+
513+ true
504 }
514 }
505
515
506 pub async fn handle_idle_timeout(&mut self) {
516 pub async fn handle_idle_timeout(&mut self) {
@@ -548,16 +558,7 @@ impl Application {
548 let bytes = doc_save_event.text.len_bytes();
558 let bytes = doc_save_event.text.len_bytes();
549
559
550 if doc.path() != Some(&doc_save_event.path) {
560 if doc.path() != Some(&doc_save_event.path) {
551− if let Err(err) = doc.set_path(Some(&doc_save_event.path)) {
561+ doc.set_path(Some(&doc_save_event.path));
552− log::error!(
553− "error setting path for doc '{:?}': {}",
554− doc.path(),
555− err.to_string(),
556− );
557−
558− self.editor.set_error(err.to_string());
559− return;
560− }
561
562
562 let loader = self.editor.syn_loader.clone();
563 let loader = self.editor.syn_loader.clone();
563
564
@@ -593,12 +594,7 @@ impl Application {
593 EditorEvent::LanguageServerMessage((id, call)) => {
594 EditorEvent::LanguageServerMessage((id, call)) => {
594 self.handle_language_server_message(call, id).await;
595 self.handle_language_server_message(call, id).await;
595 // limit render calls for fast language server messages
596 // limit render calls for fast language server messages
596− let last = self.editor.language_servers.incoming.is_empty();
597+ helix_event::request_redraw();
597−
598− if last || self.last_render.elapsed() > LSP_DEADLINE {
599− self.render().await;
600− self.last_render = Instant::now();
601− }
602 }
598 }
603 EditorEvent::DebuggerEvent(payload) => {
599 EditorEvent::DebuggerEvent(payload) => {
604 let needs_render = self.editor.handle_debugger_message(payload).await;
600 let needs_render = self.editor.handle_debugger_message(payload).await;
@@ -606,6 +602,9 @@ impl Application {
606 self.render().await;
602 self.render().await;
607 }
603 }
608 }
604 }
605+ EditorEvent::Redraw => {
606+ self.render().await;
607+ }
609 EditorEvent::IdleTimer => {
608 EditorEvent::IdleTimer => {
610 self.editor.clear_idle_timer();
609 self.editor.clear_idle_timer();
611 self.handle_idle_timeout().await;
610 self.handle_idle_timeout().await;
@@ -620,10 +619,7 @@ impl Application {
620 false
619 false
621 }
620 }
622
621
623− pub async fn handle_terminal_events(
622+ pub async fn handle_terminal_events(&mut self, event: std::io::Result<CrosstermEvent>) {
624− &mut self,
625− event: Result<CrosstermEvent, crossterm::ErrorKind>,
626− ) {
627 let mut cx = crate::compositor::Context {
623 let mut cx = crate::compositor::Context {
628 editor: &mut self.editor,
624 editor: &mut self.editor,
629 jobs: &mut self.jobs,
625 jobs: &mut self.jobs,
@@ -665,7 +661,7 @@ impl Application {
665
661
666 macro_rules! language_server {
662 macro_rules! language_server {
667 () => {
663 () => {
668− match self.editor.language_servers.get_by_id(server_id) {
664+ match self.editor.language_server_by_id(server_id) {
669 Some(language_server) => language_server,
665 Some(language_server) => language_server,
670 None => {
666 None => {
671 warn!("can't find language server with id `{}`", server_id);
667 warn!("can't find language server with id `{}`", server_id);
@@ -722,7 +718,7 @@ impl Application {
722 ));
718 ));
723 }
719 }
724 }
720 }
725− Notification::PublishDiagnostics(params) => {
721+ Notification::PublishDiagnostics(mut params) => {
726 let path = match params.uri.to_file_path() {
722 let path = match params.uri.to_file_path() {
727 Ok(path) => path,
723 Ok(path) => path,
728 Err(_) => {
724 Err(_) => {
@@ -730,32 +726,75 @@ impl Application {
730 return;
726 return;
731 }
727 }
732 };
728 };
733− let offset_encoding = language_server!().offset_encoding();
729+ let language_server = language_server!();
734− let doc = self.editor.document_by_path_mut(&path).filter(|doc| {
730+ if !language_server.is_initialized() {
731+ log::error!("Discarding publishDiagnostic notification sent by an uninitialized server: {}", language_server.name());
732+ return;
733+ }
734+ let offset_encoding = language_server.offset_encoding();
735+ // have to inline the function because of borrow checking...
736+ let doc = self.editor.documents.values_mut()
737+ .find(|doc| doc.path().map(|p| p == &path).unwrap_or(false))
738+ .filter(|doc| {
735− if let Some(version) = params.version {
739+ if let Some(version) = params.version {
736− if version != doc.version() {
740+ if version != doc.version() {
737− log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
741+ log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
738− return false;
742+ return false;
743+ }
739 }
744 }
740− }
741−
742− true
745+ true
743− });
746+ });
744
747
745 if let Some(doc) = doc {
748 if let Some(doc) = doc {
746− let lang_conf = doc.language_config();
749+ let lang_conf = doc.language.clone();
747− let text = doc.text();
750+ let text = doc.text().clone();
751+
752+ let mut unchaged_diag_sources_ = Vec::new();
753+ if let Some(lang_conf) = &lang_conf {
754+ if let Some(old_diagnostics) =
755+ self.editor.diagnostics.get(&params.uri)
756+ {
757+ if !lang_conf.persistent_diagnostic_sources.is_empty() {
758+ // Sort diagnostics first by severity and then by line numbers.
759+ // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
760+ params
761+ .diagnostics
762+ .sort_unstable_by_key(|d| (d.severity, d.range.start));
763+ }
764+ for source in &lang_conf.persistent_diagnostic_sources {
765+ let new_diagnostics = params
766+ .diagnostics
767+ .iter()
768+ .filter(|d| d.source.as_ref() == Some(source));
769+ let old_diagnostics = old_diagnostics
770+ .iter()
771+ .filter(|(d, d_server)| {
772+ *d_server == server_id
773+ && d.source.as_ref() == Some(source)
774+ })
775+ .map(|(d, _)| d);
776+ if new_diagnostics.eq(old_diagnostics) {
777+ unchaged_diag_sources_.push(source.clone())
778+ }
779+ }
780+ }
781+ }
748
782
783+ let unchaged_diag_sources = &unchaged_diag_sources_;
749− let diagnostics = params
784+ let diagnostics =
750− .diagnostics
751− .iter()
752− .filter_map(|diagnostic| {
785+ params.diagnostics.iter().filter_map(move |diagnostic| {
753 use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
786 use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
754 use lsp::DiagnosticSeverity;
787 use lsp::DiagnosticSeverity;
755
788
789+ if diagnostic.source.as_ref().map_or(false, |source| {
790+ unchaged_diag_sources.contains(source)
791+ }) {
792+ return None;
793+ }
794+
756 // TODO: convert inside server
795 // TODO: convert inside server
757 let start = if let Some(start) = lsp_pos_to_pos(
796 let start = if let Some(start) = lsp_pos_to_pos(
758− text,
797+ &text,
759 diagnostic.range.start,
798 diagnostic.range.start,
760 offset_encoding,
799 offset_encoding,
761 ) {
800 ) {
@@ -766,14 +805,13 @@ impl Application {
766 };
805 };
767
806
768 let end = if let Some(end) =
807 let end = if let Some(end) =
769− lsp_pos_to_pos(text, diagnostic.range.end, offset_encoding)
808+ lsp_pos_to_pos(&text, diagnostic.range.end, offset_encoding)
770 {
809 {
771 end
810 end
772 } else {
811 } else {
773 log::warn!("lsp position out of bounds - {:?}", diagnostic);
812 log::warn!("lsp position out of bounds - {:?}", diagnostic);
774 return None;
813 return None;
775 };
814 };
776−
777 let severity =
815 let severity =
778 diagnostic.severity.map(|severity| match severity {
816 diagnostic.severity.map(|severity| match severity {
779 DiagnosticSeverity::ERROR => Error,
817 DiagnosticSeverity::ERROR => Error,
@@ -786,7 +824,7 @@ impl Application {
786 ),
824 ),
787 });
825 });
788
826
789− if let Some(lang_conf) = lang_conf {
827+ if let Some(lang_conf) = &lang_conf {
790 if let Some(severity) = severity {
828 if let Some(severity) = severity {
791 if severity < lang_conf.diagnostic_severity {
829 if severity < lang_conf.diagnostic_severity {
792 return None;
830 return None;
@@ -825,8 +863,17 @@ impl Application {
825 Vec::new()
863 Vec::new()
826 };
864 };
827
865
866+ let ends_at_word = start != end
867+ && end != 0
868+ && text.get_char(end - 1).map_or(false, char_is_word);
869+ let starts_at_word = start != end
870+ && text.get_char(start).map_or(false, char_is_word);
871+
828 Some(Diagnostic {
872 Some(Diagnostic {
829 range: Range { start, end },
873 range: Range { start, end },
874+ ends_at_word,
875+ starts_at_word,
876+ zero_width: start == end,
830 line: diagnostic.range.start.line as usize,
877 line: diagnostic.range.start.line as usize,
831 message: diagnostic.message.clone(),
878 message: diagnostic.message.clone(),
832 severity,
879 severity,
@@ -836,38 +883,33 @@ impl Application {
836 data: diagnostic.data.clone(),
883 data: diagnostic.data.clone(),
837 language_server_id: server_id,
884 language_server_id: server_id,
838 })
885 })
839− })
886+ });
840− .collect();
841
887
842− doc.replace_diagnostics(diagnostics, server_id);
888+ doc.replace_diagnostics(diagnostics, unchaged_diag_sources, server_id);
843 }
889 }
844
890
845− let mut diagnostics = params
891+ let diagnostics = params.diagnostics.into_iter().map(|d| (d, server_id));
846− .diagnostics
847− .into_iter()
848− .map(|d| (d, server_id))
849− .collect();
850
892
851 // Insert the original lsp::Diagnostics here because we may have no open document
893 // Insert the original lsp::Diagnostics here because we may have no open document
852 // for diagnosic message and so we can't calculate the exact position.
894 // for diagnosic message and so we can't calculate the exact position.
853 // When using them later in the diagnostics picker, we calculate them on-demand.
895 // When using them later in the diagnostics picker, we calculate them on-demand.
854− match self.editor.diagnostics.entry(params.uri) {
896+ let diagnostics = match self.editor.diagnostics.entry(params.uri) {
855 Entry::Occupied(o) => {
897 Entry::Occupied(o) => {
856 let current_diagnostics = o.into_mut();
898 let current_diagnostics = o.into_mut();
857 // there may entries of other language servers, which is why we can't overwrite the whole entry
899 // there may entries of other language servers, which is why we can't overwrite the whole entry
858 current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
900 current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
859− current_diagnostics.append(&mut diagnostics);
901+ current_diagnostics.extend(diagnostics);
860− // Sort diagnostics first by severity and then by line numbers.
861− // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
862 current_diagnostics
902 current_diagnostics
863− .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
903+ // Sort diagnostics first by severity and then by line numbers.
864− }
865− Entry::Vacant(v) => {
866− diagnostics
867− .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
868− v.insert(diagnostics);
869 }
904 }
905+ Entry::Vacant(v) => v.insert(diagnostics.collect()),
870 };
906 };
907+
908+ // Sort diagnostics first by severity and then by line numbers.
909+ // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
910+ diagnostics.sort_unstable_by_key(|(d, server_id)| {
911+ (d.severity, d.range.start, *server_id)
912+ });
871 }
913 }
872 Notification::ShowMessage(params) => {
914 Notification::ShowMessage(params) => {
873 log::warn!("unhandled window/showMessage: {:?}", params);
915 log::warn!("unhandled window/showMessage: {:?}", params);
@@ -964,22 +1006,18 @@ impl Application {
964 Notification::Exit => {
1006 Notification::Exit => {
965 self.editor.set_status("Language server exited");
1007 self.editor.set_status("Language server exited");
966
1008
967− // Clear any diagnostics for documents with this server open.
1009+ // LSPs may produce diagnostics for files that haven't been opened in helix,
968− let urls: Vec<_> = self
1010+ // we need to clear those and remove the entries from the list if this leads to
969− .editor
1011+ // an empty diagnostic list for said files
970− .documents_mut()
1012+ for diags in self.editor.diagnostics.values_mut() {
971− .filter_map(|doc| {
1013+ diags.retain(|(_, lsp_id)| *lsp_id != server_id);
972− if doc.supports_language_server(server_id) {
973− doc.clear_diagnostics(server_id);
974− doc.url()
975− } else {
976− None
977− }
978− })
1014+ }
979− .collect();
980
1015
981− for url in urls {
1016+ self.editor.diagnostics.retain(|_, diags| !diags.is_empty());
982− self.editor.diagnostics.remove(&url);
1017+
1018+ // Clear any diagnostics for documents with this server open.
1019+ for doc in self.editor.documents_mut() {
1020+ doc.clear_diagnostics(server_id);
983 }
1021 }
984
1022
985 // Remove the language server from the registry.
1023 // Remove the language server from the registry.
@@ -1030,20 +1068,31 @@ impl Application {
1030 Ok(serde_json::Value::Null)
1068 Ok(serde_json::Value::Null)
1031 }
1069 }
1032 Ok(MethodCall::ApplyWorkspaceEdit(params)) => {
1070 Ok(MethodCall::ApplyWorkspaceEdit(params)) => {
1071+ let language_server = language_server!();
1072+ if language_server.is_initialized() {
1073+ let offset_encoding = language_server.offset_encoding();
1033− let res = apply_workspace_edit(
1074+ let res = apply_workspace_edit(
1034− &mut self.editor,
1075+ &mut self.editor,
1035− helix_lsp::OffsetEncoding::Utf8,
1076+ offset_encoding,
1036− &params.edit,
1077+ &params.edit,
1037− );
1078+ );
1038−
1079+
1039− Ok(json!(lsp::ApplyWorkspaceEditResponse {
1080+ Ok(json!(lsp::ApplyWorkspaceEditResponse {
1040− applied: res.is_ok(),
1081+ applied: res.is_ok(),
1041− failure_reason: res.as_ref().err().map(|err| err.kind.to_string()),
1082+ failure_reason: res.as_ref().err().map(|err| err.kind.to_string()),
1042− failed_change: res
1083+ failed_change: res
1043− .as_ref()
1084+ .as_ref()
1044− .err()
1085+ .err()
1045− .map(|err| err.failed_change_idx as u32),
1086+ .map(|err| err.failed_change_idx as u32),
1087+ }))
1088+ } else {
1089+ Err(helix_lsp::jsonrpc::Error {
1090+ code: helix_lsp::jsonrpc::ErrorCode::InvalidRequest,
1091+ message: "Server must be initialized to request workspace edits"
1092+ .to_string(),
1093+ data: None,
1046− }))
1094+ })
1095+ }
1047 }
1096 }
1048 Ok(MethodCall::WorkspaceFolders) => {
1097 Ok(MethodCall::WorkspaceFolders) => {
1049 Ok(json!(&*language_server!().workspace_folders().await))
1098 Ok(json!(&*language_server!().workspace_folders().await))
@@ -1068,17 +1117,65 @@ impl Application {
1068 .collect();
1117 .collect();
1069 Ok(json!(result))
1118 Ok(json!(result))
1070 }
1119 }
1071− Ok(MethodCall::RegisterCapability(_params)) => {
1120+ Ok(MethodCall::RegisterCapability(params)) => {
1072− log::warn!("Ignoring a client/registerCapability request because dynamic capability registration is not enabled. Please report this upstream to the language server");
1121+ if let Some(client) = self
1122+ .editor
1123+ .language_servers
1124+ .iter_clients()
1125+ .find(|client| client.id() == server_id)
1126+ {
1127+ for reg in params.registrations {
1128+ match reg.method.as_str() {
1129+ lsp::notification::DidChangeWatchedFiles::METHOD => {
1130+ let Some(options) = reg.register_options else {
1131+ continue;
1132+ };
1133+ let ops: lsp::DidChangeWatchedFilesRegistrationOptions =
1134+ match serde_json::from_value(options) {
1135+ Ok(ops) => ops,
1136+ Err(err) => {
1137+ log::warn!("Failed to deserialize DidChangeWatchedFilesRegistrationOptions: {err}");
1138+ continue;
1139+ }
1140+ };
1141+ self.editor.language_servers.file_event_handler.register(
1142+ client.id(),
1143+ Arc::downgrade(client),
1144+ reg.id,
1145+ ops,
1146+ )
1147+ }
1148+ _ => {
1073− // Language Servers based on the `vscode-languageserver-node` library often send
1149+ // Language Servers based on the `vscode-languageserver-node` library often send
1074− // client/registerCapability even though we do not enable dynamic registration
1150+ // client/registerCapability even though we do not enable dynamic registration
1075− // for any capabilities. We should send a MethodNotFound JSONRPC error in this
1151+ // for most capabilities. We should send a MethodNotFound JSONRPC error in this
1076− // case but that rejects the registration promise in the server which causes an
1152+ // case but that rejects the registration promise in the server which causes an
1077− // exit. So we work around this by ignoring the request and sending back an OK
1153+ // exit. So we work around this by ignoring the request and sending back an OK
1078− // response.
1154+ // response.
1155+ log::warn!("Ignoring a client/registerCapability request because dynamic capability registration is not enabled. Please report this upstream to the language server");
1156+ }
1157+ }
1158+ }
1159+ }
1079
1160
1080 Ok(serde_json::Value::Null)
1161 Ok(serde_json::Value::Null)
1081 }
1162 }
1163+ Ok(MethodCall::UnregisterCapability(params)) => {
1164+ for unreg in params.unregisterations {
1165+ match unreg.method.as_str() {
1166+ lsp::notification::DidChangeWatchedFiles::METHOD => {
1167+ self.editor
1168+ .language_servers
1169+ .file_event_handler
1170+ .unregister(server_id, unreg.id);
1171+ }
1172+ _ => {
1173+ log::warn!("Received unregistration request for unsupported method: {}", unreg.method);
1174+ }
1175+ }
1176+ }
1177+ Ok(serde_json::Value::Null)
1178+ }
1082 };
1179 };
1083
1180
1084 tokio::spawn(language_server!().reply(id, reply));
1181 tokio::spawn(language_server!().reply(id, reply));
@@ -1104,7 +1201,7 @@ impl Application {
1104
1201
1105 pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
1202 pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
1106 where
1203 where
1107− S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
1204+ S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
1108 {
1205 {
1109 self.claim_term().await?;
1206 self.claim_term().await?;
1110
1207
helix-term/src/args.rs+29
@@ -17,12 +17,14 @@ pub struct Args {
17 pub log_file: Option<PathBuf>,
17 pub log_file: Option<PathBuf>,
18 pub config_file: Option<PathBuf>,
18 pub config_file: Option<PathBuf>,
19 pub files: Vec<(PathBuf, Position)>,
19 pub files: Vec<(PathBuf, Position)>,
20+ pub working_directory: Option<PathBuf>,
20}
21}
21
22
22impl Args {
23impl Args {
23 pub fn parse_args() -> Result<Args> {
24 pub fn parse_args() -> Result<Args> {
24 let mut args = Args::default();
25 let mut args = Args::default();
25 let mut argv = std::env::args().peekable();
26 let mut argv = std::env::args().peekable();
27+ let mut line_number = 0;
26
28
27 argv.next(); // skip the program, we don't care about that
29 argv.next(); // skip the program, we don't care about that
28
30
@@ -59,6 +61,20 @@ impl Args {
59 Some(path) => args.log_file = Some(path.into()),
61 Some(path) => args.log_file = Some(path.into()),
60 None => anyhow::bail!("--log must specify a path to write"),
62 None => anyhow::bail!("--log must specify a path to write"),
61 },
63 },
64+ "-w" | "--working-dir" => match argv.next().as_deref() {
65+ Some(path) => {
66+ args.working_directory = if Path::new(path).is_dir() {
67+ Some(PathBuf::from(path))
68+ } else {
69+ anyhow::bail!(
70+ "--working-dir specified does not exist or is not a directory"
71+ )
72+ }
73+ }
74+ None => {
75+ anyhow::bail!("--working-dir must specify an initial working directory")
76+ }
77+ },
62 arg if arg.starts_with("--") => {
78 arg if arg.starts_with("--") => {
63 anyhow::bail!("unexpected double dash argument: {}", arg)
79 anyhow::bail!("unexpected double dash argument: {}", arg)
64 }
80 }
@@ -73,6 +89,13 @@ impl Args {
73 }
89 }
74 }
90 }
75 }
91 }
92+ arg if arg.starts_with('+') => {
93+ let arg = &arg[1..];
94+ line_number = match arg.parse::<usize>() {
95+ Ok(n) => n.saturating_sub(1),
96+ _ => anyhow::bail!("bad line number after +"),
97+ };
98+ }
76 arg => args.files.push(parse_file(arg)),
99 arg => args.files.push(parse_file(arg)),
77 }
100 }
78 }
101 }
@@ -82,6 +105,12 @@ impl Args {
82 args.files.push(parse_file(&arg));
105 args.files.push(parse_file(&arg));
83 }
106 }
84
107
108+ if let Some(file) = args.files.first_mut() {
109+ if line_number != 0 {
110+ file.1.row = line_number;
111+ }
112+ }
113+
85 Ok(args)
114 Ok(args)
86 }
115 }
87}
116}
helix-term/src/commands.rs+810−435
@@ -29,12 +29,11 @@ use helix_core::{
29 tree_sitter::Node,
29 tree_sitter::Node,
30 unicode::width::UnicodeWidthChar,
30 unicode::width::UnicodeWidthChar,
31 visual_offset_from_block, Deletion, LineEnding, Position, Range, Rope, RopeGraphemes,
31 visual_offset_from_block, Deletion, LineEnding, Position, Range, Rope, RopeGraphemes,
32− RopeSlice, Selection, SmallVec, Tendril, Transaction,
32+ RopeReader, RopeSlice, Selection, SmallVec, Tendril, Transaction,
33};
33};
34use helix_view::{
34use helix_view::{
35− clipboard::ClipboardType,
36 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
35 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
37− editor::{Action, CompleteAction, Motion},
36+ editor::{Action, CompleteAction},
38 info::Info,
37 info::Info,
39 input::KeyEvent,
38 input::KeyEvent,
40 keyboard::KeyCode,
39 keyboard::KeyCode,
@@ -44,7 +43,6 @@ use helix_view::{
44};
43};
45
44
46use anyhow::{anyhow, bail, ensure, Context as _};
45use anyhow::{anyhow, bail, ensure, Context as _};
47−use fuzzy_matcher::FuzzyMatcher;
48use insert::*;
46use insert::*;
49use movement::Movement;
47use movement::Movement;
50
48
@@ -55,15 +53,20 @@ use crate::{
55 job::Callback,
53 job::Callback,
56 keymap::ReverseKeymap,
54 keymap::ReverseKeymap,
57 ui::{
55 ui::{
58− self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, CompletionItem,
56+ self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, CompletionItem, Picker,
59− FilePicker, Picker, Popup, Prompt, PromptEvent,
57+ Popup, Prompt, PromptEvent,
60 },
58 },
61};
59};
62
60
63use crate::job::{self, Jobs};
61use crate::job::{self, Jobs};
64−use futures_util::{stream::FuturesUnordered, StreamExt, TryStreamExt};
62+use futures_util::{stream::FuturesUnordered, TryStreamExt};
63+use std::{
65−use std::{collections::HashMap, fmt, future::Future};
64+ collections::{HashMap, HashSet},
65+ fmt,
66+ future::Future,
67+ io::Read,
66−use std::{collections::HashSet, num::NonZeroUsize};
68+ num::NonZeroUsize,
69+};
67
70
68use std::{
71use std::{
69 borrow::Cow,
72 borrow::Cow,
@@ -72,11 +75,11 @@ use std::{
72
75
73use once_cell::sync::Lazy;
76use once_cell::sync::Lazy;
74use serde::de::{self, Deserialize, Deserializer};
77use serde::de::{self, Deserialize, Deserializer};
78+use url::Url;
75
79
76use grep_regex::RegexMatcherBuilder;
80use grep_regex::RegexMatcherBuilder;
77use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
81use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
78use ignore::{DirEntry, WalkBuilder, WalkState};
82use ignore::{DirEntry, WalkBuilder, WalkState};
79−use tokio_stream::wrappers::UnboundedReceiverStream;
80
83
81pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
84pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
82
85
@@ -247,6 +250,9 @@ impl MappableCommand {
247 move_next_long_word_start, "Move to start of next long word",
250 move_next_long_word_start, "Move to start of next long word",
248 move_prev_long_word_start, "Move to start of previous long word",
251 move_prev_long_word_start, "Move to start of previous long word",
249 move_next_long_word_end, "Move to end of next long word",
252 move_next_long_word_end, "Move to end of next long word",
253+ move_prev_long_word_end, "Move to end of previous long word",
254+ move_parent_node_end, "Move to end of the parent node",
255+ move_parent_node_start, "Move to beginning of the parent node",
250 extend_next_word_start, "Extend to start of next word",
256 extend_next_word_start, "Extend to start of next word",
251 extend_prev_word_start, "Extend to start of previous word",
257 extend_prev_word_start, "Extend to start of previous word",
252 extend_next_word_end, "Extend to end of next word",
258 extend_next_word_end, "Extend to end of next word",
@@ -254,6 +260,9 @@ impl MappableCommand {
254 extend_next_long_word_start, "Extend to start of next long word",
260 extend_next_long_word_start, "Extend to start of next long word",
255 extend_prev_long_word_start, "Extend to start of previous long word",
261 extend_prev_long_word_start, "Extend to start of previous long word",
256 extend_next_long_word_end, "Extend to end of next long word",
262 extend_next_long_word_end, "Extend to end of next long word",
263+ extend_prev_long_word_end, "Extend to end of prev long word",
264+ extend_parent_node_end, "Extend to end of the parent node",
265+ extend_parent_node_start, "Extend to beginning of the parent node",
257 find_till_char, "Move till next occurrence of char",
266 find_till_char, "Move till next occurrence of char",
258 find_next_char, "Move to next occurrence of char",
267 find_next_char, "Move to next occurrence of char",
259 extend_till_char, "Extend till next occurrence of char",
268 extend_till_char, "Extend till next occurrence of char",
@@ -275,6 +284,7 @@ impl MappableCommand {
275 select_regex, "Select all regex matches inside selections",
284 select_regex, "Select all regex matches inside selections",
276 split_selection, "Split selections on regex matches",
285 split_selection, "Split selections on regex matches",
277 split_selection_on_newline, "Split selection on newlines",
286 split_selection_on_newline, "Split selection on newlines",
287+ merge_selections, "Merge selections",
278 merge_consecutive_selections, "Merge consecutive selections",
288 merge_consecutive_selections, "Merge consecutive selections",
279 search, "Search for regex pattern",
289 search, "Search for regex pattern",
280 rsearch, "Reverse search for regex pattern",
290 rsearch, "Reverse search for regex pattern",
@@ -327,7 +337,7 @@ impl MappableCommand {
327 goto_implementation, "Goto implementation",
337 goto_implementation, "Goto implementation",
328 goto_file_start, "Goto line number <n> else file start",
338 goto_file_start, "Goto line number <n> else file start",
329 goto_file_end, "Goto file end",
339 goto_file_end, "Goto file end",
330− goto_file, "Goto files in selection",
340+ goto_file, "Goto files/URLs in selection",
331 goto_file_hsplit, "Goto files in selection (hsplit)",
341 goto_file_hsplit, "Goto files in selection (hsplit)",
332 goto_file_vsplit, "Goto files in selection (vsplit)",
342 goto_file_vsplit, "Goto files in selection (vsplit)",
333 goto_reference, "Goto references",
343 goto_reference, "Goto references",
@@ -359,6 +369,7 @@ impl MappableCommand {
359 extend_to_line_end, "Extend to line end",
369 extend_to_line_end, "Extend to line end",
360 extend_to_line_end_newline, "Extend to line end",
370 extend_to_line_end_newline, "Extend to line end",
361 signature_help, "Show signature help",
371 signature_help, "Show signature help",
372+ smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.",
362 insert_tab, "Insert tab char",
373 insert_tab, "Insert tab char",
363 insert_newline, "Insert newline char",
374 insert_newline, "Insert newline char",
364 delete_char_backward, "Delete previous char",
375 delete_char_backward, "Delete previous char",
@@ -373,6 +384,9 @@ impl MappableCommand {
373 later, "Move forward in history",
384 later, "Move forward in history",
374 commit_undo_checkpoint, "Commit changes to new checkpoint",
385 commit_undo_checkpoint, "Commit changes to new checkpoint",
375 yank, "Yank selection",
386 yank, "Yank selection",
387+ yank_to_clipboard, "Yank selections to clipboard",
388+ yank_to_primary_clipboard, "Yank selections to primary clipboard",
389+ yank_joined, "Join and yank selections",
376 yank_joined_to_clipboard, "Join and yank selections to clipboard",
390 yank_joined_to_clipboard, "Join and yank selections to clipboard",
377 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
391 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
378 yank_joined_to_primary_clipboard, "Join and yank selections to primary clipboard",
392 yank_joined_to_primary_clipboard, "Join and yank selections to primary clipboard",
@@ -403,6 +417,7 @@ impl MappableCommand {
403 rotate_selections_backward, "Rotate selections backward",
417 rotate_selections_backward, "Rotate selections backward",
404 rotate_selection_contents_forward, "Rotate selection contents forward",
418 rotate_selection_contents_forward, "Rotate selection contents forward",
405 rotate_selection_contents_backward, "Rotate selections contents backward",
419 rotate_selection_contents_backward, "Rotate selections contents backward",
420+ reverse_selection_contents, "Reverse selections contents",
406 expand_selection, "Expand selection to parent syntax node",
421 expand_selection, "Expand selection to parent syntax node",
407 shrink_selection, "Shrink selection to previously expanded syntax node",
422 shrink_selection, "Shrink selection to previously expanded syntax node",
408 select_next_sibling, "Select next sibling in syntax tree",
423 select_next_sibling, "Select next sibling in syntax tree",
@@ -975,6 +990,7 @@ fn align_selections(cx: &mut Context) {
975
990
976 let transaction = Transaction::change(doc.text(), changes.into_iter());
991 let transaction = Transaction::change(doc.text(), changes.into_iter());
977 doc.apply(&transaction, view.id);
992 doc.apply(&transaction, view.id);
993+ exit_select_mode(cx);
978}
994}
979
995
980fn goto_window(cx: &mut Context, align: Align) {
996fn goto_window(cx: &mut Context, align: Align) {
@@ -1066,6 +1082,10 @@ fn move_prev_long_word_start(cx: &mut Context) {
1066 move_word_impl(cx, movement::move_prev_long_word_start)
1082 move_word_impl(cx, movement::move_prev_long_word_start)
1067}
1083}
1068
1084
1085+fn move_prev_long_word_end(cx: &mut Context) {
1086+ move_word_impl(cx, movement::move_prev_long_word_end)
1087+}
1088+
1069fn move_next_long_word_end(cx: &mut Context) {
1089fn move_next_long_word_end(cx: &mut Context) {
1070 move_word_impl(cx, movement::move_next_long_word_end)
1090 move_word_impl(cx, movement::move_next_long_word_end)
1071}
1091}
@@ -1090,8 +1110,7 @@ where
1090 .transform(|range| move_fn(text, range, count, behavior));
1110 .transform(|range| move_fn(text, range, count, behavior));
1091 doc.set_selection(view.id, selection);
1111 doc.set_selection(view.id, selection);
1092 };
1112 };
1093− motion(cx.editor);
1094− cx.editor.last_motion = Some(Motion(Box::new(motion)));
1113+ cx.editor.apply_motion(motion)
1095}
1114}
1096
1115
1097fn goto_prev_paragraph(cx: &mut Context) {
1116fn goto_prev_paragraph(cx: &mut Context) {
@@ -1146,6 +1165,10 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
1146 let (view, doc) = current_ref!(cx.editor);
1165 let (view, doc) = current_ref!(cx.editor);
1147 let text = doc.text();
1166 let text = doc.text();
1148 let selections = doc.selection(view.id);
1167 let selections = doc.selection(view.id);
1168+ let rel_path = doc
1169+ .relative_path()
1170+ .map(|path| path.parent().unwrap().to_path_buf())
1171+ .unwrap_or_default();
1149 let mut paths: Vec<_> = selections
1172 let mut paths: Vec<_> = selections
1150 .iter()
1173 .iter()
1151 .map(|r| text.slice(r.from()..r.to()).to_string())
1174 .map(|r| text.slice(r.from()..r.to()).to_string())
@@ -1173,16 +1196,80 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
1173 .to_string(),
1196 .to_string(),
1174 );
1197 );
1175 }
1198 }
1199+
1176 for sel in paths {
1200 for sel in paths {
1177 let p = sel.trim();
1201 let p = sel.trim();
1178− if !p.is_empty() {
1202+ if p.is_empty() {
1203+ continue;
1204+ }
1205+
1206+ if let Ok(url) = Url::parse(p) {
1207+ return open_url(cx, url, action);
1208+ }
1209+
1210+ let path = &rel_path.join(p);
1211+ if path.is_dir() {
1212+ let picker = ui::file_picker(path.into(), &cx.editor.config());
1213+ cx.push_layer(Box::new(overlaid(picker)));
1179− if let Err(e) = cx.editor.open(&PathBuf::from(p), action) {
1214+ } else if let Err(e) = cx.editor.open(path, action) {
1215+ cx.editor.set_error(format!("Open file failed: {:?}", e));
1216+ }
1217+ }
1218+}
1219+
1220+/// Opens the given url. If the URL points to a valid textual file it is open in helix.
1221+// Otherwise, the file is open using external program.
1222+fn open_url(cx: &mut Context, url: Url, action: Action) {
1223+ let doc = doc!(cx.editor);
1224+ let rel_path = doc
1225+ .relative_path()
1226+ .map(|path| path.parent().unwrap().to_path_buf())
1227+ .unwrap_or_default();
1228+
1229+ if url.scheme() != "file" {
1230+ return open_external_url(cx, url);
1231+ }
1232+
1233+ let content_type = std::fs::File::open(url.path()).and_then(|file| {
1234+ // Read up to 1kb to detect the content type
1235+ let mut read_buffer = Vec::new();
1236+ let n = file.take(1024).read_to_end(&mut read_buffer)?;
1237+ Ok(content_inspector::inspect(&read_buffer[..n]))
1238+ });
1239+
1240+ // we attempt to open binary files - files that can't be open in helix - using external
1241+ // program as well, e.g. pdf files or images
1242+ match content_type {
1243+ Ok(content_inspector::ContentType::BINARY) => open_external_url(cx, url),
1244+ Ok(_) | Err(_) => {
1245+ let path = &rel_path.join(url.path());
1246+ if path.is_dir() {
1247+ let picker = ui::file_picker(path.into(), &cx.editor.config());
1248+ cx.push_layer(Box::new(overlaid(picker)));
1249+ } else if let Err(e) = cx.editor.open(path, action) {
1180 cx.editor.set_error(format!("Open file failed: {:?}", e));
1250 cx.editor.set_error(format!("Open file failed: {:?}", e));
1181 }
1251 }
1182 }
1252 }
1183 }
1253 }
1184}
1254}
1185
1255
1256+/// Opens URL in external program.
1257+fn open_external_url(cx: &mut Context, url: Url) {
1258+ let commands = open::commands(url.as_str());
1259+ cx.jobs.callback(async {
1260+ for cmd in commands {
1261+ let mut command = tokio::process::Command::new(cmd.get_program());
1262+ command.args(cmd.get_args());
1263+ if command.output().await.is_ok() {
1264+ return Ok(job::Callback::Editor(Box::new(|_| {})));
1265+ }
1266+ }
1267+ Ok(job::Callback::Editor(Box::new(move |editor| {
1268+ editor.set_error("Opening URL in external program failed")
1269+ })))
1270+ });
1271+}
1272+
1186fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
1273fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
1187where
1274where
1188 F: Fn(RopeSlice, Range, usize) -> Range,
1275 F: Fn(RopeSlice, Range, usize) -> Range,
@@ -1223,14 +1310,74 @@ fn extend_prev_long_word_start(cx: &mut Context) {
1223 extend_word_impl(cx, movement::move_prev_long_word_start)
1310 extend_word_impl(cx, movement::move_prev_long_word_start)
1224}
1311}
1225
1312
1313+fn extend_prev_long_word_end(cx: &mut Context) {
1314+ extend_word_impl(cx, movement::move_prev_long_word_end)
1315+}
1316+
1226fn extend_next_long_word_end(cx: &mut Context) {
1317fn extend_next_long_word_end(cx: &mut Context) {
1227 extend_word_impl(cx, movement::move_next_long_word_end)
1318 extend_word_impl(cx, movement::move_next_long_word_end)
1228}
1319}
1229
1320
1230−fn will_find_char<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool)
1321+/// Separate branch to find_char designed only for `<ret>` char.
1231−where
1322+//
1232− F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,
1323+// This is necessary because the one document can have different line endings inside. And we
1233−{
1324+// cannot predict what character to find when <ret> is pressed. On the current line it can be `lf`
1325+// but on the next line it can be `crlf`. That's why [`find_char_impl`] cannot be applied here.
1326+fn find_char_line_ending(
1327+ cx: &mut Context,
1328+ count: usize,
1329+ direction: Direction,
1330+ inclusive: bool,
1331+ extend: bool,
1332+) {
1333+ let (view, doc) = current!(cx.editor);
1334+ let text = doc.text().slice(..);
1335+
1336+ let selection = doc.selection(view.id).clone().transform(|range| {
1337+ let cursor = range.cursor(text);
1338+ let cursor_line = range.cursor_line(text);
1339+
1340+ // Finding the line where we're going to find <ret>. Depends mostly on
1341+ // `count`, but also takes into account edge cases where we're already at the end
1342+ // of a line or the beginning of a line
1343+ let find_on_line = match direction {
1344+ Direction::Forward => {
1345+ let on_edge = line_end_char_index(&text, cursor_line) == cursor;
1346+ let line = cursor_line + count - 1 + (on_edge as usize);
1347+ if line >= text.len_lines() - 1 {
1348+ return range;
1349+ } else {
1350+ line
1351+ }
1352+ }
1353+ Direction::Backward => {
1354+ let on_edge = text.line_to_char(cursor_line) == cursor && !inclusive;
1355+ let line = cursor_line as isize - (count as isize - 1 + on_edge as isize);
1356+ if line <= 0 {
1357+ return range;
1358+ } else {
1359+ line as usize
1360+ }
1361+ }
1362+ };
1363+
1364+ let pos = match (direction, inclusive) {
1365+ (Direction::Forward, true) => line_end_char_index(&text, find_on_line),
1366+ (Direction::Forward, false) => line_end_char_index(&text, find_on_line) - 1,
1367+ (Direction::Backward, true) => line_end_char_index(&text, find_on_line - 1),
1368+ (Direction::Backward, false) => text.line_to_char(find_on_line),
1369+ };
1370+
1371+ if extend {
1372+ range.put_cursor(text, pos, true)
1373+ } else {
1374+ Range::point(range.cursor(text)).put_cursor(text, pos, true)
1375+ }
1376+ });
1377+ doc.set_selection(view.id, selection);
1378+}
1379+
1380+fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bool) {
1234 // TODO: count is reset to 1 before next key so we move it into the closure here.
1381 // TODO: count is reset to 1 before next key so we move it into the closure here.
1235 // Would be nice to carry over.
1382 // Would be nice to carry over.
1236 let count = cx.count();
1383 let count = cx.count();
@@ -1243,13 +1390,9 @@ where
1243 KeyEvent {
1390 KeyEvent {
1244 code: KeyCode::Enter,
1391 code: KeyCode::Enter,
1245 ..
1392 ..
1246− } =>
1393+ } => {
1247− // TODO: this isn't quite correct when CRLF is involved.
1394+ find_char_line_ending(cx, count, direction, inclusive, extend);
1248− // This hack will work in most cases, since documents don't
1395+ return;
1249− // usually mix line endings. But we should fix it eventually
1250− // anyway.
1251− {
1252− doc!(cx.editor).line_ending.as_str().chars().next().unwrap()
1253 }
1396 }
1254
1397
1255 KeyEvent {
1398 KeyEvent {
@@ -1262,11 +1405,18 @@ where
1262 } => ch,
1405 } => ch,
1263 _ => return,
1406 _ => return,
1264 };
1407 };
1408+ let motion = move |editor: &mut Editor| {
1409+ match direction {
1410+ Direction::Forward => {
1411+ find_char_impl(editor, &find_next_char_impl, inclusive, extend, ch, count)
1412+ }
1413+ Direction::Backward => {
1414+ find_char_impl(editor, &find_prev_char_impl, inclusive, extend, ch, count)
1415+ }
1416+ };
1417+ };
1265
1418
1266− find_char_impl(cx.editor, &search_fn, inclusive, extend, ch, count);
1419+ cx.editor.apply_motion(motion);
1267− cx.editor.last_motion = Some(Motion(Box::new(move |editor: &mut Editor| {
1268− find_char_impl(editor, &search_fn, inclusive, true, ch, 1);
1269− })));
1270 })
1420 })
1271}
1421}
1272
1422
@@ -1345,46 +1495,39 @@ fn find_prev_char_impl(
1345}
1495}
1346
1496
1347fn find_till_char(cx: &mut Context) {
1497fn find_till_char(cx: &mut Context) {
1348− will_find_char(cx, find_next_char_impl, false, false)
1498+ find_char(cx, Direction::Forward, false, false);
1349}
1499}
1350
1500
1351fn find_next_char(cx: &mut Context) {
1501fn find_next_char(cx: &mut Context) {
1352− will_find_char(cx, find_next_char_impl, true, false)
1502+ find_char(cx, Direction::Forward, true, false)
1353}
1503}
1354
1504
1355fn extend_till_char(cx: &mut Context) {
1505fn extend_till_char(cx: &mut Context) {
1356− will_find_char(cx, find_next_char_impl, false, true)
1506+ find_char(cx, Direction::Forward, false, true)
1357}
1507}
1358
1508
1359fn extend_next_char(cx: &mut Context) {
1509fn extend_next_char(cx: &mut Context) {
1360− will_find_char(cx, find_next_char_impl, true, true)
1510+ find_char(cx, Direction::Forward, true, true)
1361}
1511}
1362
1512
1363fn till_prev_char(cx: &mut Context) {
1513fn till_prev_char(cx: &mut Context) {
1364− will_find_char(cx, find_prev_char_impl, false, false)
1514+ find_char(cx, Direction::Backward, false, false)
1365}
1515}
1366
1516
1367fn find_prev_char(cx: &mut Context) {
1517fn find_prev_char(cx: &mut Context) {
1368− will_find_char(cx, find_prev_char_impl, true, false)
1518+ find_char(cx, Direction::Backward, true, false)
1369}
1519}
1370
1520
1371fn extend_till_prev_char(cx: &mut Context) {
1521fn extend_till_prev_char(cx: &mut Context) {
1372− will_find_char(cx, find_prev_char_impl, false, true)
1522+ find_char(cx, Direction::Backward, false, true)
1373}
1523}
1374
1524
1375fn extend_prev_char(cx: &mut Context) {
1525fn extend_prev_char(cx: &mut Context) {
1376− will_find_char(cx, find_prev_char_impl, true, true)
1526+ find_char(cx, Direction::Backward, true, true)
1377}
1527}
1378
1528
1379fn repeat_last_motion(cx: &mut Context) {
1529fn repeat_last_motion(cx: &mut Context) {
1380− let count = cx.count();
1530+ cx.editor.repeat_last_motion(cx.count())
1381− let last_motion = cx.editor.last_motion.take();
1382− if let Some(m) = &last_motion {
1383− for _ in 0..count {
1384− m.run(cx.editor);
1385− }
1386− cx.editor.last_motion = last_motion;
1387− }
1388}
1531}
1389
1532
1390fn replace(cx: &mut Context) {
1533fn replace(cx: &mut Context) {
@@ -1451,6 +1594,7 @@ where
1451 });
1594 });
1452
1595
1453 doc.apply(&transaction, view.id);
1596 doc.apply(&transaction, view.id);
1597+ exit_select_mode(cx);
1454}
1598}
1455
1599
1456fn switch_case(cx: &mut Context) {
1600fn switch_case(cx: &mut Context) {
@@ -1692,8 +1836,8 @@ fn select_regex(cx: &mut Context) {
1692 "select:".into(),
1836 "select:".into(),
1693 Some(reg),
1837 Some(reg),
1694 ui::completers::none,
1838 ui::completers::none,
1695− move |editor, regex, event| {
1839+ move |cx, regex, event| {
1696− let (view, doc) = current!(editor);
1840+ let (view, doc) = current!(cx.editor);
1697 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1841 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1698 return;
1842 return;
1699 }
1843 }
@@ -1714,8 +1858,8 @@ fn split_selection(cx: &mut Context) {
1714 "split:".into(),
1858 "split:".into(),
1715 Some(reg),
1859 Some(reg),
1716 ui::completers::none,
1860 ui::completers::none,
1717− move |editor, regex, event| {
1861+ move |cx, regex, event| {
1718− let (view, doc) = current!(editor);
1862+ let (view, doc) = current!(cx.editor);
1719 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1863 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
1720 return;
1864 return;
1721 }
1865 }
@@ -1737,6 +1881,12 @@ fn split_selection_on_newline(cx: &mut Context) {
1737 doc.set_selection(view.id, selection);
1881 doc.set_selection(view.id, selection);
1738}
1882}
1739
1883
1884+fn merge_selections(cx: &mut Context) {
1885+ let (view, doc) = current!(cx.editor);
1886+ let selection = doc.selection(view.id).clone().merge_ranges();
1887+ doc.set_selection(view.id, selection);
1888+}
1889+
1740fn merge_consecutive_selections(cx: &mut Context) {
1890fn merge_consecutive_selections(cx: &mut Context) {
1741 let (view, doc) = current!(cx.editor);
1891 let (view, doc) = current!(cx.editor);
1742 let selection = doc.selection(view.id).clone().merge_consecutive_ranges();
1892 let selection = doc.selection(view.id).clone().merge_consecutive_ranges();
@@ -1832,11 +1982,11 @@ fn search_impl(
1832
1982
1833fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
1983fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
1834 let mut items = reg
1984 let mut items = reg
1835− .and_then(|reg| cx.editor.registers.get(reg))
1985+ .and_then(|reg| cx.editor.registers.read(reg, cx.editor))
1836− .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect());
1986+ .map_or(Vec::new(), |reg| reg.take(200).collect());
1837 items.sort_unstable();
1987 items.sort_unstable();
1838 items.dedup();
1988 items.dedup();
1839− items.into_iter().cloned().collect()
1989+ items.into_iter().map(|value| value.to_string()).collect()
1840}
1990}
1841
1991
1842fn search(cx: &mut Context) {
1992fn search(cx: &mut Context) {
@@ -1873,12 +2023,14 @@ fn searcher(cx: &mut Context, direction: Direction) {
1873 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
2023 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
1874 .collect()
2024 .collect()
1875 },
2025 },
1876− move |editor, regex, event| {
2026+ move |cx, regex, event| {
2027+ if event == PromptEvent::Validate {
2028+ cx.editor.registers.last_search_register = reg;
1877− if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
2029+ } else if event != PromptEvent::Update {
1878 return;
2030 return;
1879 }
2031 }
1880 search_impl(
2032 search_impl(
1881− editor,
2033+ cx.editor,
1882 &contents,
2034 &contents,
1883 &regex,
2035 &regex,
1884 Movement::Move,
2036 Movement::Move,
@@ -1893,11 +2045,13 @@ fn searcher(cx: &mut Context, direction: Direction) {
1893
2045
1894fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
2046fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
1895 let count = cx.count();
2047 let count = cx.count();
2048+ let register = cx
2049+ .register
2050+ .unwrap_or(cx.editor.registers.last_search_register);
1896 let config = cx.editor.config();
2051 let config = cx.editor.config();
1897 let scrolloff = config.scrolloff;
2052 let scrolloff = config.scrolloff;
1898− let (_, doc) = current!(cx.editor);
1899− let registers = &cx.editor.registers;
1900− if let Some(query) = registers.read('/').and_then(|query| query.last()) {
2053+ if let Some(query) = cx.editor.registers.first(register, cx.editor) {
2054+ let doc = doc!(cx.editor);
1901 let contents = doc.text().slice(..).to_string();
2055 let contents = doc.text().slice(..).to_string();
1902 let search_config = &config.search;
2056 let search_config = &config.search;
1903 let case_insensitive = if search_config.smart_case {
2057 let case_insensitive = if search_config.smart_case {
@@ -1906,7 +2060,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
1906 false
2060 false
1907 };
2061 };
1908 let wrap_around = search_config.wrap_around;
2062 let wrap_around = search_config.wrap_around;
1909− if let Ok(regex) = RegexBuilder::new(query)
2063+ if let Ok(regex) = RegexBuilder::new(&query)
1910 .case_insensitive(case_insensitive)
2064 .case_insensitive(case_insensitive)
1911 .multi_line(true)
2065 .multi_line(true)
1912 .build()
2066 .build()
@@ -1946,6 +2100,7 @@ fn extend_search_prev(cx: &mut Context) {
1946}
2100}
1947
2101
1948fn search_selection(cx: &mut Context) {
2102fn search_selection(cx: &mut Context) {
2103+ let register = cx.register.unwrap_or('/');
1949 let (view, doc) = current!(cx.editor);
2104 let (view, doc) = current!(cx.editor);
1950 let contents = doc.text().slice(..);
2105 let contents = doc.text().slice(..);
1951
2106
@@ -1958,13 +2113,24 @@ fn search_selection(cx: &mut Context) {
1958 .collect::<Vec<_>>()
2113 .collect::<Vec<_>>()
1959 .join("|");
2114 .join("|");
1960
2115
1961− let msg = format!("register '{}' set to '{}'", '/', &regex);
2116+ let msg = format!("register '{}' set to '{}'", register, &regex);
1962− cx.editor.registers.push('/', regex);
2117+ match cx.editor.registers.push(register, regex) {
2118+ Ok(_) => {
2119+ cx.editor.registers.last_search_register = register;
1963− cx.editor.set_status(msg);
2120+ cx.editor.set_status(msg)
2121+ }
2122+ Err(err) => cx.editor.set_error(err.to_string()),
2123+ }
1964}
2124}
1965
2125
1966fn make_search_word_bounded(cx: &mut Context) {
2126fn make_search_word_bounded(cx: &mut Context) {
2127+ // Defaults to the active search register instead `/` to be more ergonomic assuming most people
2128+ // would use this command following `search_selection`. This avoids selecting the register
2129+ // twice.
2130+ let register = cx
2131+ .register
2132+ .unwrap_or(cx.editor.registers.last_search_register);
1967− let regex = match cx.editor.registers.last('/') {
2133+ let regex = match cx.editor.registers.first(register, cx.editor) {
1968 Some(regex) => regex,
2134 Some(regex) => regex,
1969 None => return,
2135 None => return,
1970 };
2136 };
@@ -1982,14 +2148,19 @@ fn make_search_word_bounded(cx: &mut Context) {
1982 if !start_anchored {
2148 if !start_anchored {
1983 new_regex.push_str("\\b");
2149 new_regex.push_str("\\b");
1984 }
2150 }
1985− new_regex.push_str(regex);
2151+ new_regex.push_str(&regex);
1986 if !end_anchored {
2152 if !end_anchored {
1987 new_regex.push_str("\\b");
2153 new_regex.push_str("\\b");
1988 }
2154 }
1989
2155
1990− let msg = format!("register '{}' set to '{}'", '/', &new_regex);
2156+ let msg = format!("register '{}' set to '{}'", register, &new_regex);
1991− cx.editor.registers.push('/', new_regex);
2157+ match cx.editor.registers.push(register, new_regex) {
2158+ Ok(_) => {
2159+ cx.editor.registers.last_search_register = register;
1992− cx.editor.set_status(msg);
2160+ cx.editor.set_status(msg)
2161+ }
2162+ Err(err) => cx.editor.set_error(err.to_string()),
2163+ }
1993}
2164}
1994
2165
1995fn global_search(cx: &mut Context) {
2166fn global_search(cx: &mut Context) {
@@ -2028,13 +2199,11 @@ fn global_search(cx: &mut Context) {
2028 }
2199 }
2029 }
2200 }
2030
2201
2031− let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<FileResult>();
2032 let config = cx.editor.config();
2202 let config = cx.editor.config();
2033 let smart_case = config.search.smart_case;
2203 let smart_case = config.search.smart_case;
2034 let file_picker_config = config.file_picker.clone();
2204 let file_picker_config = config.file_picker.clone();
2035
2205
2036 let reg = cx.register.unwrap_or('/');
2206 let reg = cx.register.unwrap_or('/');
2037−
2038 let completions = search_completions(cx, Some(reg));
2207 let completions = search_completions(cx, Some(reg));
2039 ui::regex_prompt(
2208 ui::regex_prompt(
2040 cx,
2209 cx,
@@ -2047,43 +2216,67 @@ fn global_search(cx: &mut Context) {
2047 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
2216 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
2048 .collect()
2217 .collect()
2049 },
2218 },
2050− move |_editor, regex, event| {
2219+ move |cx, regex, event| {
2051 if event != PromptEvent::Validate {
2220 if event != PromptEvent::Validate {
2052 return;
2221 return;
2053 }
2222 }
2223+ cx.editor.registers.last_search_register = reg;
2224+
2225+ let current_path = doc_mut!(cx.editor).path().cloned();
2226+ let documents: Vec<_> = cx
2227+ .editor
2228+ .documents()
2229+ .map(|doc| (doc.path().cloned(), doc.text().to_owned()))
2230+ .collect();
2054
2231
2055 if let Ok(matcher) = RegexMatcherBuilder::new()
2232 if let Ok(matcher) = RegexMatcherBuilder::new()
2056 .case_smart(smart_case)
2233 .case_smart(smart_case)
2057 .build(regex.as_str())
2234 .build(regex.as_str())
2058 {
2235 {
2059− let searcher = SearcherBuilder::new()
2236+ let search_root = helix_loader::current_working_dir();
2060− .binary_detection(BinaryDetection::quit(b'\x00'))
2237+ if !search_root.exists() {
2061− .build();
2238+ cx.editor
2239+ .set_error("Current working directory does not exist");
2240+ return;
2241+ }
2242+
2243+ let (picker, injector) = Picker::stream(current_path);
2062
2244
2063− let search_root = std::env::current_dir()
2064− .expect("Global search error: Failed to get current dir");
2065 let dedup_symlinks = file_picker_config.deduplicate_links;
2245 let dedup_symlinks = file_picker_config.deduplicate_links;
2066 let absolute_root = search_root
2246 let absolute_root = search_root
2067 .canonicalize()
2247 .canonicalize()
2068 .unwrap_or_else(|_| search_root.clone());
2248 .unwrap_or_else(|_| search_root.clone());
2069−
2249+ let injector_ = injector.clone();
2250+
2251+ std::thread::spawn(move || {
2252+ let searcher = SearcherBuilder::new()
2253+ .binary_detection(BinaryDetection::quit(b'\x00'))
2254+ .build();
2255+
2070− WalkBuilder::new(search_root)
2256+ let mut walk_builder = WalkBuilder::new(search_root);
2257+
2258+ walk_builder
2071− .hidden(file_picker_config.hidden)
2259+ .hidden(file_picker_config.hidden)
2072− .parents(file_picker_config.parents)
2260+ .parents(file_picker_config.parents)
2073− .ignore(file_picker_config.ignore)
2261+ .ignore(file_picker_config.ignore)
2074− .follow_links(file_picker_config.follow_symlinks)
2262+ .follow_links(file_picker_config.follow_symlinks)
2075− .git_ignore(file_picker_config.git_ignore)
2263+ .git_ignore(file_picker_config.git_ignore)
2076− .git_global(file_picker_config.git_global)
2264+ .git_global(file_picker_config.git_global)
2077− .git_exclude(file_picker_config.git_exclude)
2265+ .git_exclude(file_picker_config.git_exclude)
2078− .max_depth(file_picker_config.max_depth)
2266+ .max_depth(file_picker_config.max_depth)
2079− .filter_entry(move |entry| {
2267+ .filter_entry(move |entry| {
2080− filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2268+ filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2081− })
2269+ });
2270+
2271+ walk_builder
2272+ .add_custom_ignore_filename(helix_loader::config_dir().join("ignore"));
2273+ walk_builder.add_custom_ignore_filename(".helix/ignore");
2274+
2082− .build_parallel()
2275+ walk_builder.build_parallel().run(|| {
2083− .run(|| {
2084 let mut searcher = searcher.clone();
2276 let mut searcher = searcher.clone();
2085 let matcher = matcher.clone();
2277 let matcher = matcher.clone();
2086− let all_matches_sx = all_matches_sx.clone();
2278+ let injector = injector_.clone();
2279+ let documents = &documents;
2087 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
2280 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
2088 let entry = match entry {
2281 let entry = match entry {
2089 Ok(entry) => entry,
2282 Ok(entry) => entry,
@@ -2096,17 +2289,39 @@ fn global_search(cx: &mut Context) {
2096 _ => return WalkState::Continue,
2289 _ => return WalkState::Continue,
2097 };
2290 };
2098
2291
2099− let result = searcher.search_path(
2292+ let mut stop = false;
2100− &matcher,
2101− entry.path(),
2102− sinks::UTF8(|line_num, _| {
2293+ let sink = sinks::UTF8(|line_num, _| {
2103− all_matches_sx
2294+ stop = injector
2104− .send(FileResult::new(entry.path(), line_num as usize - 1))
2295+ .push(FileResult::new(entry.path(), line_num as usize - 1))
2105− .unwrap();
2296+ .is_err();
2106−
2297+
2107− Ok(true)
2298+ Ok(!stop)
2299+ });
2300+ let doc = documents.iter().find(|&(doc_path, _)| {
2301+ doc_path
2302+ .as_ref()
2303+ .map_or(false, |doc_path| doc_path == entry.path())
2108− }),
2304+ });
2305+
2306+ let result = if let Some((_, doc)) = doc {
2307+ // there is already a buffer for this file
2308+ // search the buffer instead of the file because it's faster
2309+ // and captures new edits without requiring a save
2310+ if searcher.multi_line_with_matcher(&matcher) {
2311+ // in this case a continous buffer is required
2312+ // convert the rope to a string
2313+ let text = doc.to_string();
2314+ searcher.search_slice(&matcher, text.as_bytes(), sink)
2315+ } else {
2316+ searcher.search_reader(
2317+ &matcher,
2318+ RopeReader::new(doc.slice(..)),
2319+ sink,
2320+ )
2321+ }
2322+ } else {
2323+ searcher.search_path(&matcher, entry.path(), sink)
2109− );
2324+ };
2110
2325
2111 if let Err(err) = result {
2326 if let Err(err) = result {
2112 log::error!(
2327 log::error!(
@@ -2115,67 +2330,66 @@ fn global_search(cx: &mut Context) {
2115 err
2330 err
2116 );
2331 );
2117 }
2332 }
2333+ if stop {
2334+ WalkState::Quit
2335+ } else {
2118− WalkState::Continue
2336+ WalkState::Continue
2337+ }
2119 })
2338 })
2120 });
2339 });
2340+ });
2341+
2342+ cx.jobs.callback(async move {
2343+ let call = move |_: &mut Editor, compositor: &mut Compositor| {
2344+ let picker = Picker::with_stream(
2345+ picker,
2346+ injector,
2347+ move |cx, FileResult { path, line_num }, action| {
2348+ let doc = match cx.editor.open(path, action) {
2349+ Ok(id) => doc_mut!(cx.editor, &id),
2350+ Err(e) => {
2351+ cx.editor.set_error(format!(
2352+ "Failed to open file '{}': {}",
2353+ path.display(),
2354+ e
2355+ ));
2356+ return;
2357+ }
2358+ };
2359+
2360+ let line_num = *line_num;
2361+ let view = view_mut!(cx.editor);
2362+ let text = doc.text();
2363+ if line_num >= text.len_lines() {
2364+ cx.editor.set_error(
2365+ "The line you jumped to does not exist anymore because the file has changed.",
2366+ );
2367+ return;
2368+ }
2369+ let start = text.line_to_char(line_num);
2370+ let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2371+
2372+ doc.set_selection(view.id, Selection::single(start, end));
2373+ if action.align_view(view, doc.id()) {
2374+ align_view(doc, view, Align::Center);
2375+ }
2376+ },
2377+ )
2378+ .with_preview(
2379+ |_editor, FileResult { path, line_num }| {
2380+ Some((path.clone().into(), Some((*line_num, *line_num))))
2381+ },
2382+ );
2383+ compositor.push(Box::new(overlaid(picker)))
2384+ };
2385+ Ok(Callback::EditorCompositor(Box::new(call)))
2386+ })
2121 } else {
2387 } else {
2122 // Otherwise do nothing
2388 // Otherwise do nothing
2123 // log::warn!("Global Search Invalid Pattern")
2389 // log::warn!("Global Search Invalid Pattern")
2124 }
2390 }
2125 },
2391 },
2126 );
2392 );
2127−
2128− let current_path = doc_mut!(cx.editor).path().cloned();
2129−
2130− let show_picker = async move {
2131− let all_matches: Vec<FileResult> =
2132− UnboundedReceiverStream::new(all_matches_rx).collect().await;
2133− let call: job::Callback = Callback::EditorCompositor(Box::new(
2134− move |editor: &mut Editor, compositor: &mut Compositor| {
2135− if all_matches.is_empty() {
2136− editor.set_status("No matches found");
2137− return;
2138− }
2139−
2140− let picker = FilePicker::new(
2141− all_matches,
2142− current_path,
2143− move |cx, FileResult { path, line_num }, action| {
2144− match cx.editor.open(path, action) {
2145− Ok(_) => {}
2146− Err(e) => {
2147− cx.editor.set_error(format!(
2148− "Failed to open file '{}': {}",
2149− path.display(),
2150− e
2151− ));
2152− return;
2153− }
2154− }
2155−
2156− let line_num = *line_num;
2157− let (view, doc) = current!(cx.editor);
2158− let text = doc.text();
2159− if line_num >= text.len_lines() {
2160− cx.editor.set_error("The line you jumped to does not exist anymore because the file has changed.");
2161− return;
2162− }
2163− let start = text.line_to_char(line_num);
2164− let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2165−
2166− doc.set_selection(view.id, Selection::single(start, end));
2167− align_view(doc, view, Align::Center);
2168− },
2169− |_editor, FileResult { path, line_num }| {
2170− Some((path.clone().into(), Some((*line_num, *line_num))))
2171− },
2172− );
2173− compositor.push(Box::new(overlaid(picker)));
2174− },
2175− ));
2176− Ok(call)
2177− };
2178− cx.jobs.callback(show_picker);
2179}
2393}
2180
2394
2181enum Extend {
2395enum Extend {
@@ -2298,17 +2512,35 @@ enum Operation {
2298 Change,
2512 Change,
2299}
2513}
2300
2514
2515+fn selection_is_linewise(selection: &Selection, text: &Rope) -> bool {
2516+ selection.ranges().iter().all(|range| {
2517+ let text = text.slice(..);
2518+ if range.slice(text).len_lines() < 2 {
2519+ return false;
2520+ }
2521+ // If the start of the selection is at the start of a line and the end at the end of a line.
2522+ let (start_line, end_line) = range.line_range(text);
2523+ let start = text.line_to_char(start_line);
2524+ let end = text.line_to_char((end_line + 1).min(text.len_lines()));
2525+ start == range.from() && end == range.to()
2526+ })
2527+}
2528+
2301fn delete_selection_impl(cx: &mut Context, op: Operation) {
2529fn delete_selection_impl(cx: &mut Context, op: Operation) {
2302 let (view, doc) = current!(cx.editor);
2530 let (view, doc) = current!(cx.editor);
2303
2531
2304 let selection = doc.selection(view.id);
2532 let selection = doc.selection(view.id);
2533+ let only_whole_lines = selection_is_linewise(selection, doc.text());
2305
2534
2306 if cx.register != Some('_') {
2535 if cx.register != Some('_') {
2307 // first yank the selection
2536 // first yank the selection
2308 let text = doc.text().slice(..);
2537 let text = doc.text().slice(..);
2309 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
2538 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
2310 let reg_name = cx.register.unwrap_or('"');
2539 let reg_name = cx.register.unwrap_or('"');
2311− cx.editor.registers.write(reg_name, values);
2540+ if let Err(err) = cx.editor.registers.write(reg_name, values) {
2541+ cx.editor.set_error(err.to_string());
2542+ return;
2543+ }
2312 };
2544 };
2313
2545
2314 // then delete
2546 // then delete
@@ -2322,7 +2554,11 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
2322 exit_select_mode(cx);
2554 exit_select_mode(cx);
2323 }
2555 }
2324 Operation::Change => {
2556 Operation::Change => {
2557+ if only_whole_lines {
2558+ open_above(cx);
2559+ } else {
2325− enter_insert_mode(cx);
2560+ enter_insert_mode(cx);
2561+ }
2326 }
2562 }
2327 }
2563 }
2328}
2564}
@@ -2444,6 +2680,10 @@ fn insert_mode(cx: &mut Context) {
2444 .transform(|range| Range::new(range.to(), range.from()));
2680 .transform(|range| Range::new(range.to(), range.from()));
2445
2681
2446 doc.set_selection(view.id, selection);
2682 doc.set_selection(view.id, selection);
2683+
2684+ // [TODO] temporary workaround until we're not using the idle timer to
2685+ // trigger auto completions any more
2686+ cx.editor.clear_idle_timer();
2447}
2687}
2448
2688
2449// inserts at the end of each selection
2689// inserts at the end of each selection
@@ -2480,6 +2720,10 @@ fn append_mode(cx: &mut Context) {
2480
2720
2481fn file_picker(cx: &mut Context) {
2721fn file_picker(cx: &mut Context) {
2482 let root = find_workspace().0;
2722 let root = find_workspace().0;
2723+ if !root.exists() {
2724+ cx.editor.set_error("Workspace directory does not exist");
2725+ return;
2726+ }
2483 let picker = ui::file_picker(root, &cx.editor.config());
2727 let picker = ui::file_picker(root, &cx.editor.config());
2484 cx.push_layer(Box::new(overlaid(picker)));
2728 cx.push_layer(Box::new(overlaid(picker)));
2485}
2729}
@@ -2500,8 +2744,14 @@ fn file_picker_in_current_buffer_directory(cx: &mut Context) {
2500 let picker = ui::file_picker(path, &cx.editor.config());
2744 let picker = ui::file_picker(path, &cx.editor.config());
2501 cx.push_layer(Box::new(overlaid(picker)));
2745 cx.push_layer(Box::new(overlaid(picker)));
2502}
2746}
2747+
2503fn file_picker_in_current_directory(cx: &mut Context) {
2748fn file_picker_in_current_directory(cx: &mut Context) {
2504− let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("./"));
2749+ let cwd = helix_loader::current_working_dir();
2750+ if !cwd.exists() {
2751+ cx.editor
2752+ .set_error("Current working directory does not exist");
2753+ return;
2754+ }
2505 let picker = ui::file_picker(cwd, &cx.editor.config());
2755 let picker = ui::file_picker(cwd, &cx.editor.config());
2506 cx.push_layer(Box::new(overlaid(picker)));
2756 cx.push_layer(Box::new(overlaid(picker)));
2507}
2757}
@@ -2560,22 +2810,18 @@ fn buffer_picker(cx: &mut Context) {
2560 // mru
2810 // mru
2561 items.sort_unstable_by_key(|item| std::cmp::Reverse(item.focused_at));
2811 items.sort_unstable_by_key(|item| std::cmp::Reverse(item.focused_at));
2562
2812
2563− let picker = FilePicker::new(
2564− items,
2565− (),
2566− |cx, meta, action| {
2813+ let picker = Picker::new(items, (), |cx, meta, action| {
2567− cx.editor.switch(meta.id, action);
2814+ cx.editor.switch(meta.id, action);
2568− },
2815+ })
2569− |editor, meta| {
2816+ .with_preview(|editor, meta| {
2570− let doc = &editor.documents.get(&meta.id)?;
2817+ let doc = &editor.documents.get(&meta.id)?;
2571− let &view_id = doc.selections().keys().next()?;
2818+ let &view_id = doc.selections().keys().next()?;
2572− let line = doc
2819+ let line = doc
2573− .selection(view_id)
2820+ .selection(view_id)
2574− .primary()
2821+ .primary()
2575− .cursor_line(doc.text().slice(..));
2822+ .cursor_line(doc.text().slice(..));
2576− Some((meta.id.into(), Some((line, line))))
2823+ Some((meta.id.into(), Some((line, line))))
2577− },
2578− );
2824+ });
2579 cx.push_layer(Box::new(overlaid(picker)));
2825 cx.push_layer(Box::new(overlaid(picker)));
2580}
2826}
2581
2827
@@ -2641,7 +2887,7 @@ fn jumplist_picker(cx: &mut Context) {
2641 }
2887 }
2642 };
2888 };
2643
2889
2644− let picker = FilePicker::new(
2890+ let picker = Picker::new(
2645 cx.editor
2891 cx.editor
2646 .tree
2892 .tree
2647 .views()
2893 .views()
@@ -2655,16 +2901,18 @@ fn jumplist_picker(cx: &mut Context) {
2655 |cx, meta, action| {
2901 |cx, meta, action| {
2656 cx.editor.switch(meta.id, action);
2902 cx.editor.switch(meta.id, action);
2657 let config = cx.editor.config();
2903 let config = cx.editor.config();
2658− let (view, doc) = current!(cx.editor);
2904+ let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id));
2659 doc.set_selection(view.id, meta.selection.clone());
2905 doc.set_selection(view.id, meta.selection.clone());
2906+ if action.align_view(view, doc.id()) {
2660− view.ensure_cursor_in_view_center(doc, config.scrolloff);
2907+ view.ensure_cursor_in_view_center(doc, config.scrolloff);
2661− },
2908+ }
2662− |editor, meta| {
2663− let doc = &editor.documents.get(&meta.id)?;
2664− let line = meta.selection.primary().cursor_line(doc.text().slice(..));
2665− Some((meta.path.clone()?.into(), Some((line, line))))
2666 },
2909 },
2667− );
2910+ )
2911+ .with_preview(|editor, meta| {
2912+ let doc = &editor.documents.get(&meta.id)?;
2913+ let line = meta.selection.primary().cursor_line(doc.text().slice(..));
2914+ Some((meta.id.into(), Some((line, line))))
2915+ });
2668 cx.push_layer(Box::new(overlaid(picker)));
2916 cx.push_layer(Box::new(overlaid(picker)));
2669}
2917}
2670
2918
@@ -2698,6 +2946,9 @@ impl ui::menu::Item for MappableCommand {
2698}
2946}
2699
2947
2700pub fn command_palette(cx: &mut Context) {
2948pub fn command_palette(cx: &mut Context) {
2949+ let register = cx.register;
2950+ let count = cx.count;
2951+
2701 cx.callback = Some(Box::new(
2952 cx.callback = Some(Box::new(
2702 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
2953 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
2703 let keymap = compositor.find::<ui::EditorView>().unwrap().keymaps.map()
2954 let keymap = compositor.find::<ui::EditorView>().unwrap().keymaps.map()
@@ -2715,8 +2966,8 @@ pub fn command_palette(cx: &mut Context) {
2715
2966
2716 let picker = Picker::new(commands, keymap, move |cx, command, _action| {
2967 let picker = Picker::new(commands, keymap, move |cx, command, _action| {
2717 let mut ctx = Context {
2968 let mut ctx = Context {
2718− register: None,
2969+ register,
2719− count: std::num::NonZeroUsize::new(1),
2970+ count,
2720 editor: cx.editor,
2971 editor: cx.editor,
2721 callback: None,
2972 callback: None,
2722 on_next_key_callback: None,
2973 on_next_key_callback: None,
@@ -2755,24 +3006,88 @@ fn last_picker(cx: &mut Context) {
2755 }));
3006 }));
2756}
3007}
2757
3008
3009+/// Fallback position to use for [`insert_with_indent`].
3010+enum IndentFallbackPos {
3011+ LineStart,
3012+ LineEnd,
3013+}
3014+
2758−// I inserts at the first nonwhitespace character of each line with a selection
3015+// `I` inserts at the first nonwhitespace character of each line with a selection.
3016+// If the line is empty, automatically indent.
2759fn insert_at_line_start(cx: &mut Context) {
3017fn insert_at_line_start(cx: &mut Context) {
2760− goto_first_nonwhitespace(cx);
3018+ insert_with_indent(cx, IndentFallbackPos::LineStart);
2761− enter_insert_mode(cx);
2762}
3019}
2763
3020
2764−// A inserts at the end of each line with a selection
3021+// `A` inserts at the end of each line with a selection.
3022+// If the line is empty, automatically indent.
2765fn insert_at_line_end(cx: &mut Context) {
3023fn insert_at_line_end(cx: &mut Context) {
3024+ insert_with_indent(cx, IndentFallbackPos::LineEnd);
3025+}
3026+
3027+// Enter insert mode and auto-indent the current line if it is empty.
3028+// If the line is not empty, move the cursor to the specified fallback position.
3029+fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) {
2766 enter_insert_mode(cx);
3030 enter_insert_mode(cx);
3031+
2767 let (view, doc) = current!(cx.editor);
3032 let (view, doc) = current!(cx.editor);
2768
3033
3034+ let text = doc.text().slice(..);
3035+ let contents = doc.text();
2769− let selection = doc.selection(view.id).clone().transform(|range| {
3036+ let selection = doc.selection(view.id);
2770− let text = doc.text().slice(..);
3037+
3038+ let language_config = doc.language_config();
3039+ let syntax = doc.syntax();
3040+ let tab_width = doc.tab_width();
3041+
3042+ let mut ranges = SmallVec::with_capacity(selection.len());
3043+ let mut offs = 0;
3044+
3045+ let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
2771− let line = range.cursor_line(text);
3046+ let cursor_line = range.cursor_line(text);
3047+ let cursor_line_start = text.line_to_char(cursor_line);
3048+
2772− let pos = line_end_char_index(&text, line);
3049+ if line_end_char_index(&text, cursor_line) == cursor_line_start {
2773− Range::new(pos, pos)
3050+ // line is empty => auto indent
3051+ let line_end_index = cursor_line_start;
3052+
3053+ let indent = indent::indent_for_newline(
3054+ language_config,
3055+ syntax,
3056+ &doc.config.load().indent_heuristic,
3057+ &doc.indent_style,
3058+ tab_width,
3059+ text,
3060+ cursor_line,
3061+ line_end_index,
3062+ cursor_line,
3063+ );
3064+
3065+ // calculate new selection ranges
3066+ let pos = offs + cursor_line_start;
3067+ let indent_width = indent.chars().count();
3068+ ranges.push(Range::point(pos + indent_width));
3069+ offs += indent_width;
3070+
3071+ (line_end_index, line_end_index, Some(indent.into()))
3072+ } else {
3073+ // move cursor to the fallback position
3074+ let pos = match cursor_fallback {
3075+ IndentFallbackPos::LineStart => {
3076+ find_first_non_whitespace_char(text.line(cursor_line))
3077+ .map(|ws_offset| ws_offset + cursor_line_start)
3078+ .unwrap_or(cursor_line_start)
3079+ }
3080+ IndentFallbackPos::LineEnd => line_end_char_index(&text, cursor_line),
3081+ };
3082+
3083+ ranges.push(range.put_cursor(text, pos + offs, cx.editor.mode == Mode::Select));
3084+
3085+ (cursor_line_start, cursor_line_start, None)
3086+ }
2774 });
3087 });
2775− doc.set_selection(view.id, selection);
3088+
3089+ transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
3090+ doc.apply(&transaction, view.id);
2776}
3091}
2777
3092
2778// Creates an LspCallback that waits for formatting changes to be computed. When they're done,
3093// Creates an LspCallback that waits for formatting changes to be computed. When they're done,
@@ -2843,6 +3158,7 @@ fn open(cx: &mut Context, open: Open) {
2843 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
3158 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
2844 Open::Above => range.from(),
3159 Open::Above => range.from(),
2845 });
3160 });
3161+
2846 let new_line = match open {
3162 let new_line = match open {
2847 // adjust position to the end of the line (next line - 1)
3163 // adjust position to the end of the line (next line - 1)
2848 Open::Below => cursor_line + 1,
3164 Open::Below => cursor_line + 1,
@@ -2850,13 +3166,15 @@ fn open(cx: &mut Context, open: Open) {
2850 Open::Above => cursor_line,
3166 Open::Above => cursor_line,
2851 };
3167 };
2852
3168
3169+ let line_num = new_line.saturating_sub(1);
3170+
2853 // Index to insert newlines after, as well as the char width
3171 // Index to insert newlines after, as well as the char width
2854 // to use to compensate for those inserted newlines.
3172 // to use to compensate for those inserted newlines.
2855 let (line_end_index, line_end_offset_width) = if new_line == 0 {
3173 let (line_end_index, line_end_offset_width) = if new_line == 0 {
2856 (0, 0)
3174 (0, 0)
2857 } else {
3175 } else {
2858 (
3176 (
2859− line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
3177+ line_end_char_index(&text, line_num),
2860 doc.line_ending.len_chars(),
3178 doc.line_ending.len_chars(),
2861 )
3179 )
2862 };
3180 };
@@ -2864,13 +3182,15 @@ fn open(cx: &mut Context, open: Open) {
2864 let indent = indent::indent_for_newline(
3182 let indent = indent::indent_for_newline(
2865 doc.language_config(),
3183 doc.language_config(),
2866 doc.syntax(),
3184 doc.syntax(),
3185+ &doc.config.load().indent_heuristic,
2867 &doc.indent_style,
3186 &doc.indent_style,
2868 doc.tab_width(),
3187 doc.tab_width(),
2869 text,
3188 text,
2870− new_line.saturating_sub(1),
3189+ line_num,
2871 line_end_index,
3190 line_end_index,
2872 cursor_line,
3191 cursor_line,
2873 );
3192 );
3193+
2874 let indent_len = indent.len();
3194 let indent_len = indent.len();
2875 let mut text = String::with_capacity(1 + indent_len);
3195 let mut text = String::with_capacity(1 + indent_len);
2876 text.push_str(doc.line_ending.as_str());
3196 text.push_str(doc.line_ending.as_str());
@@ -3169,8 +3489,7 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
3169
3489
3170 doc.set_selection(view.id, selection)
3490 doc.set_selection(view.id, selection)
3171 };
3491 };
3172− motion(cx.editor);
3173− cx.editor.last_motion = Some(Motion(Box::new(motion)));
3492+ cx.editor.apply_motion(motion);
3174}
3493}
3175
3494
3176/// Returns the [Range] for a [Hunk] in the given text.
3495/// Returns the [Range] for a [Hunk] in the given text.
@@ -3297,6 +3616,7 @@ pub mod insert {
3297 }
3616 }
3298
3617
3299 use helix_core::auto_pairs;
3618 use helix_core::auto_pairs;
3619+ use helix_view::editor::SmartTabConfig;
3300
3620
3301 pub fn insert_char(cx: &mut Context, c: char) {
3621 pub fn insert_char(cx: &mut Context, c: char) {
3302 let (view, doc) = current_ref!(cx.editor);
3622 let (view, doc) = current_ref!(cx.editor);
@@ -3322,6 +3642,31 @@ pub mod insert {
3322 }
3642 }
3323 }
3643 }
3324
3644
3645+ pub fn smart_tab(cx: &mut Context) {
3646+ let (view, doc) = current_ref!(cx.editor);
3647+ let view_id = view.id;
3648+
3649+ if matches!(
3650+ cx.editor.config().smart_tab,
3651+ Some(SmartTabConfig { enable: true, .. })
3652+ ) {
3653+ let cursors_after_whitespace = doc.selection(view_id).ranges().iter().all(|range| {
3654+ let cursor = range.cursor(doc.text().slice(..));
3655+ let current_line_num = doc.text().char_to_line(cursor);
3656+ let current_line_start = doc.text().line_to_char(current_line_num);
3657+ let left = doc.text().slice(current_line_start..cursor);
3658+ left.chars().all(|c| c.is_whitespace())
3659+ });
3660+
3661+ if !cursors_after_whitespace {
3662+ move_parent_node_end(cx);
3663+ return;
3664+ }
3665+ }
3666+
3667+ insert_tab(cx);
3668+ }
3669+
3325 pub fn insert_tab(cx: &mut Context) {
3670 pub fn insert_tab(cx: &mut Context) {
3326 let (view, doc) = current!(cx.editor);
3671 let (view, doc) = current!(cx.editor);
3327 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
3672 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
@@ -3377,6 +3722,7 @@ pub mod insert {
3377 let indent = indent::indent_for_newline(
3722 let indent = indent::indent_for_newline(
3378 doc.language_config(),
3723 doc.language_config(),
3379 doc.syntax(),
3724 doc.syntax(),
3725+ &doc.config.load().indent_heuristic,
3380 &doc.indent_style,
3726 &doc.indent_style,
3381 doc.tab_width(),
3727 doc.tab_width(),
3382 text,
3728 text,
@@ -3617,34 +3963,21 @@ fn commit_undo_checkpoint(cx: &mut Context) {
3617// Yank / Paste
3963// Yank / Paste
3618
3964
3619fn yank(cx: &mut Context) {
3965fn yank(cx: &mut Context) {
3620− let (view, doc) = current!(cx.editor);
3621− let text = doc.text().slice(..);
3622−
3623− let values: Vec<String> = doc
3624− .selection(view.id)
3625− .fragments(text)
3626− .map(Cow::into_owned)
3627− .collect();
3628−
3629− let msg = format!(
3630− "yanked {} selection(s) to register {}",
3631− values.len(),
3632− cx.register.unwrap_or('"')
3966+ yank_impl(cx.editor, cx.register.unwrap_or('"'));
3633− );
3967+ exit_select_mode(cx);
3968+}
3634
3969
3970+fn yank_to_clipboard(cx: &mut Context) {
3635− cx.editor
3971+ yank_impl(cx.editor, '+');
3636− .registers
3972+ exit_select_mode(cx);
3637− .write(cx.register.unwrap_or('"'), values);
3973+}
3638
3974
3975+fn yank_to_primary_clipboard(cx: &mut Context) {
3639− cx.editor.set_status(msg);
3976+ yank_impl(cx.editor, '*');
3640 exit_select_mode(cx);
3977 exit_select_mode(cx);
3641}
3978}
3642
3979
3643−fn yank_joined_to_clipboard_impl(
3644− editor: &mut Editor,
3980+fn yank_impl(editor: &mut Editor, register: char) {
3645− separator: &str,
3646− clipboard_type: ClipboardType,
3647−) -> anyhow::Result<()> {
3648 let (view, doc) = current!(editor);
3981 let (view, doc) = current!(editor);
3649 let text = doc.text().slice(..);
3982 let text = doc.text().slice(..);
3650
3983
@@ -3653,74 +3986,79 @@ fn yank_joined_to_clipboard_impl(
3653 .fragments(text)
3986 .fragments(text)
3654 .map(Cow::into_owned)
3987 .map(Cow::into_owned)
3655 .collect();
3988 .collect();
3989+ let selections = values.len();
3656
3990
3657− let clipboard_text = match clipboard_type {
3991+ match editor.registers.write(register, values) {
3658− ClipboardType::Clipboard => "system clipboard",
3992+ Ok(_) => editor.set_status(format!(
3659− ClipboardType::Selection => "primary clipboard",
3660− };
3661−
3662− let msg = format!(
3663− "joined and yanked {} selection(s) to {}",
3993+ "yanked {selections} selection{} to register {register}",
3664− values.len(),
3994+ if selections == 1 { "" } else { "s" }
3665− clipboard_text,
3995+ )),
3666− );
3996+ Err(err) => editor.set_error(err.to_string()),
3997+ }
3998+}
3667
3999
3668− let joined = values.join(separator);
4000+fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
4001+ let (view, doc) = current!(editor);
4002+ let text = doc.text().slice(..);
3669
4003
3670− editor
4004+ let selection = doc.selection(view.id);
3671− .clipboard_provider
4005+ let selections = selection.len();
3672− .set_contents(joined, clipboard_type)
4006+ let joined = selection
3673− .context("Couldn't set system clipboard content")?;
4007+ .fragments(text)
4008+ .fold(String::new(), |mut acc, fragment| {
4009+ if !acc.is_empty() {
4010+ acc.push_str(separator);
4011+ }
4012+ acc.push_str(&fragment);
4013+ acc
4014+ });
3674
4015
4016+ match editor.registers.write(register, vec![joined]) {
3675− editor.set_status(msg);
4017+ Ok(_) => editor.set_status(format!(
4018+ "joined and yanked {selections} selection{} to register {register}",
4019+ if selections == 1 { "" } else { "s" }
4020+ )),
4021+ Err(err) => editor.set_error(err.to_string()),
4022+ }
4023+}
3676
4024
3677− Ok(())
4025+fn yank_joined(cx: &mut Context) {
4026+ let separator = doc!(cx.editor).line_ending.as_str();
4027+ yank_joined_impl(cx.editor, separator, cx.register.unwrap_or('"'));
4028+ exit_select_mode(cx);
3678}
4029}
3679
4030
3680fn yank_joined_to_clipboard(cx: &mut Context) {
4031fn yank_joined_to_clipboard(cx: &mut Context) {
3681 let line_ending = doc!(cx.editor).line_ending;
4032 let line_ending = doc!(cx.editor).line_ending;
3682− let _ =
3683− yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Clipboard);
4033+ yank_joined_impl(cx.editor, line_ending.as_str(), '+');
3684 exit_select_mode(cx);
4034 exit_select_mode(cx);
3685}
4035}
3686
4036
3687−fn yank_main_selection_to_clipboard_impl(
4037+fn yank_joined_to_primary_clipboard(cx: &mut Context) {
4038+ let line_ending = doc!(cx.editor).line_ending;
4039+ yank_joined_impl(cx.editor, line_ending.as_str(), '*');
4040+ exit_select_mode(cx);
4041+}
4042+
3688− editor: &mut Editor,
4043+fn yank_primary_selection_impl(editor: &mut Editor, register: char) {
3689− clipboard_type: ClipboardType,
3690−) -> anyhow::Result<()> {
3691 let (view, doc) = current!(editor);
4044 let (view, doc) = current!(editor);
3692 let text = doc.text().slice(..);
4045 let text = doc.text().slice(..);
3693
4046
3694− let message_text = match clipboard_type {
3695− ClipboardType::Clipboard => "yanked main selection to system clipboard",
3696− ClipboardType::Selection => "yanked main selection to primary clipboard",
3697− };
3698−
3699− let value = doc.selection(view.id).primary().fragment(text);
4047+ let selection = doc.selection(view.id).primary().fragment(text).to_string();
3700
4048
3701− if let Err(e) = editor
4049+ match editor.registers.write(register, vec![selection]) {
3702− .clipboard_provider
4050+ Ok(_) => editor.set_status(format!("yanked primary selection to register {register}",)),
3703− .set_contents(value.into_owned(), clipboard_type)
4051+ Err(err) => editor.set_error(err.to_string()),
3704− {
3705− bail!("Couldn't set system clipboard content: {}", e);
3706 }
4052 }
3707−
3708− editor.set_status(message_text);
3709− Ok(())
3710}
4053}
3711
4054
3712fn yank_main_selection_to_clipboard(cx: &mut Context) {
4055fn yank_main_selection_to_clipboard(cx: &mut Context) {
3713− let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard);
4056+ yank_primary_selection_impl(cx.editor, '+');
3714−}
4057+ exit_select_mode(cx);
3715−
3716−fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3717− let line_ending = doc!(cx.editor).line_ending;
3718− let _ =
3719− yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Selection);
3720}
4058}
3721
4059
3722fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
4060fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
3723− let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection);
4061+ yank_primary_selection_impl(cx.editor, '*');
3724 exit_select_mode(cx);
4062 exit_select_mode(cx);
3725}
4063}
3726
4064
@@ -3818,146 +4156,99 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
3818 };
4156 };
3819 let (view, doc) = current!(cx.editor);
4157 let (view, doc) = current!(cx.editor);
3820 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
4158 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
3821−}
4159+ exit_select_mode(cx);
3822−
3823−fn paste_clipboard_impl(
3824− editor: &mut Editor,
3825− action: Paste,
3826− clipboard_type: ClipboardType,
3827− count: usize,
3828−) -> anyhow::Result<()> {
3829− let (view, doc) = current!(editor);
3830− match editor.clipboard_provider.get_contents(clipboard_type) {
3831− Ok(contents) => {
3832− paste_impl(&[contents], doc, view, action, count, editor.mode);
3833− Ok(())
3834− }
3835− Err(e) => Err(e.context("Couldn't get system clipboard contents")),
3836− }
3837}
4160}
3838
4161
3839fn paste_clipboard_after(cx: &mut Context) {
4162fn paste_clipboard_after(cx: &mut Context) {
3840− let _ = paste_clipboard_impl(
3841− cx.editor,
3842− Paste::After,
4163+ paste(cx.editor, '+', Paste::After, cx.count());
3843− ClipboardType::Clipboard,
4164+ exit_select_mode(cx);
3844− cx.count(),
3845− );
3846}
4165}
3847
4166
3848fn paste_clipboard_before(cx: &mut Context) {
4167fn paste_clipboard_before(cx: &mut Context) {
3849− let _ = paste_clipboard_impl(
3850− cx.editor,
3851− Paste::Before,
4168+ paste(cx.editor, '+', Paste::Before, cx.count());
3852− ClipboardType::Clipboard,
4169+ exit_select_mode(cx);
3853− cx.count(),
3854− );
3855}
4170}
3856
4171
3857fn paste_primary_clipboard_after(cx: &mut Context) {
4172fn paste_primary_clipboard_after(cx: &mut Context) {
3858− let _ = paste_clipboard_impl(
3859− cx.editor,
3860− Paste::After,
4173+ paste(cx.editor, '*', Paste::After, cx.count());
3861− ClipboardType::Selection,
4174+ exit_select_mode(cx);
3862− cx.count(),
3863− );
3864}
4175}
3865
4176
3866fn paste_primary_clipboard_before(cx: &mut Context) {
4177fn paste_primary_clipboard_before(cx: &mut Context) {
3867− let _ = paste_clipboard_impl(
3868− cx.editor,
3869− Paste::Before,
4178+ paste(cx.editor, '*', Paste::Before, cx.count());
3870− ClipboardType::Selection,
4179+ exit_select_mode(cx);
3871− cx.count(),
3872− );
3873}
4180}
3874
4181
3875fn replace_with_yanked(cx: &mut Context) {
4182fn replace_with_yanked(cx: &mut Context) {
3876− let count = cx.count();
3877− let reg_name = cx.register.unwrap_or('"');
4183+ replace_with_yanked_impl(cx.editor, cx.register.unwrap_or('"'), cx.count());
3878− let (view, doc) = current!(cx.editor);
3879− let registers = &mut cx.editor.registers;
3880−
3881− if let Some(values) = registers.read(reg_name) {
3882− if !values.is_empty() {
3883− let repeat = std::iter::repeat(
3884− values
3885− .last()
3886− .map(|value| Tendril::from(&value.repeat(count)))
3887− .unwrap(),
3888− );
3889− let mut values = values
3890− .iter()
3891− .map(|value| Tendril::from(&value.repeat(count)))
3892− .chain(repeat);
3893− let selection = doc.selection(view.id);
3894− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3895− if !range.is_empty() {
3896− (range.from(), range.to(), Some(values.next().unwrap()))
3897− } else {
3898− (range.from(), range.to(), None)
3899− }
3900− });
3901−
3902− doc.apply(&transaction, view.id);
3903− exit_select_mode(cx);
4184+ exit_select_mode(cx);
3904− }
3905− }
3906}
4185}
3907
4186
3908−fn replace_selections_with_clipboard_impl(
4187+fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
3909− cx: &mut Context,
4188+ let Some(values) = editor.registers
3910− clipboard_type: ClipboardType,
4189+ .read(register, editor)
3911−) -> anyhow::Result<()> {
4190+ .filter(|values| values.len() > 0) else { return };
3912− let count = cx.count();
4191+ let values: Vec<_> = values.map(|value| value.to_string()).collect();
3913− let (view, doc) = current!(cx.editor);
3914−
3915− match cx.editor.clipboard_provider.get_contents(clipboard_type) {
3916− Ok(contents) => {
3917− let selection = doc.selection(view.id);
3918− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3919− (
3920− range.from(),
3921− range.to(),
3922− Some(contents.repeat(count).as_str().into()),
3923− )
3924− });
3925
4192
3926− doc.apply(&transaction, view.id);
4193+ let (view, doc) = current!(editor);
3927− doc.append_changes_to_history(view);
4194+ let repeat = std::iter::repeat(
4195+ values
4196+ .last()
4197+ .map(|value| Tendril::from(&value.repeat(count)))
4198+ .unwrap(),
4199+ );
4200+ let mut values = values
4201+ .iter()
4202+ .map(|value| Tendril::from(&value.repeat(count)))
4203+ .chain(repeat);
4204+ let selection = doc.selection(view.id);
4205+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4206+ if !range.is_empty() {
4207+ (range.from(), range.to(), Some(values.next().unwrap()))
4208+ } else {
4209+ (range.from(), range.to(), None)
3928 }
4210 }
3929− Err(e) => return Err(e.context("Couldn't get system clipboard contents")),
3930− }
4211+ });
3931
4212
3932− exit_select_mode(cx);
4213+ doc.apply(&transaction, view.id);
3933− Ok(())
3934}
4214}
3935
4215
3936fn replace_selections_with_clipboard(cx: &mut Context) {
4216fn replace_selections_with_clipboard(cx: &mut Context) {
3937− let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard);
4217+ replace_with_yanked_impl(cx.editor, '+', cx.count());
4218+ exit_select_mode(cx);
3938}
4219}
3939
4220
3940fn replace_selections_with_primary_clipboard(cx: &mut Context) {
4221fn replace_selections_with_primary_clipboard(cx: &mut Context) {
3941− let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Selection);
4222+ replace_with_yanked_impl(cx.editor, '*', cx.count());
4223+ exit_select_mode(cx);
3942}
4224}
3943
4225
3944−fn paste(cx: &mut Context, pos: Paste) {
4226+fn paste(editor: &mut Editor, register: char, pos: Paste, count: usize) {
3945− let count = cx.count();
4227+ let Some(values) = editor.registers.read(register, editor) else { return };
3946− let reg_name = cx.register.unwrap_or('"');
4228+ let values: Vec<_> = values.map(|value| value.to_string()).collect();
3947− let (view, doc) = current!(cx.editor);
3948− let registers = &mut cx.editor.registers;
3949
4229
3950− if let Some(values) = registers.read(reg_name) {
4230+ let (view, doc) = current!(editor);
3951− paste_impl(values, doc, view, pos, count, cx.editor.mode);
4231+ paste_impl(&values, doc, view, pos, count, editor.mode);
3952− }
3953}
4232}
3954
4233
3955fn paste_after(cx: &mut Context) {
4234fn paste_after(cx: &mut Context) {
4235+ paste(
4236+ cx.editor,
4237+ cx.register.unwrap_or('"'),
3956− paste(cx, Paste::After)
4238+ Paste::After,
4239+ cx.count(),
4240+ );
4241+ exit_select_mode(cx);
3957}
4242}
3958
4243
3959fn paste_before(cx: &mut Context) {
4244fn paste_before(cx: &mut Context) {
4245+ paste(
4246+ cx.editor,
4247+ cx.register.unwrap_or('"'),
3960− paste(cx, Paste::Before)
4248+ Paste::Before,
4249+ cx.count(),
4250+ );
4251+ exit_select_mode(cx);
3961}
4252}
3962
4253
3963fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
4254fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
@@ -3996,6 +4287,7 @@ fn indent(cx: &mut Context) {
3996 }),
4287 }),
3997 );
4288 );
3998 doc.apply(&transaction, view.id);
4289 doc.apply(&transaction, view.id);
4290+ exit_select_mode(cx);
3999}
4291}
4000
4292
4001fn unindent(cx: &mut Context) {
4293fn unindent(cx: &mut Context) {
@@ -4035,6 +4327,7 @@ fn unindent(cx: &mut Context) {
4035 let transaction = Transaction::change(doc.text(), changes.into_iter());
4327 let transaction = Transaction::change(doc.text(), changes.into_iter());
4036
4328
4037 doc.apply(&transaction, view.id);
4329 doc.apply(&transaction, view.id);
4330+ exit_select_mode(cx);
4038}
4331}
4039
4332
4040fn format_selections(cx: &mut Context) {
4333fn format_selections(cx: &mut Context) {
@@ -4064,7 +4357,7 @@ fn format_selections(cx: &mut Context) {
4064 })
4357 })
4065 else {
4358 else {
4066 cx.editor
4359 cx.editor
4067− .set_error("No configured language server does not support range formatting");
4360+ .set_error("No configured language server supports range formatting");
4068 return;
4361 return;
4069 };
4362 };
4070
4363
@@ -4101,10 +4394,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4101 use movement::skip_while;
4394 use movement::skip_while;
4102 let (view, doc) = current!(cx.editor);
4395 let (view, doc) = current!(cx.editor);
4103 let text = doc.text();
4396 let text = doc.text();
4104− let slice = doc.text().slice(..);
4397+ let slice = text.slice(..);
4105
4398
4106 let mut changes = Vec::new();
4399 let mut changes = Vec::new();
4107− let fragment = Tendril::from(" ");
4108
4400
4109 for selection in doc.selection(view.id) {
4401 for selection in doc.selection(view.id) {
4110 let (start, mut end) = selection.line_range(slice);
4402 let (start, mut end) = selection.line_range(slice);
@@ -4120,9 +4412,13 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4120 let mut end = text.line_to_char(line + 1);
4412 let mut end = text.line_to_char(line + 1);
4121 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
4413 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
4122
4414
4123− // need to skip from start, not end
4415+ let separator = if end == line_end_char_index(&slice, line + 1) {
4124− let change = (start, end, Some(fragment.clone()));
4416+ // the joining line contains only space-characters => don't include a whitespace when joining
4417+ None
4418+ } else {
4419+ Some(Tendril::from(" "))
4420+ };
4125− changes.push(change);
4421+ changes.push((start, end, separator));
4126 }
4422 }
4127 }
4423 }
4128
4424
@@ -4134,9 +4430,6 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4134 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
4430 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
4135 changes.dedup();
4431 changes.dedup();
4136
4432
4137− // TODO: joining multiple empty lines should be replaced by a single space.
4138− // need to merge change ranges that touch
4139−
4140 // select inserted spaces
4433 // select inserted spaces
4141 let transaction = if select_space {
4434 let transaction = if select_space {
4142 let ranges: SmallVec<_> = changes
4435 let ranges: SmallVec<_> = changes
@@ -4148,9 +4441,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
4148 })
4441 })
4149 .collect();
4442 .collect();
4150 let selection = Selection::new(ranges, 0);
4443 let selection = Selection::new(ranges, 0);
4151− Transaction::change(doc.text(), changes.into_iter()).with_selection(selection)
4444+ Transaction::change(text, changes.into_iter()).with_selection(selection)
4152 } else {
4445 } else {
4153− Transaction::change(doc.text(), changes.into_iter())
4446+ Transaction::change(text, changes.into_iter())
4154 };
4447 };
4155
4448
4156 doc.apply(&transaction, view.id);
4449 doc.apply(&transaction, view.id);
@@ -4164,8 +4457,8 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
4164 if remove { "remove:" } else { "keep:" }.into(),
4457 if remove { "remove:" } else { "keep:" }.into(),
4165 Some(reg),
4458 Some(reg),
4166 ui::completers::none,
4459 ui::completers::none,
4167− move |editor, regex, event| {
4460+ move |cx, regex, event| {
4168− let (view, doc) = current!(editor);
4461+ let (view, doc) = current!(cx.editor);
4169 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
4462 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
4170 return;
4463 return;
4171 }
4464 }
@@ -4242,7 +4535,7 @@ pub fn completion(cx: &mut Context) {
4242 .map(|language_server| {
4535 .map(|language_server| {
4243 let language_server_id = language_server.id();
4536 let language_server_id = language_server.id();
4244 let offset_encoding = language_server.offset_encoding();
4537 let offset_encoding = language_server.offset_encoding();
4245− let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
4538+ let pos = pos_to_lsp_pos(&text, cursor, offset_encoding);
4246 let doc_id = doc.identifier();
4539 let doc_id = doc.identifier();
4247 let completion_request = language_server.completion(doc_id, pos, None).unwrap();
4540 let completion_request = language_server.completion(doc_id, pos, None).unwrap();
4248
4541
@@ -4399,7 +4692,13 @@ fn rotate_selections_backward(cx: &mut Context) {
4399 rotate_selections(cx, Direction::Backward)
4692 rotate_selections(cx, Direction::Backward)
4400}
4693}
4401
4694
4402−fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
4695+enum ReorderStrategy {
4696+ RotateForward,
4697+ RotateBackward,
4698+ Reverse,
4699+}
4700+
4701+fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
4403 let count = cx.count;
4702 let count = cx.count;
4404 let (view, doc) = current!(cx.editor);
4703 let (view, doc) = current!(cx.editor);
4405 let text = doc.text().slice(..);
4704 let text = doc.text().slice(..);
@@ -4417,9 +4716,10 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
4417
4716
4418 for chunk in fragments.chunks_mut(group) {
4717 for chunk in fragments.chunks_mut(group) {
4419 // TODO: also modify main index
4718 // TODO: also modify main index
4420− match direction {
4719+ match strategy {
4421− Direction::Forward => chunk.rotate_right(1),
4720+ ReorderStrategy::RotateForward => chunk.rotate_right(1),
4422− Direction::Backward => chunk.rotate_left(1),
4721+ ReorderStrategy::RotateBackward => chunk.rotate_left(1),
4722+ ReorderStrategy::Reverse => chunk.reverse(),
4423 };
4723 };
4424 }
4724 }
4425
4725
@@ -4436,10 +4736,13 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
4436}
4736}
4437
4737
4438fn rotate_selection_contents_forward(cx: &mut Context) {
4738fn rotate_selection_contents_forward(cx: &mut Context) {
4439− rotate_selection_contents(cx, Direction::Forward)
4739+ reorder_selection_contents(cx, ReorderStrategy::RotateForward)
4440}
4740}
4441fn rotate_selection_contents_backward(cx: &mut Context) {
4741fn rotate_selection_contents_backward(cx: &mut Context) {
4442− rotate_selection_contents(cx, Direction::Backward)
4742+ reorder_selection_contents(cx, ReorderStrategy::RotateBackward)
4743+}
4744+fn reverse_selection_contents(cx: &mut Context) {
4745+ reorder_selection_contents(cx, ReorderStrategy::Reverse)
4443}
4746}
4444
4747
4445// tree sitter node selection
4748// tree sitter node selection
@@ -4463,8 +4766,7 @@ fn expand_selection(cx: &mut Context) {
4463 }
4766 }
4464 }
4767 }
4465 };
4768 };
4466− motion(cx.editor);
4467− cx.editor.last_motion = Some(Motion(Box::new(motion)));
4769+ cx.editor.apply_motion(motion);
4468}
4770}
4469
4771
4470fn shrink_selection(cx: &mut Context) {
4772fn shrink_selection(cx: &mut Context) {
@@ -4488,8 +4790,7 @@ fn shrink_selection(cx: &mut Context) {
4488 doc.set_selection(view.id, selection);
4790 doc.set_selection(view.id, selection);
4489 }
4791 }
4490 };
4792 };
4491− motion(cx.editor);
4492− cx.editor.last_motion = Some(Motion(Box::new(motion)));
4793+ cx.editor.apply_motion(motion);
4493}
4794}
4494
4795
4495fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
4796fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
@@ -4507,8 +4808,7 @@ where
4507 doc.set_selection(view.id, selection);
4808 doc.set_selection(view.id, selection);
4508 }
4809 }
4509 };
4810 };
4510− motion(cx.editor);
4511− cx.editor.last_motion = Some(Motion(Box::new(motion)));
4811+ cx.editor.apply_motion(motion);
4512}
4812}
4513
4813
4514fn select_next_sibling(cx: &mut Context) {
4814fn select_next_sibling(cx: &mut Context) {
@@ -4519,22 +4819,68 @@ fn select_prev_sibling(cx: &mut Context) {
4519 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
4819 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
4520}
4820}
4521
4821
4822+fn move_node_bound_impl(cx: &mut Context, dir: Direction, movement: Movement) {
4823+ let motion = move |editor: &mut Editor| {
4824+ let (view, doc) = current!(editor);
4825+
4826+ if let Some(syntax) = doc.syntax() {
4827+ let text = doc.text().slice(..);
4828+ let current_selection = doc.selection(view.id);
4829+
4830+ let selection = movement::move_parent_node_end(
4831+ syntax,
4832+ text,
4833+ current_selection.clone(),
4834+ dir,
4835+ movement,
4836+ );
4837+
4838+ doc.set_selection(view.id, selection);
4839+
4840+ // [TODO] temporary workaround until we're not using the idle timer to
4841+ // trigger auto completions any more
4842+ editor.clear_idle_timer();
4843+ }
4844+ };
4845+
4846+ cx.editor.apply_motion(motion);
4847+}
4848+
4849+pub fn move_parent_node_end(cx: &mut Context) {
4850+ move_node_bound_impl(cx, Direction::Forward, Movement::Move)
4851+}
4852+
4853+pub fn move_parent_node_start(cx: &mut Context) {
4854+ move_node_bound_impl(cx, Direction::Backward, Movement::Move)
4855+}
4856+
4857+pub fn extend_parent_node_end(cx: &mut Context) {
4858+ move_node_bound_impl(cx, Direction::Forward, Movement::Extend)
4859+}
4860+
4861+pub fn extend_parent_node_start(cx: &mut Context) {
4862+ move_node_bound_impl(cx, Direction::Backward, Movement::Extend)
4863+}
4864+
4522fn match_brackets(cx: &mut Context) {
4865fn match_brackets(cx: &mut Context) {
4523 let (view, doc) = current!(cx.editor);
4866 let (view, doc) = current!(cx.editor);
4867+ let is_select = cx.editor.mode == Mode::Select;
4868+ let text = doc.text();
4869+ let text_slice = text.slice(..);
4524
4870
4525− if let Some(syntax) = doc.syntax() {
4526− let text = doc.text().slice(..);
4527− let selection = doc.selection(view.id).clone().transform(|range| {
4871+ let selection = doc.selection(view.id).clone().transform(|range| {
4528− if let Some(pos) =
4872+ let pos = range.cursor(text_slice);
4873+ if let Some(matched_pos) = doc.syntax().map_or_else(
4874+ || match_brackets::find_matching_bracket_plaintext(text.slice(..), pos),
4529− match_brackets::find_matching_bracket_fuzzy(syntax, doc.text(), range.cursor(text))
4875+ |syntax| match_brackets::find_matching_bracket_fuzzy(syntax, text.slice(..), pos),
4530− {
4876+ ) {
4531− range.put_cursor(text, pos, cx.editor.mode == Mode::Select)
4877+ range.put_cursor(text_slice, matched_pos, is_select)
4532− } else {
4878+ } else {
4533− range
4879+ range
4534− }
4880+ }
4535− });
4881+ });
4882+
4536− doc.set_selection(view.id, selection);
4883+ doc.set_selection(view.id, selection);
4537− }
4538}
4884}
4539
4885
4540//
4886//
@@ -4629,17 +4975,19 @@ fn transpose_view(cx: &mut Context) {
4629 cx.editor.transpose_view()
4975 cx.editor.transpose_view()
4630}
4976}
4631
4977
4632−// split helper, clear it later
4978+/// Open a new split in the given direction specified by the action.
4979+///
4980+/// Maintain the current view (both the cursor's position and view in document).
4633−fn split(cx: &mut Context, action: Action) {
4981+fn split(editor: &mut Editor, action: Action) {
4634− let (view, doc) = current!(cx.editor);
4982+ let (view, doc) = current!(editor);
4635 let id = doc.id();
4983 let id = doc.id();
4636 let selection = doc.selection(view.id).clone();
4984 let selection = doc.selection(view.id).clone();
4637 let offset = view.offset;
4985 let offset = view.offset;
4638
4986
4639− cx.editor.switch(id, action);
4987+ editor.switch(id, action);
4640
4988
4641 // match the selection in the previous view
4989 // match the selection in the previous view
4642− let (view, doc) = current!(cx.editor);
4990+ let (view, doc) = current!(editor);
4643 doc.set_selection(view.id, selection);
4991 doc.set_selection(view.id, selection);
4644 // match the view scroll offset (switch doesn't handle this fully
4992 // match the view scroll offset (switch doesn't handle this fully
4645 // since the selection is only matched after the split)
4993 // since the selection is only matched after the split)
@@ -4647,7 +4995,7 @@ fn split(cx: &mut Context, action: Action) {
4647}
4995}
4648
4996
4649fn hsplit(cx: &mut Context) {
4997fn hsplit(cx: &mut Context) {
4650− split(cx, Action::HorizontalSplit);
4998+ split(cx.editor, Action::HorizontalSplit);
4651}
4999}
4652
5000
4653fn hsplit_new(cx: &mut Context) {
5001fn hsplit_new(cx: &mut Context) {
@@ -4655,7 +5003,7 @@ fn hsplit_new(cx: &mut Context) {
4655}
5003}
4656
5004
4657fn vsplit(cx: &mut Context) {
5005fn vsplit(cx: &mut Context) {
4658− split(cx, Action::VerticalSplit);
5006+ split(cx.editor, Action::VerticalSplit);
4659}
5007}
4660
5008
4661fn vsplit_new(cx: &mut Context) {
5009fn vsplit_new(cx: &mut Context) {
@@ -4704,7 +5052,12 @@ fn insert_register(cx: &mut Context) {
4704 if let Some(ch) = event.char() {
5052 if let Some(ch) = event.char() {
4705 cx.editor.autoinfo = None;
5053 cx.editor.autoinfo = None;
4706 cx.register = Some(ch);
5054 cx.register = Some(ch);
5055+ paste(
5056+ cx.editor,
5057+ cx.register.unwrap_or('"'),
4707− paste(cx, Paste::Cursor);
5058+ Paste::Cursor,
5059+ cx.count(),
5060+ );
4708 }
5061 }
4709 })
5062 })
4710}
5063}
@@ -4788,8 +5141,7 @@ fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direct
4788 editor.set_status("Syntax-tree is not available in current buffer");
5141 editor.set_status("Syntax-tree is not available in current buffer");
4789 }
5142 }
4790 };
5143 };
4791− motion(cx.editor);
4792− cx.editor.last_motion = Some(Motion(Box::new(motion)));
5144+ cx.editor.apply_motion(motion);
4793}
5145}
4794
5146
4795fn goto_next_function(cx: &mut Context) {
5147fn goto_next_function(cx: &mut Context) {
@@ -4910,8 +5262,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
4910 });
5262 });
4911 doc.set_selection(view.id, selection);
5263 doc.set_selection(view.id, selection);
4912 };
5264 };
4913− textobject(cx.editor);
4914− cx.editor.last_motion = Some(Motion(Box::new(textobject)));
5265+ cx.editor.apply_motion(textobject);
4915 }
5266 }
4916 });
5267 });
4917
5268
@@ -5001,11 +5352,18 @@ fn surround_replace(cx: &mut Context) {
5001 }
5352 }
5002 };
5353 };
5003
5354
5355+ let selection = selection.clone();
5356+ let ranges: SmallVec<[Range; 1]> = change_pos.iter().map(|&p| Range::point(p)).collect();
5357+ doc.set_selection(
5358+ view.id,
5359+ Selection::new(ranges, selection.primary_index() * 2),
5360+ );
5361+
5004 cx.on_next_key(move |cx, event| {
5362 cx.on_next_key(move |cx, event| {
5005 let (view, doc) = current!(cx.editor);
5363 let (view, doc) = current!(cx.editor);
5006 let to = match event.char() {
5364 let to = match event.char() {
5007 Some(to) => to,
5365 Some(to) => to,
5008− None => return,
5366+ None => return doc.set_selection(view.id, selection),
5009 };
5367 };
5010 let (open, close) = surround::get_pair(to);
5368 let (open, close) = surround::get_pair(to);
5011 let transaction = Transaction::change(
5369 let transaction = Transaction::change(
@@ -5016,6 +5374,7 @@ fn surround_replace(cx: &mut Context) {
5016 (pos, pos + 1, Some(t))
5374 (pos, pos + 1, Some(t))
5017 }),
5375 }),
5018 );
5376 );
5377+ doc.set_selection(view.id, selection);
5019 doc.apply(&transaction, view.id);
5378 doc.apply(&transaction, view.id);
5020 exit_select_mode(cx);
5379 exit_select_mode(cx);
5021 });
5380 });
@@ -5250,12 +5609,18 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
5250 };
5609 };
5251
5610
5252 // These `usize`s cannot underflow because selection ranges cannot overlap.
5611 // These `usize`s cannot underflow because selection ranges cannot overlap.
5253− // Once the MSRV is 1.66.0 (mixed_integer_ops is stabilized), we can use checked
5612+ let anchor = to
5254− // arithmetic to assert this.
5613+ .checked_add_signed(offset)
5255− let anchor = (to as isize + offset - deleted_len as isize) as usize;
5614+ .expect("Selection ranges cannot overlap")
5615+ .checked_sub(deleted_len)
5616+ .expect("Selection ranges cannot overlap");
5256 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
5617 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
5257 ranges.push(new_range);
5618 ranges.push(new_range);
5258− offset = offset + output_len as isize - deleted_len as isize;
5619+ offset = offset
5620+ .checked_add_unsigned(output_len)
5621+ .expect("Selection ranges cannot overlap")
5622+ .checked_sub_unsigned(deleted_len)
5623+ .expect("Selection ranges cannot overlap");
5259
5624
5260 changes.push((from, to, Some(output)));
5625 changes.push((from, to, Some(output)));
5261 }
5626 }
@@ -5277,7 +5642,7 @@ fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBeha
5277 cx,
5642 cx,
5278 prompt,
5643 prompt,
5279 Some('|'),
5644 Some('|'),
5280− ui::completers::none,
5645+ ui::completers::filename,
5281 move |cx, input: &str, event: PromptEvent| {
5646 move |cx, input: &str, event: PromptEvent| {
5282 if event != PromptEvent::Validate {
5647 if event != PromptEvent::Validate {
5283 return;
5648 return;
@@ -5394,6 +5759,7 @@ fn increment_impl(cx: &mut Context, increment_direction: IncrementDirection) {
5394 let transaction = Transaction::change(doc.text(), changes.into_iter());
5759 let transaction = Transaction::change(doc.text(), changes.into_iter());
5395 let transaction = transaction.with_selection(new_selection);
5760 let transaction = transaction.with_selection(new_selection);
5396 doc.apply(&transaction, view.id);
5761 doc.apply(&transaction, view.id);
5762+ exit_select_mode(cx);
5397 }
5763 }
5398}
5764}
5399
5765
@@ -5412,9 +5778,12 @@ fn record_macro(cx: &mut Context) {
5412 }
5778 }
5413 })
5779 })
5414 .collect::<String>();
5780 .collect::<String>();
5415− cx.editor.registers.write(reg, vec![s]);
5781+ match cx.editor.registers.write(reg, vec![s]) {
5782+ Ok(_) => cx
5416− cx.editor
5783+ .editor
5417− .set_status(format!("Recorded to register [{}]", reg));
5784+ .set_status(format!("Recorded to register [{}]", reg)),
5785+ Err(err) => cx.editor.set_error(err.to_string()),
5786+ }
5418 } else {
5787 } else {
5419 let reg = cx.register.take().unwrap_or('@');
5788 let reg = cx.register.take().unwrap_or('@');
5420 cx.editor.macro_recording = Some((reg, Vec::new()));
5789 cx.editor.macro_recording = Some((reg, Vec::new()));
@@ -5434,8 +5803,14 @@ fn replay_macro(cx: &mut Context) {
5434 return;
5803 return;
5435 }
5804 }
5436
5805
5437− let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
5806+ let keys: Vec<KeyEvent> = if let Some(keys) = cx
5807+ .editor
5808+ .registers
5809+ .read(reg, cx.editor)
5810+ .filter(|values| values.len() == 1)
5811+ .map(|mut values| values.next().unwrap())
5812+ {
5438− match helix_view::input::parse_macro(keys_str) {
5813+ match helix_view::input::parse_macro(&keys) {
5439 Ok(keys) => keys,
5814 Ok(keys) => keys,
5440 Err(err) => {
5815 Err(err) => {
5441 cx.editor.set_error(format!("Invalid macro: {}", err));
5816 cx.editor.set_error(format!("Invalid macro: {}", err));
helix-term/src/commands/dap.rs+58−55
@@ -2,13 +2,13 @@ use super::{Context, Editor};
2use crate::{
2use crate::{
3 compositor::{self, Compositor},
3 compositor::{self, Compositor},
4 job::{Callback, Jobs},
4 job::{Callback, Jobs},
5− ui::{self, overlay::overlaid, FilePicker, Picker, Popup, Prompt, PromptEvent, Text},
5+ ui::{self, overlay::overlaid, Picker, Popup, Prompt, PromptEvent, Text},
6};
6};
7use dap::{StackFrame, Thread, ThreadStates};
7use dap::{StackFrame, Thread, ThreadStates};
8use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion, DebugTemplate};
8use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion, DebugTemplate};
9use helix_dap::{self as dap, Client};
9use helix_dap::{self as dap, Client};
10use helix_lsp::block_on;
10use helix_lsp::block_on;
11−use helix_view::editor::Breakpoint;
11+use helix_view::{editor::Breakpoint, graphics::Margin};
12
12
13use serde_json::{to_value, Value};
13use serde_json::{to_value, Value};
14use tokio_stream::wrappers::UnboundedReceiverStream;
14use tokio_stream::wrappers::UnboundedReceiverStream;
@@ -73,21 +73,19 @@ fn thread_picker(
73 let debugger = debugger!(editor);
73 let debugger = debugger!(editor);
74
74
75 let thread_states = debugger.thread_states.clone();
75 let thread_states = debugger.thread_states.clone();
76− let picker = FilePicker::new(
76+ let picker = Picker::new(threads, thread_states, move |cx, thread, _action| {
77− threads,
78− thread_states,
79− move |cx, thread, _action| callback_fn(cx.editor, thread),
77+ callback_fn(cx.editor, thread)
78+ })
80− move |editor, thread| {
79+ .with_preview(move |editor, thread| {
81− let frames = editor.debugger.as_ref()?.stack_frames.get(&thread.id)?;
80+ let frames = editor.debugger.as_ref()?.stack_frames.get(&thread.id)?;
82− let frame = frames.get(0)?;
81+ let frame = frames.get(0)?;
83− let path = frame.source.as_ref()?.path.clone()?;
82+ let path = frame.source.as_ref()?.path.clone()?;
84− let pos = Some((
83+ let pos = Some((
85− frame.line.saturating_sub(1),
84+ frame.line.saturating_sub(1),
86− frame.end_line.unwrap_or(frame.line).saturating_sub(1),
85+ frame.end_line.unwrap_or(frame.line).saturating_sub(1),
87− ));
86+ ));
88− Some((path.into(), pos))
87+ Some((path.into(), pos))
89− },
90− );
88+ });
91 compositor.push(Box::new(picker));
89 compositor.push(Box::new(picker));
92 },
90 },
93 );
91 );
@@ -219,7 +217,7 @@ pub fn dap_start_impl(
219 }
217 }
220 }
218 }
221
219
222− args.insert("cwd", to_value(std::env::current_dir().unwrap())?);
220+ args.insert("cwd", to_value(helix_loader::current_working_dir())?);
223
221
224 let args = to_value(args).unwrap();
222 let args = to_value(args).unwrap();
225
223
@@ -341,8 +339,12 @@ fn debug_parameter_prompt(
341 .to_owned();
339 .to_owned();
342
340
343 let completer = match field_type {
341 let completer = match field_type {
344− "filename" => ui::completers::filename,
342+ "filename" => |editor: &Editor, input: &str| {
345− "directory" => ui::completers::directory,
343+ ui::completers::filename_with_git_ignore(editor, input, false)
344+ },
345+ "directory" => |editor: &Editor, input: &str| {
346+ ui::completers::directory_with_git_ignore(editor, input, false)
347+ },
346 _ => ui::completers::none,
348 _ => ui::completers::none,
347 };
349 };
348
350
@@ -579,7 +581,12 @@ pub fn dap_variables(cx: &mut Context) {
579 }
581 }
580
582
581 let contents = Text::from(tui::text::Text::from(variables));
583 let contents = Text::from(tui::text::Text::from(variables));
584+ let margin = if cx.editor.popup_border() {
585+ Margin::all(1)
586+ } else {
587+ Margin::none()
588+ };
582− let popup = Popup::new("dap-variables", contents);
589+ let popup = Popup::new("dap-variables", contents).margin(margin);
583 cx.replace_or_push_layer("dap-variables", popup);
590 cx.replace_or_push_layer("dap-variables", popup);
584}
591}
585
592
@@ -728,39 +735,35 @@ pub fn dap_switch_stack_frame(cx: &mut Context) {
728
735
729 let frames = debugger.stack_frames[&thread_id].clone();
736 let frames = debugger.stack_frames[&thread_id].clone();
730
737
731− let picker = FilePicker::new(
732− frames,
733− (),
734− move |cx, frame, _action| {
738+ let picker = Picker::new(frames, (), move |cx, frame, _action| {
735− let debugger = debugger!(cx.editor);
739+ let debugger = debugger!(cx.editor);
736− // TODO: this should be simpler to find
740+ // TODO: this should be simpler to find
737− let pos = debugger.stack_frames[&thread_id]
741+ let pos = debugger.stack_frames[&thread_id]
738− .iter()
742+ .iter()
739− .position(|f| f.id == frame.id);
743+ .position(|f| f.id == frame.id);
740− debugger.active_frame = pos;
744+ debugger.active_frame = pos;
741−
745+
742− let frame = debugger.stack_frames[&thread_id]
746+ let frame = debugger.stack_frames[&thread_id]
743− .get(pos.unwrap_or(0))
747+ .get(pos.unwrap_or(0))
744− .cloned();
748+ .cloned();
745− if let Some(frame) = &frame {
749+ if let Some(frame) = &frame {
746− jump_to_stack_frame(cx.editor, frame);
750+ jump_to_stack_frame(cx.editor, frame);
747− }
751+ }
748− },
752+ })
749− move |_editor, frame| {
753+ .with_preview(move |_editor, frame| {
750− frame
754+ frame
751− .source
755+ .source
752− .as_ref()
756+ .as_ref()
753− .and_then(|source| source.path.clone())
757+ .and_then(|source| source.path.clone())
754− .map(|path| {
758+ .map(|path| {
755− (
759+ (
756− path.into(),
760+ path.into(),
757− Some((
761+ Some((
758− frame.line.saturating_sub(1),
762+ frame.line.saturating_sub(1),
759− frame.end_line.unwrap_or(frame.line).saturating_sub(1),
763+ frame.end_line.unwrap_or(frame.line).saturating_sub(1),
760− )),
764+ )),
761− )
762− })
765+ )
763− },
766+ })
764− );
767+ });
765 cx.push_layer(Box::new(picker))
768 cx.push_layer(Box::new(picker))
766}
769}
helix-term/src/commands/lsp.rs+94−94
@@ -23,6 +23,7 @@ use helix_core::{
23use helix_view::{
23use helix_view::{
24 document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
24 document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
25 editor::Action,
25 editor::Action,
26+ graphics::Margin,
26 theme::Style,
27 theme::Style,
27 Document, View,
28 Document, View,
28};
29};
@@ -31,8 +32,8 @@ use crate::{
31 compositor::{self, Compositor},
32 compositor::{self, Compositor},
32 job::Callback,
33 job::Callback,
33 ui::{
34 ui::{
34− self, lsp::SignatureHelp, overlay::overlaid, DynamicPicker, FileLocation, FilePicker,
35+ self, lsp::SignatureHelp, overlay::overlaid, DynamicPicker, FileLocation, Picker, Popup,
35− Popup, PromptEvent,
36+ PromptEvent,
36 },
37 },
37};
38};
38
39
@@ -49,7 +50,7 @@ use std::{
49/// If there is no configured language server that supports the feature, this displays a status message.
50/// If there is no configured language server that supports the feature, this displays a status message.
50/// Using this macro in a context where the editor automatically queries the LSP
51/// Using this macro in a context where the editor automatically queries the LSP
51/// (instead of when the user explicitly does so via a keybind like `gd`)
52/// (instead of when the user explicitly does so via a keybind like `gd`)
52−/// will spam the "No configured language server supports <feature>" status message confusingly.
53+/// will spam the "No configured language server supports \<feature>" status message confusingly.
53#[macro_export]
54#[macro_export]
54macro_rules! language_server_with_feature {
55macro_rules! language_server_with_feature {
55 ($editor:expr, $doc:expr, $feature:expr) => {{
56 ($editor:expr, $doc:expr, $feature:expr) => {{
@@ -195,7 +196,6 @@ fn location_to_file_location(location: &lsp::Location) -> FileLocation {
195 (path.into(), line)
196 (path.into(), line)
196}
197}
197
198
198−// TODO: share with symbol picker(symbol.location)
199fn jump_to_location(
199fn jump_to_location(
200 editor: &mut Editor,
200 editor: &mut Editor,
201 location: &lsp::Location,
201 location: &lsp::Location,
@@ -213,15 +213,16 @@ fn jump_to_location(
213 return;
213 return;
214 }
214 }
215 };
215 };
216+
216− match editor.open(&path, action) {
217+ let doc = match editor.open(&path, action) {
217− Ok(_) => (),
218+ Ok(id) => doc_mut!(editor, &id),
218 Err(err) => {
219 Err(err) => {
219 let err = format!("failed to open path: {:?}: {:?}", location.uri, err);
220 let err = format!("failed to open path: {:?}: {:?}", location.uri, err);
220 editor.set_error(err);
221 editor.set_error(err);
221 return;
222 return;
222 }
223 }
223− }
224+ };
224− let (view, doc) = current!(editor);
225+ let view = view_mut!(editor);
225 // TODO: convert inside server
226 // TODO: convert inside server
226 let new_range =
227 let new_range =
227 if let Some(new_range) = lsp_range_to_range(doc.text(), location.range, offset_encoding) {
228 if let Some(new_range) = lsp_range_to_range(doc.text(), location.range, offset_encoding) {
@@ -233,51 +234,24 @@ fn jump_to_location(
233 // we flip the range so that the cursor sits on the start of the symbol
234 // we flip the range so that the cursor sits on the start of the symbol
234 // (for example start of the function).
235 // (for example start of the function).
235 doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
236 doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
237+ if action.align_view(view, doc.id()) {
236− align_view(doc, view, Align::Center);
238+ align_view(doc, view, Align::Center);
239+ }
237}
240}
238
241
239−type SymbolPicker = FilePicker<SymbolInformationItem>;
242+type SymbolPicker = Picker<SymbolInformationItem>;
240
243
241fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url>) -> SymbolPicker {
244fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url>) -> SymbolPicker {
242 // TODO: drop current_path comparison and instead use workspace: bool flag?
245 // TODO: drop current_path comparison and instead use workspace: bool flag?
243− FilePicker::new(
244− symbols,
245− current_path.clone(),
246− move |cx, item, action| {
246+ Picker::new(symbols, current_path, move |cx, item, action| {
247+ jump_to_location(
247− let (view, doc) = current!(cx.editor);
248+ cx.editor,
248− push_jump(view, doc);
249−
250− if current_path.as_ref() != Some(&item.symbol.location.uri) {
251− let uri = &item.symbol.location.uri;
249+ &item.symbol.location,
252− let path = match uri.to_file_path() {
253− Ok(path) => path,
254− Err(_) => {
255− let err = format!("unable to convert URI to filepath: {}", uri);
256− cx.editor.set_error(err);
257− return;
258− }
259− };
260− if let Err(err) = cx.editor.open(&path, action) {
261− let err = format!("failed to open document: {}: {}", uri, err);
262− log::error!("{}", err);
263− cx.editor.set_error(err);
264− return;
265− }
266− }
267−
268− let (view, doc) = current!(cx.editor);
269−
270− if let Some(range) =
271− lsp_range_to_range(doc.text(), item.symbol.location.range, item.offset_encoding)
250+ item.offset_encoding,
272− {
251+ action,
273− // we flip the range so that the cursor sits on the start of the symbol
252+ );
274− // (for example start of the function).
275− doc.set_selection(view.id, Selection::single(range.head, range.anchor));
276− align_view(doc, view, Align::Center);
277− }
278− },
253+ })
279− move |_editor, item| Some(location_to_file_location(&item.symbol.location)),
254+ .with_preview(move |_editor, item| Some(location_to_file_location(&item.symbol.location)))
280− )
281 .truncate_start(false)
255 .truncate_start(false)
282}
256}
283
257
@@ -290,9 +264,9 @@ enum DiagnosticsFormat {
290fn diag_picker(
264fn diag_picker(
291 cx: &Context,
265 cx: &Context,
292 diagnostics: BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize)>>,
266 diagnostics: BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize)>>,
293− current_path: Option<lsp::Url>,
267+ _current_path: Option<lsp::Url>,
294 format: DiagnosticsFormat,
268 format: DiagnosticsFormat,
295−) -> FilePicker<PickerDiagnostic> {
269+) -> Picker<PickerDiagnostic> {
296 // TODO: drop current_path comparison and instead use workspace: bool flag?
270 // TODO: drop current_path comparison and instead use workspace: bool flag?
297
271
298 // flatten the map to a vec of (url, diag) pairs
272 // flatten the map to a vec of (url, diag) pairs
@@ -301,7 +275,7 @@ fn diag_picker(
301 flat_diag.reserve(diags.len());
275 flat_diag.reserve(diags.len());
302
276
303 for (diag, ls) in diags {
277 for (diag, ls) in diags {
304− if let Some(ls) = cx.editor.language_servers.get_by_id(ls) {
278+ if let Some(ls) = cx.editor.language_server_by_id(ls) {
305 flat_diag.push(PickerDiagnostic {
279 flat_diag.push(PickerDiagnostic {
306 url: url.clone(),
280 url: url.clone(),
307 diag,
281 diag,
@@ -318,7 +292,7 @@ fn diag_picker(
318 error: cx.editor.theme.get("error"),
292 error: cx.editor.theme.get("error"),
319 };
293 };
320
294
321− FilePicker::new(
295+ Picker::new(
322 flat_diag,
296 flat_diag,
323 (styles, format),
297 (styles, format),
324 move |cx,
298 move |cx,
@@ -328,28 +302,18 @@ fn diag_picker(
328 offset_encoding,
302 offset_encoding,
329 },
303 },
330 action| {
304 action| {
331− if current_path.as_ref() == Some(url) {
305+ jump_to_location(
332− let (view, doc) = current!(cx.editor);
306+ cx.editor,
333− push_jump(view, doc);
334− } else {
335− let path = url.to_file_path().unwrap();
336− cx.editor.open(&path, action).expect("editor.open failed");
337− }
338−
339− let (view, doc) = current!(cx.editor);
340−
341− if let Some(range) = lsp_range_to_range(doc.text(), diag.range, *offset_encoding) {
342− // we flip the range so that the cursor sits on the start of the symbol
343− // (for example start of the function).
344− doc.set_selection(view.id, Selection::single(range.head, range.anchor));
345− align_view(doc, view, Align::Center);
346− }
347− },
348− move |_editor, PickerDiagnostic { url, diag, .. }| {
349− let location = lsp::Location::new(url.clone(), diag.range);
307+ &lsp::Location::new(url.clone(), diag.range),
350− Some(location_to_file_location(&location))
308+ *offset_encoding,
309+ action,
310+ )
351 },
311 },
352 )
312 )
313+ .with_preview(move |_editor, PickerDiagnostic { url, diag, .. }| {
314+ let location = lsp::Location::new(url.clone(), diag.range);
315+ Some(location_to_file_location(&location))
316+ })
353 .truncate_start(false)
317 .truncate_start(false)
354}
318}
355
319
@@ -442,6 +406,15 @@ pub fn symbol_picker(cx: &mut Context) {
442
406
443pub fn workspace_symbol_picker(cx: &mut Context) {
407pub fn workspace_symbol_picker(cx: &mut Context) {
444 let doc = doc!(cx.editor);
408 let doc = doc!(cx.editor);
409+ if doc
410+ .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
411+ .count()
412+ == 0
413+ {
414+ cx.editor
415+ .set_error("No configured language server supports workspace symbols");
416+ return;
417+ }
445
418
446 let get_symbols = move |pattern: String, editor: &mut Editor| {
419 let get_symbols = move |pattern: String, editor: &mut Editor| {
447 let doc = doc!(editor);
420 let doc = doc!(editor);
@@ -535,7 +508,6 @@ pub fn workspace_diagnostics_picker(cx: &mut Context) {
535
508
536struct CodeActionOrCommandItem {
509struct CodeActionOrCommandItem {
537 lsp_item: lsp::CodeActionOrCommand,
510 lsp_item: lsp::CodeActionOrCommand,
538− offset_encoding: OffsetEncoding,
539 language_server_id: usize,
511 language_server_id: usize,
540}
512}
541
513
@@ -637,9 +609,9 @@ pub fn code_action(cx: &mut Context) {
637 };
609 };
638 let code_action_request =
610 let code_action_request =
639 language_server.code_actions(doc.identifier(), range, code_action_context)?;
611 language_server.code_actions(doc.identifier(), range, code_action_context)?;
640− Some((code_action_request, offset_encoding, language_server_id))
612+ Some((code_action_request, language_server_id))
641 })
613 })
642− .map(|(request, offset_encoding, ls_id)| async move {
614+ .map(|(request, ls_id)| async move {
643 let json = request.await?;
615 let json = request.await?;
644 let response: Option<lsp::CodeActionResponse> = serde_json::from_value(json)?;
616 let response: Option<lsp::CodeActionResponse> = serde_json::from_value(json)?;
645 let mut actions = match response {
617 let mut actions = match response {
@@ -695,7 +667,6 @@ pub fn code_action(cx: &mut Context) {
695 .into_iter()
667 .into_iter()
696 .map(|lsp_item| CodeActionOrCommandItem {
668 .map(|lsp_item| CodeActionOrCommandItem {
697 lsp_item,
669 lsp_item,
698− offset_encoding,
699 language_server_id: ls_id,
670 language_server_id: ls_id,
700 })
671 })
701 .collect())
672 .collect())
@@ -727,7 +698,12 @@ pub fn code_action(cx: &mut Context) {
727
698
728 // always present here
699 // always present here
729 let action = action.unwrap();
700 let action = action.unwrap();
730− let offset_encoding = action.offset_encoding;
701+ let Some(language_server) = editor.language_server_by_id(action.language_server_id)
702+ else {
703+ editor.set_error("Language Server disappeared");
704+ return;
705+ };
706+ let offset_encoding = language_server.offset_encoding();
731
707
732 match &action.lsp_item {
708 match &action.lsp_item {
733 lsp::CodeActionOrCommand::Command(command) => {
709 lsp::CodeActionOrCommand::Command(command) => {
@@ -736,7 +712,25 @@ pub fn code_action(cx: &mut Context) {
736 }
712 }
737 lsp::CodeActionOrCommand::CodeAction(code_action) => {
713 lsp::CodeActionOrCommand::CodeAction(code_action) => {
738 log::debug!("code action: {:?}", code_action);
714 log::debug!("code action: {:?}", code_action);
715+ // we support lsp "codeAction/resolve" for `edit` and `command` fields
716+ let mut resolved_code_action = None;
717+ if code_action.edit.is_none() || code_action.command.is_none() {
718+ if let Some(future) =
719+ language_server.resolve_code_action(code_action.clone())
720+ {
721+ if let Ok(response) = helix_lsp::block_on(future) {
722+ if let Ok(code_action) =
723+ serde_json::from_value::<CodeAction>(response)
724+ {
725+ resolved_code_action = Some(code_action);
726+ }
727+ }
728+ }
729+ }
730+ let resolved_code_action =
731+ resolved_code_action.as_ref().unwrap_or(code_action);
732+
739− if let Some(ref workspace_edit) = code_action.edit {
733+ if let Some(ref workspace_edit) = resolved_code_action.edit {
740 log::debug!("edit: {:?}", workspace_edit);
734 log::debug!("edit: {:?}", workspace_edit);
741 let _ = apply_workspace_edit(editor, offset_encoding, workspace_edit);
735 let _ = apply_workspace_edit(editor, offset_encoding, workspace_edit);
742 }
736 }
@@ -751,7 +745,16 @@ pub fn code_action(cx: &mut Context) {
751 });
745 });
752 picker.move_down(); // pre-select the first item
746 picker.move_down(); // pre-select the first item
753
747
748+ let margin = if editor.menu_border() {
749+ Margin::vertical(1)
750+ } else {
751+ Margin::none()
752+ };
753+
754− let popup = Popup::new("code-action", picker).with_scrollbar(false);
754+ let popup = Popup::new("code-action", picker)
755+ .with_scrollbar(false)
756+ .margin(margin);
757+
755 compositor.replace_or_push("code-action", popup);
758 compositor.replace_or_push("code-action", popup);
756 };
759 };
757
760
@@ -770,8 +773,7 @@ pub fn execute_lsp_command(editor: &mut Editor, language_server_id: usize, cmd:
770 // the command is executed on the server and communicated back
773 // the command is executed on the server and communicated back
771 // to the client asynchronously using workspace edits
774 // to the client asynchronously using workspace edits
772 let future = match editor
775 let future = match editor
773− .language_servers
774− .get_by_id(language_server_id)
776+ .language_server_by_id(language_server_id)
775 .and_then(|language_server| language_server.command(cmd))
777 .and_then(|language_server| language_server.command(cmd))
776 {
778 {
777 Some(future) => future,
779 Some(future) => future,
@@ -1027,7 +1029,7 @@ fn goto_impl(
1027 locations: Vec<lsp::Location>,
1029 locations: Vec<lsp::Location>,
1028 offset_encoding: OffsetEncoding,
1030 offset_encoding: OffsetEncoding,
1029) {
1031) {
1030− let cwdir = std::env::current_dir().unwrap_or_default();
1032+ let cwdir = helix_loader::current_working_dir();
1031
1033
1032 match locations.as_slice() {
1034 match locations.as_slice() {
1033 [location] => {
1035 [location] => {
@@ -1037,14 +1039,10 @@ fn goto_impl(
1037 editor.set_error("No definition found.");
1039 editor.set_error("No definition found.");
1038 }
1040 }
1039 _locations => {
1041 _locations => {
1040− let picker = FilePicker::new(
1041− locations,
1042− cwdir,
1043− move |cx, location, action| {
1042+ let picker = Picker::new(locations, cwdir, move |cx, location, action| {
1044− jump_to_location(cx.editor, location, offset_encoding, action)
1043+ jump_to_location(cx.editor, location, offset_encoding, action)
1045− },
1044+ })
1046− move |_editor, location| Some(location_to_file_location(location)),
1045+ .with_preview(move |_editor, location| Some(location_to_file_location(location)));
1047− );
1048 compositor.push(Box::new(overlaid(picker)));
1046 compositor.push(Box::new(overlaid(picker)));
1049 }
1047 }
1050 }
1048 }
@@ -1171,7 +1169,8 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
1171 // Do not show the message if signature help was invoked
1169 // Do not show the message if signature help was invoked
1172 // automatically on backspace, trigger characters, etc.
1170 // automatically on backspace, trigger characters, etc.
1173 if invoked == SignatureHelpInvoked::Manual {
1171 if invoked == SignatureHelpInvoked::Manual {
1172+ cx.editor
1174− cx.editor.set_error("No configured language server supports signature-help");
1173+ .set_error("No configured language server supports signature-help");
1175 }
1174 }
1176 return;
1175 return;
1177 };
1176 };
@@ -1394,9 +1393,10 @@ pub fn rename_symbol(cx: &mut Context) {
1394
1393
1395 let Some(language_server) = doc
1394 let Some(language_server) = doc
1396 .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1395 .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1397− .find(|ls| language_server_id.is_none() || Some(ls.id()) == language_server_id)
1396+ .find(|ls| language_server_id.map_or(true, |id| id == ls.id()))
1398 else {
1397 else {
1398+ cx.editor
1399− cx.editor.set_error("No configured language server supports symbol renaming");
1399+ .set_error("No configured language server supports symbol renaming");
1400 return;
1400 return;
1401 };
1401 };
1402
1402
@@ -1483,7 +1483,7 @@ pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
1483 };
1483 };
1484 let (view, doc) = current!(editor);
1484 let (view, doc) = current!(editor);
1485 let text = doc.text();
1485 let text = doc.text();
1486− let pos = doc.selection(view.id).primary().head;
1486+ let pos = doc.selection(view.id).primary().cursor(text.slice(..));
1487
1487
1488 // We must find the range that contains our primary cursor to prevent our primary cursor to move
1488 // We must find the range that contains our primary cursor to prevent our primary cursor to move
1489 let mut primary_index = 0;
1489 let mut primary_index = 0;
helix-term/src/commands/typed.rs+987−704
@@ -5,7 +5,10 @@ use crate::job::Job;
5
5
6use super::*;
6use super::*;
7
7
8+use helix_core::fuzzy::fuzzy_match;
9+use helix_core::indent::MAX_INDENT;
8−use helix_core::{encoding, shellwords::Shellwords};
10+use helix_core::{encoding, line_ending, path::get_canonicalized_path, shellwords::Shellwords};
11+use helix_lsp::{OffsetEncoding, Url};
9use helix_view::document::DEFAULT_LANGUAGE_NAME;
12use helix_view::document::DEFAULT_LANGUAGE_NAME;
10use helix_view::editor::{Action, CloseError, ConfigEvent};
13use helix_view::editor::{Action, CloseError, ConfigEvent};
11use serde_json::Value;
14use serde_json::Value;
@@ -329,12 +332,16 @@ fn write_impl(
329 path: Option<&Cow<str>>,
332 path: Option<&Cow<str>>,
330 force: bool,
333 force: bool,
331) -> anyhow::Result<()> {
334) -> anyhow::Result<()> {
332− let editor_auto_fmt = cx.editor.config().auto_format;
335+ let config = cx.editor.config();
333 let jobs = &mut cx.jobs;
336 let jobs = &mut cx.jobs;
334 let (view, doc) = current!(cx.editor);
337 let (view, doc) = current!(cx.editor);
335 let path = path.map(AsRef::as_ref);
338 let path = path.map(AsRef::as_ref);
336
339
337− let fmt = if editor_auto_fmt {
340+ if config.insert_final_newline {
341+ insert_final_newline(doc, view);
342+ }
343+
344+ let fmt = if config.auto_format {
338 doc.auto_format().map(|fmt| {
345 doc.auto_format().map(|fmt| {
339 let callback = make_format_callback(
346 let callback = make_format_callback(
340 doc.id(),
347 doc.id(),
@@ -358,6 +365,16 @@ fn write_impl(
358 Ok(())
365 Ok(())
359}
366}
360
367
368+fn insert_final_newline(doc: &mut Document, view: &mut View) {
369+ let text = doc.text();
370+ if line_ending::get_line_ending(&text.slice(..)).is_none() {
371+ let eof = Selection::point(text.len_chars());
372+ let insert = Transaction::insert(text, &eof, doc.line_ending.as_str().into());
373+ doc.apply(&insert, view.id);
374+ doc.append_changes_to_history(view);
375+ }
376+}
377+
361fn write(
378fn write(
362 cx: &mut compositor::Context,
379 cx: &mut compositor::Context,
363 args: &[Cow<str>],
380 args: &[Cow<str>],
@@ -460,8 +477,7 @@ fn set_indent_style(
460 cx.editor.set_status(match style {
477 cx.editor.set_status(match style {
461 Tabs => "tabs".to_owned(),
478 Tabs => "tabs".to_owned(),
462 Spaces(1) => "1 space".to_owned(),
479 Spaces(1) => "1 space".to_owned(),
463− Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
480+ Spaces(n) => format!("{} spaces", n),
464− _ => unreachable!(), // Shouldn't happen.
465 });
481 });
466 return Ok(());
482 return Ok(());
467 }
483 }
@@ -473,7 +489,7 @@ fn set_indent_style(
473 Some(arg) => arg
489 Some(arg) => arg
474 .parse::<u8>()
490 .parse::<u8>()
475 .ok()
491 .ok()
476− .filter(|n| (1..=8).contains(n))
492+ .filter(|n| (1..=MAX_INDENT).contains(n))
477 .map(Spaces),
493 .map(Spaces),
478 _ => None,
494 _ => None,
479 };
495 };
@@ -558,7 +574,6 @@ fn set_line_ending(
558
574
559 Ok(())
575 Ok(())
560}
576}
561−
562fn earlier(
577fn earlier(
563 cx: &mut compositor::Context,
578 cx: &mut compositor::Context,
564 args: &[Cow<str>],
579 args: &[Cow<str>],
@@ -657,11 +672,10 @@ pub fn write_all_impl(
657 write_scratch: bool,
672 write_scratch: bool,
658) -> anyhow::Result<()> {
673) -> anyhow::Result<()> {
659 let mut errors: Vec<&'static str> = Vec::new();
674 let mut errors: Vec<&'static str> = Vec::new();
660− let auto_format = cx.editor.config().auto_format;
675+ let config = cx.editor.config();
661 let jobs = &mut cx.jobs;
676 let jobs = &mut cx.jobs;
662 let current_view = view!(cx.editor);
677 let current_view = view!(cx.editor);
663
678
664− // save all documents
665 let saves: Vec<_> = cx
679 let saves: Vec<_> = cx
666 .editor
680 .editor
667 .documents
681 .documents
@@ -672,7 +686,7 @@ pub fn write_all_impl(
672 }
686 }
673 if doc.path().is_none() {
687 if doc.path().is_none() {
674 if write_scratch {
688 if write_scratch {
675− errors.push("cannot write a buffer without a filename\n");
689+ errors.push("cannot write a buffer without a filename");
676 }
690 }
677 return None;
691 return None;
678 }
692 }
@@ -692,32 +706,35 @@ pub fn write_all_impl(
692 current_view.id
706 current_view.id
693 };
707 };
694
708
695− let fmt = if auto_format {
696− doc.auto_format().map(|fmt| {
697− let callback = make_format_callback(
698− doc.id(),
699− doc.version(),
700− target_view,
709+ Some((doc.id(), target_view))
701− fmt,
702− Some((None, force)),
703− );
710+ })
704− jobs.add(Job::with_callback(callback).wait_before_exiting());
711+ .collect();
705− })
706− } else {
707− None
708− };
709
712
710− if fmt.is_none() {
713+ for (doc_id, target_view) in saves {
711− return Some(doc.id());
714+ let doc = doc_mut!(cx.editor, &doc_id);
712− }
713
715
716+ if config.insert_final_newline {
717+ insert_final_newline(doc, view_mut!(cx.editor, target_view));
718+ }
719+
720+ let fmt = if config.auto_format {
721+ doc.auto_format().map(|fmt| {
722+ let callback = make_format_callback(
723+ doc_id,
724+ doc.version(),
725+ target_view,
726+ fmt,
727+ Some((None, force)),
728+ );
729+ jobs.add(Job::with_callback(callback).wait_before_exiting());
730+ })
731+ } else {
714 None
732 None
715− })
733+ };
716− .collect();
717
734
718− // manually call save for the rest of docs that don't have a formatter
735+ if fmt.is_none() {
719− for id in saves {
720− cx.editor.save::<PathBuf>(id, None, force)?;
736+ cx.editor.save::<PathBuf>(doc_id, None, force)?;
737+ }
721 }
738 }
722
739
723 if !errors.is_empty() && !force {
740 if !errors.is_empty() && !force {
@@ -739,6 +756,18 @@ fn write_all(
739 write_all_impl(cx, false, true)
756 write_all_impl(cx, false, true)
740}
757}
741
758
759+fn force_write_all(
760+ cx: &mut compositor::Context,
761+ _args: &[Cow<str>],
762+ event: PromptEvent,
763+) -> anyhow::Result<()> {
764+ if event != PromptEvent::Validate {
765+ return Ok(());
766+ }
767+
768+ write_all_impl(cx, true, true)
769+}
770+
742fn write_all_quit(
771fn write_all_quit(
743 cx: &mut compositor::Context,
772 cx: &mut compositor::Context,
744 _args: &[Cow<str>],
773 _args: &[Cow<str>],
@@ -892,7 +921,27 @@ fn yank_main_selection_to_clipboard(
892 return Ok(());
921 return Ok(());
893 }
922 }
894
923
895− yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard)
924+ yank_primary_selection_impl(cx.editor, '+');
925+ Ok(())
926+}
927+
928+fn yank_joined(
929+ cx: &mut compositor::Context,
930+ args: &[Cow<str>],
931+ event: PromptEvent,
932+) -> anyhow::Result<()> {
933+ if event != PromptEvent::Validate {
934+ return Ok(());
935+ }
936+
937+ ensure!(args.len() <= 1, ":yank-join takes at most 1 argument");
938+
939+ let doc = doc!(cx.editor);
940+ let default_sep = Cow::Borrowed(doc.line_ending.as_str());
941+ let separator = args.first().unwrap_or(&default_sep);
942+ let register = cx.editor.selected_register.unwrap_or('"');
943+ yank_joined_impl(cx.editor, separator, register);
944+ Ok(())
896}
945}
897
946
898fn yank_joined_to_clipboard(
947fn yank_joined_to_clipboard(
@@ -907,7 +956,8 @@ fn yank_joined_to_clipboard(
907 let doc = doc!(cx.editor);
956 let doc = doc!(cx.editor);
908 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
957 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
909 let separator = args.first().unwrap_or(&default_sep);
958 let separator = args.first().unwrap_or(&default_sep);
910− yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
959+ yank_joined_impl(cx.editor, separator, '+');
960+ Ok(())
911}
961}
912
962
913fn yank_main_selection_to_primary_clipboard(
963fn yank_main_selection_to_primary_clipboard(
@@ -919,7 +969,8 @@ fn yank_main_selection_to_primary_clipboard(
919 return Ok(());
969 return Ok(());
920 }
970 }
921
971
922− yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection)
972+ yank_primary_selection_impl(cx.editor, '*');
973+ Ok(())
923}
974}
924
975
925fn yank_joined_to_primary_clipboard(
976fn yank_joined_to_primary_clipboard(
@@ -934,7 +985,8 @@ fn yank_joined_to_primary_clipboard(
934 let doc = doc!(cx.editor);
985 let doc = doc!(cx.editor);
935 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
986 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
936 let separator = args.first().unwrap_or(&default_sep);
987 let separator = args.first().unwrap_or(&default_sep);
937− yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
988+ yank_joined_impl(cx.editor, separator, '*');
989+ Ok(())
938}
990}
939
991
940fn paste_clipboard_after(
992fn paste_clipboard_after(
@@ -946,7 +998,8 @@ fn paste_clipboard_after(
946 return Ok(());
998 return Ok(());
947 }
999 }
948
1000
949− paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard, 1)
1001+ paste(cx.editor, '+', Paste::After, 1);
1002+ Ok(())
950}
1003}
951
1004
952fn paste_clipboard_before(
1005fn paste_clipboard_before(
@@ -958,7 +1011,8 @@ fn paste_clipboard_before(
958 return Ok(());
1011 return Ok(());
959 }
1012 }
960
1013
961− paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Clipboard, 1)
1014+ paste(cx.editor, '+', Paste::Before, 1);
1015+ Ok(())
962}
1016}
963
1017
964fn paste_primary_clipboard_after(
1018fn paste_primary_clipboard_after(
@@ -970,7 +1024,8 @@ fn paste_primary_clipboard_after(
970 return Ok(());
1024 return Ok(());
971 }
1025 }
972
1026
973− paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection, 1)
1027+ paste(cx.editor, '*', Paste::After, 1);
1028+ Ok(())
974}
1029}
975
1030
976fn paste_primary_clipboard_before(
1031fn paste_primary_clipboard_before(
@@ -982,30 +1037,8 @@ fn paste_primary_clipboard_before(
982 return Ok(());
1037 return Ok(());
983 }
1038 }
984
1039
985− paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Selection, 1)
1040+ paste(cx.editor, '*', Paste::Before, 1);
986−}
1041+ Ok(())
987−
988−fn replace_selections_with_clipboard_impl(
989− cx: &mut compositor::Context,
990− clipboard_type: ClipboardType,
991−) -> anyhow::Result<()> {
992− let scrolloff = cx.editor.config().scrolloff;
993− let (view, doc) = current!(cx.editor);
994−
995− match cx.editor.clipboard_provider.get_contents(clipboard_type) {
996− Ok(contents) => {
997− let selection = doc.selection(view.id);
998− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
999− (range.from(), range.to(), Some(contents.as_str().into()))
1000− });
1001−
1002− doc.apply(&transaction, view.id);
1003− doc.append_changes_to_history(view);
1004− view.ensure_cursor_in_view(doc, scrolloff);
1005− Ok(())
1006− }
1007− Err(e) => Err(e.context("Couldn't get system clipboard contents")),
1008− }
1009}
1042}
1010
1043
1011fn replace_selections_with_clipboard(
1044fn replace_selections_with_clipboard(
@@ -1017,7 +1050,8 @@ fn replace_selections_with_clipboard(
1017 return Ok(());
1050 return Ok(());
1018 }
1051 }
1019
1052
1020− replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
1053+ replace_with_yanked_impl(cx.editor, '+', 1);
1054+ Ok(())
1021}
1055}
1022
1056
1023fn replace_selections_with_primary_clipboard(
1057fn replace_selections_with_primary_clipboard(
@@ -1029,7 +1063,8 @@ fn replace_selections_with_primary_clipboard(
1029 return Ok(());
1063 return Ok(());
1030 }
1064 }
1031
1065
1032− replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
1066+ replace_with_yanked_impl(cx.editor, '*', 1);
1067+ Ok(())
1033}
1068}
1034
1069
1035fn show_clipboard_provider(
1070fn show_clipboard_provider(
@@ -1042,7 +1077,7 @@ fn show_clipboard_provider(
1042 }
1077 }
1043
1078
1044 cx.editor
1079 cx.editor
1045− .set_status(cx.editor.clipboard_provider.name().to_string());
1080+ .set_status(cx.editor.registers.clipboard_provider_name().to_string());
1046 Ok(())
1081 Ok(())
1047}
1082}
1048
1083
@@ -1062,14 +1097,11 @@ fn change_current_directory(
1062 .as_ref(),
1097 .as_ref(),
1063 );
1098 );
1064
1099
1065− if let Err(e) = std::env::set_current_dir(dir) {
1100+ helix_loader::set_current_working_dir(dir)?;
1066− bail!("Couldn't change the current working directory: {}", e);
1067− }
1068
1101
1069− let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1070 cx.editor.set_status(format!(
1102 cx.editor.set_status(format!(
1071 "Current working directory is now {}",
1103 "Current working directory is now {}",
1072− cwd.display()
1104+ helix_loader::current_working_dir().display()
1073 ));
1105 ));
1074 Ok(())
1106 Ok(())
1075}
1107}
@@ -1083,9 +1115,14 @@ fn show_current_directory(
1083 return Ok(());
1115 return Ok(());
1084 }
1116 }
1085
1117
1086− let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1118+ let cwd = helix_loader::current_working_dir();
1087− cx.editor
1088− .set_status(format!("Current working directory is {}", cwd.display()));
1119+ let message = format!("Current working directory is {}", cwd.display());
1120+
1121+ if cwd.exists() {
1122+ cx.editor.set_status(message);
1123+ } else {
1124+ cx.editor.set_error(format!("{} (deleted)", message));
1125+ }
1089 Ok(())
1126 Ok(())
1090}
1127}
1091
1128
@@ -1245,12 +1282,17 @@ fn reload(
1245 }
1282 }
1246
1283
1247 let scrolloff = cx.editor.config().scrolloff;
1284 let scrolloff = cx.editor.config().scrolloff;
1248− let redraw_handle = cx.editor.redraw_handle.clone();
1249 let (view, doc) = current!(cx.editor);
1285 let (view, doc) = current!(cx.editor);
1250− doc.reload(view, &cx.editor.diff_providers, redraw_handle)
1286+ doc.reload(view, &cx.editor.diff_providers).map(|_| {
1251− .map(|_| {
1252− view.ensure_cursor_in_view(doc, scrolloff);
1287+ view.ensure_cursor_in_view(doc, scrolloff);
1288+ })?;
1289+ if let Some(path) = doc.path() {
1290+ cx.editor
1291+ .language_servers
1292+ .file_event_handler
1293+ .file_changed(path.clone());
1253− })
1294+ }
1295+ Ok(())
1254}
1296}
1255
1297
1256fn reload_all(
1298fn reload_all(
@@ -1289,8 +1331,13 @@ fn reload_all(
1289 // Ensure that the view is synced with the document's history.
1331 // Ensure that the view is synced with the document's history.
1290 view.sync_changes(doc);
1332 view.sync_changes(doc);
1291
1333
1292− let redraw_handle = cx.editor.redraw_handle.clone();
1293− doc.reload(view, &cx.editor.diff_providers, redraw_handle)?;
1334+ doc.reload(view, &cx.editor.diff_providers)?;
1335+ if let Some(path) = doc.path() {
1336+ cx.editor
1337+ .language_servers
1338+ .file_event_handler
1339+ .file_changed(path.clone());
1340+ }
1294
1341
1295 for view_id in view_ids {
1342 for view_id in view_ids {
1296 let view = view_mut!(cx.editor, view_id);
1343 let view = view_mut!(cx.editor, view_id);
@@ -1339,9 +1386,8 @@ fn lsp_workspace_command(
1339 .map(|options| (ls.id(), options))
1386 .map(|options| (ls.id(), options))
1340 })
1387 })
1341 else {
1388 else {
1342− cx.editor.set_status(
1389+ cx.editor
1343− "No active language servers for this document support workspace commands",
1390+ .set_status("No active language servers for this document support workspace commands");
1344− );
1345 return Ok(());
1391 return Ok(());
1346 };
1392 };
1347
1393
@@ -1404,7 +1450,6 @@ fn lsp_restart(
1404 .language_config()
1450 .language_config()
1405 .context("LSP not defined for the current document")?;
1451 .context("LSP not defined for the current document")?;
1406
1452
1407− let scope = config.scope.clone();
1408 cx.editor.language_servers.restart(
1453 cx.editor.language_servers.restart(
1409 config,
1454 config,
1410 doc.path(),
1455 doc.path(),
@@ -1417,7 +1462,16 @@ fn lsp_restart(
1417 .editor
1462 .editor
1418 .documents()
1463 .documents()
1419 .filter_map(|doc| match doc.language_config() {
1464 .filter_map(|doc| match doc.language_config() {
1420− Some(config) if config.scope.eq(&scope) => Some(doc.id()),
1465+ Some(config)
1466+ if config.language_servers.iter().any(|ls| {
1467+ config
1468+ .language_servers
1469+ .iter()
1470+ .any(|restarted_ls| restarted_ls.name == ls.name)
1471+ }) =>
1472+ {
1473+ Some(doc.id())
1474+ }
1421 _ => None,
1475 _ => None,
1422 })
1476 })
1423 .collect();
1477 .collect();
@@ -1489,6 +1543,84 @@ fn tree_sitter_scopes(
1489 Ok(())
1543 Ok(())
1490}
1544}
1491
1545
1546+fn tree_sitter_highlight_name(
1547+ cx: &mut compositor::Context,
1548+ _args: &[Cow<str>],
1549+ event: PromptEvent,
1550+) -> anyhow::Result<()> {
1551+ fn find_highlight_at_cursor(
1552+ cx: &mut compositor::Context<'_>,
1553+ ) -> Option<helix_core::syntax::Highlight> {
1554+ use helix_core::syntax::HighlightEvent;
1555+
1556+ let (view, doc) = current!(cx.editor);
1557+ let syntax = doc.syntax()?;
1558+ let text = doc.text().slice(..);
1559+ let cursor = doc.selection(view.id).primary().cursor(text);
1560+ let byte = text.char_to_byte(cursor);
1561+ let node = syntax
1562+ .tree()
1563+ .root_node()
1564+ .descendant_for_byte_range(byte, byte)?;
1565+ // Query the same range as the one used in syntax highlighting.
1566+ let range = {
1567+ // Calculate viewport byte ranges:
1568+ let row = text.char_to_line(view.offset.anchor.min(text.len_chars()));
1569+ // Saturating subs to make it inclusive zero indexing.
1570+ let last_line = text.len_lines().saturating_sub(1);
1571+ let height = view.inner_area(doc).height;
1572+ let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
1573+ let start = text.line_to_byte(row.min(last_line));
1574+ let end = text.line_to_byte(last_visible_line + 1);
1575+
1576+ start..end
1577+ };
1578+
1579+ let mut highlight = None;
1580+
1581+ for event in syntax.highlight_iter(text, Some(range), None) {
1582+ match event.unwrap() {
1583+ HighlightEvent::Source { start, end }
1584+ if start == node.start_byte() && end == node.end_byte() =>
1585+ {
1586+ return highlight;
1587+ }
1588+ HighlightEvent::HighlightStart(hl) => {
1589+ highlight = Some(hl);
1590+ }
1591+ _ => (),
1592+ }
1593+ }
1594+
1595+ None
1596+ }
1597+
1598+ if event != PromptEvent::Validate {
1599+ return Ok(());
1600+ }
1601+
1602+ let Some(highlight) = find_highlight_at_cursor(cx) else {
1603+ return Ok(());
1604+ };
1605+
1606+ let content = cx.editor.theme.scope(highlight.0).to_string();
1607+
1608+ let callback = async move {
1609+ let call: job::Callback = Callback::EditorCompositor(Box::new(
1610+ move |editor: &mut Editor, compositor: &mut Compositor| {
1611+ let content = ui::Markdown::new(content, editor.syn_loader.clone());
1612+ let popup = Popup::new("hover", content).auto_close(true);
1613+ compositor.replace_or_push("hover", popup);
1614+ },
1615+ ));
1616+ Ok(call)
1617+ };
1618+
1619+ cx.jobs.callback(callback);
1620+
1621+ Ok(())
1622+}
1623+
1492fn vsplit(
1624fn vsplit(
1493 cx: &mut compositor::Context,
1625 cx: &mut compositor::Context,
1494 args: &[Cow<str>],
1626 args: &[Cow<str>],
@@ -1498,10 +1630,8 @@ fn vsplit(
1498 return Ok(());
1630 return Ok(());
1499 }
1631 }
1500
1632
1501− let id = view!(cx.editor).doc;
1502−
1503 if args.is_empty() {
1633 if args.is_empty() {
1504− cx.editor.switch(id, Action::VerticalSplit);
1634+ split(cx.editor, Action::VerticalSplit);
1505 } else {
1635 } else {
1506 for arg in args {
1636 for arg in args {
1507 cx.editor
1637 cx.editor
@@ -1521,10 +1651,8 @@ fn hsplit(
1521 return Ok(());
1651 return Ok(());
1522 }
1652 }
1523
1653
1524− let id = view!(cx.editor).doc;
1525−
1526 if args.is_empty() {
1654 if args.is_empty() {
1527− cx.editor.switch(id, Action::HorizontalSplit);
1655+ split(cx.editor, Action::HorizontalSplit);
1528 } else {
1656 } else {
1529 for arg in args {
1657 for arg in args {
1530 cx.editor
1658 cx.editor
@@ -1639,7 +1767,7 @@ fn tutor(
1639 let path = helix_loader::runtime_file(Path::new("tutor"));
1767 let path = helix_loader::runtime_file(Path::new("tutor"));
1640 cx.editor.open(&path, Action::Replace)?;
1768 cx.editor.open(&path, Action::Replace)?;
1641 // Unset path to prevent accidentally saving to the original tutor file.
1769 // Unset path to prevent accidentally saving to the original tutor file.
1642− doc_mut!(cx.editor).set_path(None)?;
1770+ doc_mut!(cx.editor).set_path(None);
1643 Ok(())
1771 Ok(())
1644}
1772}
1645
1773
@@ -1757,7 +1885,7 @@ fn set_option(
1757
1885
1758 *value = if value.is_string() {
1886 *value = if value.is_string() {
1759 // JSON strings require quotes, so we can't .parse() directly
1887 // JSON strings require quotes, so we can't .parse() directly
1760− serde_json::Value::String(arg.to_string())
1888+ Value::String(arg.to_string())
1761 } else {
1889 } else {
1762 arg.parse().map_err(field_error)?
1890 arg.parse().map_err(field_error)?
1763 };
1891 };
@@ -1782,8 +1910,8 @@ fn toggle_option(
1782 return Ok(());
1910 return Ok(());
1783 }
1911 }
1784
1912
1785− if args.len() != 1 {
1913+ if args.is_empty() {
1786− anyhow::bail!("Bad arguments. Usage: `:toggle key`");
1914+ anyhow::bail!("Bad arguments. Usage: `:toggle key [values]?`");
1787 }
1915 }
1788 let key = &args[0].to_lowercase();
1916 let key = &args[0].to_lowercase();
1789
1917
@@ -1793,22 +1921,58 @@ fn toggle_option(
1793 let pointer = format!("/{}", key.replace('.', "/"));
1921 let pointer = format!("/{}", key.replace('.', "/"));
1794 let value = config.pointer_mut(&pointer).ok_or_else(key_error)?;
1922 let value = config.pointer_mut(&pointer).ok_or_else(key_error)?;
1795
1923
1796− let Value::Bool(old_value) = *value else {
1924+ *value = match value {
1925+ Value::Bool(ref value) => {
1926+ ensure!(
1927+ args.len() == 1,
1928+ "Bad arguments. For boolean configurations use: `:toggle key`"
1929+ );
1930+ Value::Bool(!value)
1931+ }
1932+ Value::String(ref value) => {
1933+ ensure!(
1934+ args.len() > 2,
1935+ "Bad arguments. For string configurations use: `:toggle key val1 val2 ...`",
1936+ );
1937+
1938+ Value::String(
1939+ args[1..]
1940+ .iter()
1941+ .skip_while(|e| *e != value)
1942+ .nth(1)
1943+ .unwrap_or_else(|| &args[1])
1944+ .to_string(),
1945+ )
1946+ }
1947+ Value::Number(ref value) => {
1948+ ensure!(
1949+ args.len() > 2,
1950+ "Bad arguments. For number configurations use: `:toggle key val1 val2 ...`",
1951+ );
1952+
1953+ Value::Number(
1954+ args[1..]
1955+ .iter()
1956+ .skip_while(|&e| value.to_string() != *e.to_string())
1957+ .nth(1)
1958+ .unwrap_or_else(|| &args[1])
1959+ .parse()?,
1960+ )
1961+ }
1962+ Value::Null | Value::Object(_) | Value::Array(_) => {
1797− anyhow::bail!("Key `{}` is not toggle-able", key)
1963+ anyhow::bail!("Configuration {key} does not support toggle yet")
1964+ }
1798 };
1965 };
1799
1966
1800− let new_value = !old_value;
1967+ let status = format!("'{key}' is now set to {value}");
1801− *value = Value::Bool(new_value);
1802− // This unwrap should never fail because we only replace one boolean value
1803− // with another, maintaining a valid json config
1804− let config = serde_json::from_value(config).unwrap();
1968+ let config = serde_json::from_value(config)
1969+ .map_err(|err| anyhow::anyhow!("Cannot parse `{:?}`, {}", &args, err))?;
1805
1970
1806 cx.editor
1971 cx.editor
1807 .config_events
1972 .config_events
1808 .0
1973 .0
1809 .send(ConfigEvent::Update(config))?;
1974 .send(ConfigEvent::Update(config))?;
1810− cx.editor
1975+ cx.editor.set_status(status);
1811− .set_status(format!("Option `{}` is now set to `{}`", key, new_value));
1812 Ok(())
1976 Ok(())
1813}
1977}
1814
1978
@@ -2211,592 +2375,721 @@ fn clear_register(
2211 format!("Invalid register {}", args[0])
2375 format!("Invalid register {}", args[0])
2212 );
2376 );
2213 let register = args[0].chars().next().unwrap_or_default();
2377 let register = args[0].chars().next().unwrap_or_default();
2214− match cx.editor.registers.remove(register) {
2378+ if cx.editor.registers.remove(register) {
2215− Some(_) => cx
2216− .editor
2379+ cx.editor
2217− .set_status(format!("Register {} cleared", register)),
2380+ .set_status(format!("Register {} cleared", register));
2218− None => cx
2381+ } else {
2219− .editor
2382+ cx.editor
2220− .set_error(format!("Register {} not found", register)),
2383+ .set_error(format!("Register {} not found", register));
2221 }
2384 }
2222 Ok(())
2385 Ok(())
2223}
2386}
2224
2387
2388+fn redraw(
2389+ cx: &mut compositor::Context,
2390+ _args: &[Cow<str>],
2391+ event: PromptEvent,
2392+) -> anyhow::Result<()> {
2393+ if event != PromptEvent::Validate {
2394+ return Ok(());
2395+ }
2396+
2397+ let callback = Box::pin(async move {
2398+ let call: job::Callback =
2399+ job::Callback::EditorCompositor(Box::new(|_editor, compositor| {
2400+ compositor.need_full_redraw();
2401+ }));
2402+
2403+ Ok(call)
2404+ });
2405+
2406+ cx.jobs.callback(callback);
2407+
2408+ Ok(())
2409+}
2410+
2411+fn move_buffer(
2412+ cx: &mut compositor::Context,
2413+ args: &[Cow<str>],
2414+ event: PromptEvent,
2415+) -> anyhow::Result<()> {
2416+ if event != PromptEvent::Validate {
2417+ return Ok(());
2418+ }
2419+
2420+ ensure!(args.len() == 1, format!(":move takes one argument"));
2421+ let doc = doc!(cx.editor);
2422+
2423+ let new_path = get_canonicalized_path(&PathBuf::from(args.first().unwrap().to_string()));
2424+ let old_path = doc
2425+ .path()
2426+ .ok_or_else(|| anyhow!("Scratch buffer cannot be moved. Use :write instead"))?
2427+ .clone();
2428+ let old_path_as_url = doc.url().unwrap();
2429+ let new_path_as_url = Url::from_file_path(&new_path).unwrap();
2430+
2431+ let edits: Vec<(
2432+ helix_lsp::Result<helix_lsp::lsp::WorkspaceEdit>,
2433+ OffsetEncoding,
2434+ String,
2435+ )> = doc
2436+ .language_servers()
2437+ .map(|lsp| {
2438+ (
2439+ lsp.prepare_file_rename(&old_path_as_url, &new_path_as_url),
2440+ lsp.offset_encoding(),
2441+ lsp.name().to_owned(),
2442+ )
2443+ })
2444+ .filter(|(f, _, _)| f.is_some())
2445+ .map(|(f, encoding, name)| (helix_lsp::block_on(f.unwrap()), encoding, name))
2446+ .collect();
2447+
2448+ for (lsp_reply, encoding, name) in edits {
2449+ match lsp_reply {
2450+ Ok(edit) => {
2451+ if let Err(e) = apply_workspace_edit(cx.editor, encoding, &edit) {
2452+ log::error!(
2453+ ":move command failed to apply edits from lsp {}: {:?}",
2454+ name,
2455+ e
2456+ );
2457+ };
2458+ }
2459+ Err(e) => {
2460+ log::error!("LSP {} failed to treat willRename request: {:?}", name, e);
2461+ }
2462+ };
2463+ }
2464+
2465+ let doc = doc_mut!(cx.editor);
2466+
2467+ doc.set_path(Some(new_path.as_path()));
2468+ if let Err(e) = std::fs::rename(&old_path, &new_path) {
2469+ doc.set_path(Some(old_path.as_path()));
2470+ bail!("Could not move file: {}", e);
2471+ };
2472+
2473+ doc.language_servers().for_each(|lsp| {
2474+ lsp.did_file_rename(&old_path_as_url, &new_path_as_url);
2475+ });
2476+
2477+ cx.editor
2478+ .language_servers
2479+ .file_event_handler
2480+ .file_changed(new_path);
2481+
2482+ Ok(())
2483+}
2484+
2225pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2485pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2226− TypableCommand {
2486+ TypableCommand {
2227− name: "quit",
2487+ name: "quit",
2228− aliases: &["q"],
2488+ aliases: &["q"],
2229− doc: "Close the current view.",
2489+ doc: "Close the current view.",
2230− fun: quit,
2490+ fun: quit,
2231− signature: CommandSignature::none(),
2491+ signature: CommandSignature::none(),
2232− },
2492+ },
2233− TypableCommand {
2493+ TypableCommand {
2234− name: "quit!",
2494+ name: "quit!",
2235− aliases: &["q!"],
2495+ aliases: &["q!"],
2236− doc: "Force close the current view, ignoring unsaved changes.",
2496+ doc: "Force close the current view, ignoring unsaved changes.",
2237− fun: force_quit,
2497+ fun: force_quit,
2238− signature: CommandSignature::none(),
2498+ signature: CommandSignature::none(),
2239− },
2499+ },
2240− TypableCommand {
2500+ TypableCommand {
2241− name: "open",
2501+ name: "open",
2242− aliases: &["o"],
2502+ aliases: &["o"],
2243− doc: "Open a file from disk into the current view.",
2503+ doc: "Open a file from disk into the current view.",
2244− fun: open,
2504+ fun: open,
2245− signature: CommandSignature::all(completers::filename),
2505+ signature: CommandSignature::all(completers::filename),
2246− },
2506+ },
2247− TypableCommand {
2507+ TypableCommand {
2248− name: "buffer-close",
2508+ name: "buffer-close",
2249− aliases: &["bc", "bclose"],
2509+ aliases: &["bc", "bclose"],
2250− doc: "Close the current buffer.",
2510+ doc: "Close the current buffer.",
2251− fun: buffer_close,
2511+ fun: buffer_close,
2252− signature: CommandSignature::all(completers::buffer),
2512+ signature: CommandSignature::all(completers::buffer),
2253− },
2513+ },
2254− TypableCommand {
2514+ TypableCommand {
2255− name: "buffer-close!",
2515+ name: "buffer-close!",
2256− aliases: &["bc!", "bclose!"],
2516+ aliases: &["bc!", "bclose!"],
2257− doc: "Close the current buffer forcefully, ignoring unsaved changes.",
2517+ doc: "Close the current buffer forcefully, ignoring unsaved changes.",
2258− fun: force_buffer_close,
2518+ fun: force_buffer_close,
2259− signature: CommandSignature::all(completers::buffer)
2519+ signature: CommandSignature::all(completers::buffer)
2260− },
2520+ },
2261− TypableCommand {
2521+ TypableCommand {
2262− name: "buffer-close-others",
2522+ name: "buffer-close-others",
2263− aliases: &["bco", "bcloseother"],
2523+ aliases: &["bco", "bcloseother"],
2264− doc: "Close all buffers but the currently focused one.",
2524+ doc: "Close all buffers but the currently focused one.",
2265− fun: buffer_close_others,
2525+ fun: buffer_close_others,
2266− signature: CommandSignature::none(),
2526+ signature: CommandSignature::none(),
2267− },
2527+ },
2268− TypableCommand {
2528+ TypableCommand {
2269− name: "buffer-close-others!",
2529+ name: "buffer-close-others!",
2270− aliases: &["bco!", "bcloseother!"],
2530+ aliases: &["bco!", "bcloseother!"],
2271− doc: "Force close all buffers but the currently focused one.",
2531+ doc: "Force close all buffers but the currently focused one.",
2272− fun: force_buffer_close_others,
2532+ fun: force_buffer_close_others,
2273− signature: CommandSignature::none(),
2533+ signature: CommandSignature::none(),
2274− },
2534+ },
2275− TypableCommand {
2535+ TypableCommand {
2276− name: "buffer-close-all",
2536+ name: "buffer-close-all",
2277− aliases: &["bca", "bcloseall"],
2537+ aliases: &["bca", "bcloseall"],
2278− doc: "Close all buffers without quitting.",
2538+ doc: "Close all buffers without quitting.",
2279− fun: buffer_close_all,
2539+ fun: buffer_close_all,
2280− signature: CommandSignature::none(),
2540+ signature: CommandSignature::none(),
2281− },
2541+ },
2282− TypableCommand {
2542+ TypableCommand {
2283− name: "buffer-close-all!",
2543+ name: "buffer-close-all!",
2284− aliases: &["bca!", "bcloseall!"],
2544+ aliases: &["bca!", "bcloseall!"],
2285− doc: "Force close all buffers ignoring unsaved changes without quitting.",
2545+ doc: "Force close all buffers ignoring unsaved changes without quitting.",
2286− fun: force_buffer_close_all,
2546+ fun: force_buffer_close_all,
2287− signature: CommandSignature::none(),
2547+ signature: CommandSignature::none(),
2288− },
2548+ },
2289− TypableCommand {
2549+ TypableCommand {
2290− name: "buffer-next",
2550+ name: "buffer-next",
2291− aliases: &["bn", "bnext"],
2551+ aliases: &["bn", "bnext"],
2292− doc: "Goto next buffer.",
2552+ doc: "Goto next buffer.",
2293− fun: buffer_next,
2553+ fun: buffer_next,
2294− signature: CommandSignature::none(),
2554+ signature: CommandSignature::none(),
2295− },
2555+ },
2296− TypableCommand {
2556+ TypableCommand {
2297− name: "buffer-previous",
2557+ name: "buffer-previous",
2298− aliases: &["bp", "bprev"],
2558+ aliases: &["bp", "bprev"],
2299− doc: "Goto previous buffer.",
2559+ doc: "Goto previous buffer.",
2300− fun: buffer_previous,
2560+ fun: buffer_previous,
2301− signature: CommandSignature::none(),
2561+ signature: CommandSignature::none(),
2302− },
2562+ },
2303− TypableCommand {
2563+ TypableCommand {
2304− name: "write",
2564+ name: "write",
2305− aliases: &["w"],
2565+ aliases: &["w"],
2306− doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
2566+ doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
2307− fun: write,
2567+ fun: write,
2308− signature: CommandSignature::positional(&[completers::filename]),
2568+ signature: CommandSignature::positional(&[completers::filename]),
2309− },
2569+ },
2310− TypableCommand {
2570+ TypableCommand {
2311− name: "write!",
2571+ name: "write!",
2312− aliases: &["w!"],
2572+ aliases: &["w!"],
2313− doc: "Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt)",
2573+ doc: "Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt)",
2314− fun: force_write,
2574+ fun: force_write,
2315− signature: CommandSignature::positional(&[completers::filename]),
2575+ signature: CommandSignature::positional(&[completers::filename]),
2316− },
2576+ },
2317− TypableCommand {
2577+ TypableCommand {
2318− name: "write-buffer-close",
2578+ name: "write-buffer-close",
2319− aliases: &["wbc"],
2579+ aliases: &["wbc"],
2320− doc: "Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt)",
2580+ doc: "Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt)",
2321− fun: write_buffer_close,
2581+ fun: write_buffer_close,
2322− signature: CommandSignature::positional(&[completers::filename]),
2582+ signature: CommandSignature::positional(&[completers::filename]),
2323− },
2583+ },
2324− TypableCommand {
2584+ TypableCommand {
2325− name: "write-buffer-close!",
2585+ name: "write-buffer-close!",
2326− aliases: &["wbc!"],
2586+ aliases: &["wbc!"],
2327− doc: "Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt)",
2587+ doc: "Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt)",
2328− fun: force_write_buffer_close,
2588+ fun: force_write_buffer_close,
2329− signature: CommandSignature::positional(&[completers::filename]),
2589+ signature: CommandSignature::positional(&[completers::filename]),
2330− },
2590+ },
2331− TypableCommand {
2591+ TypableCommand {
2332− name: "new",
2592+ name: "new",
2333− aliases: &["n"],
2593+ aliases: &["n"],
2334− doc: "Create a new scratch buffer.",
2594+ doc: "Create a new scratch buffer.",
2335− fun: new_file,
2595+ fun: new_file,
2336− // TODO: This seems to complete with a filename, but doesn't use that filename to
2596+ signature: CommandSignature::none(),
2337− // set the path of the newly created buffer.
2597+ },
2338− signature: CommandSignature::positional(&[completers::filename]),
2598+ TypableCommand {
2339− },
2599+ name: "format",
2340− TypableCommand {
2600+ aliases: &["fmt"],
2341− name: "format",
2601+ doc: "Format the file using the LSP formatter.",
2342− aliases: &["fmt"],
2602+ fun: format,
2343− doc: "Format the file using the LSP formatter.",
2603+ signature: CommandSignature::none(),
2344− fun: format,
2604+ },
2345− signature: CommandSignature::none(),
2605+ TypableCommand {
2346− },
2606+ name: "indent-style",
2347− TypableCommand {
2607+ aliases: &[],
2348− name: "indent-style",
2608+ doc: "Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.)",
2349− aliases: &[],
2609+ fun: set_indent_style,
2350− doc: "Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)",
2610+ signature: CommandSignature::none(),
2351− fun: set_indent_style,
2611+ },
2352− signature: CommandSignature::none(),
2612+ TypableCommand {
2353− },
2613+ name: "line-ending",
2354− TypableCommand {
2614+ aliases: &[],
2355− name: "line-ending",
2615+ #[cfg(not(feature = "unicode-lines"))]
2356− aliases: &[],
2616+ doc: "Set the document's default line ending. Options: crlf, lf.",
2357− #[cfg(not(feature = "unicode-lines"))]
2617+ #[cfg(feature = "unicode-lines")]
2358− doc: "Set the document's default line ending. Options: crlf, lf.",
2618+ doc: "Set the document's default line ending. Options: crlf, lf, cr, ff, nel.",
2359− #[cfg(feature = "unicode-lines")]
2619+ fun: set_line_ending,
2360− doc: "Set the document's default line ending. Options: crlf, lf, cr, ff, nel.",
2620+ signature: CommandSignature::none(),
2361− fun: set_line_ending,
2621+ },
2362− signature: CommandSignature::none(),
2622+ TypableCommand {
2363− },
2623+ name: "earlier",
2364− TypableCommand {
2624+ aliases: &["ear"],
2365− name: "earlier",
2625+ doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
2366− aliases: &["ear"],
2626+ fun: earlier,
2367− doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
2627+ signature: CommandSignature::none(),
2368− fun: earlier,
2628+ },
2369− signature: CommandSignature::none(),
2629+ TypableCommand {
2370− },
2630+ name: "later",
2371− TypableCommand {
2631+ aliases: &["lat"],
2372− name: "later",
2632+ doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
2373− aliases: &["lat"],
2633+ fun: later,
2374− doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
2634+ signature: CommandSignature::none(),
2375− fun: later,
2635+ },
2376− signature: CommandSignature::none(),
2636+ TypableCommand {
2377− },
2637+ name: "write-quit",
2378− TypableCommand {
2638+ aliases: &["wq", "x"],
2379− name: "write-quit",
2639+ doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
2380− aliases: &["wq", "x"],
2640+ fun: write_quit,
2381− doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
2641+ signature: CommandSignature::positional(&[completers::filename]),
2382− fun: write_quit,
2642+ },
2383− signature: CommandSignature::positional(&[completers::filename]),
2643+ TypableCommand {
2384− },
2644+ name: "write-quit!",
2385− TypableCommand {
2645+ aliases: &["wq!", "x!"],
2386− name: "write-quit!",
2646+ doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
2387− aliases: &["wq!", "x!"],
2647+ fun: force_write_quit,
2388− doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
2648+ signature: CommandSignature::positional(&[completers::filename]),
2389− fun: force_write_quit,
2649+ },
2390− signature: CommandSignature::positional(&[completers::filename]),
2650+ TypableCommand {
2391− },
2651+ name: "write-all",
2392− TypableCommand {
2652+ aliases: &["wa"],
2393− name: "write-all",
2653+ doc: "Write changes from all buffers to disk.",
2394− aliases: &["wa"],
2654+ fun: write_all,
2395− doc: "Write changes from all buffers to disk.",
2655+ signature: CommandSignature::none(),
2396− fun: write_all,
2656+ },
2397− signature: CommandSignature::none(),
2657+ TypableCommand {
2398− },
2658+ name: "write-all!",
2399− TypableCommand {
2659+ aliases: &["wa!"],
2400− name: "write-quit-all",
2660+ doc: "Forcefully write changes from all buffers to disk creating necessary subdirectories.",
2401− aliases: &["wqa", "xa"],
2661+ fun: force_write_all,
2402− doc: "Write changes from all buffers to disk and close all views.",
2662+ signature: CommandSignature::none(),
2403− fun: write_all_quit,
2663+ },
2404− signature: CommandSignature::none(),
2664+ TypableCommand {
2405− },
2665+ name: "write-quit-all",
2406− TypableCommand {
2666+ aliases: &["wqa", "xa"],
2407− name: "write-quit-all!",
2667+ doc: "Write changes from all buffers to disk and close all views.",
2408− aliases: &["wqa!", "xa!"],
2668+ fun: write_all_quit,
2409− doc: "Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes).",
2669+ signature: CommandSignature::none(),
2410− fun: force_write_all_quit,
2670+ },
2411− signature: CommandSignature::none(),
2671+ TypableCommand {
2412− },
2672+ name: "write-quit-all!",
2413− TypableCommand {
2673+ aliases: &["wqa!", "xa!"],
2414− name: "quit-all",
2674+ doc: "Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes).",
2415− aliases: &["qa"],
2675+ fun: force_write_all_quit,
2416− doc: "Close all views.",
2676+ signature: CommandSignature::none(),
2417− fun: quit_all,
2677+ },
2418− signature: CommandSignature::none(),
2678+ TypableCommand {
2419− },
2679+ name: "quit-all",
2420− TypableCommand {
2680+ aliases: &["qa"],
2421− name: "quit-all!",
2681+ doc: "Close all views.",
2422− aliases: &["qa!"],
2682+ fun: quit_all,
2423− doc: "Force close all views ignoring unsaved changes.",
2683+ signature: CommandSignature::none(),
2424− fun: force_quit_all,
2684+ },
2425− signature: CommandSignature::none(),
2685+ TypableCommand {
2426− },
2686+ name: "quit-all!",
2427− TypableCommand {
2687+ aliases: &["qa!"],
2428− name: "cquit",
2688+ doc: "Force close all views ignoring unsaved changes.",
2429− aliases: &["cq"],
2689+ fun: force_quit_all,
2430− doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).",
2690+ signature: CommandSignature::none(),
2431− fun: cquit,
2691+ },
2432− signature: CommandSignature::none(),
2692+ TypableCommand {
2433− },
2693+ name: "cquit",
2434− TypableCommand {
2694+ aliases: &["cq"],
2435− name: "cquit!",
2695+ doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).",
2436− aliases: &["cq!"],
2696+ fun: cquit,
2437− doc: "Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).",
2697+ signature: CommandSignature::none(),
2438− fun: force_cquit,
2698+ },
2439− signature: CommandSignature::none(),
2699+ TypableCommand {
2440− },
2700+ name: "cquit!",
2441− TypableCommand {
2701+ aliases: &["cq!"],
2442− name: "theme",
2702+ doc: "Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).",
2443− aliases: &[],
2703+ fun: force_cquit,
2444− doc: "Change the editor theme (show current theme if no name specified).",
2704+ signature: CommandSignature::none(),
2445− fun: theme,
2705+ },
2446− signature: CommandSignature::positional(&[completers::theme]),
2706+ TypableCommand {
2447− },
2707+ name: "theme",
2448− TypableCommand {
2708+ aliases: &[],
2449− name: "clipboard-yank",
2709+ doc: "Change the editor theme (show current theme if no name specified).",
2450− aliases: &[],
2710+ fun: theme,
2451− doc: "Yank main selection into system clipboard.",
2711+ signature: CommandSignature::positional(&[completers::theme]),
2452− fun: yank_main_selection_to_clipboard,
2712+ },
2453− signature: CommandSignature::none(),
2713+ TypableCommand {
2454− },
2714+ name: "yank-join",
2455− TypableCommand {
2715+ aliases: &[],
2456− name: "clipboard-yank-join",
2716+ doc: "Yank joined selections. A separator can be provided as first argument. Default value is newline.",
2457− aliases: &[],
2717+ fun: yank_joined,
2458− doc: "Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2718+ signature: CommandSignature::none(),
2459− fun: yank_joined_to_clipboard,
2719+ },
2460− signature: CommandSignature::none(),
2720+ TypableCommand {
2461− },
2721+ name: "clipboard-yank",
2462− TypableCommand {
2722+ aliases: &[],
2463− name: "primary-clipboard-yank",
2723+ doc: "Yank main selection into system clipboard.",
2464− aliases: &[],
2724+ fun: yank_main_selection_to_clipboard,
2465− doc: "Yank main selection into system primary clipboard.",
2725+ signature: CommandSignature::none(),
2466− fun: yank_main_selection_to_primary_clipboard,
2726+ },
2467− signature: CommandSignature::none(),
2727+ TypableCommand {
2468− },
2728+ name: "clipboard-yank-join",
2469− TypableCommand {
2729+ aliases: &[],
2470− name: "primary-clipboard-yank-join",
2730+ doc: "Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2471− aliases: &[],
2731+ fun: yank_joined_to_clipboard,
2472− doc: "Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2732+ signature: CommandSignature::none(),
2473− fun: yank_joined_to_primary_clipboard,
2733+ },
2474− signature: CommandSignature::none(),
2734+ TypableCommand {
2475− },
2735+ name: "primary-clipboard-yank",
2476− TypableCommand {
2736+ aliases: &[],
2477− name: "clipboard-paste-after",
2737+ doc: "Yank main selection into system primary clipboard.",
2478− aliases: &[],
2738+ fun: yank_main_selection_to_primary_clipboard,
2479− doc: "Paste system clipboard after selections.",
2739+ signature: CommandSignature::none(),
2480− fun: paste_clipboard_after,
2740+ },
2481− signature: CommandSignature::none(),
2741+ TypableCommand {
2482− },
2742+ name: "primary-clipboard-yank-join",
2483− TypableCommand {
2743+ aliases: &[],
2484− name: "clipboard-paste-before",
2744+ doc: "Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2485− aliases: &[],
2745+ fun: yank_joined_to_primary_clipboard,
2486− doc: "Paste system clipboard before selections.",
2746+ signature: CommandSignature::none(),
2487− fun: paste_clipboard_before,
2747+ },
2488− signature: CommandSignature::none(),
2748+ TypableCommand {
2489− },
2749+ name: "clipboard-paste-after",
2490− TypableCommand {
2750+ aliases: &[],
2491− name: "clipboard-paste-replace",
2751+ doc: "Paste system clipboard after selections.",
2492− aliases: &[],
2752+ fun: paste_clipboard_after,
2493− doc: "Replace selections with content of system clipboard.",
2753+ signature: CommandSignature::none(),
2494− fun: replace_selections_with_clipboard,
2754+ },
2495− signature: CommandSignature::none(),
2755+ TypableCommand {
2496− },
2756+ name: "clipboard-paste-before",
2497− TypableCommand {
2757+ aliases: &[],
2498− name: "primary-clipboard-paste-after",
2758+ doc: "Paste system clipboard before selections.",
2499− aliases: &[],
2759+ fun: paste_clipboard_before,
2500− doc: "Paste primary clipboard after selections.",
2760+ signature: CommandSignature::none(),
2501− fun: paste_primary_clipboard_after,
2761+ },
2502− signature: CommandSignature::none(),
2762+ TypableCommand {
2503− },
2763+ name: "clipboard-paste-replace",
2504− TypableCommand {
2764+ aliases: &[],
2505− name: "primary-clipboard-paste-before",
2765+ doc: "Replace selections with content of system clipboard.",
2506− aliases: &[],
2766+ fun: replace_selections_with_clipboard,
2507− doc: "Paste primary clipboard before selections.",
2767+ signature: CommandSignature::none(),
2508− fun: paste_primary_clipboard_before,
2768+ },
2509− signature: CommandSignature::none(),
2769+ TypableCommand {
2510− },
2770+ name: "primary-clipboard-paste-after",
2511− TypableCommand {
2771+ aliases: &[],
2512− name: "primary-clipboard-paste-replace",
2772+ doc: "Paste primary clipboard after selections.",
2513− aliases: &[],
2773+ fun: paste_primary_clipboard_after,
2514− doc: "Replace selections with content of system primary clipboard.",
2774+ signature: CommandSignature::none(),
2515− fun: replace_selections_with_primary_clipboard,
2775+ },
2516− signature: CommandSignature::none(),
2776+ TypableCommand {
2517− },
2777+ name: "primary-clipboard-paste-before",
2518− TypableCommand {
2778+ aliases: &[],
2519− name: "show-clipboard-provider",
2779+ doc: "Paste primary clipboard before selections.",
2520− aliases: &[],
2780+ fun: paste_primary_clipboard_before,
2521− doc: "Show clipboard provider name in status bar.",
2781+ signature: CommandSignature::none(),
2522− fun: show_clipboard_provider,
2782+ },
2523− signature: CommandSignature::none(),
2783+ TypableCommand {
2524− },
2784+ name: "primary-clipboard-paste-replace",
2525− TypableCommand {
2785+ aliases: &[],
2526− name: "change-current-directory",
2786+ doc: "Replace selections with content of system primary clipboard.",
2527− aliases: &["cd"],
2787+ fun: replace_selections_with_primary_clipboard,
2528− doc: "Change the current working directory.",
2788+ signature: CommandSignature::none(),
2529− fun: change_current_directory,
2789+ },
2530− signature: CommandSignature::positional(&[completers::directory]),
2790+ TypableCommand {
2531− },
2791+ name: "show-clipboard-provider",
2532− TypableCommand {
2792+ aliases: &[],
2533− name: "show-directory",
2793+ doc: "Show clipboard provider name in status bar.",
2534− aliases: &["pwd"],
2794+ fun: show_clipboard_provider,
2535− doc: "Show the current working directory.",
2795+ signature: CommandSignature::none(),
2536− fun: show_current_directory,
2796+ },
2537− signature: CommandSignature::none(),
2797+ TypableCommand {
2538− },
2798+ name: "change-current-directory",
2539− TypableCommand {
2799+ aliases: &["cd"],
2540− name: "encoding",
2800+ doc: "Change the current working directory.",
2541− aliases: &[],
2801+ fun: change_current_directory,
2542− doc: "Set encoding. Based on `https://encoding.spec.whatwg.org`.",
2802+ signature: CommandSignature::positional(&[completers::directory]),
2543− fun: set_encoding,
2803+ },
2544− signature: CommandSignature::none(),
2804+ TypableCommand {
2545− },
2805+ name: "show-directory",
2546− TypableCommand {
2806+ aliases: &["pwd"],
2547− name: "character-info",
2807+ doc: "Show the current working directory.",
2548− aliases: &["char"],
2808+ fun: show_current_directory,
2549− doc: "Get info about the character under the primary cursor.",
2809+ signature: CommandSignature::none(),
2550− fun: get_character_info,
2810+ },
2551− signature: CommandSignature::none(),
2811+ TypableCommand {
2552− },
2812+ name: "encoding",
2553− TypableCommand {
2813+ aliases: &[],
2554− name: "reload",
2814+ doc: "Set encoding. Based on `https://encoding.spec.whatwg.org`.",
2555− aliases: &[],
2815+ fun: set_encoding,
2556− doc: "Discard changes and reload from the source file.",
2816+ signature: CommandSignature::none(),
2557− fun: reload,
2817+ },
2558− signature: CommandSignature::none(),
2818+ TypableCommand {
2559− },
2819+ name: "character-info",
2560− TypableCommand {
2820+ aliases: &["char"],
2561− name: "reload-all",
2821+ doc: "Get info about the character under the primary cursor.",
2562− aliases: &[],
2822+ fun: get_character_info,
2563− doc: "Discard changes and reload all documents from the source files.",
2823+ signature: CommandSignature::none(),
2564− fun: reload_all,
2824+ },
2565− signature: CommandSignature::none(),
2825+ TypableCommand {
2566− },
2826+ name: "reload",
2567− TypableCommand {
2827+ aliases: &["rl"],
2568− name: "update",
2828+ doc: "Discard changes and reload from the source file.",
2569− aliases: &["u"],
2829+ fun: reload,
2570− doc: "Write changes only if the file has been modified.",
2830+ signature: CommandSignature::none(),
2571− fun: update,
2831+ },
2572− signature: CommandSignature::none(),
2832+ TypableCommand {
2573− },
2833+ name: "reload-all",
2574− TypableCommand {
2834+ aliases: &["rla"],
2575− name: "lsp-workspace-command",
2835+ doc: "Discard changes and reload all documents from the source files.",
2576− aliases: &[],
2836+ fun: reload_all,
2577− doc: "Open workspace command picker",
2837+ signature: CommandSignature::none(),
2578− fun: lsp_workspace_command,
2838+ },
2579− signature: CommandSignature::positional(&[completers::lsp_workspace_command]),
2839+ TypableCommand {
2580− },
2840+ name: "update",
2581− TypableCommand {
2841+ aliases: &["u"],
2582− name: "lsp-restart",
2842+ doc: "Write changes only if the file has been modified.",
2583− aliases: &[],
2843+ fun: update,
2584− doc: "Restarts the language servers used by the current doc",
2844+ signature: CommandSignature::none(),
2585− fun: lsp_restart,
2845+ },
2586− signature: CommandSignature::none(),
2846+ TypableCommand {
2587− },
2847+ name: "lsp-workspace-command",
2588− TypableCommand {
2848+ aliases: &[],
2589− name: "lsp-stop",
2849+ doc: "Open workspace command picker",
2590− aliases: &[],
2850+ fun: lsp_workspace_command,
2591− doc: "Stops the language servers that are used by the current doc",
2851+ signature: CommandSignature::positional(&[completers::lsp_workspace_command]),
2592− fun: lsp_stop,
2852+ },
2593− signature: CommandSignature::none(),
2853+ TypableCommand {
2594− },
2854+ name: "lsp-restart",
2595− TypableCommand {
2855+ aliases: &[],
2596− name: "tree-sitter-scopes",
2856+ doc: "Restarts the language servers used by the current doc",
2597− aliases: &[],
2857+ fun: lsp_restart,
2598− doc: "Display tree sitter scopes, primarily for theming and development.",
2858+ signature: CommandSignature::none(),
2599− fun: tree_sitter_scopes,
2859+ },
2600− signature: CommandSignature::none(),
2860+ TypableCommand {
2601− },
2861+ name: "lsp-stop",
2602− TypableCommand {
2862+ aliases: &[],
2603− name: "debug-start",
2863+ doc: "Stops the language servers that are used by the current doc",
2604− aliases: &["dbg"],
2864+ fun: lsp_stop,
2605− doc: "Start a debug session from a given template with given parameters.",
2865+ signature: CommandSignature::none(),
2606− fun: debug_start,
2866+ },
2607− signature: CommandSignature::none(),
2867+ TypableCommand {
2608− },
2868+ name: "tree-sitter-scopes",
2609− TypableCommand {
2869+ aliases: &[],
2610− name: "debug-remote",
2870+ doc: "Display tree sitter scopes, primarily for theming and development.",
2611− aliases: &["dbg-tcp"],
2871+ fun: tree_sitter_scopes,
2612− doc: "Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.",
2872+ signature: CommandSignature::none(),
2613− fun: debug_remote,
2873+ },
2614− signature: CommandSignature::none(),
2874+ TypableCommand {
2615− },
2875+ name: "tree-sitter-highlight-name",
2616− TypableCommand {
2876+ aliases: &[],
2617− name: "debug-eval",
2877+ doc: "Display name of tree-sitter highlight scope under the cursor.",
2618− aliases: &[],
2878+ fun: tree_sitter_highlight_name,
2619− doc: "Evaluate expression in current debug context.",
2879+ signature: CommandSignature::none(),
2620− fun: debug_eval,
2880+ },
2621− signature: CommandSignature::none(),
2881+ TypableCommand {
2622− },
2882+ name: "debug-start",
2623− TypableCommand {
2883+ aliases: &["dbg"],
2624− name: "vsplit",
2884+ doc: "Start a debug session from a given template with given parameters.",
2625− aliases: &["vs"],
2885+ fun: debug_start,
2626− doc: "Open the file in a vertical split.",
2886+ signature: CommandSignature::none(),
2627− fun: vsplit,
2887+ },
2628− signature: CommandSignature::all(completers::filename)
2888+ TypableCommand {
2629− },
2889+ name: "debug-remote",
2630− TypableCommand {
2890+ aliases: &["dbg-tcp"],
2631− name: "vsplit-new",
2891+ doc: "Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.",
2632− aliases: &["vnew"],
2892+ fun: debug_remote,
2633− doc: "Open a scratch buffer in a vertical split.",
2893+ signature: CommandSignature::none(),
2634− fun: vsplit_new,
2894+ },
2635− signature: CommandSignature::none(),
2895+ TypableCommand {
2636− },
2896+ name: "debug-eval",
2637− TypableCommand {
2897+ aliases: &[],
2638− name: "hsplit",
2898+ doc: "Evaluate expression in current debug context.",
2639− aliases: &["hs", "sp"],
2899+ fun: debug_eval,
2640− doc: "Open the file in a horizontal split.",
2900+ signature: CommandSignature::none(),
2641− fun: hsplit,
2901+ },
2642− signature: CommandSignature::all(completers::filename)
2902+ TypableCommand {
2643− },
2903+ name: "vsplit",
2644− TypableCommand {
2904+ aliases: &["vs"],
2645− name: "hsplit-new",
2905+ doc: "Open the file in a vertical split.",
2646− aliases: &["hnew"],
2906+ fun: vsplit,
2647− doc: "Open a scratch buffer in a horizontal split.",
2907+ signature: CommandSignature::all(completers::filename)
2648− fun: hsplit_new,
2908+ },
2649− signature: CommandSignature::none(),
2909+ TypableCommand {
2650− },
2910+ name: "vsplit-new",
2651− TypableCommand {
2911+ aliases: &["vnew"],
2652− name: "tutor",
2912+ doc: "Open a scratch buffer in a vertical split.",
2653− aliases: &[],
2913+ fun: vsplit_new,
2654− doc: "Open the tutorial.",
2914+ signature: CommandSignature::none(),
2655− fun: tutor,
2915+ },
2656− signature: CommandSignature::none(),
2916+ TypableCommand {
2657− },
2917+ name: "hsplit",
2658− TypableCommand {
2918+ aliases: &["hs", "sp"],
2659− name: "goto",
2919+ doc: "Open the file in a horizontal split.",
2660− aliases: &["g"],
2920+ fun: hsplit,
2661− doc: "Goto line number.",
2921+ signature: CommandSignature::all(completers::filename)
2662− fun: goto_line_number,
2922+ },
2663− signature: CommandSignature::none(),
2923+ TypableCommand {
2664− },
2924+ name: "hsplit-new",
2665− TypableCommand {
2925+ aliases: &["hnew"],
2666− name: "set-language",
2926+ doc: "Open a scratch buffer in a horizontal split.",
2667− aliases: &["lang"],
2927+ fun: hsplit_new,
2668− doc: "Set the language of current buffer (show current language if no value specified).",
2928+ signature: CommandSignature::none(),
2669− fun: language,
2929+ },
2670− signature: CommandSignature::positional(&[completers::language]),
2930+ TypableCommand {
2671− },
2931+ name: "tutor",
2672− TypableCommand {
2932+ aliases: &[],
2673− name: "set-option",
2933+ doc: "Open the tutorial.",
2674− aliases: &["set"],
2934+ fun: tutor,
2675− doc: "Set a config option at runtime.\nFor example to disable smart case search, use `:set search.smart-case false`.",
2935+ signature: CommandSignature::none(),
2676− fun: set_option,
2936+ },
2677− // TODO: Add support for completion of the options value(s), when appropriate.
2937+ TypableCommand {
2678− signature: CommandSignature::positional(&[completers::setting]),
2938+ name: "goto",
2679− },
2939+ aliases: &["g"],
2680− TypableCommand {
2940+ doc: "Goto line number.",
2681− name: "toggle-option",
2941+ fun: goto_line_number,
2682− aliases: &["toggle"],
2942+ signature: CommandSignature::none(),
2683− doc: "Toggle a boolean config option at runtime.\nFor example to toggle smart case search, use `:toggle search.smart-case`.",
2943+ },
2684− fun: toggle_option,
2944+ TypableCommand {
2685− signature: CommandSignature::positional(&[completers::setting]),
2945+ name: "set-language",
2686− },
2946+ aliases: &["lang"],
2687− TypableCommand {
2947+ doc: "Set the language of current buffer (show current language if no value specified).",
2688− name: "get-option",
2948+ fun: language,
2689− aliases: &["get"],
2949+ signature: CommandSignature::positional(&[completers::language]),
2690− doc: "Get the current value of a config option.",
2950+ },
2691− fun: get_option,
2951+ TypableCommand {
2692− signature: CommandSignature::positional(&[completers::setting]),
2952+ name: "set-option",
2693− },
2953+ aliases: &["set"],
2694− TypableCommand {
2954+ doc: "Set a config option at runtime.\nFor example to disable smart case search, use `:set search.smart-case false`.",
2695− name: "sort",
2955+ fun: set_option,
2696− aliases: &[],
2956+ // TODO: Add support for completion of the options value(s), when appropriate.
2697− doc: "Sort ranges in selection.",
2957+ signature: CommandSignature::positional(&[completers::setting]),
2698− fun: sort,
2958+ },
2699− signature: CommandSignature::none(),
2959+ TypableCommand {
2700− },
2960+ name: "toggle-option",
2701− TypableCommand {
2961+ aliases: &["toggle"],
2702− name: "rsort",
2962+ doc: "Toggle a boolean config option at runtime.\nFor example to toggle smart case search, use `:toggle search.smart-case`.",
2703− aliases: &[],
2963+ fun: toggle_option,
2704− doc: "Sort ranges in selection in reverse order.",
2964+ signature: CommandSignature::positional(&[completers::setting]),
2705− fun: sort_reverse,
2965+ },
2706− signature: CommandSignature::none(),
2966+ TypableCommand {
2707− },
2967+ name: "get-option",
2708− TypableCommand {
2968+ aliases: &["get"],
2709− name: "reflow",
2969+ doc: "Get the current value of a config option.",
2710− aliases: &[],
2970+ fun: get_option,
2711− doc: "Hard-wrap the current selection of lines to a given width.",
2971+ signature: CommandSignature::positional(&[completers::setting]),
2712− fun: reflow,
2972+ },
2713− signature: CommandSignature::none(),
2973+ TypableCommand {
2714− },
2974+ name: "sort",
2715− TypableCommand {
2975+ aliases: &[],
2716− name: "tree-sitter-subtree",
2976+ doc: "Sort ranges in selection.",
2717− aliases: &["ts-subtree"],
2977+ fun: sort,
2718− doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
2978+ signature: CommandSignature::none(),
2719− fun: tree_sitter_subtree,
2979+ },
2720− signature: CommandSignature::none(),
2980+ TypableCommand {
2721− },
2981+ name: "rsort",
2722− TypableCommand {
2982+ aliases: &[],
2723− name: "config-reload",
2983+ doc: "Sort ranges in selection in reverse order.",
2724− aliases: &[],
2984+ fun: sort_reverse,
2725− doc: "Refresh user config.",
2985+ signature: CommandSignature::none(),
2726− fun: refresh_config,
2986+ },
2727− signature: CommandSignature::none(),
2987+ TypableCommand {
2728− },
2988+ name: "reflow",
2729− TypableCommand {
2989+ aliases: &[],
2730− name: "config-open",
2990+ doc: "Hard-wrap the current selection of lines to a given width.",
2731− aliases: &[],
2991+ fun: reflow,
2732− doc: "Open the user config.toml file.",
2992+ signature: CommandSignature::none(),
2733− fun: open_config,
2993+ },
2734− signature: CommandSignature::none(),
2994+ TypableCommand {
2735− },
2995+ name: "tree-sitter-subtree",
2736− TypableCommand {
2996+ aliases: &["ts-subtree"],
2737− name: "config-open-workspace",
2997+ doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
2738− aliases: &[],
2998+ fun: tree_sitter_subtree,
2739− doc: "Open the workspace config.toml file.",
2999+ signature: CommandSignature::none(),
2740− fun: open_workspace_config,
3000+ },
2741− signature: CommandSignature::none(),
3001+ TypableCommand {
2742− },
3002+ name: "config-reload",
2743− TypableCommand {
3003+ aliases: &[],
2744− name: "log-open",
3004+ doc: "Refresh user config.",
2745− aliases: &[],
3005+ fun: refresh_config,
2746− doc: "Open the helix log file.",
3006+ signature: CommandSignature::none(),
2747− fun: open_log,
3007+ },
2748− signature: CommandSignature::none(),
3008+ TypableCommand {
2749− },
3009+ name: "config-open",
2750− TypableCommand {
3010+ aliases: &[],
2751− name: "insert-output",
3011+ doc: "Open the user config.toml file.",
2752− aliases: &[],
3012+ fun: open_config,
2753− doc: "Run shell command, inserting output before each selection.",
3013+ signature: CommandSignature::none(),
2754− fun: insert_output,
3014+ },
2755− signature: CommandSignature::none(),
3015+ TypableCommand {
2756− },
3016+ name: "config-open-workspace",
2757− TypableCommand {
3017+ aliases: &[],
2758− name: "append-output",
3018+ doc: "Open the workspace config.toml file.",
2759− aliases: &[],
3019+ fun: open_workspace_config,
2760− doc: "Run shell command, appending output after each selection.",
3020+ signature: CommandSignature::none(),
2761− fun: append_output,
3021+ },
2762− signature: CommandSignature::none(),
3022+ TypableCommand {
2763− },
3023+ name: "log-open",
2764− TypableCommand {
3024+ aliases: &[],
2765− name: "pipe",
3025+ doc: "Open the helix log file.",
2766− aliases: &[],
3026+ fun: open_log,
2767− doc: "Pipe each selection to the shell command.",
3027+ signature: CommandSignature::none(),
2768− fun: pipe,
3028+ },
2769− signature: CommandSignature::none(),
3029+ TypableCommand {
2770− },
3030+ name: "insert-output",
2771− TypableCommand {
3031+ aliases: &[],
2772− name: "pipe-to",
3032+ doc: "Run shell command, inserting output before each selection.",
2773− aliases: &[],
3033+ fun: insert_output,
2774− doc: "Pipe each selection to the shell command, ignoring output.",
3034+ signature: CommandSignature::none(),
2775− fun: pipe_to,
3035+ },
2776− signature: CommandSignature::none(),
3036+ TypableCommand {
2777− },
3037+ name: "append-output",
2778− TypableCommand {
3038+ aliases: &[],
2779− name: "run-shell-command",
3039+ doc: "Run shell command, appending output after each selection.",
2780− aliases: &["sh"],
3040+ fun: append_output,
2781− doc: "Run a shell command",
3041+ signature: CommandSignature::none(),
2782− fun: run_shell_command,
3042+ },
2783− signature: CommandSignature::all(completers::filename)
3043+ TypableCommand {
2784− },
3044+ name: "pipe",
2785− TypableCommand {
3045+ aliases: &[],
2786− name: "reset-diff-change",
3046+ doc: "Pipe each selection to the shell command.",
2787− aliases: &["diffget", "diffg"],
3047+ fun: pipe,
2788− doc: "Reset the diff change at the cursor position.",
3048+ signature: CommandSignature::none(),
2789− fun: reset_diff_change,
3049+ },
2790− signature: CommandSignature::none(),
3050+ TypableCommand {
2791− },
3051+ name: "pipe-to",
2792− TypableCommand {
3052+ aliases: &[],
2793− name: "clear-register",
3053+ doc: "Pipe each selection to the shell command, ignoring output.",
2794− aliases: &[],
3054+ fun: pipe_to,
2795− doc: "Clear given register. If no argument is provided, clear all registers.",
3055+ signature: CommandSignature::none(),
2796− fun: clear_register,
3056+ },
2797− signature: CommandSignature::none(),
3057+ TypableCommand {
2798− },
3058+ name: "run-shell-command",
2799− ];
3059+ aliases: &["sh"],
3060+ doc: "Run a shell command",
3061+ fun: run_shell_command,
3062+ signature: CommandSignature::all(completers::filename)
3063+ },
3064+ TypableCommand {
3065+ name: "reset-diff-change",
3066+ aliases: &["diffget", "diffg"],
3067+ doc: "Reset the diff change at the cursor position.",
3068+ fun: reset_diff_change,
3069+ signature: CommandSignature::none(),
3070+ },
3071+ TypableCommand {
3072+ name: "clear-register",
3073+ aliases: &[],
3074+ doc: "Clear given register. If no argument is provided, clear all registers.",
3075+ fun: clear_register,
3076+ signature: CommandSignature::none(),
3077+ },
3078+ TypableCommand {
3079+ name: "redraw",
3080+ aliases: &[],
3081+ doc: "Clear and re-render the whole UI",
3082+ fun: redraw,
3083+ signature: CommandSignature::none(),
3084+ },
3085+ TypableCommand {
3086+ name: "move",
3087+ aliases: &[],
3088+ doc: "Move the current buffer and its corresponding file to a different path",
3089+ fun: move_buffer,
3090+ signature: CommandSignature::positional(&[completers::filename]),
3091+ },
3092+];
2800
3093
2801pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
3094pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
2802 Lazy::new(|| {
3095 Lazy::new(|| {
@@ -2815,28 +3108,18 @@ pub(super) fn command_mode(cx: &mut Context) {
2815 ":".into(),
3108 ":".into(),
2816 Some(':'),
3109 Some(':'),
2817 |editor: &Editor, input: &str| {
3110 |editor: &Editor, input: &str| {
2818− static FUZZY_MATCHER: Lazy<fuzzy_matcher::skim::SkimMatcherV2> =
2819− Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default);
2820−
2821 let shellwords = Shellwords::from(input);
3111 let shellwords = Shellwords::from(input);
2822 let words = shellwords.words();
3112 let words = shellwords.words();
2823
3113
2824 if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) {
3114 if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) {
2825− // If the command has not been finished yet, complete commands.
3115+ fuzzy_match(
3116+ input,
2826− let mut matches: Vec<_> = typed::TYPABLE_COMMAND_LIST
3117+ TYPABLE_COMMAND_LIST.iter().map(|command| command.name),
2827− .iter()
3118+ false,
2828− .filter_map(|command| {
3119+ )
2829− FUZZY_MATCHER
2830− .fuzzy_match(command.name, input)
2831− .map(|score| (command.name, score))
2832− })
2833− .collect();
2834−
2835− matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score));
2836− matches
2837− .into_iter()
3120+ .into_iter()
2838− .map(|(name, _)| (0.., name.into()))
3121+ .map(|(name, _)| (0.., name.into()))
2839− .collect()
3122+ .collect()
2840 } else {
3123 } else {
2841 // Otherwise, use the command's completer and the last shellword
3124 // Otherwise, use the command's completer and the last shellword
2842 // as completion input.
3125 // as completion input.
helix-term/src/compositor.rs+12
@@ -16,6 +16,7 @@ pub enum EventResult {
16}
16}
17
17
18use crate::job::Jobs;
18use crate::job::Jobs;
19+use crate::ui::picker;
19use helix_view::Editor;
20use helix_view::Editor;
20
21
21pub use helix_view::input::Event;
22pub use helix_view::input::Event;
@@ -79,6 +80,7 @@ pub struct Compositor {
79 area: Rect,
80 area: Rect,
80
81
81 pub(crate) last_picker: Option<Box<dyn Component>>,
82 pub(crate) last_picker: Option<Box<dyn Component>>,
83+ pub(crate) full_redraw: bool,
82}
84}
83
85
84impl Compositor {
86impl Compositor {
@@ -87,6 +89,7 @@ impl Compositor {
87 layers: Vec::new(),
89 layers: Vec::new(),
88 area,
90 area,
89 last_picker: None,
91 last_picker: None,
92+ full_redraw: false,
90 }
93 }
91 }
94 }
92
95
@@ -100,6 +103,11 @@ impl Compositor {
100
103
101 /// Add a layer to be rendered in front of all existing layers.
104 /// Add a layer to be rendered in front of all existing layers.
102 pub fn push(&mut self, mut layer: Box<dyn Component>) {
105 pub fn push(&mut self, mut layer: Box<dyn Component>) {
106+ // immediately clear last_picker field to avoid excessive memory
107+ // consumption for picker with many items
108+ if layer.id() == Some(picker::ID) {
109+ self.last_picker = None;
110+ }
103 let size = self.size();
111 let size = self.size();
104 // trigger required_size on init
112 // trigger required_size on init
105 layer.required_size((size.width, size.height));
113 layer.required_size((size.width, size.height));
@@ -200,6 +208,10 @@ impl Compositor {
200 .find(|component| component.id() == Some(id))
208 .find(|component| component.id() == Some(id))
201 .and_then(|component| component.as_any_mut().downcast_mut())
209 .and_then(|component| component.as_any_mut().downcast_mut())
202 }
210 }
211+
212+ pub fn need_full_redraw(&mut self) {
213+ self.full_redraw = true;
214+ }
203}
215}
204
216
205// View casting, taken straight from Cursive
217// View casting, taken straight from Cursive
helix-term/src/config.rs+8−8
@@ -1,5 +1,5 @@
1use crate::keymap;
1use crate::keymap;
2−use crate::keymap::{merge_keys, Keymap};
2+use crate::keymap::{merge_keys, KeyTrie};
3use helix_loader::merge_toml_values;
3use helix_loader::merge_toml_values;
4use helix_view::document::Mode;
4use helix_view::document::Mode;
5use serde::Deserialize;
5use serde::Deserialize;
@@ -12,7 +12,7 @@ use toml::de::Error as TomlError;
12#[derive(Debug, Clone, PartialEq)]
12#[derive(Debug, Clone, PartialEq)]
13pub struct Config {
13pub struct Config {
14 pub theme: Option<String>,
14 pub theme: Option<String>,
15− pub keys: HashMap<Mode, Keymap>,
15+ pub keys: HashMap<Mode, KeyTrie>,
16 pub editor: helix_view::editor::Config,
16 pub editor: helix_view::editor::Config,
17}
17}
18
18
@@ -20,7 +20,7 @@ pub struct Config {
20#[serde(deny_unknown_fields)]
20#[serde(deny_unknown_fields)]
21pub struct ConfigRaw {
21pub struct ConfigRaw {
22 pub theme: Option<String>,
22 pub theme: Option<String>,
23− pub keys: Option<HashMap<Mode, Keymap>>,
23+ pub keys: Option<HashMap<Mode, KeyTrie>>,
24 pub editor: Option<toml::Value>,
24 pub editor: Option<toml::Value>,
25}
25}
26
26
@@ -109,6 +109,7 @@ impl Config {
109 )?,
109 )?,
110 }
110 }
111 }
111 }
112+
112 // these are just two io errors return the one for the global config
113 // these are just two io errors return the one for the global config
113 (Err(err), Err(_)) => return Err(err),
114 (Err(err), Err(_)) => return Err(err),
114 };
115 };
@@ -138,7 +139,6 @@ mod tests {
138 #[test]
139 #[test]
139 fn parsing_keymaps_config_file() {
140 fn parsing_keymaps_config_file() {
140 use crate::keymap;
141 use crate::keymap;
141− use crate::keymap::Keymap;
142 use helix_core::hashmap;
142 use helix_core::hashmap;
143 use helix_view::document::Mode;
143 use helix_view::document::Mode;
144
144
@@ -155,13 +155,13 @@ mod tests {
155 merge_keys(
155 merge_keys(
156 &mut keys,
156 &mut keys,
157 hashmap! {
157 hashmap! {
158− Mode::Insert => Keymap::new(keymap!({ "Insert mode"
158+ Mode::Insert => keymap!({ "Insert mode"
159 "y" => move_line_down,
159 "y" => move_line_down,
160 "S-C-a" => delete_selection,
160 "S-C-a" => delete_selection,
161− })),
161+ }),
162− Mode::Normal => Keymap::new(keymap!({ "Normal mode"
162+ Mode::Normal => keymap!({ "Normal mode"
163 "A-F12" => move_next_word_end,
163 "A-F12" => move_next_word_end,
164− })),
164+ }),
165 },
165 },
166 );
166 );
167
167
helix-term/src/health.rs+44−16
@@ -181,8 +181,8 @@ pub fn languages_all() -> std::io::Result<()> {
181 .language
181 .language
182 .sort_unstable_by_key(|l| l.language_id.clone());
182 .sort_unstable_by_key(|l| l.language_id.clone());
183
183
184− let check_binary = |cmd: Option<String>| match cmd {
184+ let check_binary = |cmd: Option<&str>| match cmd {
185− Some(cmd) => match which::which(&cmd) {
185+ Some(cmd) => match which::which(cmd) {
186 Ok(_) => column(&format!("✓ {}", cmd), Color::Green),
186 Ok(_) => column(&format!("✓ {}", cmd), Color::Green),
187 Err(_) => column(&format!("✘ {}", cmd), Color::Red),
187 Err(_) => column(&format!("✘ {}", cmd), Color::Red),
188 },
188 },
@@ -192,17 +192,15 @@ pub fn languages_all() -> std::io::Result<()> {
192 for lang in &syn_loader_conf.language {
192 for lang in &syn_loader_conf.language {
193 column(&lang.language_id, Color::Reset);
193 column(&lang.language_id, Color::Reset);
194
194
195− // TODO multiple language servers (check binary for each supported language server, not just the first)
196−
197− let lsp = lang.language_servers.first().and_then(|ls| {
195+ let mut cmds = lang.language_servers.iter().filter_map(|ls| {
198 syn_loader_conf
196 syn_loader_conf
199 .language_server
197 .language_server
200 .get(&ls.name)
198 .get(&ls.name)
201− .map(|config| config.command.clone())
199+ .map(|config| config.command.as_str())
202 });
200 });
203− check_binary(lsp);
201+ check_binary(cmds.next());
204
202
205− let dap = lang.debugger.as_ref().map(|dap| dap.command.to_string());
203+ let dap = lang.debugger.as_ref().map(|dap| dap.command.as_str());
206 check_binary(dap);
204 check_binary(dap);
207
205
208 for ts_feat in TsFeature::all() {
206 for ts_feat in TsFeature::all() {
@@ -213,6 +211,12 @@ pub fn languages_all() -> std::io::Result<()> {
213 }
211 }
214
212
215 writeln!(stdout)?;
213 writeln!(stdout)?;
214+
215+ for cmd in cmds {
216+ column("", Color::Reset);
217+ check_binary(Some(cmd));
218+ writeln!(stdout)?;
219+ }
216 }
220 }
217
221
218 Ok(())
222 Ok(())
@@ -268,15 +272,12 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
268 }
272 }
269 };
273 };
270
274
271− // TODO multiple language servers
275+ probe_protocols(
272− probe_protocol(
273 "language server",
276 "language server",
274− lang.language_servers.first().and_then(|ls| {
277+ lang.language_servers
278+ .iter()
275− syn_loader_conf
279+ .filter_map(|ls| syn_loader_conf.language_server.get(&ls.name))
276− .language_server
277− .get(&ls.name)
278− .map(|config| config.command.clone())
280+ .map(|config| config.command.as_str()),
279− }),
280 )?;
281 )?;
281
282
282 probe_protocol(
283 probe_protocol(
@@ -291,6 +292,33 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
291 Ok(())
292 Ok(())
292}
293}
293
294
295+/// Display diagnostics about multiple LSPs and DAPs.
296+fn probe_protocols<'a, I: Iterator<Item = &'a str> + 'a>(
297+ protocol_name: &str,
298+ server_cmds: I,
299+) -> std::io::Result<()> {
300+ let stdout = std::io::stdout();
301+ let mut stdout = stdout.lock();
302+ let mut server_cmds = server_cmds.peekable();
303+
304+ write!(stdout, "Configured {}s:", protocol_name)?;
305+ if server_cmds.peek().is_none() {
306+ writeln!(stdout, "{}", " None".yellow())?;
307+ return Ok(());
308+ }
309+ writeln!(stdout)?;
310+
311+ for cmd in server_cmds {
312+ let (path, icon) = match which::which(cmd) {
313+ Ok(path) => (path.display().to_string().green(), "✓".green()),
314+ Err(_) => (format!("'{}' not found in $PATH", cmd).red(), "✘".red()),
315+ };
316+ writeln!(stdout, " {} {}: {}", icon, cmd, path)?;
317+ }
318+
319+ Ok(())
320+}
321+
294/// Display diagnostics about LSP and DAP.
322/// Display diagnostics about LSP and DAP.
295fn probe_protocol(protocol_name: &str, server_cmd: Option<String>) -> std::io::Result<()> {
323fn probe_protocol(protocol_name: &str, server_cmd: Option<String>) -> std::io::Result<()> {
296 let stdout = std::io::stdout();
324 let stdout = std::io::stdout();
helix-term/src/keymap.rs+105−145
@@ -18,7 +18,7 @@ use std::{
18pub use default::default;
18pub use default::default;
19use macros::key;
19use macros::key;
20
20
21−#[derive(Debug, Clone)]
21+#[derive(Debug, Clone, Default)]
22pub struct KeyTrieNode {
22pub struct KeyTrieNode {
23 /// A label for keys coming under this node, like "Goto mode"
23 /// A label for keys coming under this node, like "Goto mode"
24 name: String,
24 name: String,
@@ -52,10 +52,6 @@ impl KeyTrieNode {
52 }
52 }
53 }
53 }
54
54
55− pub fn name(&self) -> &str {
56− &self.name
57− }
58−
59 /// Merge another Node in. Leaves and subnodes from the other node replace
55 /// Merge another Node in. Leaves and subnodes from the other node replace
60 /// corresponding keyevent in self, except when both other and self have
56 /// corresponding keyevent in self, except when both other and self have
61 /// subnodes for same key. In that case the merge is recursive.
57 /// subnodes for same key. In that case the merge is recursive.
@@ -77,49 +73,40 @@ impl KeyTrieNode {
77 }
73 }
78
74
79 pub fn infobox(&self) -> Info {
75 pub fn infobox(&self) -> Info {
80− let mut body: Vec<(&str, BTreeSet<KeyEvent>)> = Vec::with_capacity(self.len());
76+ let mut body: Vec<(BTreeSet<KeyEvent>, &str)> = Vec::with_capacity(self.len());
81 for (&key, trie) in self.iter() {
77 for (&key, trie) in self.iter() {
82 let desc = match trie {
78 let desc = match trie {
83− KeyTrie::Leaf(cmd) => {
79+ KeyTrie::MappableCommand(cmd) => {
84 if cmd.name() == "no_op" {
80 if cmd.name() == "no_op" {
85 continue;
81 continue;
86 }
82 }
87 cmd.doc()
83 cmd.doc()
88 }
84 }
89− KeyTrie::Node(n) => n.name(),
85+ KeyTrie::Node(n) => &n.name,
90 KeyTrie::Sequence(_) => "[Multiple commands]",
86 KeyTrie::Sequence(_) => "[Multiple commands]",
91 };
87 };
92− match body.iter().position(|(d, _)| d == &desc) {
88+ match body.iter().position(|(_, d)| d == &desc) {
93 Some(pos) => {
89 Some(pos) => {
94− body[pos].1.insert(key);
90+ body[pos].0.insert(key);
95 }
91 }
96− None => body.push((desc, BTreeSet::from([key]))),
92+ None => body.push((BTreeSet::from([key]), desc)),
97 }
93 }
98 }
94 }
99− body.sort_unstable_by_key(|(_, keys)| {
95+ body.sort_unstable_by_key(|(keys, _)| {
100 self.order
96 self.order
101 .iter()
97 .iter()
102 .position(|&k| k == *keys.iter().next().unwrap())
98 .position(|&k| k == *keys.iter().next().unwrap())
103 .unwrap()
99 .unwrap()
104 });
100 });
105− let prefix = format!("{} ", self.name());
106− if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) {
107− body = body
108− .into_iter()
109− .map(|(desc, keys)| (desc.strip_prefix(&prefix).unwrap(), keys))
110− .collect();
111− }
112− Info::from_keymap(self.name(), body)
113− }
114− /// Get a reference to the key trie node's order.
115− pub fn order(&self) -> &[KeyEvent] {
116− self.order.as_slice()
117− }
118−}
119
101
120−impl Default for KeyTrieNode {
102+ let body: Vec<_> = body
121− fn default() -> Self {
103+ .into_iter()
122− Self::new("", HashMap::new(), Vec::new())
104+ .map(|(events, desc)| {
105+ let events = events.iter().map(ToString::to_string).collect::<Vec<_>>();
106+ (events.join(", "), desc)
107+ })
108+ .collect();
109+ Info::new(&self.name, &body)
123 }
110 }
124}
111}
125
112
@@ -145,7 +132,7 @@ impl DerefMut for KeyTrieNode {
145
132
146#[derive(Debug, Clone, PartialEq)]
133#[derive(Debug, Clone, PartialEq)]
147pub enum KeyTrie {
134pub enum KeyTrie {
148− Leaf(MappableCommand),
135+ MappableCommand(MappableCommand),
149 Sequence(Vec<MappableCommand>),
136 Sequence(Vec<MappableCommand>),
150 Node(KeyTrieNode),
137 Node(KeyTrieNode),
151}
138}
@@ -174,7 +161,7 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
174 {
161 {
175 command
162 command
176 .parse::<MappableCommand>()
163 .parse::<MappableCommand>()
177− .map(KeyTrie::Leaf)
164+ .map(KeyTrie::MappableCommand)
178 .map_err(E::custom)
165 .map_err(E::custom)
179 }
166 }
180
167
@@ -208,17 +195,43 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
208}
195}
209
196
210impl KeyTrie {
197impl KeyTrie {
198+ pub fn reverse_map(&self) -> ReverseKeymap {
199+ // recursively visit all nodes in keymap
200+ fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec<KeyEvent>) {
201+ match node {
202+ KeyTrie::MappableCommand(cmd) => {
203+ let name = cmd.name();
204+ if name != "no_op" {
205+ cmd_map.entry(name.into()).or_default().push(keys.clone())
206+ }
207+ }
208+ KeyTrie::Node(next) => {
209+ for (key, trie) in &next.map {
210+ keys.push(*key);
211+ map_node(cmd_map, trie, keys);
212+ keys.pop();
213+ }
214+ }
215+ KeyTrie::Sequence(_) => {}
216+ };
217+ }
218+
219+ let mut res = HashMap::new();
220+ map_node(&mut res, self, &mut Vec::new());
221+ res
222+ }
223+
211 pub fn node(&self) -> Option<&KeyTrieNode> {
224 pub fn node(&self) -> Option<&KeyTrieNode> {
212 match *self {
225 match *self {
213 KeyTrie::Node(ref node) => Some(node),
226 KeyTrie::Node(ref node) => Some(node),
214− KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
227+ KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
215 }
228 }
216 }
229 }
217
230
218 pub fn node_mut(&mut self) -> Option<&mut KeyTrieNode> {
231 pub fn node_mut(&mut self) -> Option<&mut KeyTrieNode> {
219 match *self {
232 match *self {
220 KeyTrie::Node(ref mut node) => Some(node),
233 KeyTrie::Node(ref mut node) => Some(node),
221− KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
234+ KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
222 }
235 }
223 }
236 }
224
237
@@ -235,7 +248,7 @@ impl KeyTrie {
235 trie = match trie {
248 trie = match trie {
236 KeyTrie::Node(map) => map.get(key),
249 KeyTrie::Node(map) => map.get(key),
237 // leaf encountered while keys left to process
250 // leaf encountered while keys left to process
238− KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
251+ KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
239 }?
252 }?
240 }
253 }
241 Some(trie)
254 Some(trie)
@@ -256,75 +269,11 @@ pub enum KeymapResult {
256 Cancelled(Vec<KeyEvent>),
269 Cancelled(Vec<KeyEvent>),
257}
270}
258
271
259−#[derive(Debug, Clone, PartialEq, Deserialize)]
260−#[serde(transparent)]
261−pub struct Keymap {
262− /// Always a Node
263− root: KeyTrie,
264−}
265−
266/// A map of command names to keybinds that will execute the command.
272/// A map of command names to keybinds that will execute the command.
267pub type ReverseKeymap = HashMap<String, Vec<Vec<KeyEvent>>>;
273pub type ReverseKeymap = HashMap<String, Vec<Vec<KeyEvent>>>;
268
274
269−impl Keymap {
270− pub fn new(root: KeyTrie) -> Self {
271− Keymap { root }
272− }
273−
274− pub fn reverse_map(&self) -> ReverseKeymap {
275− // recursively visit all nodes in keymap
276− fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec<KeyEvent>) {
277− match node {
278− KeyTrie::Leaf(cmd) => match cmd {
279− MappableCommand::Typable { name, .. } => {
280− cmd_map.entry(name.into()).or_default().push(keys.clone())
281− }
282− MappableCommand::Static { name, .. } => cmd_map
283− .entry(name.to_string())
284− .or_default()
285− .push(keys.clone()),
286− },
287− KeyTrie::Node(next) => {
288− for (key, trie) in &next.map {
289− keys.push(*key);
290− map_node(cmd_map, trie, keys);
291− keys.pop();
292− }
293− }
294− KeyTrie::Sequence(_) => {}
295− };
296− }
297−
298− let mut res = HashMap::new();
299− map_node(&mut res, &self.root, &mut Vec::new());
300− res
301− }
302−
303− pub fn root(&self) -> &KeyTrie {
304− &self.root
305− }
306−
307− pub fn merge(&mut self, other: Self) {
308− self.root.merge_nodes(other.root);
309− }
310−}
311−
312−impl Deref for Keymap {
313− type Target = KeyTrieNode;
314−
315− fn deref(&self) -> &Self::Target {
316− self.root.node().unwrap()
317− }
318−}
319−
320−impl Default for Keymap {
321− fn default() -> Self {
322− Self::new(KeyTrie::Node(KeyTrieNode::default()))
323− }
324−}
325−
326pub struct Keymaps {
275pub struct Keymaps {
327− pub map: Box<dyn DynAccess<HashMap<Mode, Keymap>>>,
276+ pub map: Box<dyn DynAccess<HashMap<Mode, KeyTrie>>>,
328 /// Stores pending keys waiting for the next key. This is relative to a
277 /// Stores pending keys waiting for the next key. This is relative to a
329 /// sticky node if one is in use.
278 /// sticky node if one is in use.
330 state: Vec<KeyEvent>,
279 state: Vec<KeyEvent>,
@@ -333,7 +282,7 @@ pub struct Keymaps {
333}
282}
334
283
335impl Keymaps {
284impl Keymaps {
336− pub fn new(map: Box<dyn DynAccess<HashMap<Mode, Keymap>>>) -> Self {
285+ pub fn new(map: Box<dyn DynAccess<HashMap<Mode, KeyTrie>>>) -> Self {
337 Self {
286 Self {
338 map,
287 map,
339 state: Vec::new(),
288 state: Vec::new(),
@@ -341,7 +290,7 @@ impl Keymaps {
341 }
290 }
342 }
291 }
343
292
344− pub fn map(&self) -> DynGuard<HashMap<Mode, Keymap>> {
293+ pub fn map(&self) -> DynGuard<HashMap<Mode, KeyTrie>> {
345 self.map.load()
294 self.map.load()
346 }
295 }
347
296
@@ -373,11 +322,11 @@ impl Keymaps {
373 let first = self.state.get(0).unwrap_or(&key);
322 let first = self.state.get(0).unwrap_or(&key);
374 let trie_node = match self.sticky {
323 let trie_node = match self.sticky {
375 Some(ref trie) => Cow::Owned(KeyTrie::Node(trie.clone())),
324 Some(ref trie) => Cow::Owned(KeyTrie::Node(trie.clone())),
376− None => Cow::Borrowed(&keymap.root),
325+ None => Cow::Borrowed(keymap),
377 };
326 };
378
327
379 let trie = match trie_node.search(&[*first]) {
328 let trie = match trie_node.search(&[*first]) {
380− Some(KeyTrie::Leaf(ref cmd)) => {
329+ Some(KeyTrie::MappableCommand(ref cmd)) => {
381 return KeymapResult::Matched(cmd.clone());
330 return KeymapResult::Matched(cmd.clone());
382 }
331 }
383 Some(KeyTrie::Sequence(ref cmds)) => {
332 Some(KeyTrie::Sequence(ref cmds)) => {
@@ -396,7 +345,7 @@ impl Keymaps {
396 }
345 }
397 KeymapResult::Pending(map.clone())
346 KeymapResult::Pending(map.clone())
398 }
347 }
399− Some(KeyTrie::Leaf(cmd)) => {
348+ Some(KeyTrie::MappableCommand(cmd)) => {
400 self.state.clear();
349 self.state.clear();
401 KeymapResult::Matched(cmd.clone())
350 KeymapResult::Matched(cmd.clone())
402 }
351 }
@@ -416,9 +365,13 @@ impl Default for Keymaps {
416}
365}
417
366
418/// Merge default config keys with user overwritten keys for custom user config.
367/// Merge default config keys with user overwritten keys for custom user config.
419−pub fn merge_keys(dst: &mut HashMap<Mode, Keymap>, mut delta: HashMap<Mode, Keymap>) {
368+pub fn merge_keys(dst: &mut HashMap<Mode, KeyTrie>, mut delta: HashMap<Mode, KeyTrie>) {
420 for (mode, keys) in dst {
369 for (mode, keys) in dst {
421− keys.merge(delta.remove(mode).unwrap_or_default())
370+ keys.merge_nodes(
371+ delta
372+ .remove(mode)
373+ .unwrap_or_else(|| KeyTrie::Node(KeyTrieNode::default())),
374+ )
422 }
375 }
423}
376}
424
377
@@ -447,17 +400,15 @@ mod tests {
447 #[test]
400 #[test]
448 fn merge_partial_keys() {
401 fn merge_partial_keys() {
449 let keymap = hashmap! {
402 let keymap = hashmap! {
450− Mode::Normal => Keymap::new(
451− keymap!({ "Normal mode"
403+ Mode::Normal => keymap!({ "Normal mode"
452− "i" => normal_mode,
404+ "i" => normal_mode,
453− "无" => insert_mode,
405+ "无" => insert_mode,
454− "z" => jump_backward,
406+ "z" => jump_backward,
455− "g" => { "Merge into goto mode"
407+ "g" => { "Merge into goto mode"
456− "$" => goto_line_end,
408+ "$" => goto_line_end,
457− "g" => delete_char_forward,
409+ "g" => delete_char_forward,
458− },
459− })
410+ },
460− )
411+ })
461 };
412 };
462 let mut merged_keyamp = default();
413 let mut merged_keyamp = default();
463 merge_keys(&mut merged_keyamp, keymap.clone());
414 merge_keys(&mut merged_keyamp, keymap.clone());
@@ -484,40 +435,52 @@ mod tests {
484 let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
435 let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
485 // Assumes that `g` is a node in default keymap
436 // Assumes that `g` is a node in default keymap
486 assert_eq!(
437 assert_eq!(
487− keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
438+ keymap.search(&[key!('g'), key!('$')]).unwrap(),
488− &KeyTrie::Leaf(MappableCommand::goto_line_end),
439+ &KeyTrie::MappableCommand(MappableCommand::goto_line_end),
489 "Leaf should be present in merged subnode"
440 "Leaf should be present in merged subnode"
490 );
441 );
491 // Assumes that `gg` is in default keymap
442 // Assumes that `gg` is in default keymap
492 assert_eq!(
443 assert_eq!(
493− keymap.root().search(&[key!('g'), key!('g')]).unwrap(),
444+ keymap.search(&[key!('g'), key!('g')]).unwrap(),
494− &KeyTrie::Leaf(MappableCommand::delete_char_forward),
445+ &KeyTrie::MappableCommand(MappableCommand::delete_char_forward),
495 "Leaf should replace old leaf in merged subnode"
446 "Leaf should replace old leaf in merged subnode"
496 );
447 );
497 // Assumes that `ge` is in default keymap
448 // Assumes that `ge` is in default keymap
498 assert_eq!(
449 assert_eq!(
499− keymap.root().search(&[key!('g'), key!('e')]).unwrap(),
450+ keymap.search(&[key!('g'), key!('e')]).unwrap(),
500− &KeyTrie::Leaf(MappableCommand::goto_last_line),
451+ &KeyTrie::MappableCommand(MappableCommand::goto_last_line),
501 "Old leaves in subnode should be present in merged node"
452 "Old leaves in subnode should be present in merged node"
502 );
453 );
503
454
455+ assert!(
504− assert!(merged_keyamp.get(&Mode::Normal).unwrap().len() > 1);
456+ merged_keyamp
457+ .get(&Mode::Normal)
458+ .and_then(|key_trie| key_trie.node())
459+ .unwrap()
460+ .len()
461+ > 1
462+ );
463+ assert!(
505− assert!(merged_keyamp.get(&Mode::Insert).unwrap().len() > 0);
464+ merged_keyamp
465+ .get(&Mode::Insert)
466+ .and_then(|key_trie| key_trie.node())
467+ .unwrap()
468+ .len()
469+ > 0
470+ );
506 }
471 }
507
472
508 #[test]
473 #[test]
509 fn order_should_be_set() {
474 fn order_should_be_set() {
510 let keymap = hashmap! {
475 let keymap = hashmap! {
511− Mode::Normal => Keymap::new(
512− keymap!({ "Normal mode"
476+ Mode::Normal => keymap!({ "Normal mode"
513− "space" => { ""
477+ "space" => { ""
514− "s" => { ""
478+ "s" => { ""
515− "v" => vsplit,
479+ "v" => vsplit,
516− "c" => hsplit,
480+ "c" => hsplit,
517− },
518 },
481 },
519− })
482+ },
520− )
483+ })
521 };
484 };
522 let mut merged_keyamp = default();
485 let mut merged_keyamp = default();
523 merge_keys(&mut merged_keyamp, keymap.clone());
486 merge_keys(&mut merged_keyamp, keymap.clone());
@@ -525,22 +488,19 @@ mod tests {
525 let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
488 let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
526 // Make sure mapping works
489 // Make sure mapping works
527 assert_eq!(
490 assert_eq!(
528− keymap
529− .root()
530− .search(&[key!(' '), key!('s'), key!('v')])
491+ keymap.search(&[key!(' '), key!('s'), key!('v')]).unwrap(),
531− .unwrap(),
532− &KeyTrie::Leaf(MappableCommand::vsplit),
492+ &KeyTrie::MappableCommand(MappableCommand::vsplit),
533 "Leaf should be present in merged subnode"
493 "Leaf should be present in merged subnode"
534 );
494 );
535 // Make sure an order was set during merge
495 // Make sure an order was set during merge
536− let node = keymap.root().search(&[crate::key!(' ')]).unwrap();
496+ let node = keymap.search(&[crate::key!(' ')]).unwrap();
537− assert!(!node.node().unwrap().order().is_empty())
497+ assert!(!node.node().unwrap().order.as_slice().is_empty())
538 }
498 }
539
499
540 #[test]
500 #[test]
541 fn aliased_modes_are_same_in_default_keymap() {
501 fn aliased_modes_are_same_in_default_keymap() {
542 let keymaps = Keymaps::default().map();
502 let keymaps = Keymaps::default().map();
543− let root = keymaps.get(&Mode::Normal).unwrap().root();
503+ let root = keymaps.get(&Mode::Normal).unwrap();
544 assert_eq!(
504 assert_eq!(
545 root.search(&[key!(' '), key!('w')]).unwrap(),
505 root.search(&[key!(' '), key!('w')]).unwrap(),
546 root.search(&["C-w".parse::<KeyEvent>().unwrap()]).unwrap(),
506 root.search(&["C-w".parse::<KeyEvent>().unwrap()]).unwrap(),
@@ -563,7 +523,7 @@ mod tests {
563 },
523 },
564 "j" | "k" => move_line_down,
524 "j" | "k" => move_line_down,
565 });
525 });
566− let keymap = Keymap::new(normal_mode);
526+ let keymap = normal_mode;
567 let mut reverse_map = keymap.reverse_map();
527 let mut reverse_map = keymap.reverse_map();
568
528
569 // sort keybindings in order to have consistent tests
529 // sort keybindings in order to have consistent tests
@@ -611,7 +571,7 @@ mod tests {
611 modifiers: KeyModifiers::NONE,
571 modifiers: KeyModifiers::NONE,
612 };
572 };
613
573
614− let expectation = Keymap::new(KeyTrie::Node(KeyTrieNode::new(
574+ let expectation = KeyTrie::Node(KeyTrieNode::new(
615 "",
575 "",
616 hashmap! {
576 hashmap! {
617 key => KeyTrie::Sequence(vec!{
577 key => KeyTrie::Sequence(vec!{
@@ -628,7 +588,7 @@ mod tests {
628 })
588 })
629 },
589 },
630 vec![key],
590 vec![key],
631− )));
591+ ));
632
592
633 assert_eq!(toml::from_str(keys), Ok(expectation));
593 assert_eq!(toml::from_str(keys), Ok(expectation));
634 }
594 }
helix-term/src/keymap/default.rs+14−7
@@ -1,10 +1,10 @@
1use std::collections::HashMap;
1use std::collections::HashMap;
2
2
3use super::macros::keymap;
3use super::macros::keymap;
4−use super::{Keymap, Mode};
4+use super::{KeyTrie, Mode};
5use helix_core::hashmap;
5use helix_core::hashmap;
6
6
7−pub fn default() -> HashMap<Mode, Keymap> {
7+pub fn default() -> HashMap<Mode, KeyTrie> {
8 let normal = keymap!({ "Normal mode"
8 let normal = keymap!({ "Normal mode"
9 "h" | "left" => move_char_left,
9 "h" | "left" => move_char_left,
10 "j" | "down" => move_visual_line_down,
10 "j" | "down" => move_visual_line_down,
@@ -79,6 +79,7 @@ pub fn default() -> HashMap<Mode, Keymap> {
79
79
80 "s" => select_regex,
80 "s" => select_regex,
81 "A-s" => split_selection_on_newline,
81 "A-s" => split_selection_on_newline,
82+ "A-minus" => merge_selections,
82 "A-_" => merge_consecutive_selections,
83 "A-_" => merge_consecutive_selections,
83 "S" => split_selection,
84 "S" => split_selection,
84 ";" => collapse_selection,
85 ";" => collapse_selection,
@@ -87,6 +88,8 @@ pub fn default() -> HashMap<Mode, Keymap> {
87 "A-i" | "A-down" => shrink_selection,
88 "A-i" | "A-down" => shrink_selection,
88 "A-p" | "A-left" => select_prev_sibling,
89 "A-p" | "A-left" => select_prev_sibling,
89 "A-n" | "A-right" => select_next_sibling,
90 "A-n" | "A-right" => select_next_sibling,
91+ "A-e" => move_parent_node_end,
92+ "A-b" => move_parent_node_start,
90
93
91 "%" => select_all,
94 "%" => select_all,
92 "x" => extend_line_below,
95 "x" => extend_line_below,
@@ -264,7 +267,7 @@ pub fn default() -> HashMap<Mode, Keymap> {
264 "C-v" | "v" => vsplit_new,
267 "C-v" | "v" => vsplit_new,
265 },
268 },
266 },
269 },
267− "y" => yank_joined_to_clipboard,
270+ "y" => yank_to_clipboard,
268 "Y" => yank_main_selection_to_clipboard,
271 "Y" => yank_main_selection_to_clipboard,
269 "p" => paste_clipboard_after,
272 "p" => paste_clipboard_after,
270 "P" => paste_clipboard_before,
273 "P" => paste_clipboard_before,
@@ -335,6 +338,9 @@ pub fn default() -> HashMap<Mode, Keymap> {
335 "B" => extend_prev_long_word_start,
338 "B" => extend_prev_long_word_start,
336 "E" => extend_next_long_word_end,
339 "E" => extend_next_long_word_end,
337
340
341+ "A-e" => extend_parent_node_end,
342+ "A-b" => extend_parent_node_start,
343+
338 "n" => extend_search_next,
344 "n" => extend_search_next,
339 "N" => extend_search_prev,
345 "N" => extend_search_prev,
340
346
@@ -367,7 +373,8 @@ pub fn default() -> HashMap<Mode, Keymap> {
367 "C-h" | "backspace" | "S-backspace" => delete_char_backward,
373 "C-h" | "backspace" | "S-backspace" => delete_char_backward,
368 "C-d" | "del" => delete_char_forward,
374 "C-d" | "del" => delete_char_forward,
369 "C-j" | "ret" => insert_newline,
375 "C-j" | "ret" => insert_newline,
376+ "tab" => smart_tab,
370− "tab" => insert_tab,
377+ "S-tab" => insert_tab,
371
378
372 "up" => move_visual_line_up,
379 "up" => move_visual_line_up,
373 "down" => move_visual_line_down,
380 "down" => move_visual_line_down,
@@ -379,8 +386,8 @@ pub fn default() -> HashMap<Mode, Keymap> {
379 "end" => goto_line_end_newline,
386 "end" => goto_line_end_newline,
380 });
387 });
381 hashmap!(
388 hashmap!(
382− Mode::Normal => Keymap::new(normal),
389+ Mode::Normal => normal,
383− Mode::Select => Keymap::new(select),
390+ Mode::Select => select,
384− Mode::Insert => Keymap::new(insert),
391+ Mode::Insert => insert,
385 )
392 )
386}
393}
helix-term/src/keymap/macros.rs+3−4
@@ -62,12 +62,11 @@ macro_rules! alt {
62 };
62 };
63}
63}
64
64
65−/// Macro for defining the root of a `Keymap` object. Example:
65+/// Macro for defining a `KeyTrie`. Example:
66///
66///
67/// ```
67/// ```
68/// # use helix_core::hashmap;
68/// # use helix_core::hashmap;
69/// # use helix_term::keymap;
69/// # use helix_term::keymap;
70−/// # use helix_term::keymap::Keymap;
71/// let normal_mode = keymap!({ "Normal mode"
70/// let normal_mode = keymap!({ "Normal mode"
72/// "i" => insert_mode,
71/// "i" => insert_mode,
73/// "g" => { "Goto"
72/// "g" => { "Goto"
@@ -76,12 +75,12 @@ macro_rules! alt {
76/// },
75/// },
77/// "j" | "down" => move_line_down,
76/// "j" | "down" => move_line_down,
78/// });
77/// });
79−/// let keymap = Keymap::new(normal_mode);
78+/// let keymap = normal_mode;
80/// ```
79/// ```
81#[macro_export]
80#[macro_export]
82macro_rules! keymap {
81macro_rules! keymap {
83 (@trie $cmd:ident) => {
82 (@trie $cmd:ident) => {
84− $crate::keymap::KeyTrie::Leaf($crate::commands::MappableCommand::$cmd)
83+ $crate::keymap::KeyTrie::MappableCommand($crate::commands::MappableCommand::$cmd)
85 };
84 };
86
85
87 (@trie
86 (@trie
helix-term/src/main.rs+22−18
@@ -4,9 +4,8 @@ use helix_loader::VERSION_AND_GIT_HASH;
4use helix_term::application::Application;
4use helix_term::application::Application;
5use helix_term::args::Args;
5use helix_term::args::Args;
6use helix_term::config::{Config, ConfigLoadError};
6use helix_term::config::{Config, ConfigLoadError};
7−use std::path::PathBuf;
8
7
9−fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
8+fn setup_logging(verbosity: u64) -> Result<()> {
10 let mut base_config = fern::Dispatch::new();
9 let mut base_config = fern::Dispatch::new();
11
10
12 base_config = match verbosity {
11 base_config = match verbosity {
@@ -27,7 +26,7 @@ fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
27 message
26 message
28 ))
27 ))
29 })
28 })
30− .chain(fern::log_file(logpath)?);
29+ .chain(fern::log_file(helix_loader::log_file())?);
31
30
32 base_config.chain(file_config).apply()?;
31 base_config.chain(file_config).apply()?;
33
32
@@ -41,12 +40,6 @@ fn main() -> Result<()> {
41
40
42#[tokio::main]
41#[tokio::main]
43async fn main_impl() -> Result<i32> {
42async fn main_impl() -> Result<i32> {
44− let logpath = helix_loader::log_file();
45− let parent = logpath.parent().unwrap();
46− if !parent.exists() {
47− std::fs::create_dir_all(parent).ok();
48− }
49−
50 let help = format!(
43 let help = format!(
51 "\
44 "\
52{} {}
45{} {}
@@ -68,20 +61,25 @@ FLAGS:
68 -g, --grammar {{fetch|build}} Fetches or builds tree-sitter grammars listed in languages.toml
61 -g, --grammar {{fetch|build}} Fetches or builds tree-sitter grammars listed in languages.toml
69 -c, --config <file> Specifies a file to use for configuration
62 -c, --config <file> Specifies a file to use for configuration
70 -v Increases logging verbosity each use for up to 3 times
63 -v Increases logging verbosity each use for up to 3 times
71− --log Specifies a file to use for logging
64+ --log <file> Specifies a file to use for logging
72 (default file: {})
65 (default file: {})
73 -V, --version Prints version information
66 -V, --version Prints version information
74 --vsplit Splits all given files vertically into different windows
67 --vsplit Splits all given files vertically into different windows
75 --hsplit Splits all given files horizontally into different windows
68 --hsplit Splits all given files horizontally into different windows
69+ -w, --working-dir <path> Specify an initial working directory
70+ +N Open the first given file at line number N
76",
71",
77 env!("CARGO_PKG_NAME"),
72 env!("CARGO_PKG_NAME"),
78 VERSION_AND_GIT_HASH,
73 VERSION_AND_GIT_HASH,
79 env!("CARGO_PKG_AUTHORS"),
74 env!("CARGO_PKG_AUTHORS"),
80 env!("CARGO_PKG_DESCRIPTION"),
75 env!("CARGO_PKG_DESCRIPTION"),
81− logpath.display(),
76+ helix_loader::default_log_file().display(),
82 );
77 );
83
78
84− let args = Args::parse_args().context("could not parse arguments")?;
79+ let mut args = Args::parse_args().context("could not parse arguments")?;
80+
81+ helix_loader::initialize_config_file(args.config_file.clone());
82+ helix_loader::initialize_log_file(args.log_file.clone());
85
83
86 // Help has a higher priority and should be handled separately.
84 // Help has a higher priority and should be handled separately.
87 if args.display_help {
85 if args.display_help {
@@ -116,15 +114,21 @@ FLAGS:
116 return Ok(0);
114 return Ok(0);
117 }
115 }
118
116
119− let logpath = args.log_file.as_ref().cloned().unwrap_or(logpath);
120− setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
117+ setup_logging(args.verbosity).context("failed to initialize logging")?;
121
118
122− let config_dir = helix_loader::config_dir();
119+ // Before setting the working directory, resolve all the paths in args.files
123− if !config_dir.exists() {
120+ for (path, _) in args.files.iter_mut() {
124− std::fs::create_dir_all(&config_dir).ok();
121+ *path = helix_core::path::get_canonicalized_path(path);
125 }
122 }
126
123
127− helix_loader::initialize_config_file(args.config_file.clone());
124+ // NOTE: Set the working directory early so the correct configuration is loaded. Be aware that
125+ // Application::new() depends on this logic so it must be updated if this changes.
126+ if let Some(path) = &args.working_directory {
127+ helix_loader::set_current_working_dir(path)?;
128+ } else if let Some((path, _)) = args.files.first().filter(|p| p.0.is_dir()) {
129+ // If the first file is a directory, it will be the working directory unless -w was specified
130+ helix_loader::set_current_working_dir(path)?;
131+ }
128
132
129 let config = match Config::load_default() {
133 let config = match Config::load_default() {
130 Ok(config) => config,
134 Ok(config) => config,
helix-term/src/ui/completion.rs+60−32
@@ -2,6 +2,7 @@ use crate::compositor::{Component, Context, Event, EventResult};
2use helix_view::{
2use helix_view::{
3 document::SavePoint,
3 document::SavePoint,
4 editor::CompleteAction,
4 editor::CompleteAction,
5+ graphics::Margin,
5 theme::{Modifier, Style},
6 theme::{Modifier, Style},
6 ViewId,
7 ViewId,
7};
8};
@@ -110,6 +111,7 @@ impl Completion {
110 start_offset: usize,
111 start_offset: usize,
111 trigger_offset: usize,
112 trigger_offset: usize,
112 ) -> Self {
113 ) -> Self {
114+ let preview_completion_insert = editor.config().preview_completion_insert;
113 let replace_mode = editor.config().completion_replace;
115 let replace_mode = editor.config().completion_replace;
114 // Sort completion items according to their preselect status (given by the LSP server)
116 // Sort completion items according to their preselect status (given by the LSP server)
115 items.sort_by_key(|item| !item.item.preselect.unwrap_or(false));
117 items.sort_by_key(|item| !item.item.preselect.unwrap_or(false));
@@ -119,7 +121,7 @@ impl Completion {
119 fn item_to_transaction(
121 fn item_to_transaction(
120 doc: &Document,
122 doc: &Document,
121 view_id: ViewId,
123 view_id: ViewId,
122− item: &CompletionItem,
124+ item: &lsp::CompletionItem,
123 offset_encoding: OffsetEncoding,
125 offset_encoding: OffsetEncoding,
124 trigger_offset: usize,
126 trigger_offset: usize,
125 include_placeholder: bool,
127 include_placeholder: bool,
@@ -130,7 +132,7 @@ impl Completion {
130 let text = doc.text().slice(..);
132 let text = doc.text().slice(..);
131 let primary_cursor = selection.primary().cursor(text);
133 let primary_cursor = selection.primary().cursor(text);
132
134
133− let (edit_offset, new_text) = if let Some(edit) = &item.item.text_edit {
135+ let (edit_offset, new_text) = if let Some(edit) = &item.text_edit {
134 let edit = match edit {
136 let edit = match edit {
135 lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
137 lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
136 lsp::CompletionTextEdit::InsertAndReplace(item) => {
138 lsp::CompletionTextEdit::InsertAndReplace(item) => {
@@ -143,7 +145,9 @@ impl Completion {
143 }
145 }
144 };
146 };
145
147
148+ let Some(range) =
146− let Some(range) = util::lsp_range_to_range(doc.text(), edit.range, offset_encoding) else{
149+ util::lsp_range_to_range(doc.text(), edit.range, offset_encoding)
150+ else {
147 return Transaction::new(doc.text());
151 return Transaction::new(doc.text());
148 };
152 };
149
153
@@ -153,10 +157,9 @@ impl Completion {
153 (Some((start_offset, end_offset)), edit.new_text)
157 (Some((start_offset, end_offset)), edit.new_text)
154 } else {
158 } else {
155 let new_text = item
159 let new_text = item
156− .item
157 .insert_text
160 .insert_text
158 .clone()
161 .clone()
159− .unwrap_or_else(|| item.item.label.clone());
162+ .unwrap_or_else(|| item.label.clone());
160 // check that we are still at the correct savepoint
163 // check that we are still at the correct savepoint
161 // we can still generate a transaction regardless but if the
164 // we can still generate a transaction regardless but if the
162 // document changed (and not just the selection) then we will
165 // document changed (and not just the selection) then we will
@@ -165,9 +168,9 @@ impl Completion {
165 (None, new_text)
168 (None, new_text)
166 };
169 };
167
170
168− if matches!(item.item.kind, Some(lsp::CompletionItemKind::SNIPPET))
171+ if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET))
169 || matches!(
172 || matches!(
170− item.item.insert_text_format,
173+ item.insert_text_format,
171 Some(lsp::InsertTextFormat::SNIPPET)
174 Some(lsp::InsertTextFormat::SNIPPET)
172 )
175 )
173 {
176 {
@@ -212,9 +215,26 @@ impl Completion {
212
215
213 let (view, doc) = current!(editor);
216 let (view, doc) = current!(editor);
214
217
218+ macro_rules! language_server {
219+ ($item:expr) => {
220+ match editor
221+ .language_servers
222+ .get_by_id($item.language_server_id)
223+ {
224+ Some(ls) => ls,
225+ None => {
226+ editor.set_error("completions are outdated");
227+ // TODO close the completion menu somehow,
228+ // currently there is no trivial way to access the EditorView to close the completion menu
229+ return;
230+ }
231+ }
232+ };
233+ }
234+
215 match event {
235 match event {
216 PromptEvent::Abort => {}
236 PromptEvent::Abort => {}
217− PromptEvent::Update => {
237+ PromptEvent::Update if preview_completion_insert => {
218 // Update creates "ghost" transactions which are not sent to the
238 // Update creates "ghost" transactions which are not sent to the
219 // lsp server to avoid messing up re-requesting completions. Once a
239 // lsp server to avoid messing up re-requesting completions. Once a
220 // completion has been selected (with tab, c-n or c-p) it's always accepted whenever anything
240 // completion has been selected (with tab, c-n or c-p) it's always accepted whenever anything
@@ -236,23 +256,18 @@ impl Completion {
236 // always present here
256 // always present here
237 let item = item.unwrap();
257 let item = item.unwrap();
238
258
239− let offset_encoding = editor
240− .language_servers
241− .get_by_id(item.language_server_id)
242− .expect("language server disappeared between completion request and application")
243− .offset_encoding();
244−
245 let transaction = item_to_transaction(
259 let transaction = item_to_transaction(
246 doc,
260 doc,
247 view.id,
261 view.id,
248− item,
262+ &item.item,
249− offset_encoding,
263+ language_server!(item).offset_encoding(),
250 trigger_offset,
264 trigger_offset,
251 true,
265 true,
252 replace_mode,
266 replace_mode,
253 );
267 );
254 doc.apply_temporary(&transaction, view.id);
268 doc.apply_temporary(&transaction, view.id);
255 }
269 }
270+ PromptEvent::Update => {}
256 PromptEvent::Validate => {
271 PromptEvent::Validate => {
257 if let Some(CompleteAction::Selected { savepoint }) =
272 if let Some(CompleteAction::Selected { savepoint }) =
258 editor.last_completion.take()
273 editor.last_completion.take()
@@ -262,11 +277,8 @@ impl Completion {
262 // always present here
277 // always present here
263 let mut item = item.unwrap().clone();
278 let mut item = item.unwrap().clone();
264
279
265− let offset_encoding = editor
280+ let language_server = language_server!(item);
266− .language_servers
267− .get_by_id(item.language_server_id)
268− .expect("language server disappeared between completion request and application")
269− .offset_encoding();
281+ let offset_encoding = language_server.offset_encoding();
270
282
271 let language_server = editor
283 let language_server = editor
272 .language_servers
284 .language_servers
@@ -283,10 +295,12 @@ impl Completion {
283 };
295 };
284 // if more text was entered, remove it
296 // if more text was entered, remove it
285 doc.restore(view, &savepoint, true);
297 doc.restore(view, &savepoint, true);
298+ // save an undo checkpoint before the completion
299+ doc.append_changes_to_history(view);
286 let transaction = item_to_transaction(
300 let transaction = item_to_transaction(
287 doc,
301 doc,
288 view.id,
302 view.id,
289− &item,
303+ &item.item,
290 offset_encoding,
304 offset_encoding,
291 trigger_offset,
305 trigger_offset,
292 false,
306 false,
@@ -313,9 +327,18 @@ impl Completion {
313 }
327 }
314 };
328 };
315 });
329 });
330+
331+ let margin = if editor.menu_border() {
332+ Margin::vertical(1)
333+ } else {
334+ Margin::none()
335+ };
336+
316 let popup = Popup::new(Self::ID, menu)
337 let popup = Popup::new(Self::ID, menu)
317 .with_scrollbar(false)
338 .with_scrollbar(false)
318− .ignore_escape_key(true);
339+ .ignore_escape_key(true)
340+ .margin(margin);
341+
319 let mut completion = Self {
342 let mut completion = Self {
320 popup,
343 popup,
321 start_offset,
344 start_offset,
@@ -401,19 +424,18 @@ impl Completion {
401 Some(item) if !item.resolved => item.clone(),
424 Some(item) if !item.resolved => item.clone(),
402 _ => return false,
425 _ => return false,
403 };
426 };
404− let language_server = match cx
427+
428+ let Some(language_server) = cx
405 .editor
429 .editor
406− .language_servers
430+ .language_server_by_id(current_item.language_server_id)
407− .get_by_id(current_item.language_server_id)
431+ else {
408− {
432+ return false;
409− Some(language_server) => language_server,
410− None => return false,
411 };
433 };
412
434
413 // This method should not block the compositor so we handle the response asynchronously.
435 // This method should not block the compositor so we handle the response asynchronously.
414− let future = match language_server.resolve_completion_item(current_item.item.clone()) {
436+ let Some(future) = language_server.resolve_completion_item(current_item.item.clone())
415− Some(future) => future,
437+ else {
416− None => return false,
438+ return false;
417 };
439 };
418
440
419 cx.callback(
441 cx.callback(
@@ -557,6 +579,12 @@ impl Component for Completion {
557 // clear area
579 // clear area
558 let background = cx.editor.theme.get("ui.popup");
580 let background = cx.editor.theme.get("ui.popup");
559 surface.clear_with(doc_area, background);
581 surface.clear_with(doc_area, background);
582+
583+ if cx.editor.popup_border() {
584+ use tui::widgets::{Block, Borders, Widget};
585+ Widget::render(Block::default().borders(Borders::ALL), doc_area, surface);
586+ }
587+
560 markdown_doc.render(doc_area, surface, cx);
588 markdown_doc.render(doc_area, surface, cx);
561 }
589 }
562}
590}
helix-term/src/ui/document.rs+60−24
@@ -97,7 +97,8 @@ pub fn render_document(
97 doc: &Document,
97 doc: &Document,
98 offset: ViewPosition,
98 offset: ViewPosition,
99 doc_annotations: &TextAnnotations,
99 doc_annotations: &TextAnnotations,
100− highlight_iter: impl Iterator<Item = HighlightEvent>,
100+ syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
101+ overlay_highlight_iter: impl Iterator<Item = HighlightEvent>,
101 theme: &Theme,
102 theme: &Theme,
102 line_decoration: &mut [Box<dyn LineDecoration + '_>],
103 line_decoration: &mut [Box<dyn LineDecoration + '_>],
103 translated_positions: &mut [TranslatedPosition],
104 translated_positions: &mut [TranslatedPosition],
@@ -109,7 +110,8 @@ pub fn render_document(
109 offset,
110 offset,
110 &doc.text_format(viewport.width, Some(theme)),
111 &doc.text_format(viewport.width, Some(theme)),
111 doc_annotations,
112 doc_annotations,
112− highlight_iter,
113+ syntax_highlight_iter,
114+ overlay_highlight_iter,
113 theme,
115 theme,
114 line_decoration,
116 line_decoration,
115 translated_positions,
117 translated_positions,
@@ -157,7 +159,8 @@ pub fn render_text<'t>(
157 offset: ViewPosition,
159 offset: ViewPosition,
158 text_fmt: &TextFormat,
160 text_fmt: &TextFormat,
159 text_annotations: &TextAnnotations,
161 text_annotations: &TextAnnotations,
160− highlight_iter: impl Iterator<Item = HighlightEvent>,
162+ syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
163+ overlay_highlight_iter: impl Iterator<Item = HighlightEvent>,
161 theme: &Theme,
164 theme: &Theme,
162 line_decorations: &mut [Box<dyn LineDecoration + '_>],
165 line_decorations: &mut [Box<dyn LineDecoration + '_>],
163 translated_positions: &mut [TranslatedPosition],
166 translated_positions: &mut [TranslatedPosition],
@@ -178,10 +181,16 @@ pub fn render_text<'t>(
178
181
179 let (mut formatter, mut first_visible_char_idx) =
182 let (mut formatter, mut first_visible_char_idx) =
180 DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, offset.anchor);
183 DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, offset.anchor);
181− let mut styles = StyleIter {
184+ let mut syntax_styles = StyleIter {
182 text_style: renderer.text_style,
185 text_style: renderer.text_style,
183 active_highlights: Vec::with_capacity(64),
186 active_highlights: Vec::with_capacity(64),
184− highlight_iter,
187+ highlight_iter: syntax_highlight_iter,
188+ theme,
189+ };
190+ let mut overlay_styles = StyleIter {
191+ text_style: Style::default(),
192+ active_highlights: Vec::with_capacity(64),
193+ highlight_iter: overlay_highlight_iter,
185 theme,
194 theme,
186 };
195 };
187
196
@@ -193,7 +202,10 @@ pub fn render_text<'t>(
193 };
202 };
194 let mut is_in_indent_area = true;
203 let mut is_in_indent_area = true;
195 let mut last_line_indent_level = 0;
204 let mut last_line_indent_level = 0;
196− let mut style_span = styles
205+ let mut syntax_style_span = syntax_styles
206+ .next()
207+ .unwrap_or_else(|| (Style::default(), usize::MAX));
208+ let mut overlay_style_span = overlay_styles
197 .next()
209 .next()
198 .unwrap_or_else(|| (Style::default(), usize::MAX));
210 .unwrap_or_else(|| (Style::default(), usize::MAX));
199
211
@@ -221,9 +233,16 @@ pub fn render_text<'t>(
221
233
222 // skip any graphemes on visual lines before the block start
234 // skip any graphemes on visual lines before the block start
223 if pos.row < row_off {
235 if pos.row < row_off {
224− if char_pos >= style_span.1 {
236+ if char_pos >= syntax_style_span.1 {
225− style_span = if let Some(style_span) = styles.next() {
237+ syntax_style_span = if let Some(syntax_style_span) = syntax_styles.next() {
226− style_span
238+ syntax_style_span
239+ } else {
240+ break;
241+ }
242+ }
243+ if char_pos >= overlay_style_span.1 {
244+ overlay_style_span = if let Some(overlay_style_span) = overlay_styles.next() {
245+ overlay_style_span
227 } else {
246 } else {
228 break;
247 break;
229 }
248 }
@@ -260,8 +279,15 @@ pub fn render_text<'t>(
260 }
279 }
261
280
262 // acquire the correct grapheme style
281 // acquire the correct grapheme style
263− if char_pos >= style_span.1 {
282+ if char_pos >= syntax_style_span.1 {
264− style_span = styles.next().unwrap_or((Style::default(), usize::MAX));
283+ syntax_style_span = syntax_styles
284+ .next()
285+ .unwrap_or((Style::default(), usize::MAX));
286+ }
287+ if char_pos >= overlay_style_span.1 {
288+ overlay_style_span = overlay_styles
289+ .next()
290+ .unwrap_or((Style::default(), usize::MAX));
265 }
291 }
266 char_pos += grapheme.doc_chars();
292 char_pos += grapheme.doc_chars();
267
293
@@ -275,22 +301,25 @@ pub fn render_text<'t>(
275 pos,
301 pos,
276 );
302 );
277
303
278− let grapheme_style = if let GraphemeSource::VirtualText { highlight } = grapheme.source {
304+ let (syntax_style, overlay_style) =
279− let style = renderer.text_style;
305+ if let GraphemeSource::VirtualText { highlight } = grapheme.source {
280− if let Some(highlight) = highlight {
306+ let mut style = renderer.text_style;
281− style.patch(theme.highlight(highlight.0))
307+ if let Some(highlight) = highlight {
308+ style = style.patch(theme.highlight(highlight.0))
309+ }
310+ (style, Style::default())
282 } else {
311 } else {
283− style
312+ (syntax_style_span.0, overlay_style_span.0)
284− }
313+ };
285− } else {
286− style_span.0
287− };
288
314
289− let virt = grapheme.is_virtual();
315+ let is_virtual = grapheme.is_virtual();
290 renderer.draw_grapheme(
316 renderer.draw_grapheme(
291 grapheme.grapheme,
317 grapheme.grapheme,
292− grapheme_style,
318+ GraphemeStyle {
293− virt,
319+ syntax_style,
320+ overlay_style,
321+ },
322+ is_virtual,
294 &mut last_line_indent_level,
323 &mut last_line_indent_level,
295 &mut is_in_indent_area,
324 &mut is_in_indent_area,
296 pos,
325 pos,
@@ -322,6 +351,11 @@ pub struct TextRenderer<'a> {
322 pub viewport: Rect,
351 pub viewport: Rect,
323}
352}
324
353
354+pub struct GraphemeStyle {
355+ syntax_style: Style,
356+ overlay_style: Style,
357+}
358+
325impl<'a> TextRenderer<'a> {
359impl<'a> TextRenderer<'a> {
326 pub fn new(
360 pub fn new(
327 surface: &'a mut Surface,
361 surface: &'a mut Surface,
@@ -395,7 +429,7 @@ impl<'a> TextRenderer<'a> {
395 pub fn draw_grapheme(
429 pub fn draw_grapheme(
396 &mut self,
430 &mut self,
397 grapheme: Grapheme,
431 grapheme: Grapheme,
398− mut style: Style,
432+ grapheme_style: GraphemeStyle,
399 is_virtual: bool,
433 is_virtual: bool,
400 last_indent_level: &mut usize,
434 last_indent_level: &mut usize,
401 is_in_indent_area: &mut bool,
435 is_in_indent_area: &mut bool,
@@ -405,9 +439,11 @@ impl<'a> TextRenderer<'a> {
405 let is_whitespace = grapheme.is_whitespace();
439 let is_whitespace = grapheme.is_whitespace();
406
440
407 // TODO is it correct to apply the whitespace style to all unicode white spaces?
441 // TODO is it correct to apply the whitespace style to all unicode white spaces?
442+ let mut style = grapheme_style.syntax_style;
408 if is_whitespace {
443 if is_whitespace {
409 style = style.patch(self.whitespace_style);
444 style = style.patch(self.whitespace_style);
410 }
445 }
446+ style = style.patch(grapheme_style.overlay_style);
411
447
412 let width = grapheme.width();
448 let width = grapheme.width();
413 let space = if is_virtual { " " } else { &self.space };
449 let space = if is_virtual { " " } else { &self.space };
helix-term/src/ui/editor.rs+110−57
@@ -43,6 +43,8 @@ pub struct EditorView {
43 pub(crate) last_insert: (commands::MappableCommand, Vec<InsertEvent>),
43 pub(crate) last_insert: (commands::MappableCommand, Vec<InsertEvent>),
44 pub(crate) completion: Option<Completion>,
44 pub(crate) completion: Option<Completion>,
45 spinners: ProgressSpinners,
45 spinners: ProgressSpinners,
46+ /// Tracks if the terminal window is focused by reaction to terminal focus events
47+ terminal_focused: bool,
46}
48}
47
49
48#[derive(Debug, Clone)]
50#[derive(Debug, Clone)]
@@ -71,6 +73,7 @@ impl EditorView {
71 last_insert: (commands::MappableCommand::normal_mode, Vec::new()),
73 last_insert: (commands::MappableCommand::normal_mode, Vec::new()),
72 completion: None,
74 completion: None,
73 spinners: ProgressSpinners::default(),
75 spinners: ProgressSpinners::default(),
76+ terminal_focused: true,
74 }
77 }
75 }
78 }
76
79
@@ -121,16 +124,20 @@ impl EditorView {
121 line_decorations.push(Box::new(line_decoration));
124 line_decorations.push(Box::new(line_decoration));
122 }
125 }
123
126
124− let mut highlights =
127+ let syntax_highlights =
125 Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
128 Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
126− let overlay_highlights = Self::overlay_syntax_highlights(
129+
130+ let mut overlay_highlights =
131+ Self::empty_highlight_iter(doc, view.offset.anchor, inner.height);
132+ let overlay_syntax_highlights = Self::overlay_syntax_highlights(
127 doc,
133 doc,
128 view.offset.anchor,
134 view.offset.anchor,
129 inner.height,
135 inner.height,
130 &text_annotations,
136 &text_annotations,
131 );
137 );
132− if !overlay_highlights.is_empty() {
138+ if !overlay_syntax_highlights.is_empty() {
133− highlights = Box::new(syntax::merge(highlights, overlay_highlights));
139+ overlay_highlights =
140+ Box::new(syntax::merge(overlay_highlights, overlay_syntax_highlights));
134 }
141 }
135
142
136 for diagnostic in Self::doc_diagnostics_highlights(doc, theme) {
143 for diagnostic in Self::doc_diagnostics_highlights(doc, theme) {
@@ -139,39 +146,41 @@ impl EditorView {
139 if diagnostic.is_empty() {
146 if diagnostic.is_empty() {
140 continue;
147 continue;
141 }
148 }
142− highlights = Box::new(syntax::merge(highlights, diagnostic));
149+ overlay_highlights = Box::new(syntax::merge(overlay_highlights, diagnostic));
143 }
150 }
144
151
145− let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
152+ if is_focused {
146 let highlights = syntax::merge(
153 let highlights = syntax::merge(
147− highlights,
154+ overlay_highlights,
148 Self::doc_selection_highlights(
155 Self::doc_selection_highlights(
149 editor.mode(),
156 editor.mode(),
150 doc,
157 doc,
151 view,
158 view,
152 theme,
159 theme,
153 &config.cursor_shape,
160 &config.cursor_shape,
161+ self.terminal_focused,
154 ),
162 ),
155 );
163 );
156 let focused_view_elements = Self::highlight_focused_view_elements(view, doc, theme);
164 let focused_view_elements = Self::highlight_focused_view_elements(view, doc, theme);
157 if focused_view_elements.is_empty() {
165 if focused_view_elements.is_empty() {
158− Box::new(highlights)
166+ overlay_highlights = Box::new(highlights)
159 } else {
167 } else {
160− Box::new(syntax::merge(highlights, focused_view_elements))
168+ overlay_highlights = Box::new(syntax::merge(highlights, focused_view_elements))
161 }
169 }
162− } else {
170+ }
163− Box::new(highlights)
164− };
165
171
166− Self::render_gutter(
172+ let gutter_overflow = view.gutter_offset(doc) == 0;
167− editor,
173+ if !gutter_overflow {
168− doc,
174+ Self::render_gutter(
169− view,
175+ editor,
170− view.area,
176+ doc,
171− theme,
177+ view,
172− is_focused,
178+ view.area,
173− &mut line_decorations,
179+ theme,
174− );
180+ is_focused & self.terminal_focused,
181+ &mut line_decorations,
182+ );
183+ }
175
184
176 if is_focused {
185 if is_focused {
177 let cursor = doc
186 let cursor = doc
@@ -191,7 +200,8 @@ impl EditorView {
191 doc,
200 doc,
192 view.offset,
201 view.offset,
193 &text_annotations,
202 &text_annotations,
194− highlights,
203+ syntax_highlights,
204+ overlay_highlights,
195 theme,
205 theme,
196 &mut line_decorations,
206 &mut line_decorations,
197 &mut translated_positions,
207 &mut translated_positions,
@@ -251,27 +261,39 @@ impl EditorView {
251 .for_each(|area| surface.set_style(area, ruler_theme))
261 .for_each(|area| surface.set_style(area, ruler_theme))
252 }
262 }
253
263
254− pub fn overlay_syntax_highlights(
264+ fn viewport_byte_range(
265+ text: helix_core::RopeSlice,
266+ row: usize,
267+ height: u16,
268+ ) -> std::ops::Range<usize> {
269+ // Calculate viewport byte ranges:
270+ // Saturating subs to make it inclusive zero indexing.
271+ let last_line = text.len_lines().saturating_sub(1);
272+ let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
273+ let start = text.line_to_byte(row.min(last_line));
274+ let end = text.line_to_byte(last_visible_line + 1);
275+
276+ start..end
277+ }
278+
279+ pub fn empty_highlight_iter(
255 doc: &Document,
280 doc: &Document,
256 anchor: usize,
281 anchor: usize,
257 height: u16,
282 height: u16,
258− text_annotations: &TextAnnotations,
283+ ) -> Box<dyn Iterator<Item = HighlightEvent>> {
259− ) -> Vec<(usize, std::ops::Range<usize>)> {
260 let text = doc.text().slice(..);
284 let text = doc.text().slice(..);
261 let row = text.char_to_line(anchor.min(text.len_chars()));
285 let row = text.char_to_line(anchor.min(text.len_chars()));
262
286
263− let range = {
287+ // Calculate viewport byte ranges:
264− // Calculate viewport byte ranges:
288+ // Saturating subs to make it inclusive zero indexing.
265− // Saturating subs to make it inclusive zero indexing.
289+ let range = Self::viewport_byte_range(text, row, height);
266− let last_line = text.len_lines().saturating_sub(1);
290+ Box::new(
267− let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
291+ [HighlightEvent::Source {
268− let start = text.line_to_byte(row.min(last_line));
292+ start: text.byte_to_char(range.start),
269− let end = text.line_to_byte(last_visible_line + 1);
293+ end: text.byte_to_char(range.end),
270−
294+ }]
271− start..end
295+ .into_iter(),
272− };
296+ )
273−
274− text_annotations.collect_overlay_highlights(range)
275 }
297 }
276
298
277 /// Get syntax highlights for a document in a view represented by the first line
299 /// Get syntax highlights for a document in a view represented by the first line
@@ -286,16 +308,7 @@ impl EditorView {
286 let text = doc.text().slice(..);
308 let text = doc.text().slice(..);
287 let row = text.char_to_line(anchor.min(text.len_chars()));
309 let row = text.char_to_line(anchor.min(text.len_chars()));
288
310
289− let range = {
311+ let range = Self::viewport_byte_range(text, row, height);
290− // Calculate viewport byte ranges:
291− // Saturating subs to make it inclusive zero indexing.
292− let last_line = text.len_lines().saturating_sub(1);
293− let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
294− let start = text.line_to_byte(row.min(last_line));
295− let end = text.line_to_byte(last_visible_line + 1);
296−
297− start..end
298− };
299
312
300 match doc.syntax() {
313 match doc.syntax() {
301 Some(syntax) => {
314 Some(syntax) => {
@@ -328,6 +341,20 @@ impl EditorView {
328 }
341 }
329 }
342 }
330
343
344+ pub fn overlay_syntax_highlights(
345+ doc: &Document,
346+ anchor: usize,
347+ height: u16,
348+ text_annotations: &TextAnnotations,
349+ ) -> Vec<(usize, std::ops::Range<usize>)> {
350+ let text = doc.text().slice(..);
351+ let row = text.char_to_line(anchor.min(text.len_chars()));
352+
353+ let range = Self::viewport_byte_range(text, row, height);
354+
355+ text_annotations.collect_overlay_highlights(range)
356+ }
357+
331 /// Get highlight spans for document diagnostics
358 /// Get highlight spans for document diagnostics
332 pub fn doc_diagnostics_highlights(
359 pub fn doc_diagnostics_highlights(
333 doc: &Document,
360 doc: &Document,
@@ -359,7 +386,7 @@ impl EditorView {
359 let mut warning_vec = Vec::new();
386 let mut warning_vec = Vec::new();
360 let mut error_vec = Vec::new();
387 let mut error_vec = Vec::new();
361
388
362− for diagnostic in doc.diagnostics() {
389+ for diagnostic in doc.shown_diagnostics() {
363 // Separate diagnostics into different Vecs by severity.
390 // Separate diagnostics into different Vecs by severity.
364 let (vec, scope) = match diagnostic.severity {
391 let (vec, scope) = match diagnostic.severity {
365 Some(Severity::Info) => (&mut info_vec, info),
392 Some(Severity::Info) => (&mut info_vec, info),
@@ -394,6 +421,7 @@ impl EditorView {
394 view: &View,
421 view: &View,
395 theme: &Theme,
422 theme: &Theme,
396 cursor_shape_config: &CursorShapeConfig,
423 cursor_shape_config: &CursorShapeConfig,
424+ is_terminal_focused: bool,
397 ) -> Vec<(usize, std::ops::Range<usize>)> {
425 ) -> Vec<(usize, std::ops::Range<usize>)> {
398 let text = doc.text().slice(..);
426 let text = doc.text().slice(..);
399 let selection = doc.selection(view.id);
427 let selection = doc.selection(view.id);
@@ -441,7 +469,7 @@ impl EditorView {
441
469
442 // Special-case: cursor at end of the rope.
470 // Special-case: cursor at end of the rope.
443 if range.head == range.anchor && range.head == text.len_chars() {
471 if range.head == range.anchor && range.head == text.len_chars() {
444− if !selection_is_primary || cursor_is_block {
472+ if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
445 // Bar and underline cursors are drawn by the terminal
473 // Bar and underline cursors are drawn by the terminal
446 // BUG: If the editor area loses focus while having a bar or
474 // BUG: If the editor area loses focus while having a bar or
447 // underline cursor (eg. when a regex prompt has focus) then
475 // underline cursor (eg. when a regex prompt has focus) then
@@ -464,13 +492,17 @@ impl EditorView {
464 cursor_start
492 cursor_start
465 };
493 };
466 spans.push((selection_scope, range.anchor..selection_end));
494 spans.push((selection_scope, range.anchor..selection_end));
467− if !selection_is_primary || cursor_is_block {
495+ // add block cursors
496+ // skip primary cursor if terminal is unfocused - crossterm cursor is used in that case
497+ if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
468 spans.push((cursor_scope, cursor_start..range.head));
498 spans.push((cursor_scope, cursor_start..range.head));
469 }
499 }
470 } else {
500 } else {
471 // Reverse case.
501 // Reverse case.
472 let cursor_end = next_grapheme_boundary(text, range.head);
502 let cursor_end = next_grapheme_boundary(text, range.head);
473− if !selection_is_primary || cursor_is_block {
503+ // add block cursors
504+ // skip primary cursor if terminal is unfocused - crossterm cursor is used in that case
505+ if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
474 spans.push((cursor_scope, range.head..cursor_end));
506 spans.push((cursor_scope, range.head..cursor_end));
475 }
507 }
476 // non block cursors look like they exclude the cursor
508 // non block cursors look like they exclude the cursor
@@ -501,7 +533,9 @@ impl EditorView {
501 use helix_core::match_brackets;
533 use helix_core::match_brackets;
502 let pos = doc.selection(view.id).primary().cursor(text);
534 let pos = doc.selection(view.id).primary().cursor(text);
503
535
504− if let Some(pos) = match_brackets::find_matching_bracket(syntax, doc.text(), pos) {
536+ if let Some(pos) =
537+ match_brackets::find_matching_bracket(syntax, doc.text().slice(..), pos)
538+ {
505 // ensure col is on screen
539 // ensure col is on screen
506 if let Some(highlight) = theme.find_scope_index_exact("ui.cursor.match") {
540 if let Some(highlight) = theme.find_scope_index_exact("ui.cursor.match") {
507 return vec![(highlight, pos..pos + 1)];
541 return vec![(highlight, pos..pos + 1)];
@@ -907,8 +941,9 @@ impl EditorView {
907 let text = doc.text().slice(..);
941 let text = doc.text().slice(..);
908 let cursor = doc.selection(view.id).primary().cursor(text);
942 let cursor = doc.selection(view.id).primary().cursor(text);
909
943
910− let shift_position =
944+ let shift_position = |pos: usize| -> usize {
911− |pos: usize| -> usize { pos + cursor - trigger_offset };
945+ (pos + cursor).saturating_sub(trigger_offset)
946+ };
912
947
913 let tx = Transaction::change(
948 let tx = Transaction::change(
914 doc.text(),
949 doc.text(),
@@ -943,6 +978,8 @@ impl EditorView {
943 self.handle_keymap_event(mode, cxt, event);
978 self.handle_keymap_event(mode, cxt, event);
944 if self.keymaps.pending().is_empty() {
979 if self.keymaps.pending().is_empty() {
945 cxt.editor.count = None
980 cxt.editor.count = None
981+ } else {
982+ cxt.editor.selected_register = cxt.register.take();
946 }
983 }
947 }
984 }
948 }
985 }
@@ -1062,6 +1099,7 @@ impl EditorView {
1062 let editor = &mut cxt.editor;
1099 let editor = &mut cxt.editor;
1063
1100
1064 if let Some((pos, view_id)) = pos_and_view(editor, row, column, true) {
1101 if let Some((pos, view_id)) = pos_and_view(editor, row, column, true) {
1102+ let prev_view_id = view!(editor).id;
1065 let doc = doc_mut!(editor, &view!(editor, view_id).doc);
1103 let doc = doc_mut!(editor, &view!(editor, view_id).doc);
1066
1104
1067 if modifiers == KeyModifiers::ALT {
1105 if modifiers == KeyModifiers::ALT {
@@ -1071,6 +1109,10 @@ impl EditorView {
1071 doc.set_selection(view_id, Selection::point(pos));
1109 doc.set_selection(view_id, Selection::point(pos));
1072 }
1110 }
1073
1111
1112+ if view_id != prev_view_id {
1113+ self.clear_completion(editor);
1114+ }
1115+
1074 editor.focus(view_id);
1116 editor.focus(view_id);
1075 editor.ensure_cursor_in_view(view_id);
1117 editor.ensure_cursor_in_view(view_id);
1076
1118
@@ -1364,13 +1406,17 @@ impl Component for EditorView {
1364
1406
1365 Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
1407 Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
1366 Event::IdleTimeout => self.handle_idle_timeout(&mut cx),
1408 Event::IdleTimeout => self.handle_idle_timeout(&mut cx),
1367− Event::FocusGained => EventResult::Ignored(None),
1409+ Event::FocusGained => {
1410+ self.terminal_focused = true;
1411+ EventResult::Consumed(None)
1412+ }
1368 Event::FocusLost => {
1413 Event::FocusLost => {
1369 if context.editor.config().auto_save {
1414 if context.editor.config().auto_save {
1370 if let Err(e) = commands::typed::write_all_impl(context, false, false) {
1415 if let Err(e) = commands::typed::write_all_impl(context, false, false) {
1371 context.editor.set_error(format!("{}", e));
1416 context.editor.set_error(format!("{}", e));
1372 }
1417 }
1373 }
1418 }
1419+ self.terminal_focused = false;
1374 EventResult::Consumed(None)
1420 EventResult::Consumed(None)
1375 }
1421 }
1376 }
1422 }
@@ -1480,8 +1526,15 @@ impl Component for EditorView {
1480
1526
1481 fn cursor(&self, _area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
1527 fn cursor(&self, _area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
1482 match editor.cursor() {
1528 match editor.cursor() {
1483− // All block cursors are drawn manually
1529+ // all block cursors are drawn manually
1484− (pos, CursorKind::Block) => (pos, CursorKind::Hidden),
1530+ (pos, CursorKind::Block) => {
1531+ if self.terminal_focused {
1532+ (pos, CursorKind::Hidden)
1533+ } else {
1534+ // use crossterm cursor when terminal loses focus
1535+ (pos, CursorKind::Underline)
1536+ }
1537+ }
1485 cursor => cursor,
1538 cursor => cursor,
1486 }
1539 }
1487 }
1540 }
helix-term/src/ui/fuzzy_match.rsdeleted−239
@@ -1,239 +0,0 @@
1−use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
2−use fuzzy_matcher::FuzzyMatcher;
3−
4−#[cfg(test)]
5−mod test;
6−
7−struct QueryAtom {
8− kind: QueryAtomKind,
9− atom: String,
10− ignore_case: bool,
11− inverse: bool,
12−}
13−impl QueryAtom {
14− fn new(atom: &str) -> Option<QueryAtom> {
15− let mut atom = atom.to_string();
16− let inverse = atom.starts_with('!');
17− if inverse {
18− atom.remove(0);
19− }
20−
21− let mut kind = match atom.chars().next() {
22− Some('^') => QueryAtomKind::Prefix,
23− Some('\'') => QueryAtomKind::Substring,
24− _ if inverse => QueryAtomKind::Substring,
25− _ => QueryAtomKind::Fuzzy,
26− };
27−
28− if atom.starts_with(['^', '\'']) {
29− atom.remove(0);
30− }
31−
32− if atom.is_empty() {
33− return None;
34− }
35−
36− if atom.ends_with('$') && !atom.ends_with("\\$") {
37− atom.pop();
38− kind = if kind == QueryAtomKind::Prefix {
39− QueryAtomKind::Exact
40− } else {
41− QueryAtomKind::Postfix
42− }
43− }
44−
45− Some(QueryAtom {
46− kind,
47− atom: atom.replace('\\', ""),
48− // not ideal but fuzzy_matches only knows ascii uppercase so more consistent
49− // to behave the same
50− ignore_case: kind != QueryAtomKind::Fuzzy
51− && atom.chars().all(|c| c.is_ascii_lowercase()),
52− inverse,
53− })
54− }
55−
56− fn indices(&self, matcher: &Matcher, item: &str, indices: &mut Vec<usize>) -> bool {
57− // for inverse there are no indices to return
58− // just return whether we matched
59− if self.inverse {
60− return self.matches(matcher, item);
61− }
62− let buf;
63− let item = if self.ignore_case {
64− buf = item.to_ascii_lowercase();
65− &buf
66− } else {
67− item
68− };
69− let off = match self.kind {
70− QueryAtomKind::Fuzzy => {
71− if let Some((_, fuzzy_indices)) = matcher.fuzzy_indices(item, &self.atom) {
72− indices.extend_from_slice(&fuzzy_indices);
73− return true;
74− } else {
75− return false;
76− }
77− }
78− QueryAtomKind::Substring => {
79− if let Some(off) = item.find(&self.atom) {
80− off
81− } else {
82− return false;
83− }
84− }
85− QueryAtomKind::Prefix if item.starts_with(&self.atom) => 0,
86− QueryAtomKind::Postfix if item.ends_with(&self.atom) => item.len() - self.atom.len(),
87− QueryAtomKind::Exact if item == self.atom => 0,
88− _ => return false,
89− };
90−
91− indices.extend(off..(off + self.atom.len()));
92− true
93− }
94−
95− fn matches(&self, matcher: &Matcher, item: &str) -> bool {
96− let buf;
97− let item = if self.ignore_case {
98− buf = item.to_ascii_lowercase();
99− &buf
100− } else {
101− item
102− };
103− let mut res = match self.kind {
104− QueryAtomKind::Fuzzy => matcher.fuzzy_match(item, &self.atom).is_some(),
105− QueryAtomKind::Substring => item.contains(&self.atom),
106− QueryAtomKind::Prefix => item.starts_with(&self.atom),
107− QueryAtomKind::Postfix => item.ends_with(&self.atom),
108− QueryAtomKind::Exact => item == self.atom,
109− };
110− if self.inverse {
111− res = !res;
112− }
113− res
114− }
115−}
116−
117−#[derive(Debug, PartialEq, Eq, Clone, Copy)]
118−enum QueryAtomKind {
119− /// Item is a fuzzy match of this behaviour
120− ///
121− /// Usage: `foo`
122− Fuzzy,
123− /// Item contains query atom as a continuous substring
124− ///
125− /// Usage `'foo`
126− Substring,
127− /// Item starts with query atom
128− ///
129− /// Usage: `^foo`
130− Prefix,
131− /// Item ends with query atom
132− ///
133− /// Usage: `foo$`
134− Postfix,
135− /// Item is equal to query atom
136− ///
137− /// Usage `^foo$`
138− Exact,
139−}
140−
141−#[derive(Default)]
142−pub struct FuzzyQuery {
143− first_fuzzy_atom: Option<String>,
144− query_atoms: Vec<QueryAtom>,
145−}
146−
147−fn query_atoms(query: &str) -> impl Iterator<Item = &str> + '_ {
148− let mut saw_backslash = false;
149− query.split(move |c| {
150− saw_backslash = match c {
151− ' ' if !saw_backslash => return true,
152− '\\' => true,
153− _ => false,
154− };
155− false
156− })
157−}
158−
159−impl FuzzyQuery {
160− pub fn refine(&self, query: &str, old_query: &str) -> (FuzzyQuery, bool) {
161− // TODO: we could be a lot smarter about this
162− let new_query = Self::new(query);
163− let mut is_refinement = query.starts_with(old_query);
164−
165− // if the last atom is an inverse atom adding more text to it
166− // will actually increase the number of matches and we can not refine
167− // the matches.
168− if is_refinement && !self.query_atoms.is_empty() {
169− let last_idx = self.query_atoms.len() - 1;
170− if self.query_atoms[last_idx].inverse
171− && self.query_atoms[last_idx].atom != new_query.query_atoms[last_idx].atom
172− {
173− is_refinement = false;
174− }
175− }
176−
177− (new_query, is_refinement)
178− }
179−
180− pub fn new(query: &str) -> FuzzyQuery {
181− let mut first_fuzzy_query = None;
182− let query_atoms = query_atoms(query)
183− .filter_map(|atom| {
184− let atom = QueryAtom::new(atom)?;
185− if atom.kind == QueryAtomKind::Fuzzy && first_fuzzy_query.is_none() {
186− first_fuzzy_query = Some(atom.atom);
187− None
188− } else {
189− Some(atom)
190− }
191− })
192− .collect();
193− FuzzyQuery {
194− first_fuzzy_atom: first_fuzzy_query,
195− query_atoms,
196− }
197− }
198−
199− pub fn fuzzy_match(&self, item: &str, matcher: &Matcher) -> Option<i64> {
200− // use the rank of the first fuzzzy query for the rank, because merging ranks is not really possible
201− // this behaviour matches fzf and skim
202− let score = self
203− .first_fuzzy_atom
204− .as_ref()
205− .map_or(Some(0), |atom| matcher.fuzzy_match(item, atom))?;
206− if self
207− .query_atoms
208− .iter()
209− .any(|atom| !atom.matches(matcher, item))
210− {
211− return None;
212− }
213− Some(score)
214− }
215−
216− pub fn fuzzy_indices(&self, item: &str, matcher: &Matcher) -> Option<(i64, Vec<usize>)> {
217− let (score, mut indices) = self.first_fuzzy_atom.as_ref().map_or_else(
218− || Some((0, Vec::new())),
219− |atom| matcher.fuzzy_indices(item, atom),
220− )?;
221−
222− // fast path for the common case of just a single atom
223− if self.query_atoms.is_empty() {
224− return Some((score, indices));
225− }
226−
227− for atom in &self.query_atoms {
228− if !atom.indices(matcher, item, &mut indices) {
229− return None;
230− }
231− }
232−
233− // deadup and remove duplicate matches
234− indices.sort_unstable();
235− indices.dedup();
236−
237− Some((score, indices))
238− }
239−}
helix-term/src/ui/fuzzy_match/test.rsdeleted−47
@@ -1,47 +0,0 @@
1−use crate::ui::fuzzy_match::FuzzyQuery;
2−use crate::ui::fuzzy_match::Matcher;
3−
4−fn run_test<'a>(query: &str, items: &'a [&'a str]) -> Vec<String> {
5− let query = FuzzyQuery::new(query);
6− let matcher = Matcher::default();
7− items
8− .iter()
9− .filter_map(|item| {
10− let (_, indices) = query.fuzzy_indices(item, &matcher)?;
11− let matched_string = indices
12− .iter()
13− .map(|&pos| item.chars().nth(pos).unwrap())
14− .collect();
15− Some(matched_string)
16− })
17− .collect()
18−}
19−
20−#[test]
21−fn match_single_value() {
22− let matches = run_test("foo", &["foobar", "foo", "bar"]);
23− assert_eq!(matches, &["foo", "foo"])
24−}
25−
26−#[test]
27−fn match_multiple_values() {
28− let matches = run_test(
29− "foo bar",
30− &["foo bar", "foo bar", "bar foo", "bar", "foo"],
31− );
32− assert_eq!(matches, &["foobar", "foobar", "barfoo"])
33−}
34−
35−#[test]
36−fn space_escape() {
37− let matches = run_test(r"foo\ bar", &["bar foo", "foo bar", "foobar"]);
38− assert_eq!(matches, &["foo bar"])
39−}
40−
41−#[test]
42−fn trim() {
43− let matches = run_test(r" foo bar ", &["bar foo", "foo bar", "foobar"]);
44− assert_eq!(matches, &["barfoo", "foobar", "foobar"]);
45− let matches = run_test(r" foo bar\ ", &["bar foo", "foo bar", "foobar"]);
46− assert_eq!(matches, &["bar foo"])
47−}
helix-term/src/ui/lsp.rs+5−3
@@ -62,7 +62,7 @@ impl Component for SignatureHelp {
62 });
62 });
63
63
64 let sig_text = crate::ui::markdown::highlighted_code_block(
64 let sig_text = crate::ui::markdown::highlighted_code_block(
65− self.signature.clone(),
65+ &self.signature,
66 &self.language,
66 &self.language,
67 Some(&cx.editor.theme),
67 Some(&cx.editor.theme),
68 Arc::clone(&self.config_loader),
68 Arc::clone(&self.config_loader),
@@ -92,7 +92,9 @@ impl Component for SignatureHelp {
92 Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
92 Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
93 };
93 };
94 let sig_doc = sig_doc.parse(Some(&cx.editor.theme));
94 let sig_doc = sig_doc.parse(Some(&cx.editor.theme));
95− let sig_doc_area = area.clip_top(sig_text_area.height + 2);
95+ let sig_doc_area = area
96+ .clip_top(sig_text_area.height + 2)
97+ .clip_bottom(u16::from(cx.editor.popup_border()));
96 let sig_doc_para = Paragraph::new(sig_doc)
98 let sig_doc_para = Paragraph::new(sig_doc)
97 .wrap(Wrap { trim: false })
99 .wrap(Wrap { trim: false })
98 .scroll((cx.scroll.unwrap_or_default() as u16, 0));
100 .scroll((cx.scroll.unwrap_or_default() as u16, 0));
@@ -109,7 +111,7 @@ impl Component for SignatureHelp {
109 let max_text_width = (viewport.0 - PADDING).min(120);
111 let max_text_width = (viewport.0 - PADDING).min(120);
110
112
111 let signature_text = crate::ui::markdown::highlighted_code_block(
113 let signature_text = crate::ui::markdown::highlighted_code_block(
112− self.signature.clone(),
114+ &self.signature,
113 &self.language,
115 &self.language,
114 None,
116 None,
115 Arc::clone(&self.config_loader),
117 Arc::clone(&self.config_loader),
helix-term/src/ui/markdown.rs+19−12
@@ -10,14 +10,15 @@ use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag};
10
10
11use helix_core::{
11use helix_core::{
12 syntax::{self, HighlightEvent, InjectionLanguageMarker, Syntax},
12 syntax::{self, HighlightEvent, InjectionLanguageMarker, Syntax},
13− Rope,
13+ RopeSlice,
14};
14};
15use helix_view::{
15use helix_view::{
16 graphics::{Margin, Rect, Style},
16 graphics::{Margin, Rect, Style},
17+ theme::Modifier,
17 Theme,
18 Theme,
18};
19};
19
20
20−fn styled_multiline_text<'a>(text: String, style: Style) -> Text<'a> {
21+fn styled_multiline_text<'a>(text: &str, style: Style) -> Text<'a> {
21 let spans: Vec<_> = text
22 let spans: Vec<_> = text
22 .lines()
23 .lines()
23 .map(|line| Span::styled(line.to_string(), style))
24 .map(|line| Span::styled(line.to_string(), style))
@@ -27,7 +28,7 @@ fn styled_multiline_text<'a>(text: String, style: Style) -> Text<'a> {
27}
28}
28
29
29pub fn highlighted_code_block<'a>(
30pub fn highlighted_code_block<'a>(
30− text: String,
31+ text: &str,
31 language: &str,
32 language: &str,
32 theme: Option<&Theme>,
33 theme: Option<&Theme>,
33 config_loader: Arc<syntax::Loader>,
34 config_loader: Arc<syntax::Loader>,
@@ -45,13 +46,13 @@ pub fn highlighted_code_block<'a>(
45 None => return styled_multiline_text(text, code_style),
46 None => return styled_multiline_text(text, code_style),
46 };
47 };
47
48
48− let rope = Rope::from(text.as_ref());
49+ let ropeslice = RopeSlice::from(text);
49 let syntax = config_loader
50 let syntax = config_loader
50 .language_configuration_for_injection_string(&InjectionLanguageMarker::Name(
51 .language_configuration_for_injection_string(&InjectionLanguageMarker::Name(
51 language.into(),
52 language.into(),
52 ))
53 ))
53 .and_then(|config| config.highlight_config(theme.scopes()))
54 .and_then(|config| config.highlight_config(theme.scopes()))
54− .and_then(|config| Syntax::new(&rope, config, Arc::clone(&config_loader)));
55+ .and_then(|config| Syntax::new(ropeslice, config, Arc::clone(&config_loader)));
55
56
56 let syntax = match syntax {
57 let syntax = match syntax {
57 Some(s) => s,
58 Some(s) => s,
@@ -59,7 +60,7 @@ pub fn highlighted_code_block<'a>(
59 };
60 };
60
61
61 let highlight_iter = syntax
62 let highlight_iter = syntax
62− .highlight_iter(rope.slice(..), None, None)
63+ .highlight_iter(ropeslice, None, None)
63 .map(|e| e.unwrap());
64 .map(|e| e.unwrap());
64 let highlight_iter: Box<dyn Iterator<Item = HighlightEvent>> =
65 let highlight_iter: Box<dyn Iterator<Item = HighlightEvent>> =
65 if let Some(spans) = additional_highlight_spans {
66 if let Some(spans) = additional_highlight_spans {
@@ -183,7 +184,9 @@ impl Markdown {
183 // Transform text in `<code>` blocks into `Event::Code`
184 // Transform text in `<code>` blocks into `Event::Code`
184 let mut in_code = false;
185 let mut in_code = false;
185 let parser = parser.filter_map(|event| match event {
186 let parser = parser.filter_map(|event| match event {
186− Event::Html(tag) if *tag == *"<code>" => {
187+ Event::Html(tag)
188+ if tag.starts_with("<code") && matches!(tag.chars().nth(5), Some(' ' | '>')) =>
189+ {
187 in_code = true;
190 in_code = true;
188 None
191 None
189 }
192 }
@@ -267,7 +270,7 @@ impl Markdown {
267 CodeBlockKind::Indented => "",
270 CodeBlockKind::Indented => "",
268 };
271 };
269 let tui_text = highlighted_code_block(
272 let tui_text = highlighted_code_block(
270− text.to_string(),
273+ &text,
271 language,
274 language,
272 theme,
275 theme,
273 Arc::clone(&self.config_loader),
276 Arc::clone(&self.config_loader),
@@ -275,17 +278,21 @@ impl Markdown {
275 );
278 );
276 lines.extend(tui_text.lines.into_iter());
279 lines.extend(tui_text.lines.into_iter());
277 } else {
280 } else {
278− let style = if let Some(Tag::Heading(level, ..)) = tags.last() {
281+ let style = match tags.last() {
279− match level {
282+ Some(Tag::Heading(level, ..)) => match level {
280 HeadingLevel::H1 => heading_styles[0],
283 HeadingLevel::H1 => heading_styles[0],
281 HeadingLevel::H2 => heading_styles[1],
284 HeadingLevel::H2 => heading_styles[1],
282 HeadingLevel::H3 => heading_styles[2],
285 HeadingLevel::H3 => heading_styles[2],
283 HeadingLevel::H4 => heading_styles[3],
286 HeadingLevel::H4 => heading_styles[3],
284 HeadingLevel::H5 => heading_styles[4],
287 HeadingLevel::H5 => heading_styles[4],
285 HeadingLevel::H6 => heading_styles[5],
288 HeadingLevel::H6 => heading_styles[5],
289+ },
290+ Some(Tag::Emphasis) => text_style.add_modifier(Modifier::ITALIC),
291+ Some(Tag::Strong) => text_style.add_modifier(Modifier::BOLD),
292+ Some(Tag::Strikethrough) => {
293+ text_style.add_modifier(Modifier::CROSSED_OUT)
286 }
294 }
287− } else {
295+ _ => text_style,
288− text_style
289 };
296 };
290 spans.push(Span::styled(text, style));
297 spans.push(Span::styled(text, style));
291 }
298 }
helix-term/src/ui/menu.rs+73−40
@@ -1,22 +1,29 @@
1−use std::{borrow::Cow, path::PathBuf};
1+use std::{borrow::Cow, cmp::Reverse, path::PathBuf};
2
2
3use crate::{
3use crate::{
4 compositor::{Callback, Component, Compositor, Context, Event, EventResult},
4 compositor::{Callback, Component, Compositor, Context, Event, EventResult},
5 ctrl, key, shift,
5 ctrl, key, shift,
6};
6};
7−use tui::{buffer::Buffer as Surface, widgets::Table};
7+use helix_core::fuzzy::MATCHER;
8+use nucleo::pattern::{Atom, AtomKind, CaseMatching};
9+use nucleo::{Config, Utf32Str};
10+use tui::{
11+ buffer::Buffer as Surface,
12+ widgets::{Block, Borders, Table, Widget},
13+};
8
14
9pub use tui::widgets::{Cell, Row};
15pub use tui::widgets::{Cell, Row};
10
16
11−use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
17+use helix_view::{
12−use fuzzy_matcher::FuzzyMatcher;
18+ editor::SmartTabConfig,
13−
19+ graphics::{Margin, Rect},
14−use helix_view::{graphics::Rect, Editor};
20+ Editor,
21+};
15use tui::layout::Constraint;
22use tui::layout::Constraint;
16
23
17−pub trait Item {
24+pub trait Item: Sync + Send + 'static {
18 /// Additional editor state that is used for label calculation.
25 /// Additional editor state that is used for label calculation.
19− type Data;
26+ type Data: Sync + Send + 'static;
20
27
21 fn format(&self, data: &Self::Data) -> Row;
28 fn format(&self, data: &Self::Data) -> Row;
22
29
@@ -51,9 +58,8 @@ pub struct Menu<T: Item> {
51
58
52 cursor: Option<usize>,
59 cursor: Option<usize>,
53
60
54− matcher: Box<Matcher>,
55 /// (index, score)
61 /// (index, score)
56− matches: Vec<(usize, i64)>,
62+ matches: Vec<(u32, u32)>,
57
63
58 widths: Vec<Constraint>,
64 widths: Vec<Constraint>,
59
65
@@ -75,11 +81,10 @@ impl<T: Item> Menu<T> {
75 editor_data: <T as Item>::Data,
81 editor_data: <T as Item>::Data,
76 callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
82 callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
77 ) -> Self {
83 ) -> Self {
78− let matches = (0..options.len()).map(|i| (i, 0)).collect();
84+ let matches = (0..options.len() as u32).map(|i| (i, 0)).collect();
79 Self {
85 Self {
80 options,
86 options,
81 editor_data,
87 editor_data,
82− matcher: Box::new(Matcher::default().ignore_case()),
83 matches,
88 matches,
84 cursor: None,
89 cursor: None,
85 widths: Vec::new(),
90 widths: Vec::new(),
@@ -94,20 +99,19 @@ impl<T: Item> Menu<T> {
94 pub fn score(&mut self, pattern: &str) {
99 pub fn score(&mut self, pattern: &str) {
95 // reuse the matches allocation
100 // reuse the matches allocation
96 self.matches.clear();
101 self.matches.clear();
97− self.matches.extend(
102+ let mut matcher = MATCHER.lock();
98− self.options
103+ matcher.config = Config::DEFAULT;
99− .iter()
104+ let pattern = Atom::new(pattern, CaseMatching::Ignore, AtomKind::Fuzzy, false);
100− .enumerate()
105+ let mut buf = Vec::new();
101− .filter_map(|(index, option)| {
106+ let matches = self.options.iter().enumerate().filter_map(|(i, option)| {
102− let text = option.filter_text(&self.editor_data);
107+ let text = option.filter_text(&self.editor_data);
103− // TODO: using fuzzy_indices could give us the char idx for match highlighting
108+ pattern
104− self.matcher
109+ .score(Utf32Str::new(&text, &mut buf), &mut matcher)
105− .fuzzy_match(&text, pattern)
110+ .map(|score| (i as u32, score as u32))
106− .map(|score| (index, score))
111+ });
107− }),
112+ self.matches.extend(matches);
108− );
113+ self.matches
109− // Order of equal elements needs to be preserved as LSP preselected items come in order of high to low priority
114+ .sort_unstable_by_key(|&(i, score)| (Reverse(score), i));
110− self.matches.sort_by_key(|(_, score)| -score);
111
115
112 // reset cursor position
116 // reset cursor position
113 self.cursor = None;
117 self.cursor = None;
@@ -201,7 +205,7 @@ impl<T: Item> Menu<T> {
201 self.cursor.and_then(|cursor| {
205 self.cursor.and_then(|cursor| {
202 self.matches
206 self.matches
203 .get(cursor)
207 .get(cursor)
204− .map(|(index, _score)| &self.options[*index])
208+ .map(|(index, _score)| &self.options[*index as usize])
205 })
209 })
206 }
210 }
207
211
@@ -209,7 +213,7 @@ impl<T: Item> Menu<T> {
209 self.cursor.and_then(|cursor| {
213 self.cursor.and_then(|cursor| {
210 self.matches
214 self.matches
211 .get(cursor)
215 .get(cursor)
212− .map(|(index, _score)| &mut self.options[*index])
216+ .map(|(index, _score)| &mut self.options[*index as usize])
213 })
217 })
214 }
218 }
215
219
@@ -247,6 +251,21 @@ impl<T: Item + 'static> Component for Menu<T> {
247 compositor.pop();
251 compositor.pop();
248 }));
252 }));
249
253
254+ // Ignore tab key when supertab is turned on in order not to interfere
255+ // with it. (Is there a better way to do this?)
256+ if (event == key!(Tab) || event == shift!(Tab))
257+ && cx.editor.config().auto_completion
258+ && matches!(
259+ cx.editor.config().smart_tab,
260+ Some(SmartTabConfig {
261+ enable: true,
262+ supersede_menu: true,
263+ })
264+ )
265+ {
266+ return EventResult::Ignored(None);
267+ }
268+
250 match event {
269 match event {
251 // esc or ctrl-c aborts the completion and closes the menu
270 // esc or ctrl-c aborts the completion and closes the menu
252 key!(Esc) | ctrl!('c') => {
271 key!(Esc) | ctrl!('c') => {
@@ -310,6 +329,15 @@ impl<T: Item + 'static> Component for Menu<T> {
310 let selected = theme.get("ui.menu.selected");
329 let selected = theme.get("ui.menu.selected");
311 surface.clear_with(area, style);
330 surface.clear_with(area, style);
312
331
332+ let render_borders = cx.editor.menu_border();
333+
334+ let area = if render_borders {
335+ Widget::render(Block::default().borders(Borders::ALL), area, surface);
336+ area.inner(&Margin::vertical(1))
337+ } else {
338+ area
339+ };
340+
313 let scroll = self.scroll;
341 let scroll = self.scroll;
314
342
315 let options: Vec<_> = self
343 let options: Vec<_> = self
@@ -317,7 +345,7 @@ impl<T: Item + 'static> Component for Menu<T> {
317 .iter()
345 .iter()
318 .map(|(index, _score)| {
346 .map(|(index, _score)| {
319 // (index, self.options.get(*index).unwrap()) // get_unchecked
347 // (index, self.options.get(*index).unwrap()) // get_unchecked
320− &self.options[*index] // get_unchecked
348+ &self.options[*index as usize] // get_unchecked
321 })
349 })
322 .collect();
350 .collect();
323
351
@@ -350,15 +378,19 @@ impl<T: Item + 'static> Component for Menu<T> {
350 false,
378 false,
351 );
379 );
352
380
353− if let Some(cursor) = self.cursor {
381+ let render_borders = cx.editor.menu_border();
354− let offset_from_top = cursor - scroll;
382+
355− let left = &mut surface[(area.left(), area.y + offset_from_top as u16)];
383+ if !render_borders {
356− left.set_style(selected);
384+ if let Some(cursor) = self.cursor {
357− let right = &mut surface[(
385+ let offset_from_top = cursor - scroll;
358− area.right().saturating_sub(1),
386+ let left = &mut surface[(area.left(), area.y + offset_from_top as u16)];
359− area.y + offset_from_top as u16,
387+ left.set_style(selected);
360− )];
388+ let right = &mut surface[(
361− right.set_style(selected);
389+ area.right().saturating_sub(1),
390+ area.y + offset_from_top as u16,
391+ )];
392+ right.set_style(selected);
393+ }
362 }
394 }
363
395
364 let fits = len <= win_height;
396 let fits = len <= win_height;
@@ -373,12 +405,13 @@ impl<T: Item + 'static> Component for Menu<T> {
373 for i in 0..win_height {
405 for i in 0..win_height {
374 cell = &mut surface[(area.right() - 1, area.top() + i as u16)];
406 cell = &mut surface[(area.right() - 1, area.top() + i as u16)];
375
407
376− cell.set_symbol("▐"); // right half block
408+ let half_block = if render_borders { "▌" } else { "▐" };
377
409
378 if scroll_line <= i && i < scroll_line + scroll_height {
410 if scroll_line <= i && i < scroll_line + scroll_height {
379 // Draw scroll thumb
411 // Draw scroll thumb
412+ cell.set_symbol(half_block);
380 cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
413 cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
381− } else {
414+ } else if !render_borders {
382 // Draw scroll track
415 // Draw scroll track
383 cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
416 cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
384 }
417 }
helix-term/src/ui/mod.rs+101−164
@@ -1,13 +1,12 @@
1mod completion;
1mod completion;
2mod document;
2mod document;
3pub(crate) mod editor;
3pub(crate) mod editor;
4−mod fuzzy_match;
5mod info;
4mod info;
6pub mod lsp;
5pub mod lsp;
7mod markdown;
6mod markdown;
8pub mod menu;
7pub mod menu;
9pub mod overlay;
8pub mod overlay;
10−mod picker;
9+pub mod picker;
11pub mod popup;
10pub mod popup;
12mod prompt;
11mod prompt;
13mod spinner;
12mod spinner;
@@ -21,7 +20,7 @@ pub use completion::{Completion, CompletionItem};
21pub use editor::EditorView;
20pub use editor::EditorView;
22pub use markdown::Markdown;
21pub use markdown::Markdown;
23pub use menu::Menu;
22pub use menu::Menu;
24−pub use picker::{DynamicPicker, FileLocation, FilePicker, Picker};
23+pub use picker::{DynamicPicker, FileLocation, Picker};
25pub use popup::Popup;
24pub use popup::Popup;
26pub use prompt::{Prompt, PromptEvent};
25pub use prompt::{Prompt, PromptEvent};
27pub use spinner::{ProgressSpinners, Spinner};
26pub use spinner::{ProgressSpinners, Spinner};
@@ -64,7 +63,7 @@ pub fn regex_prompt(
64 prompt: std::borrow::Cow<'static, str>,
63 prompt: std::borrow::Cow<'static, str>,
65 history_register: Option<char>,
64 history_register: Option<char>,
66 completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
65 completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
67− fun: impl Fn(&mut Editor, Regex, PromptEvent) + 'static,
66+ fun: impl Fn(&mut crate::compositor::Context, Regex, PromptEvent) + 'static,
68) {
67) {
69 let (view, doc) = current!(cx.editor);
68 let (view, doc) = current!(cx.editor);
70 let doc_id = view.doc;
69 let doc_id = view.doc;
@@ -111,7 +110,7 @@ pub fn regex_prompt(
111 view.jumps.push((doc_id, snapshot.clone()));
110 view.jumps.push((doc_id, snapshot.clone()));
112 }
111 }
113
112
114− fun(cx.editor, regex, event);
113+ fun(cx, regex, event);
115
114
116 let (view, doc) = current!(cx.editor);
115 let (view, doc) = current!(cx.editor);
117 view.ensure_cursor_in_view(doc, config.scrolloff);
116 view.ensure_cursor_in_view(doc, config.scrolloff);
@@ -142,23 +141,21 @@ pub fn regex_prompt(
142 };
141 };
143
142
144 cx.jobs.callback(callback);
143 cx.jobs.callback(callback);
145− } else {
146− // Update
147− // TODO: mark command line as error
148 }
144 }
149 }
145 }
150 }
146 }
151 }
147 }
152 }
148 }
153 },
149 },
154− );
150+ )
151+ .with_language("regex", std::sync::Arc::clone(&cx.editor.syn_loader));
155 // Calculate initial completion
152 // Calculate initial completion
156 prompt.recalculate_completion(cx.editor);
153 prompt.recalculate_completion(cx.editor);
157 // prompt
154 // prompt
158 cx.push_layer(Box::new(prompt));
155 cx.push_layer(Box::new(prompt));
159}
156}
160
157
161−pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePicker<PathBuf> {
158+pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker<PathBuf> {
162 use ignore::{types::TypesBuilder, WalkBuilder};
159 use ignore::{types::TypesBuilder, WalkBuilder};
163 use std::time::Instant;
160 use std::time::Instant;
164
161
@@ -176,9 +173,13 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
176 .git_ignore(config.file_picker.git_ignore)
173 .git_ignore(config.file_picker.git_ignore)
177 .git_global(config.file_picker.git_global)
174 .git_global(config.file_picker.git_global)
178 .git_exclude(config.file_picker.git_exclude)
175 .git_exclude(config.file_picker.git_exclude)
176+ .sort_by_file_name(|name1, name2| name1.cmp(name2))
179 .max_depth(config.file_picker.max_depth)
177 .max_depth(config.file_picker.max_depth)
180 .filter_entry(move |entry| filter_picker_entry(entry, &absolute_root, dedup_symlinks));
178 .filter_entry(move |entry| filter_picker_entry(entry, &absolute_root, dedup_symlinks));
181
179
180+ walk_builder.add_custom_ignore_filename(helix_loader::config_dir().join("ignore"));
181+ walk_builder.add_custom_ignore_filename(".helix/ignore");
182+
182 // We want to exclude files that the editor can't handle yet
183 // We want to exclude files that the editor can't handle yet
183 let mut type_builder = TypesBuilder::new();
184 let mut type_builder = TypesBuilder::new();
184 type_builder
185 type_builder
@@ -192,59 +193,60 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePi
192 .build()
193 .build()
193 .expect("failed to build excluded_types");
194 .expect("failed to build excluded_types");
194 walk_builder.types(excluded_types);
195 walk_builder.types(excluded_types);
195−
196+ let mut files = walk_builder.build().filter_map(|entry| {
196− // We want files along with their modification date for sorting
197− let files = walk_builder.build().filter_map(|entry| {
198 let entry = entry.ok()?;
197 let entry = entry.ok()?;
199− // This is faster than entry.path().is_dir() since it uses cached fs::Metadata fetched by ignore/walkdir
198+ if !entry.file_type()?.is_file() {
200− if entry.file_type()?.is_file() {
199+ return None;
201− Some(entry.into_path())
202− } else {
203− None
204 }
200 }
201+ Some(entry.into_path())
205 });
202 });
206−
207− // Cap the number of files if we aren't in a git project, preventing
208− // hangs when using the picker in your home directory
209− let mut files: Vec<PathBuf> = if root.join(".git").exists() {
210− files.collect()
211− } else {
212− // const MAX: usize = 8192;
213− const MAX: usize = 100_000;
214− files.take(MAX).collect()
215− };
216− files.sort();
217−
218 log::debug!("file_picker init {:?}", Instant::now().duration_since(now));
203 log::debug!("file_picker init {:?}", Instant::now().duration_since(now));
219
204
220− FilePicker::new(
205+ let picker = Picker::new(Vec::new(), root, move |cx, path: &PathBuf, action| {
221− files,
206+ if let Err(e) = cx.editor.open(path, action) {
222− root,
207+ let err = if let Some(err) = e.source() {
223− move |cx, path: &PathBuf, action| {
208+ format!("{}", err)
224− if let Err(e) = cx.editor.open(path, action) {
209+ } else {
225− let err = if let Some(err) = e.source() {
210+ format!("unable to open \"{}\"", path.display())
226− format!("{}", err)
211+ };
227− } else {
212+ cx.editor.set_error(err);
228− format!("unable to open \"{}\"", path.display())
213+ }
229− };
214+ })
230− cx.editor.set_error(err);
215+ .with_preview(|_editor, path| Some((path.clone().into(), None)));
216+ let injector = picker.injector();
217+ let timeout = std::time::Instant::now() + std::time::Duration::from_millis(30);
218+
219+ let mut hit_timeout = false;
220+ for file in &mut files {
221+ if injector.push(file).is_err() {
222+ break;
223+ }
224+ if std::time::Instant::now() >= timeout {
225+ hit_timeout = true;
226+ break;
227+ }
228+ }
229+ if hit_timeout {
230+ std::thread::spawn(move || {
231+ for file in files {
232+ if injector.push(file).is_err() {
233+ break;
234+ }
231 }
235 }
232− },
236+ });
233− |_editor, path| Some((path.clone().into(), None)),
237+ }
234− )
238+ picker
235}
239}
236
240
237pub mod completers {
241pub mod completers {
238 use crate::ui::prompt::Completion;
242 use crate::ui::prompt::Completion;
239− use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
243+ use helix_core::fuzzy::fuzzy_match;
240− use fuzzy_matcher::FuzzyMatcher;
241 use helix_core::syntax::LanguageServerFeature;
244 use helix_core::syntax::LanguageServerFeature;
242 use helix_view::document::SCRATCH_BUFFER_NAME;
245 use helix_view::document::SCRATCH_BUFFER_NAME;
243 use helix_view::theme;
246 use helix_view::theme;
244 use helix_view::{editor::Config, Editor};
247 use helix_view::{editor::Config, Editor};
245 use once_cell::sync::Lazy;
248 use once_cell::sync::Lazy;
246 use std::borrow::Cow;
249 use std::borrow::Cow;
247− use std::cmp::Reverse;
248
250
249 pub type Completer = fn(&Editor, &str) -> Vec<Completion>;
251 pub type Completer = fn(&Editor, &str) -> Vec<Completion>;
250
252
@@ -253,31 +255,16 @@ pub mod completers {
253 }
255 }
254
256
255 pub fn buffer(editor: &Editor, input: &str) -> Vec<Completion> {
257 pub fn buffer(editor: &Editor, input: &str) -> Vec<Completion> {
256− let mut names: Vec<_> = editor
258+ let names = editor.documents.values().map(|doc| {
257− .documents
259+ doc.relative_path()
258− .values()
260+ .map(|p| p.display().to_string().into())
259− .map(|doc| {
261+ .unwrap_or_else(|| Cow::from(SCRATCH_BUFFER_NAME))
260− let name = doc
262+ });
261− .relative_path()
262− .map(|p| p.display().to_string())
263− .unwrap_or_else(|| String::from(SCRATCH_BUFFER_NAME));
264− ((0..), Cow::from(name))
265− })
266− .collect();
267−
268− let matcher = Matcher::default();
269−
270− let mut matches: Vec<_> = names
271− .into_iter()
272− .filter_map(|(_range, name)| {
273− matcher.fuzzy_match(&name, input).map(|score| (name, score))
274− })
275− .collect();
276−
277− matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
278− names = matches.into_iter().map(|(name, _)| ((0..), name)).collect();
279
263
280− names
264+ fuzzy_match(input, names, true)
265+ .into_iter()
266+ .map(|(name, _)| ((0..), name))
267+ .collect()
281 }
268 }
282
269
283 pub fn theme(_editor: &Editor, input: &str) -> Vec<Completion> {
270 pub fn theme(_editor: &Editor, input: &str) -> Vec<Completion> {
@@ -290,26 +277,10 @@ pub mod completers {
290 names.sort();
277 names.sort();
291 names.dedup();
278 names.dedup();
292
279
293− let mut names: Vec<_> = names
280+ fuzzy_match(input, names, false)
294− .into_iter()
295− .map(|name| ((0..), Cow::from(name)))
296− .collect();
297−
298− let matcher = Matcher::default();
299−
300− let mut matches: Vec<_> = names
301 .into_iter()
281 .into_iter()
302− .filter_map(|(_range, name)| {
282+ .map(|(name, _)| ((0..), name.into()))
303− matcher.fuzzy_match(&name, input).map(|score| (name, score))
283+ .collect()
304− })
305− .collect();
306−
307− matches.sort_unstable_by(|(name1, score1), (name2, score2)| {
308− (Reverse(*score1), name1).cmp(&(Reverse(*score2), name2))
309− });
310− names = matches.into_iter().map(|(name, _)| ((0..), name)).collect();
311−
312− names
313 }
284 }
314
285
315 /// Recursive function to get all keys from this value and add them to vec
286 /// Recursive function to get all keys from this value and add them to vec
@@ -336,22 +307,22 @@ pub mod completers {
336 keys
307 keys
337 });
308 });
338
309
339− let matcher = Matcher::default();
310+ fuzzy_match(input, &*KEYS, false)
340−
341− let mut matches: Vec<_> = KEYS
342− .iter()
343− .filter_map(|name| matcher.fuzzy_match(name, input).map(|score| (name, score)))
344− .collect();
345−
346− matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
347− matches
348 .into_iter()
311 .into_iter()
349 .map(|(name, _)| ((0..), name.into()))
312 .map(|(name, _)| ((0..), name.into()))
350 .collect()
313 .collect()
351 }
314 }
352
315
353 pub fn filename(editor: &Editor, input: &str) -> Vec<Completion> {
316 pub fn filename(editor: &Editor, input: &str) -> Vec<Completion> {
354− filename_impl(editor, input, |entry| {
317+ filename_with_git_ignore(editor, input, true)
318+ }
319+
320+ pub fn filename_with_git_ignore(
321+ editor: &Editor,
322+ input: &str,
323+ git_ignore: bool,
324+ ) -> Vec<Completion> {
325+ filename_impl(editor, input, git_ignore, |entry| {
355 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
326 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
356
327
357 if is_dir {
328 if is_dir {
@@ -363,8 +334,6 @@ pub mod completers {
363 }
334 }
364
335
365 pub fn language(editor: &Editor, input: &str) -> Vec<Completion> {
336 pub fn language(editor: &Editor, input: &str) -> Vec<Completion> {
366− let matcher = Matcher::default();
367−
368 let text: String = "text".into();
337 let text: String = "text".into();
369
338
370 let language_ids = editor
339 let language_ids = editor
@@ -373,27 +342,13 @@ pub mod completers {
373 .map(|config| &config.language_id)
342 .map(|config| &config.language_id)
374 .chain(std::iter::once(&text));
343 .chain(std::iter::once(&text));
375
344
376− let mut matches: Vec<_> = language_ids
345+ fuzzy_match(input, language_ids, false)
377− .filter_map(|language_id| {
378− matcher
379− .fuzzy_match(language_id, input)
380− .map(|score| (language_id, score))
381− })
382− .collect();
383−
384− matches.sort_unstable_by(|(language1, score1), (language2, score2)| {
385− (Reverse(*score1), language1).cmp(&(Reverse(*score2), language2))
386− });
387−
388− matches
389 .into_iter()
346 .into_iter()
390− .map(|(language, _score)| ((0..), language.clone().into()))
347+ .map(|(name, _)| ((0..), name.to_owned().into()))
391 .collect()
348 .collect()
392 }
349 }
393
350
394 pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
351 pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
395− let matcher = Matcher::default();
396−
397 let Some(options) = doc!(editor)
352 let Some(options) = doc!(editor)
398 .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
353 .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
399 .find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
354 .find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
@@ -401,28 +356,22 @@ pub mod completers {
401 return vec![];
356 return vec![];
402 };
357 };
403
358
404− let mut matches: Vec<_> = options
359+ fuzzy_match(input, &options.commands, false)
405− .commands
406− .iter()
407− .filter_map(|command| {
408− matcher
409− .fuzzy_match(command, input)
410− .map(|score| (command, score))
411− })
412− .collect();
413−
414− matches.sort_unstable_by(|(command1, score1), (command2, score2)| {
415− (Reverse(*score1), command1).cmp(&(Reverse(*score2), command2))
416− });
417−
418− matches
419 .into_iter()
360 .into_iter()
420− .map(|(command, _score)| ((0..), command.clone().into()))
361+ .map(|(name, _)| ((0..), name.to_owned().into()))
421 .collect()
362 .collect()
422 }
363 }
423
364
424 pub fn directory(editor: &Editor, input: &str) -> Vec<Completion> {
365 pub fn directory(editor: &Editor, input: &str) -> Vec<Completion> {
425− filename_impl(editor, input, |entry| {
366+ directory_with_git_ignore(editor, input, true)
367+ }
368+
369+ pub fn directory_with_git_ignore(
370+ editor: &Editor,
371+ input: &str,
372+ git_ignore: bool,
373+ ) -> Vec<Completion> {
374+ filename_impl(editor, input, git_ignore, |entry| {
426 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
375 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
427
376
428 if is_dir {
377 if is_dir {
@@ -445,7 +394,12 @@ pub mod completers {
445 }
394 }
446
395
447 // TODO: we could return an iter/lazy thing so it can fetch as many as it needs.
396 // TODO: we could return an iter/lazy thing so it can fetch as many as it needs.
448− fn filename_impl<F>(_editor: &Editor, input: &str, filter_fn: F) -> Vec<Completion>
397+ fn filename_impl<F>(
398+ _editor: &Editor,
399+ input: &str,
400+ git_ignore: bool,
401+ filter_fn: F,
402+ ) -> Vec<Completion>
449 where
403 where
450 F: Fn(&ignore::DirEntry) -> FileMatch,
404 F: Fn(&ignore::DirEntry) -> FileMatch,
451 {
405 {
@@ -476,7 +430,7 @@ pub mod completers {
476 match path.parent() {
430 match path.parent() {
477 Some(path) if !path.as_os_str().is_empty() => path.to_path_buf(),
431 Some(path) if !path.as_os_str().is_empty() => path.to_path_buf(),
478 // Path::new("h")'s parent is Some("")...
432 // Path::new("h")'s parent is Some("")...
479− _ => std::env::current_dir().expect("couldn't determine current directory"),
433+ _ => helix_loader::current_working_dir(),
480 }
434 }
481 };
435 };
482
436
@@ -485,9 +439,10 @@ pub mod completers {
485
439
486 let end = input.len()..;
440 let end = input.len()..;
487
441
488− let mut files: Vec<_> = WalkBuilder::new(&dir)
442+ let files = WalkBuilder::new(&dir)
489 .hidden(false)
443 .hidden(false)
490 .follow_links(false) // We're scanning over depth 1
444 .follow_links(false) // We're scanning over depth 1
445+ .git_ignore(git_ignore)
491 .max_depth(Some(1))
446 .max_depth(Some(1))
492 .build()
447 .build()
493 .filter_map(|file| {
448 .filter_map(|file| {
@@ -516,43 +471,25 @@ pub mod completers {
516 path.push("");
471 path.push("");
517 }
472 }
518
473
519− let path = path.to_str()?.to_owned();
474+ let path = path.into_os_string().into_string().ok()?;
520− Some((end.clone(), Cow::from(path)))
475+ Some(Cow::from(path))
521 })
476 })
522 }) // TODO: unwrap or skip
477 }) // TODO: unwrap or skip
523− .filter(|(_, path)| !path.is_empty()) // TODO
478+ .filter(|path| !path.is_empty());
524− .collect();
525
479
526 // if empty, return a list of dirs and files in current dir
480 // if empty, return a list of dirs and files in current dir
527 if let Some(file_name) = file_name {
481 if let Some(file_name) = file_name {
528− let matcher = Matcher::default();
529−
530− // inefficient, but we need to calculate the scores, filter out None, then sort.
531− let mut matches: Vec<_> = files
532− .into_iter()
533− .filter_map(|(_range, file)| {
534− matcher
535− .fuzzy_match(&file, &file_name)
536− .map(|score| (file, score))
537− })
538− .collect();
539−
540 let range = (input.len().saturating_sub(file_name.len()))..;
482 let range = (input.len().saturating_sub(file_name.len()))..;
541−
483+ fuzzy_match(&file_name, files, true)
542− matches.sort_unstable_by(|(file1, score1), (file2, score2)| {
543− (Reverse(*score1), file1).cmp(&(Reverse(*score2), file2))
544− });
545−
546− files = matches
547 .into_iter()
484 .into_iter()
548− .map(|(file, _)| (range.clone(), file))
485+ .map(|(name, _)| (range.clone(), name))
549− .collect();
486+ .collect()
550
487
551 // TODO: complete to longest common match
488 // TODO: complete to longest common match
552 } else {
489 } else {
490+ let mut files: Vec<_> = files.map(|file| (end.clone(), file)).collect();
553 files.sort_unstable_by(|(_, path1), (_, path2)| path1.cmp(path2));
491 files.sort_unstable_by(|(_, path1), (_, path2)| path1.cmp(path2));
492+ files
554 }
493 }
555−
556− files
557 }
494 }
558}
495}
helix-term/src/ui/picker.rs+572−577

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/src/ui/statusline.rs+25−1

This diff is too big to show here.

helix-term/tests/integration.rs+1

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/tests/test/commands.rs+100

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/commands/write.rs+122−3

This diff is too big to show here.

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

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+39

This diff is too big to show here.

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

This diff is too big to show here.

helix-tui/Cargo.toml+15−15

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-tui/src/terminal.rs−14

This diff is too big to show here.

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

This diff is too big to show here.

helix-vcs/Cargo.toml+12−10

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-view/Cargo.toml+22−18

This diff is too big to show here.

helix-view/src/clipboard.rs+7−2

This diff is too big to show here.

helix-view/src/document.rs+220−76

This diff is too big to show here.

helix-view/src/editor.rs+253−94

This diff is too big to show here.

helix-view/src/gutter.rs+15−28

This diff is too big to show here.

helix-view/src/handlers/dap.rs+1−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/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+25−4

This diff is too big to show here.

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

This diff is too big to show here.

languages.toml+720−295

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/indents.scm+18−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/comment/highlights.scm+8−8

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/ecma/README.mdnew file+14

This diff is too big to show here.

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

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/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/gemini/highlights.scmnew file+26

This diff is too big to show here.

runtime/queries/gleam/highlights.scm+16−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+13−5

This diff is too big to show here.

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

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

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/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/markdown.inline/injections.scm+4−1

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/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/nix/injections.scm+14

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/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/pod/highlights.scmnew file+61

This diff is too big to show here.

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

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+6

This diff is too big to show here.

runtime/queries/python/indents.scm+9

This diff is too big to show here.

runtime/queries/racket/indents.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/robot/highlights.scm+51−13

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/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+6

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/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/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+85−51

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/webc/highlights.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+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/autumn.toml+67−65

This diff is too big to show here.

runtime/themes/autumn_night.toml+9−8

This diff is too big to show here.

runtime/themes/catppuccin_frappe.toml+2

This diff is too big to show here.

runtime/themes/catppuccin_latte.toml+2

This diff is too big to show here.

runtime/themes/catppuccin_macchiato.toml+2

This diff is too big to show here.

runtime/themes/catppuccin_mocha.toml+11

This diff is too big to show here.

runtime/themes/cyan_light.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+10−4

This diff is too big to show here.

runtime/themes/dark_plus.toml+2−1

This diff is too big to show here.

runtime/themes/dracula.toml+2

This diff is too big to show here.

runtime/themes/everblush.toml+1

This diff is too big to show here.

runtime/themes/everforest_dark.toml+4

This diff is too big to show here.

runtime/themes/ferra.toml+1−2

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+1

This diff is too big to show here.

runtime/themes/gruvbox_dark_hard.toml−2

This diff is too big to show here.

runtime/themes/gruvbox_dark_soft.tomlnew file+7

This diff is too big to show here.

runtime/themes/gruvbox_light.toml−2

This diff is too big to show here.

runtime/themes/horizon-dark.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/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/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.toml+1−1

This diff is too big to show here.

runtime/themes/monokai.toml+5

This diff is too big to show here.

runtime/themes/naysayer.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+2−2

This diff is too big to show here.

runtime/themes/noctis.toml+26−26

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/nord.toml+162−105

This diff is too big to show here.

runtime/themes/onedark.toml+1

This diff is too big to show here.

runtime/themes/onedarker.toml+1

This diff is too big to show here.

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

This diff is too big to show here.

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

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

This diff is too big to show here.

runtime/themes/rose_pine.toml+31−29

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/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+1−1

This diff is too big to show here.

runtime/tutor+157−3

This diff is too big to show here.

rust-toolchain.toml+2−2

This diff is too big to show here.

xtask/Cargo.toml+12−6

This diff is too big to show here.