on master help sign in
342 files changed+16740−7908
since splitsnapshots
unifiedsplit
.envrc+1 .github/ISSUE_TEMPLATE/bug_report.yaml+10 .github/workflows/build.yml+15−7 .github/workflows/cachix.yml+3−3 .github/workflows/gh-pages.yml+5−5 .github/workflows/release.yml+15−15 .ignore−3 CHANGELOG.md+260 Cargo.lock+551−603 Cargo.toml+16 README.md+2−2 VERSION−1 book/book.toml+4 book/custom.css+231 book/src/configuration.md+38−11 book/src/generated/lang-support.md+36−15 book/src/generated/typable-cmd.md+5−1 book/src/guides/adding_languages.md+2 book/src/guides/indent.md+319−43 book/src/guides/injection.md+1−1 book/src/install.md+41−6 book/src/keymap.md+16−15 book/src/languages.md+3−1 book/src/themes.md+9−1 book/src/usage.md+21−5 book/theme/book.js−660 book/theme/css/chrome.css−499 book/theme/css/general.css−233 book/theme/css/print.css−54 book/theme/css/variables.css−411 book/theme/highlight.css−56 book/theme/highlight.js−6 book/theme/index.hbs+87−51 contrib/Helix.appdata.xml+15 contrib/Helix.desktop+3−1 docs/CONTRIBUTING.md+10−3 docs/releases.md+12−5 flake.lock+21−237 flake.nix+170−143 grammars.nix+24−21 helix-core/Cargo.toml+20−16 helix-core/src/diagnostic.rs+4 helix-core/src/fuzzy.rs+43 helix-core/src/graphemes.rs+1−1 helix-core/src/history.rs+2−2 helix-core/src/indent.rs+586−127 helix-core/src/lib.rs+3−1 helix-core/src/match_brackets.rs+6−2 helix-core/src/movement.rs+80−1 helix-core/src/path.rs+5−7 helix-core/src/register.rs−89 helix-core/src/rope_reader.rs+37 helix-core/src/selection.rs+10−2 helix-core/src/syntax.rs+88−80 helix-core/src/transaction.rs+84−21 helix-core/src/wrap.rs+3−1 helix-core/tests/data/indent/cpp.cpp+48 helix-core/tests/data/indent/indent.rs−1 helix-core/tests/data/indent/languages.toml+13 helix-core/tests/indent.rs+153−25 helix-dap/Cargo.toml+11−9 helix-event/Cargo.toml+16 helix-event/src/lib.rs+8 helix-event/src/redraw.rs+49 helix-loader/Cargo.toml+14−12 helix-loader/build.rs+20−6 helix-loader/src/lib.rs+78−17 helix-lsp/Cargo.toml+15−13 helix-lsp/src/client.rs+123−11 helix-lsp/src/file_event.rs+193 helix-lsp/src/lib.rs+45−30 helix-parsec/Cargo.toml+8−7 helix-term/Cargo.toml+33−25 helix-term/src/application.rs+175−106 helix-term/src/args.rs+29 helix-term/src/commands.rs+647−396 helix-term/src/commands/dap.rs+14−5 helix-term/src/commands/lsp.rs+60−62 helix-term/src/commands/typed.rs+927−699 helix-term/src/compositor.rs+12 helix-term/src/config.rs+1 helix-term/src/health.rs+44−16 helix-term/src/keymap.rs+14−14 helix-term/src/keymap/default.rs+8−2 helix-term/src/main.rs+21−17 helix-term/src/ui/completion.rs+32−4 helix-term/src/ui/document.rs+60−24 helix-term/src/ui/editor.rs+102−54 helix-term/src/ui/fuzzy_match.rs−239 helix-term/src/ui/fuzzy_match/test.rs−47 helix-term/src/ui/lsp.rs+5−3 helix-term/src/ui/markdown.rs+19−12 helix-term/src/ui/menu.rs+73−40 helix-term/src/ui/mod.rs+91−150 helix-term/src/ui/picker.rs+362−325 helix-term/src/ui/popup.rs+27−7 helix-term/src/ui/prompt.rs+55−48 helix-term/src/ui/statusline.rs+14 helix-term/tests/integration.rs+1 helix-term/tests/test/commands.rs+47 helix-term/tests/test/commands/movement.rs+452 helix-term/tests/test/commands/write.rs+122−3 helix-term/tests/test/helpers.rs+9−1 helix-term/tests/test/languages/go.rs+41 helix-term/tests/test/languages/mod.rs+4 helix-term/tests/test/languages/yaml.rs+819 helix-term/tests/test/movement.rs+39 helix-term/tests/test/splits.rs+3−3 helix-tui/Cargo.toml+15−15 helix-tui/src/backend/crossterm.rs+51−33 helix-tui/src/buffer.rs+2−2 helix-tui/src/terminal.rs−14 helix-vcs/Cargo.toml+12−10 helix-vcs/src/diff.rs+6−19 helix-vcs/src/diff/worker.rs+3−9 helix-vcs/src/diff/worker/test.rs+1−5 helix-vcs/src/git.rs+5−5 helix-view/Cargo.toml+22−18 helix-view/src/clipboard.rs+7−2 helix-view/src/document.rs+122−46 helix-view/src/editor.rs+170−91 helix-view/src/gutter.rs+4−5 helix-view/src/handlers/dap.rs+1−3 helix-view/src/info.rs+4−11 helix-view/src/input.rs+67−3 helix-view/src/lib.rs+1 helix-view/src/register.rs+333 helix-view/src/theme.rs+6 helix-view/src/view.rs+8−2 languages.toml+581−210 logo.svg+1−1 runtime/queries/_javascript/highlights.scm+36 runtime/queries/_javascript/locals.scm+14 runtime/queries/_javascript/tags.scm+88 runtime/queries/_jsx/highlights.scm+43 runtime/queries/_jsx/indents.scm+6 runtime/queries/_typescript/highlights.scm+142 runtime/queries/_typescript/indents.scm+5 runtime/queries/_typescript/locals.scm+33 runtime/queries/_typescript/tags.scm+23 runtime/queries/_typescript/textobjects.scm+6 runtime/queries/agda/highlights.scm+124 runtime/queries/bash/indents.scm+1 runtime/queries/bash/textobjects.scm+9 runtime/queries/blueprint/highlights.scm+5 runtime/queries/c/indents.scm+18−1 runtime/queries/comment/highlights.scm+8−8 runtime/queries/common-lisp/indents.scm+1 runtime/queries/dart/highlights.scm+3−1 runtime/queries/dbml/highlights.scm+44 runtime/queries/ecma/README.md+14 runtime/queries/ecma/highlights.scm+9−2 runtime/queries/ecma/locals.scm+17−3 runtime/queries/fsharp/highlights.scm+196 runtime/queries/fsharp/locals.scm+25 runtime/queries/gas/highlights.scm+21 runtime/queries/gas/injections.scm+2 runtime/queries/gas/textobjects.scm+2 runtime/queries/gemini/highlights.scm+26 runtime/queries/gleam/highlights.scm+16−5 runtime/queries/gn/highlights.scm+42 runtime/queries/gn/injections.scm+15 runtime/queries/gn/locals.scm+16 runtime/queries/go/highlights.scm+13−5 runtime/queries/go/locals.scm+10−2 runtime/queries/hare/highlights.scm+4−1 runtime/queries/haskell-persistent/folds.scm+3 runtime/queries/haskell-persistent/highlights.scm+37 runtime/queries/haskell-persistent/locals.scm+1 runtime/queries/haskell/highlights.scm+24−11 runtime/queries/haskell/injections.scm+8 runtime/queries/janet/highlights.scm+1 runtime/queries/java/indents.scm+35 runtime/queries/javascript/highlights.scm+2−37 runtime/queries/javascript/indents.scm+3−1 runtime/queries/javascript/injections.scm+3−1 runtime/queries/javascript/locals.scm+3−1 runtime/queries/javascript/tags.scm+2−87 runtime/queries/javascript/textobjects.scm+3−1 runtime/queries/jinja/highlights.scm+7 runtime/queries/jinja/injections.scm+4 runtime/queries/json5/highlights.scm+11 runtime/queries/jsx/highlights.scm+2−36 runtime/queries/jsx/indents.scm+2−8 runtime/queries/jsx/injections.scm+3−1 runtime/queries/jsx/locals.scm+3−1 runtime/queries/jsx/tags.scm+3 runtime/queries/jsx/textobjects.scm+3−1 runtime/queries/kdl/highlights.scm+4−4 runtime/queries/kdl/indents.scm+3 runtime/queries/kdl/textobjects.scm+27 runtime/queries/kotlin/highlights.scm+4−1 runtime/queries/kotlin/locals.scm+15 runtime/queries/log/highlights.scm+10 runtime/queries/lpf/highlights.scm+19 runtime/queries/markdown.inline/injections.scm+4−1 runtime/queries/nix/injections.scm+14 runtime/queries/nu/folds.scm−4 runtime/queries/nu/highlights.scm+276−44 runtime/queries/nu/injections.scm+1−1 runtime/queries/nu/locals.scm−13 runtime/queries/nunjucks/highlights.scm+1 runtime/queries/nunjucks/injections.scm+1 runtime/queries/ocaml/highlights.scm+2 runtime/queries/perl/fold.scm+19 runtime/queries/perl/highlights.scm+72−155 runtime/queries/perl/indents.scm+25−11 runtime/queries/perl/injections.scm+3−1 runtime/queries/perl/textobjects.scm+10−13 runtime/queries/pod/highlights.scm+61 runtime/queries/prql/highlights.scm+19−12 runtime/queries/purescript/highlights.scm+164−1 runtime/queries/purescript/injections.scm+2−1 runtime/queries/purescript/locals.scm+4−1 runtime/queries/purescript/textobjects.scm+13 runtime/queries/python/highlights.scm+6 runtime/queries/python/indents.scm+9 runtime/queries/racket/indents.scm+1 runtime/queries/regex/highlights.scm+1−1 runtime/queries/robot/highlights.scm+51−13 runtime/queries/ron/highlights.scm+43−1 runtime/queries/ron/indents.scm+12−1 runtime/queries/ron/injections.scm+2−1 runtime/queries/rust/highlights.scm+7 runtime/queries/rust/indents.scm+69−2 runtime/queries/rust/injections.scm+6 runtime/queries/rust/locals.scm+14−1 runtime/queries/rust/textobjects.scm+4−1 runtime/queries/scheme/highlights.scm+5 runtime/queries/scheme/indents.scm+43 runtime/queries/slint/highlights.scm+118−72 runtime/queries/slint/indents.scm+6−5 runtime/queries/slint/injections.scm+2 runtime/queries/slint/locals.scm+1−1 runtime/queries/smali/folds.scm+12 runtime/queries/smali/highlights.scm+216 runtime/queries/smali/indents.scm+31 runtime/queries/smali/injections.scm+2 runtime/queries/smali/locals.scm+42 runtime/queries/sql/highlights.scm+317−231 runtime/queries/strace/highlights.scm+8 runtime/queries/svelte/indents.scm+18 runtime/queries/svelte/injections.scm+10 runtime/queries/t32/highlights.scm+108−26 runtime/queries/templ/highlights.scm+99 runtime/queries/templ/injections.scm+4 runtime/queries/todotxt/highlights.scm+6 runtime/queries/tsx/highlights.scm+3−1 runtime/queries/tsx/indents.scm+3−1 runtime/queries/tsx/injections.scm+3−1 runtime/queries/tsx/locals.scm+3 runtime/queries/tsx/tags.scm+3 runtime/queries/tsx/textobjects.scm+3−1 runtime/queries/typescript/highlights.scm+2−81 runtime/queries/typescript/indents.scm+2−6 runtime/queries/typescript/injections.scm+3−1 runtime/queries/typescript/locals.scm+3−2 runtime/queries/typescript/tags.scm+2−22 runtime/queries/typescript/textobjects.scm+2−7 runtime/queries/typst/highlights.scm+77 runtime/queries/typst/injections.scm+6 runtime/queries/unison/highlights.scm+87 runtime/queries/unison/injections.scm+1 runtime/queries/v/highlights.scm+85−51 runtime/queries/v/indents.scm+2−3 runtime/queries/v/textobjects.scm+3−3 runtime/queries/wren/highlights.scm+82 runtime/queries/wren/indents.scm+29 runtime/queries/wren/injections.scm+3 runtime/queries/wren/locals.scm+21 runtime/queries/wren/textobjects.scm+16 runtime/queries/yaml/indents.scm+35−1 runtime/queries/yaml/injections.scm+52 runtime/queries/yuck/highlights.scm+1−1 runtime/queries/zig/highlights.scm+5−2 runtime/queries/zig/textobjects.scm+3−3 runtime/themes/README.md+8 runtime/themes/autumn.toml+62−65 runtime/themes/autumn_night.toml+6−6 runtime/themes/catppuccin_frappe.toml+2 runtime/themes/catppuccin_latte.toml+2 runtime/themes/catppuccin_macchiato.toml+2 runtime/themes/catppuccin_mocha.toml+11 runtime/themes/cyan_light.toml+154 runtime/themes/darcula-solid.toml+1−4 runtime/themes/darcula.toml+10−4 runtime/themes/dark_plus.toml+2−1 runtime/themes/dracula.toml+2 runtime/themes/everblush.toml+1 runtime/themes/everforest_dark.toml+4 runtime/themes/gruber-darker.toml+114 runtime/themes/gruvbox.toml+1 runtime/themes/gruvbox_dark_hard.toml−2 runtime/themes/gruvbox_light.toml−2 runtime/themes/horizon-dark.toml+80 runtime/themes/jellybeans.toml+1−1 runtime/themes/kaolin-dark.toml+108 runtime/themes/kaolin-light.toml+60 runtime/themes/kaolin-valley-dark.toml+73 runtime/themes/licenses/everforest.LICENSE+21 runtime/themes/licenses/modus_vivendi.LICENSE+5 runtime/themes/licenses/modus_vivendi_deuteranopia.LICENSE+5 runtime/themes/licenses/modus_vivendi_tinted.LICENSE+5 runtime/themes/licenses/modus_vivendi_tritanopia.LICENSE+5 runtime/themes/licenses/poimandres.LICENSE+21 runtime/themes/licenses/serika-syntax.LICENSE+21 runtime/themes/licenses/sonokai.LICENSE+21 runtime/themes/licenses/starlight.LICENSE+24 runtime/themes/material_darker.toml+20 runtime/themes/material_deep_ocean.toml+123 runtime/themes/material_oceanic.toml+21 runtime/themes/material_palenight.toml+19 runtime/themes/modus_operandi.toml+194 runtime/themes/modus_operandi_deuteranopia.toml+46 runtime/themes/modus_operandi_tinted.toml+45 runtime/themes/modus_operandi_tritanopia.toml+73 runtime/themes/modus_vivendi.toml+242 runtime/themes/modus_vivendi_deuteranopia.toml+158 runtime/themes/modus_vivendi_tinted.toml+144 runtime/themes/modus_vivendi_tritanopia.toml+186 runtime/themes/molokai.toml+1−1 runtime/themes/monokai.toml+5 runtime/themes/naysayer.toml+91 runtime/themes/new_moon.toml+124 runtime/themes/nightfox.toml+2−2 runtime/themes/noctis.toml+26−26 runtime/themes/nord-night.toml+23 runtime/themes/nord.toml+162−105 runtime/themes/onedark.toml+1 runtime/themes/papercolor-dark.toml+67−116 runtime/themes/papercolor-light.toml+194−103 runtime/themes/poimandres.toml+113 runtime/themes/poimandres_storm.toml+19 runtime/themes/pop-dark.toml+4−4 runtime/themes/rose_pine.toml+31−29 runtime/themes/starlight.toml+113 runtime/themes/vim_dark_high_contrast.toml+72 runtime/themes/yellowed.toml+135 runtime/themes/zenburn.toml+1−1 runtime/tutor+157−3 rust-toolchain.toml+2−2 xtask/Cargo.toml+12−6
.envrc+1
@@ -1,5 +1,6 @@
11watch_file shell.nix
22watch_file flake.lock
3+watch_file rust-toolchain.toml
34
45# try to use flakes, if it fails use normal nix (ie. shell.nix)
56use flake || use nix
.github/ISSUE_TEMPLATE/bug_report.yaml+10
@@ -55,6 +55,16 @@ body:
5555 placeholder: wezterm 20220101-133340-7edc5b5a
5656 validations:
5757 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
5868 - type: input
5969 id: helix-version
6070 attributes:
.github/workflows/build.yml+15−7
@@ -14,7 +14,7 @@ jobs:
1414 runs-on: ubuntu-latest
1515 steps:
1616 - name: Checkout sources
17− uses: actions/checkout@v3
17+ uses: actions/checkout@v4
1818 - name: Install stable toolchain
1919 uses: helix-editor/rust-toolchain@v1
2020 with:
@@ -22,6 +22,8 @@ jobs:
2222 override: true
2323
2424 - uses: Swatinem/rust-cache@v2
25+ with:
26+ shared-key: "build"
2527
2628 - name: Run cargo check
2729 run: cargo check
@@ -34,12 +36,14 @@ jobs:
3436 HELIX_LOG_LEVEL: info
3537 steps:
3638 - name: Checkout sources
37− uses: actions/checkout@v3
39+ uses: actions/checkout@v4
3840
3941 - name: Install stable toolchain
40− uses: dtolnay/rust-toolchain@1.65
42+ uses: dtolnay/rust-toolchain@1.70
4143
4244 - uses: Swatinem/rust-cache@v2
45+ with:
46+ shared-key: "build"
4347
4448 - name: Cache test tree-sitter grammar
4549 uses: actions/cache@v3
@@ -63,14 +67,16 @@ jobs:
6367 runs-on: ubuntu-latest
6468 steps:
6569 - name: Checkout sources
66− uses: actions/checkout@v3
70+ uses: actions/checkout@v4
6771
6872 - name: Install stable toolchain
69− uses: dtolnay/rust-toolchain@1.65
73+ uses: dtolnay/rust-toolchain@1.70
7074 with:
7175 components: rustfmt, clippy
7276
7377 - uses: Swatinem/rust-cache@v2
78+ with:
79+ shared-key: "build"
7480
7581 - name: Run cargo fmt
7682 run: cargo fmt --all --check
@@ -88,12 +94,14 @@ jobs:
8894 runs-on: ubuntu-latest
8995 steps:
9096 - name: Checkout sources
91− uses: actions/checkout@v3
97+ uses: actions/checkout@v4
9298
9399 - name: Install stable toolchain
94− uses: dtolnay/rust-toolchain@1.65
100+ uses: dtolnay/rust-toolchain@1.70
95101
96102 - uses: Swatinem/rust-cache@v2
103+ with:
104+ shared-key: "build"
97105
98106 - name: Validate queries
99107 run: cargo xtask query-check
.github/workflows/cachix.yml+3−3
@@ -11,13 +11,13 @@ jobs:
1111 runs-on: ubuntu-latest
1212 steps:
1313 - name: Checkout sources
14− uses: actions/checkout@v3
14+ uses: actions/checkout@v4
1515
1616 - name: Install nix
17− uses: cachix/install-nix-action@v22
17+ uses: cachix/install-nix-action@v24
1818
1919 - name: Authenticate with Cachix
20− uses: cachix/cachix-action@v12
20+ uses: cachix/cachix-action@v13
2121 with:
2222 name: helix
2323 authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
.github/workflows/gh-pages.yml+5−5
@@ -11,7 +11,7 @@ jobs:
1111 deploy:
1212 runs-on: ubuntu-latest
1313 steps:
14− - uses: actions/checkout@v3
14+ - uses: actions/checkout@v4
1515
1616 - name: Setup mdBook
1717 uses: peaceiris/actions-mdbook@v1
@@ -26,16 +26,16 @@ jobs:
2626 OUTDIR=$(basename ${{ github.ref }})
2727 echo "OUTDIR=$OUTDIR" >> $GITHUB_ENV
2828
29− - name: Deploy
29+ - name: Deploy stable
3030 uses: peaceiris/actions-gh-pages@v3
31+ if: startswith(github.ref, 'refs/tags/')
3132 with:
3233 github_token: ${{ secrets.GITHUB_TOKEN }}
3334 publish_dir: ./book/book
34− destination_dir: ./${{ env.OUTDIR }}
3535
36− - name: Deploy stable
36+ - name: Deploy
3737 uses: peaceiris/actions-gh-pages@v3
38− if: startswith(github.ref, 'refs/tags/')
3938 with:
4039 github_token: ${{ secrets.GITHUB_TOKEN }}
4140 publish_dir: ./book/book
41+ destination_dir: ./${{ env.OUTDIR }}
.github/workflows/release.yml+15−15
@@ -23,7 +23,7 @@ jobs:
2323 runs-on: ubuntu-latest
2424 steps:
2525 - name: Checkout sources
26− uses: actions/checkout@v3
26+ uses: actions/checkout@v4
2727
2828 - name: Install stable toolchain
2929 uses: dtolnay/rust-toolchain@stable
@@ -36,7 +36,7 @@ jobs:
3636 - name: Bundle grammars
3737 run: tar cJf grammars.tar.xz -C runtime/grammars/sources .
3838
39− - uses: actions/upload-artifact@v3
39+ - uses: actions/upload-artifact@v4
4040 with:
4141 name: grammars
4242 path: grammars.tar.xz
@@ -70,11 +70,11 @@ jobs:
7070 rust: stable
7171 target: aarch64-unknown-linux-gnu
7272 cross: true
73− - build: riscv64-linux
74− os: ubuntu-latest
75− rust: stable
76− target: riscv64gc-unknown-linux-gnu
77− cross: true
73+ # - build: riscv64-linux
74+ # os: ubuntu-latest
75+ # rust: stable
76+ # target: riscv64gc-unknown-linux-gnu
77+ # cross: true
7878 - build: x86_64-macos
7979 os: macos-latest
8080 rust: stable
@@ -103,10 +103,10 @@ jobs:
103103
104104 steps:
105105 - name: Checkout sources
106− uses: actions/checkout@v3
106+ uses: actions/checkout@v4
107107
108108 - name: Download grammars
109− uses: actions/download-artifact@v3
109+ uses: actions/download-artifact@v4
110110
111111 - name: Move grammars under runtime
112112 if: "!startsWith(matrix.os, 'windows')"
@@ -160,7 +160,7 @@ jobs:
160160
161161 - name: Build AppImage
162162 shell: bash
163− if: matrix.build == 'aarch64-linux' || matrix.build == 'x86_64-linux'
163+ if: matrix.build == 'x86_64-linux'
164164 run: |
165165 # Required as of 22.x https://github.com/AppImage/AppImageKit/wiki/FUSE
166166 sudo add-apt-repository universe
@@ -220,7 +220,7 @@ jobs:
220220 fi
221221 cp -r runtime dist
222222
223− - uses: actions/upload-artifact@v3
223+ - uses: actions/upload-artifact@v4
224224 with:
225225 name: bins-${{ matrix.build }}
226226 path: dist
@@ -231,9 +231,9 @@ jobs:
231231 runs-on: ubuntu-latest
232232 steps:
233233 - name: Checkout sources
234− uses: actions/checkout@v3
234+ uses: actions/checkout@v4
235235
236− - uses: actions/download-artifact@v3
236+ - uses: actions/download-artifact@v4
237237
238238 - name: Build archive
239239 shell: bash
@@ -263,7 +263,7 @@ jobs:
263263 mv bins-$platform/hx$exe $pkgname
264264 chmod +x $pkgname/hx$exe
265265
266− if [[ "$platform" = "aarch64-linux" || "$platform" = "x86_64-linux" ]]; then
266+ if [[ "$platform" = "x86_64-linux" ]]; then
267267 mv bins-$platform/helix-*.AppImage* dist/
268268 fi
269269
@@ -288,7 +288,7 @@ jobs:
288288 overwrite: true
289289
290290 - name: Upload binaries as artifact
291− uses: actions/upload-artifact@v3
291+ uses: actions/upload-artifact@v4
292292 if: env.preview == 'true'
293293 with:
294294 name: release
.ignore−3
@@ -1,5 +1,2 @@
11# Things that we don't want ripgrep to search that we do want in git
22# 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+260
@@ -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+
1261# 23.05 (2023-05-18)
2262
326323.05 is a smaller release focusing on fixes. There were 88 contributors in this release. Thank you all!
Cargo.lock+551−603
@@ -19,41 +19,22 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
1919
2020[[package]]
2121name = "ahash"
22−version = "0.7.6"
23−source = "registry+https://github.com/rust-lang/crates.io-index"
24−checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
25−dependencies = [
26− "getrandom",
27− "once_cell",
28− "version_check",
29−]
30−
31−[[package]]
32−name = "ahash"
33−version = "0.8.3"
22+version = "0.8.6"
3423source = "registry+https://github.com/rust-lang/crates.io-index"
35−checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
24+checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
3625dependencies = [
3726 "cfg-if",
3827 "getrandom",
3928 "once_cell",
4029 "version_check",
30+ "zerocopy",
4131]
4232
4333[[package]]
4434name = "aho-corasick"
45−version = "0.7.20"
35+version = "1.1.2"
4636source = "registry+https://github.com/rust-lang/crates.io-index"
47−checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
48−dependencies = [
49− "memchr",
50−]
51−
52−[[package]]
53−name = "aho-corasick"
54−version = "1.0.1"
55−source = "registry+https://github.com/rust-lang/crates.io-index"
56−checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
37+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
5738dependencies = [
5839 "memchr",
5940]
@@ -81,9 +62,9 @@ dependencies = [
8162
8263[[package]]
8364name = "anyhow"
84−version = "1.0.71"
65+version = "1.0.76"
8566source = "registry+https://github.com/rust-lang/crates.io-index"
86−checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
67+checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
8768
8869[[package]]
8970name = "arc-swap"
@@ -107,7 +88,7 @@ dependencies = [
10788 "cc",
10889 "cfg-if",
10990 "libc",
110− "miniz_oxide 0.7.1",
91+ "miniz_oxide",
11192 "object",
11293 "rustc-demangle",
11394]
@@ -120,18 +101,17 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
120101
121102[[package]]
122103name = "bitflags"
123−version = "2.3.3"
104+version = "2.4.1"
124105source = "registry+https://github.com/rust-lang/crates.io-index"
125−checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
106+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
126107
127108[[package]]
128109name = "bstr"
129−version = "1.4.0"
110+version = "1.8.0"
130111source = "registry+https://github.com/rust-lang/crates.io-index"
131−checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
112+checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
132113dependencies = [
133114 "memchr",
134− "once_cell",
135115 "regex-automata",
136116 "serde",
137117]
@@ -151,12 +131,6 @@ version = "3.12.0"
151131source = "registry+https://github.com/rust-lang/crates.io-index"
152132checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
153133
154−[[package]]
155−name = "bytecount"
156−version = "0.6.3"
157−source = "registry+https://github.com/rust-lang/crates.io-index"
158−checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
159−
160134[[package]]
161135name = "bytes"
162136version = "1.4.0"
@@ -171,9 +145,12 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
171145
172146[[package]]
173147name = "cc"
174−version = "1.0.79"
148+version = "1.0.84"
175149source = "registry+https://github.com/rust-lang/crates.io-index"
176−checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
150+checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
151+dependencies = [
152+ "libc",
153+]
177154
178155[[package]]
179156name = "cfg-if"
@@ -194,25 +171,23 @@ dependencies = [
194171
195172[[package]]
196173name = "chrono"
197−version = "0.4.26"
174+version = "0.4.31"
198175source = "registry+https://github.com/rust-lang/crates.io-index"
199−checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
176+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
200177dependencies = [
201178 "android-tzdata",
202179 "iana-time-zone",
203180 "num-traits",
204− "winapi",
181+ "windows-targets 0.48.0",
205182]
206183
207184[[package]]
208185name = "clipboard-win"
209−version = "4.5.0"
186+version = "5.0.0"
210187source = "registry+https://github.com/rust-lang/crates.io-index"
211−checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
188+checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
212189dependencies = [
213190 "error-code",
214− "str-buf",
215− "winapi",
216191]
217192
218193[[package]]
@@ -246,6 +221,12 @@ version = "0.8.4"
246221source = "registry+https://github.com/rust-lang/crates.io-index"
247222checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
248223
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+
249230[[package]]
250231name = "crc32fast"
251232version = "1.3.2"
@@ -255,14 +236,58 @@ dependencies = [
255236 "cfg-if",
256237]
257238
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+
258282[[package]]
259283name = "crossterm"
260−version = "0.26.1"
284+version = "0.27.0"
261285source = "registry+https://github.com/rust-lang/crates.io-index"
262−checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
286+checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
263287dependencies = [
264− "bitflags 1.3.2",
288+ "bitflags 2.4.1",
265289 "crossterm_winapi",
290+ "filedescriptor",
266291 "futures-core",
267292 "libc",
268293 "mio",
@@ -274,9 +299,9 @@ dependencies = [
274299
275300[[package]]
276301name = "crossterm_winapi"
277−version = "0.9.0"
302+version = "0.9.1"
278303source = "registry+https://github.com/rust-lang/crates.io-index"
279−checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
304+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
280305dependencies = [
281306 "winapi",
282307]
@@ -305,7 +330,7 @@ dependencies = [
305330 "proc-macro2",
306331 "quote",
307332 "scratch",
308− "syn 2.0.23",
333+ "syn 2.0.38",
309334]
310335
311336[[package]]
@@ -322,7 +347,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
322347dependencies = [
323348 "proc-macro2",
324349 "quote",
325− "syn 2.0.23",
350+ "syn 2.0.38",
326351]
327352
328353[[package]]
@@ -339,9 +364,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
339364
340365[[package]]
341366name = "encoding_rs"
342−version = "0.8.32"
367+version = "0.8.33"
343368source = "registry+https://github.com/rust-lang/crates.io-index"
344−checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
369+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
345370dependencies = [
346371 "cfg-if",
347372]
@@ -363,34 +388,19 @@ checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
363388
364389[[package]]
365390name = "errno"
366−version = "0.3.1"
367−source = "registry+https://github.com/rust-lang/crates.io-index"
368−checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
369−dependencies = [
370− "errno-dragonfly",
371− "libc",
372− "windows-sys 0.48.0",
373−]
374−
375−[[package]]
376−name = "errno-dragonfly"
377−version = "0.1.2"
391+version = "0.3.8"
378392source = "registry+https://github.com/rust-lang/crates.io-index"
379−checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
393+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
380394dependencies = [
381− "cc",
382395 "libc",
396+ "windows-sys 0.52.0",
383397]
384398
385399[[package]]
386400name = "error-code"
387−version = "2.3.1"
401+version = "3.0.0"
388402source = "registry+https://github.com/rust-lang/crates.io-index"
389−checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
390−dependencies = [
391− "libc",
392− "str-buf",
393−]
403+checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
394404
395405[[package]]
396406name = "etcetera"
@@ -404,12 +414,12 @@ dependencies = [
404414]
405415
406416[[package]]
407−name = "fastrand"
408−version = "1.9.0"
417+name = "faster-hex"
418+version = "0.9.0"
409419source = "registry+https://github.com/rust-lang/crates.io-index"
410−checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
420+checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
411421dependencies = [
412− "instant",
422+ "serde",
413423]
414424
415425[[package]]
@@ -428,53 +438,46 @@ dependencies = [
428438]
429439
430440[[package]]
431−name = "filetime"
432−version = "0.2.21"
441+name = "filedescriptor"
442+version = "0.8.2"
433443source = "registry+https://github.com/rust-lang/crates.io-index"
434−checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
444+checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
435445dependencies = [
436− "cfg-if",
437446 "libc",
438− "redox_syscall 0.2.16",
439− "windows-sys 0.48.0",
447+ "thiserror",
448+ "winapi",
440449]
441450
442451[[package]]
443452name = "flate2"
444−version = "1.0.25"
453+version = "1.0.27"
445454source = "registry+https://github.com/rust-lang/crates.io-index"
446−checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
455+checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
447456dependencies = [
448457 "crc32fast",
449− "miniz_oxide 0.6.2",
458+ "miniz_oxide",
450459]
451460
452−[[package]]
453−name = "fnv"
454−version = "1.0.7"
455−source = "registry+https://github.com/rust-lang/crates.io-index"
456−checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
457−
458461[[package]]
459462name = "form_urlencoded"
460−version = "1.2.0"
463+version = "1.2.1"
461464source = "registry+https://github.com/rust-lang/crates.io-index"
462−checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
465+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
463466dependencies = [
464467 "percent-encoding",
465468]
466469
467470[[package]]
468471name = "futures-core"
469−version = "0.3.28"
472+version = "0.3.30"
470473source = "registry+https://github.com/rust-lang/crates.io-index"
471−checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
474+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
472475
473476[[package]]
474477name = "futures-executor"
475−version = "0.3.28"
478+version = "0.3.29"
476479source = "registry+https://github.com/rust-lang/crates.io-index"
477−checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
480+checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
478481dependencies = [
479482 "futures-core",
480483 "futures-task",
@@ -483,15 +486,15 @@ dependencies = [
483486
484487[[package]]
485488name = "futures-task"
486−version = "0.3.28"
489+version = "0.3.30"
487490source = "registry+https://github.com/rust-lang/crates.io-index"
488−checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
491+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
489492
490493[[package]]
491494name = "futures-util"
492−version = "0.3.28"
495+version = "0.3.30"
493496source = "registry+https://github.com/rust-lang/crates.io-index"
494−checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
497+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
495498dependencies = [
496499 "futures-core",
497500 "futures-task",
@@ -500,15 +503,6 @@ dependencies = [
500503 "slab",
501504]
502505
503−[[package]]
504−name = "fuzzy-matcher"
505−version = "0.3.7"
506−source = "registry+https://github.com/rust-lang/crates.io-index"
507−checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
508−dependencies = [
509− "thread_local",
510−]
511−
512506[[package]]
513507name = "getrandom"
514508version = "0.2.9"
@@ -528,15 +522,13 @@ checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
528522
529523[[package]]
530524name = "gix"
531−version = "0.48.0"
525+version = "0.57.0"
532526source = "registry+https://github.com/rust-lang/crates.io-index"
533−checksum = "c1e74cea676de7f53a79f3c0365812b11f6814b81e671b8ee4abae6ca09c7881"
527+checksum = "721c7497ab24b665ed5a1eb2b3526936aa60068e61ba260651f7e77c52feec69"
534528dependencies = [
535529 "gix-actor",
536− "gix-attributes",
537530 "gix-commitgraph",
538531 "gix-config",
539− "gix-credentials",
540532 "gix-date",
541533 "gix-diff",
542534 "gix-discover",
@@ -545,19 +537,16 @@ dependencies = [
545537 "gix-glob",
546538 "gix-hash",
547539 "gix-hashtable",
548− "gix-ignore",
549− "gix-index",
550540 "gix-lock",
551− "gix-mailmap",
552− "gix-negotiate",
541+ "gix-macros",
553542 "gix-object",
554543 "gix-odb",
555544 "gix-pack",
556545 "gix-path",
557− "gix-prompt",
558546 "gix-ref",
559547 "gix-refspec",
560548 "gix-revision",
549+ "gix-revwalk",
561550 "gix-sec",
562551 "gix-tempfile",
563552 "gix-trace",
@@ -565,10 +554,8 @@ dependencies = [
565554 "gix-url",
566555 "gix-utils",
567556 "gix-validate",
568− "gix-worktree",
569− "log",
570557 "once_cell",
571− "signal-hook",
558+ "parking_lot",
572559 "smallvec",
573560 "thiserror",
574561 "unicode-normalization",
@@ -576,81 +563,46 @@ dependencies = [
576563
577564[[package]]
578565name = "gix-actor"
579−version = "0.23.0"
566+version = "0.29.0"
580567source = "registry+https://github.com/rust-lang/crates.io-index"
581−checksum = "1969b77b9ee4cc1755c841987ec6f7622aaca95e952bcafb76973ae59d1b8716"
568+checksum = "886014c4865b93ce268f1d3eddd4fd3261242c3f3ee61eb36009f913016a9059"
582569dependencies = [
583570 "bstr",
584571 "btoi",
585572 "gix-date",
586573 "itoa",
587− "nom",
588− "thiserror",
589−]
590−
591−[[package]]
592−name = "gix-attributes"
593−version = "0.14.1"
594−source = "registry+https://github.com/rust-lang/crates.io-index"
595−checksum = "e3772b0129dcd1fc73e985bbd08a1482d082097d2915cb1ee31ce8092b8e4434"
596−dependencies = [
597− "bstr",
598− "gix-glob",
599− "gix-path",
600− "gix-quote",
601− "kstring",
602− "log",
603− "smallvec",
604− "thiserror",
605− "unicode-bom",
606−]
607−
608−[[package]]
609−name = "gix-bitmap"
610−version = "0.2.5"
611−source = "registry+https://github.com/rust-lang/crates.io-index"
612−checksum = "311e2fa997be6560c564b070c5da2d56d038b645a94e1e5796d5d85a350da33c"
613−dependencies = [
614574 "thiserror",
575+ "winnow 0.5.28",
615576]
616577
617578[[package]]
618579name = "gix-chunk"
619−version = "0.4.3"
580+version = "0.4.6"
620581source = "registry+https://github.com/rust-lang/crates.io-index"
621−checksum = "39db5ed0fc0a2e9b1b8265993f7efdbc30379dec268f3b91b7af0c2de4672fdd"
582+checksum = "2ade37ef69870de0ed966b97c57a3a947a22ff3482a52c3b99b205f77bcb08fb"
622583dependencies = [
623584 "thiserror",
624585]
625586
626−[[package]]
627−name = "gix-command"
628−version = "0.2.6"
629−source = "registry+https://github.com/rust-lang/crates.io-index"
630−checksum = "bb49ab557a37b0abb2415bca2b10e541277dff0565deb5bd5e99fd95f93f51eb"
631−dependencies = [
632− "bstr",
633−]
634−
635587[[package]]
636588name = "gix-commitgraph"
637−version = "0.17.1"
589+version = "0.23.0"
638590source = "registry+https://github.com/rust-lang/crates.io-index"
639−checksum = "ed42baa50075d41c1a0931074ce1a97c5797c7c6fe7591d9f1f2dcd448532c26"
591+checksum = "e7559ea9cefee188cd88b05afcc8e3ef7a3cb4a5c647bccf06b981e591b02b77"
640592dependencies = [
641593 "bstr",
642594 "gix-chunk",
643595 "gix-features",
644596 "gix-hash",
645− "memmap2 0.7.1",
597+ "memmap2",
646598 "thiserror",
647599]
648600
649601[[package]]
650602name = "gix-config"
651−version = "0.25.1"
603+version = "0.33.0"
652604source = "registry+https://github.com/rust-lang/crates.io-index"
653−checksum = "817688c7005a716d9363e267913526adea402dabd947f4ba63842d10cc5132af"
605+checksum = "9c35dc7f9c00a42bbc9cfa1ca2ec0a78ad1b76ff0736d3d35dfd612962244467"
654606dependencies = [
655607 "bstr",
656608 "gix-config-value",
@@ -659,49 +611,32 @@ dependencies = [
659611 "gix-path",
660612 "gix-ref",
661613 "gix-sec",
662− "log",
663614 "memchr",
664− "nom",
665615 "once_cell",
666616 "smallvec",
667617 "thiserror",
668618 "unicode-bom",
619+ "winnow 0.5.28",
669620]
670621
671622[[package]]
672623name = "gix-config-value"
673−version = "0.12.3"
624+version = "0.14.2"
674625source = "registry+https://github.com/rust-lang/crates.io-index"
675−checksum = "83960be5e99266bcf55dae5a24731bbd39f643bfb68f27e939d6b06836b5b87d"
626+checksum = "39f388cb2396aee82d6f460a2e7770659bdf8854e9e5478f7d2b1324a9698284"
676627dependencies = [
677− "bitflags 2.3.3",
628+ "bitflags 2.4.1",
678629 "bstr",
679630 "gix-path",
680631 "libc",
681632 "thiserror",
682633]
683634
684−[[package]]
685−name = "gix-credentials"
686−version = "0.16.1"
687−source = "registry+https://github.com/rust-lang/crates.io-index"
688−checksum = "75a75565e0e6e7f80cfa4eb1b05cc448c6846ddd48dcf413a28875fbc11ee9af"
689−dependencies = [
690− "bstr",
691− "gix-command",
692− "gix-config-value",
693− "gix-path",
694− "gix-prompt",
695− "gix-sec",
696− "gix-url",
697− "thiserror",
698−]
699−
700635[[package]]
701636name = "gix-date"
702−version = "0.7.0"
637+version = "0.8.2"
703638source = "registry+https://github.com/rust-lang/crates.io-index"
704−checksum = "8e9a04a1d2387c955ec91059d56b673000dd24f3c07cad08ed253e36381782bf"
639+checksum = "9d85f4a01e0d05c3de585e28bae514d4baf01655e3fc3f14ce6f30bf62405345"
705640dependencies = [
706641 "bstr",
707642 "itoa",
@@ -711,21 +646,21 @@ dependencies = [
711646
712647[[package]]
713648name = "gix-diff"
714−version = "0.32.0"
649+version = "0.39.0"
715650source = "registry+https://github.com/rust-lang/crates.io-index"
716−checksum = "aaf5d9b9b521b284ebe53ee69eee33341835ec70edc314f36b2100ea81396121"
651+checksum = "ac0e75f5afd2f6c47c800b6b0a000a08045739d0450d20482e8faa42543f62d1"
717652dependencies = [
653+ "bstr",
718654 "gix-hash",
719655 "gix-object",
720− "imara-diff",
721656 "thiserror",
722657]
723658
724659[[package]]
725660name = "gix-discover"
726−version = "0.21.1"
661+version = "0.28.0"
727662source = "registry+https://github.com/rust-lang/crates.io-index"
728−checksum = "272aad20dc63dedba76615373dd8885fb5aebe4795e5b5b0aa2a24e63c82085c"
663+checksum = "f0b63ce2ad81632ac1a84ac370f85a5e580c3261580bd85ea17ff35d3a0bba18"
729664dependencies = [
730665 "bstr",
731666 "dunce",
@@ -738,9 +673,9 @@ dependencies = [
738673
739674[[package]]
740675name = "gix-features"
741−version = "0.31.1"
676+version = "0.37.0"
742677source = "registry+https://github.com/rust-lang/crates.io-index"
743−checksum = "06142d8cff5d17509399b04052b64d2f9b3a311d5cff0b1a32b220f62cd0d595"
678+checksum = "befe7edea299a824504b5acc96d7a3a538125b38c42f3a8379f6912a29c90c81"
744679dependencies = [
745680 "crc32fast",
746681 "flate2",
@@ -756,20 +691,20 @@ dependencies = [
756691
757692[[package]]
758693name = "gix-fs"
759−version = "0.3.0"
694+version = "0.9.0"
760695source = "registry+https://github.com/rust-lang/crates.io-index"
761−checksum = "bb15956bc0256594c62a2399fcf6958a02a11724217eddfdc2b49b21b6292496"
696+checksum = "007017ce93b819ea52c0ec68306f7d72212a2d307c3d70f1548c7141c015d0a1"
762697dependencies = [
763698 "gix-features",
764699]
765700
766701[[package]]
767702name = "gix-glob"
768−version = "0.9.1"
703+version = "0.15.0"
769704source = "registry+https://github.com/rust-lang/crates.io-index"
770−checksum = "c18bdff83143d61e7d60da6183b87542a870d026b2a2d0b30170b8e9c0cd321a"
705+checksum = "61fb116c2516d3a1170e010118f639944a6389872c875a008960cdab2a44ac72"
771706dependencies = [
772− "bitflags 2.3.3",
707+ "bitflags 2.4.1",
773708 "bstr",
774709 "gix-features",
775710 "gix-path",
@@ -777,64 +712,30 @@ dependencies = [
777712
778713[[package]]
779714name = "gix-hash"
780−version = "0.11.3"
715+version = "0.14.0"
781716source = "registry+https://github.com/rust-lang/crates.io-index"
782−checksum = "a0dd58cdbe7ffa4032fc111864c80d5f8cecd9a2c9736c97ae7e5be834188272"
717+checksum = "c52c3170a17b6031833cd2eb4ad7bc23f2755d06e6e70f78dec21d42e8fe1b30"
783718dependencies = [
784− "hex",
719+ "faster-hex",
785720 "thiserror",
786721]
787722
788723[[package]]
789724name = "gix-hashtable"
790−version = "0.2.3"
725+version = "0.5.0"
791726source = "registry+https://github.com/rust-lang/crates.io-index"
792−checksum = "9e133bc56d938eaec1c675af7c681a51de9662b0ada779f45607b967a10da77a"
727+checksum = "40a838e6366a5e5b84668b6997ce0981b833136468e8ba949f424c0ef2927eba"
793728dependencies = [
794729 "gix-hash",
795− "hashbrown 0.14.0",
730+ "hashbrown 0.14.3",
796731 "parking_lot",
797732]
798733
799−[[package]]
800−name = "gix-ignore"
801−version = "0.4.1"
802−source = "registry+https://github.com/rust-lang/crates.io-index"
803−checksum = "ca801f2d0535210f77b33e2c067d565aedecacc82f1b3dbce26da1388ebc4634"
804−dependencies = [
805− "bstr",
806− "gix-glob",
807− "gix-path",
808− "unicode-bom",
809−]
810−
811−[[package]]
812−name = "gix-index"
813−version = "0.20.0"
814−source = "registry+https://github.com/rust-lang/crates.io-index"
815−checksum = "68099abdf6ee50ae3c897e8b05de96871cbe54d52a37cdf559101f911b883562"
816−dependencies = [
817− "bitflags 2.3.3",
818− "bstr",
819− "btoi",
820− "filetime",
821− "gix-bitmap",
822− "gix-features",
823− "gix-hash",
824− "gix-lock",
825− "gix-object",
826− "gix-traverse",
827− "itoa",
828− "memmap2 0.7.1",
829− "smallvec",
830− "thiserror",
831−]
832−
833734[[package]]
834735name = "gix-lock"
835−version = "7.0.0"
736+version = "12.0.0"
836737source = "registry+https://github.com/rust-lang/crates.io-index"
837−checksum = "328f50aad713ab606caeaf834459ef915ccdfbb9133ac6cd54616d601aa9249f"
738+checksum = "6cf112ddee94223c119a8534dad027740dc3aba3365ac5edeef8a7f6660c74db"
838739dependencies = [
839740 "gix-tempfile",
840741 "gix-utils",
@@ -842,38 +743,21 @@ dependencies = [
842743]
843744
844745[[package]]
845−name = "gix-mailmap"
846−version = "0.15.0"
847−source = "registry+https://github.com/rust-lang/crates.io-index"
848−checksum = "1787e3c37fc43b1f7c0e3be6196c6837b3ba5f869190dfeaa444b816f0a7f34b"
849−dependencies = [
850− "bstr",
851− "gix-actor",
852− "gix-date",
853− "thiserror",
854−]
855−
856−[[package]]
857−name = "gix-negotiate"
858−version = "0.4.0"
746+name = "gix-macros"
747+version = "0.1.2"
859748source = "registry+https://github.com/rust-lang/crates.io-index"
860−checksum = "4e7bce64d4452dd609f44d04b14b29da2e0ad2c45fcdf4ce1472a5f5f8ec21c2"
749+checksum = "fc207b64189cf71bcb17fc841ab99a5d63d0845546b611e2703ce467f659323a"
861750dependencies = [
862− "bitflags 2.3.3",
863− "gix-commitgraph",
864− "gix-date",
865− "gix-hash",
866− "gix-object",
867− "gix-revwalk",
868− "smallvec",
869− "thiserror",
751+ "proc-macro2",
752+ "quote",
753+ "syn 2.0.38",
870754]
871755
872756[[package]]
873757name = "gix-object"
874−version = "0.32.0"
758+version = "0.40.0"
875759source = "registry+https://github.com/rust-lang/crates.io-index"
876−checksum = "a953f3d7ffad16734aa3ab1d05807972c80e339d1bd9dde03e0198716b99e2a6"
760+checksum = "e4c77e47ffba92127faf632a841fce23d19547e269bd8b88e68961a70eab4e93"
877761dependencies = [
878762 "bstr",
879763 "btoi",
@@ -882,18 +766,17 @@ dependencies = [
882766 "gix-features",
883767 "gix-hash",
884768 "gix-validate",
885− "hex",
886769 "itoa",
887− "nom",
888770 "smallvec",
889771 "thiserror",
772+ "winnow 0.5.28",
890773]
891774
892775[[package]]
893776name = "gix-odb"
894−version = "0.49.1"
777+version = "0.56.0"
895778source = "registry+https://github.com/rust-lang/crates.io-index"
896−checksum = "f6418cff00ecc2713b58c8e04bff30dda808fbba1a080e7248b299d069894a01"
779+checksum = "a3254f2005cc7553ea78e85e816a09150c6f7a64e6b7627b8d1fdc56721bea73"
897780dependencies = [
898781 "arc-swap",
899782 "gix-date",
@@ -910,21 +793,19 @@ dependencies = [
910793
911794[[package]]
912795name = "gix-pack"
913−version = "0.39.1"
796+version = "0.46.0"
914797source = "registry+https://github.com/rust-lang/crates.io-index"
915−checksum = "414935138d90043ea5898de7a93f02c2558e52652492719470e203ef26a8fd0a"
798+checksum = "02ebc8cd657eec207d82d8f876ca402361308ecd3c87a47935b0299506257b4f"
916799dependencies = [
917800 "clru",
918801 "gix-chunk",
919− "gix-diff",
920802 "gix-features",
921803 "gix-hash",
922804 "gix-hashtable",
923805 "gix-object",
924806 "gix-path",
925807 "gix-tempfile",
926− "gix-traverse",
927− "memmap2 0.7.1",
808+ "memmap2",
928809 "parking_lot",
929810 "smallvec",
930811 "thiserror",
@@ -932,9 +813,9 @@ dependencies = [
932813
933814[[package]]
934815name = "gix-path"
935−version = "0.8.3"
816+version = "0.10.2"
936817source = "registry+https://github.com/rust-lang/crates.io-index"
937−checksum = "dfca182d2575ded2ed38280f1ebf75cd5d3790b77e0872de07854cf085821fbe"
818+checksum = "10931652a3da126990ac93bce1b1c600cc99d7c268d712b6360ed52174ce1b68"
938819dependencies = [
939820 "bstr",
940821 "gix-trace",
@@ -943,24 +824,11 @@ dependencies = [
943824 "thiserror",
944825]
945826
946−[[package]]
947−name = "gix-prompt"
948−version = "0.5.2"
949−source = "registry+https://github.com/rust-lang/crates.io-index"
950−checksum = "8dfd363fd89a40c1e7bff9c9c1b136cd2002480f724b0c627c1bc771cd5480ec"
951−dependencies = [
952− "gix-command",
953− "gix-config-value",
954− "parking_lot",
955− "rustix",
956− "thiserror",
957−]
958−
959827[[package]]
960828name = "gix-quote"
961−version = "0.4.5"
829+version = "0.4.9"
962830source = "registry+https://github.com/rust-lang/crates.io-index"
963−checksum = "3874de636c2526de26a3405b8024b23ef1a327bebf4845d770d00d48700b6a40"
831+checksum = "6c43530cb94a7759807e6f8d180e17ac9c65673b891645c6c433831dc0cf4342"
964832dependencies = [
965833 "bstr",
966834 "btoi",
@@ -969,9 +837,9 @@ dependencies = [
969837
970838[[package]]
971839name = "gix-ref"
972−version = "0.32.1"
840+version = "0.40.0"
973841source = "registry+https://github.com/rust-lang/crates.io-index"
974−checksum = "39453f4e5f23cddc2e6e4cca2ba20adfdbec29379e3ca829714dfe98ae068ccd"
842+checksum = "baa951b3b850d6d1be4f900768e49af20b76bf9505beac22af723d57249a2f1d"
975843dependencies = [
976844 "gix-actor",
977845 "gix-date",
@@ -983,16 +851,16 @@ dependencies = [
983851 "gix-path",
984852 "gix-tempfile",
985853 "gix-validate",
986− "memmap2 0.7.1",
987− "nom",
854+ "memmap2",
988855 "thiserror",
856+ "winnow 0.5.28",
989857]
990858
991859[[package]]
992860name = "gix-refspec"
993−version = "0.13.0"
861+version = "0.21.0"
994862source = "registry+https://github.com/rust-lang/crates.io-index"
995−checksum = "b8e76ff1f82fba295a121e31ab02f69642994e532c45c0c899aa393f4b740302"
863+checksum = "88c61f849d58c06e3068a0b601cf10127d2a07cdad00a725ed66cf303f76f6b3"
996864dependencies = [
997865 "bstr",
998866 "gix-hash",
@@ -1004,9 +872,9 @@ dependencies = [
1004872
1005873[[package]]
1006874name = "gix-revision"
1007−version = "0.17.0"
875+version = "0.25.0"
1008876source = "registry+https://github.com/rust-lang/crates.io-index"
1009−checksum = "237428a7d3978e8572964e1e45d984027c2acc94df47e594baa6c4b0da7c9922"
877+checksum = "bcbbf91f4200c5c76802ef5f057b96f5a336827508881fe55a780be71a794d22"
1010878dependencies = [
1011879 "bstr",
1012880 "gix-date",
@@ -1014,14 +882,15 @@ dependencies = [
1014882 "gix-hashtable",
1015883 "gix-object",
1016884 "gix-revwalk",
885+ "gix-trace",
1017886 "thiserror",
1018887]
1019888
1020889[[package]]
1021890name = "gix-revwalk"
1022−version = "0.3.0"
891+version = "0.11.0"
1023892source = "registry+https://github.com/rust-lang/crates.io-index"
1024−checksum = "028d50fcaf8326a8f79a359490d9ca9fb4e2b51ac9ac86503560d0bcc888d2eb"
893+checksum = "33ab66354d83f70f2730391747d0c75f94ef3c3dd40ebde2e206db9faaf7a0a7"
1025894dependencies = [
1026895 "gix-commitgraph",
1027896 "gix-date",
@@ -1034,42 +903,40 @@ dependencies = [
1034903
1035904[[package]]
1036905name = "gix-sec"
1037−version = "0.8.3"
906+version = "0.10.2"
1038907source = "registry+https://github.com/rust-lang/crates.io-index"
1039−checksum = "ede298863db2a0574a14070991710551e76d1f47c9783b62d4fcbca17f56371c"
908+checksum = "a9963f38a42144253ed4d571882d7db5ef644c12e6726e4b75135597cc9d0e1a"
1040909dependencies = [
1041− "bitflags 2.3.3",
910+ "bitflags 2.4.1",
1042911 "gix-path",
1043912 "libc",
1044− "windows",
913+ "windows 0.52.0",
1045914]
1046915
1047916[[package]]
1048917name = "gix-tempfile"
1049−version = "7.0.0"
918+version = "12.0.0"
1050919source = "registry+https://github.com/rust-lang/crates.io-index"
1051−checksum = "4fac8310c17406ea619af72f42ee46dac795110f68f41b4f4fa231b69889c6a2"
920+checksum = "e76a494bd530e1a1309188ff971825a24f159c76c2db0bf71fa5dfb469a2c915"
1052921dependencies = [
1053922 "gix-fs",
1054923 "libc",
1055924 "once_cell",
1056925 "parking_lot",
1057− "signal-hook",
1058− "signal-hook-registry",
1059926 "tempfile",
1060927]
1061928
1062929[[package]]
1063930name = "gix-trace"
1064−version = "0.1.2"
931+version = "0.1.5"
1065932source = "registry+https://github.com/rust-lang/crates.io-index"
1066−checksum = "103eac621617be3ebe0605c9065ca51a223279a23218aaf67d10daa6e452f663"
933+checksum = "bda62acb44dd86a40c7c3762a5403cfc1ac789ea559df54085cedf79864f809e"
1067934
1068935[[package]]
1069936name = "gix-traverse"
1070−version = "0.29.0"
937+version = "0.36.0"
1071938source = "registry+https://github.com/rust-lang/crates.io-index"
1072−checksum = "e3cdfd54598db4fae57d5ae6f52958422b2d13382d2745796bfe5c8015ffa86e"
939+checksum = "8661fab39985c9214e56d81a63ceb5886ac948cec2fba76c39494d1e0e307ea8"
1073940dependencies = [
1074941 "gix-commitgraph",
1075942 "gix-date",
@@ -1083,9 +950,9 @@ dependencies = [
1083950
1084951[[package]]
1085952name = "gix-url"
1086−version = "0.20.1"
953+version = "0.26.0"
1087954source = "registry+https://github.com/rust-lang/crates.io-index"
1088−checksum = "beaede6dbc83f408b19adfd95bb52f1dbf01fb8862c3faf6c6243e2e67fcdfa1"
955+checksum = "10a0129c1e8b52736d7c5128300a4485dbc85863001371e2771ac1754bd89fd7"
1089956dependencies = [
1090957 "bstr",
1091958 "gix-features",
@@ -1097,94 +964,71 @@ dependencies = [
1097964
1098965[[package]]
1099966name = "gix-utils"
1100−version = "0.1.4"
967+version = "0.1.7"
1101968source = "registry+https://github.com/rust-lang/crates.io-index"
1102−checksum = "7058c94f4164fcf5b8457d35f6d8f6e1007f9f7f938c9c7684a7e01d23c6ddde"
969+checksum = "ab9277a5e32e85d53f738096d872a4a9b76067ad471894ad31bd99c8fa2da1dc"
1103970dependencies = [
1104− "fastrand 2.0.0",
971+ "fastrand",
1105972]
1106973
1107974[[package]]
1108975name = "gix-validate"
1109−version = "0.7.6"
1110−source = "registry+https://github.com/rust-lang/crates.io-index"
1111−checksum = "8d092b594c8af00a3a31fe526d363ee8a51a6f29d8496cdb991ed2f01ec0ec13"
1112−dependencies = [
1113− "bstr",
1114− "thiserror",
1115−]
1116−
1117−[[package]]
1118−name = "gix-worktree"
1119−version = "0.21.1"
976+version = "0.8.2"
1120977source = "registry+https://github.com/rust-lang/crates.io-index"
1121−checksum = "c1363b9aa66b9e14412ac04e1f759827203f491729d92172535a8ce6cde02efa"
978+checksum = "f805ebbdbaa4bfd98e2ee43e6d14099b8a4d9141f5d7b8202fea4d48e44263e7"
1122979dependencies = [
1123980 "bstr",
1124− "filetime",
1125− "gix-attributes",
1126− "gix-features",
1127− "gix-fs",
1128− "gix-glob",
1129− "gix-hash",
1130− "gix-ignore",
1131− "gix-index",
1132− "gix-object",
1133− "gix-path",
1134− "io-close",
1135981 "thiserror",
1136982]
1137983
1138984[[package]]
1139985name = "globset"
1140−version = "0.4.10"
986+version = "0.4.14"
1141987source = "registry+https://github.com/rust-lang/crates.io-index"
1142−checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
988+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
1143989dependencies = [
1144− "aho-corasick 0.7.20",
990+ "aho-corasick",
1145991 "bstr",
1146− "fnv",
1147992 "log",
1148− "regex",
993+ "regex-automata",
994+ "regex-syntax",
1149995]
1150996
1151997[[package]]
1152998name = "grep-matcher"
1153−version = "0.1.6"
999+version = "0.1.7"
11541000source = "registry+https://github.com/rust-lang/crates.io-index"
1155−checksum = "3902ca28f26945fe35cad349d776f163981d777fee382ccd6ef451126f51b319"
1001+checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02"
11561002dependencies = [
11571003 "memchr",
11581004]
11591005
11601006[[package]]
11611007name = "grep-regex"
1162−version = "0.1.11"
1008+version = "0.1.12"
11631009source = "registry+https://github.com/rust-lang/crates.io-index"
1164−checksum = "997598b41d53a37a2e3fc5300d5c11d825368c054420a9c65125b8fe1078463f"
1010+checksum = "f748bb135ca835da5cbc67ca0e6955f968db9c5df74ca4f56b18e1ddbc68230d"
11651011dependencies = [
1166− "aho-corasick 0.7.20",
11671012 "bstr",
11681013 "grep-matcher",
11691014 "log",
1170− "regex",
1171− "regex-syntax 0.6.29",
1172− "thread_local",
1015+ "regex-automata",
1016+ "regex-syntax",
11731017]
11741018
11751019[[package]]
11761020name = "grep-searcher"
1177−version = "0.1.11"
1021+version = "0.1.13"
11781022source = "registry+https://github.com/rust-lang/crates.io-index"
1179−checksum = "5601c4b9f480f0c9ebb40b1f6cbf447b8a50c5369223937a6c5214368c58779f"
1023+checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54"
11801024dependencies = [
11811025 "bstr",
1182− "bytecount",
11831026 "encoding_rs",
11841027 "encoding_rs_io",
11851028 "grep-matcher",
11861029 "log",
1187− "memmap2 0.5.10",
1030+ "memchr",
1031+ "memmap2",
11881032]
11891033
11901034[[package]]
@@ -1192,37 +1036,36 @@ name = "hashbrown"
11921036version = "0.12.3"
11931037source = "registry+https://github.com/rust-lang/crates.io-index"
11941038checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1195−dependencies = [
1196− "ahash 0.7.6",
1197−]
11981039
11991040[[package]]
12001041name = "hashbrown"
1201−version = "0.14.0"
1042+version = "0.14.3"
12021043source = "registry+https://github.com/rust-lang/crates.io-index"
1203−checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
1044+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
12041045dependencies = [
1205− "ahash 0.8.3",
1046+ "ahash",
12061047 "allocator-api2",
12071048]
12081049
12091050[[package]]
12101051name = "helix-core"
1211−version = "0.6.0"
1052+version = "23.10.0"
12121053dependencies = [
1213− "ahash 0.8.3",
1054+ "ahash",
12141055 "arc-swap",
1215− "bitflags 2.3.3",
1056+ "bitflags 2.4.1",
12161057 "chrono",
12171058 "dunce",
12181059 "encoding_rs",
12191060 "etcetera",
1220− "hashbrown 0.14.0",
1061+ "hashbrown 0.14.3",
12211062 "helix-loader",
12221063 "imara-diff",
12231064 "indoc",
12241065 "log",
1066+ "nucleo",
12251067 "once_cell",
1068+ "parking_lot",
12261069 "quickcheck",
12271070 "regex",
12281071 "ropey",
@@ -1241,7 +1084,7 @@ dependencies = [
12411084
12421085[[package]]
12431086name = "helix-dap"
1244−version = "0.6.0"
1087+version = "23.10.0"
12451088dependencies = [
12461089 "anyhow",
12471090 "fern",
@@ -1254,12 +1097,21 @@ dependencies = [
12541097 "which",
12551098]
12561099
1100+[[package]]
1101+name = "helix-event"
1102+version = "23.10.0"
1103+dependencies = [
1104+ "parking_lot",
1105+ "tokio",
1106+]
1107+
12571108[[package]]
12581109name = "helix-loader"
1259−version = "0.6.0"
1110+version = "23.10.0"
12601111dependencies = [
12611112 "anyhow",
12621113 "cc",
1114+ "dunce",
12631115 "etcetera",
12641116 "libloading",
12651117 "log",
@@ -1274,11 +1126,12 @@ dependencies = [
12741126
12751127[[package]]
12761128name = "helix-lsp"
1277−version = "0.6.0"
1129+version = "23.10.0"
12781130dependencies = [
12791131 "anyhow",
12801132 "futures-executor",
12811133 "futures-util",
1134+ "globset",
12821135 "helix-core",
12831136 "helix-loader",
12841137 "helix-parsec",
@@ -1295,11 +1148,11 @@ dependencies = [
12951148
12961149[[package]]
12971150name = "helix-parsec"
1298−version = "0.6.0"
1151+version = "23.10.0"
12991152
13001153[[package]]
13011154name = "helix-term"
1302−version = "0.6.0"
1155+version = "23.10.0"
13031156dependencies = [
13041157 "anyhow",
13051158 "arc-swap",
@@ -1308,11 +1161,11 @@ dependencies = [
13081161 "crossterm",
13091162 "fern",
13101163 "futures-util",
1311− "fuzzy-matcher",
13121164 "grep-regex",
13131165 "grep-searcher",
13141166 "helix-core",
13151167 "helix-dap",
1168+ "helix-event",
13161169 "helix-loader",
13171170 "helix-lsp",
13181171 "helix-tui",
@@ -1322,7 +1175,9 @@ dependencies = [
13221175 "indoc",
13231176 "libc",
13241177 "log",
1178+ "nucleo",
13251179 "once_cell",
1180+ "open",
13261181 "pulldown-cmark",
13271182 "serde",
13281183 "serde_json",
@@ -1333,14 +1188,15 @@ dependencies = [
13331188 "tokio",
13341189 "tokio-stream",
13351190 "toml",
1191+ "url",
13361192 "which",
13371193]
13381194
13391195[[package]]
13401196name = "helix-tui"
1341−version = "0.6.0"
1197+version = "23.10.0"
13421198dependencies = [
1343− "bitflags 2.3.3",
1199+ "bitflags 2.4.1",
13441200 "cassowary",
13451201 "crossterm",
13461202 "helix-core",
@@ -1354,12 +1210,13 @@ dependencies = [
13541210
13551211[[package]]
13561212name = "helix-vcs"
1357−version = "0.6.0"
1213+version = "23.10.0"
13581214dependencies = [
13591215 "anyhow",
13601216 "arc-swap",
13611217 "gix",
13621218 "helix-core",
1219+ "helix-event",
13631220 "imara-diff",
13641221 "log",
13651222 "parking_lot",
@@ -1369,17 +1226,18 @@ dependencies = [
13691226
13701227[[package]]
13711228name = "helix-view"
1372−version = "0.6.0"
1229+version = "23.10.0"
13731230dependencies = [
13741231 "anyhow",
13751232 "arc-swap",
1376− "bitflags 2.3.3",
1233+ "bitflags 2.4.1",
13771234 "chardetng",
13781235 "clipboard-win",
13791236 "crossterm",
13801237 "futures-util",
13811238 "helix-core",
13821239 "helix-dap",
1240+ "helix-event",
13831241 "helix-loader",
13841242 "helix-lsp",
13851243 "helix-tui",
@@ -1388,6 +1246,7 @@ dependencies = [
13881246 "log",
13891247 "once_cell",
13901248 "parking_lot",
1249+ "rustix",
13911250 "serde",
13921251 "serde_json",
13931252 "slotmap",
@@ -1407,25 +1266,13 @@ dependencies = [
14071266 "libc",
14081267]
14091268
1410−[[package]]
1411−name = "hermit-abi"
1412−version = "0.3.1"
1413−source = "registry+https://github.com/rust-lang/crates.io-index"
1414−checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
1415−
1416−[[package]]
1417−name = "hex"
1418−version = "0.4.3"
1419−source = "registry+https://github.com/rust-lang/crates.io-index"
1420−checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1421−
14221269[[package]]
14231270name = "home"
1424−version = "0.5.4"
1271+version = "0.5.5"
14251272source = "registry+https://github.com/rust-lang/crates.io-index"
1426−checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
1273+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
14271274dependencies = [
1428− "winapi",
1275+ "windows-sys 0.48.0",
14291276]
14301277
14311278[[package]]
@@ -1439,7 +1286,7 @@ dependencies = [
14391286 "iana-time-zone-haiku",
14401287 "js-sys",
14411288 "wasm-bindgen",
1442− "windows",
1289+ "windows 0.48.0",
14431290]
14441291
14451292[[package]]
@@ -1454,9 +1301,9 @@ dependencies = [
14541301
14551302[[package]]
14561303name = "idna"
1457−version = "0.4.0"
1304+version = "0.5.0"
14581305source = "registry+https://github.com/rust-lang/crates.io-index"
1459−checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
1306+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
14601307dependencies = [
14611308 "unicode-bidi",
14621309 "unicode-normalization",
@@ -1464,17 +1311,16 @@ dependencies = [
14641311
14651312[[package]]
14661313name = "ignore"
1467−version = "0.4.20"
1314+version = "0.4.21"
14681315source = "registry+https://github.com/rust-lang/crates.io-index"
1469−checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
1316+checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
14701317dependencies = [
1318+ "crossbeam-deque",
14711319 "globset",
1472− "lazy_static",
14731320 "log",
14741321 "memchr",
1475− "regex",
1322+ "regex-automata",
14761323 "same-file",
1477− "thread_local",
14781324 "walkdir",
14791325 "winapi-util",
14801326]
@@ -1485,7 +1331,7 @@ version = "0.1.5"
14851331source = "registry+https://github.com/rust-lang/crates.io-index"
14861332checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
14871333dependencies = [
1488− "ahash 0.8.3",
1334+ "ahash",
14891335 "hashbrown 0.12.3",
14901336]
14911337
@@ -1496,43 +1342,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
14961342checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
14971343dependencies = [
14981344 "equivalent",
1499− "hashbrown 0.14.0",
1345+ "hashbrown 0.14.3",
15001346]
15011347
15021348[[package]]
15031349name = "indoc"
1504−version = "2.0.2"
1505−source = "registry+https://github.com/rust-lang/crates.io-index"
1506−checksum = "761cde40c27e2a9877f8c928fd248b7eec9dd48623dd514b256858ca593fbba7"
1507−
1508−[[package]]
1509−name = "instant"
1510−version = "0.1.12"
1350+version = "2.0.4"
15111351source = "registry+https://github.com/rust-lang/crates.io-index"
1512−checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
1513−dependencies = [
1514− "cfg-if",
1515−]
1352+checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
15161353
15171354[[package]]
1518−name = "io-close"
1519−version = "0.3.7"
1355+name = "is-docker"
1356+version = "0.2.0"
15201357source = "registry+https://github.com/rust-lang/crates.io-index"
1521−checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc"
1358+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
15221359dependencies = [
1523− "libc",
1524− "winapi",
1360+ "once_cell",
15251361]
15261362
15271363[[package]]
1528−name = "io-lifetimes"
1529−version = "1.0.10"
1364+name = "is-wsl"
1365+version = "0.4.0"
15301366source = "registry+https://github.com/rust-lang/crates.io-index"
1531−checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
1367+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
15321368dependencies = [
1533− "hermit-abi 0.3.1",
1534− "libc",
1535− "windows-sys 0.48.0",
1369+ "is-docker",
1370+ "once_cell",
15361371]
15371372
15381373[[package]]
@@ -1550,32 +1385,17 @@ dependencies = [
15501385 "wasm-bindgen",
15511386]
15521387
1553−[[package]]
1554−name = "kstring"
1555−version = "2.0.0"
1556−source = "registry+https://github.com/rust-lang/crates.io-index"
1557−checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747"
1558−dependencies = [
1559− "static_assertions",
1560−]
1561−
1562−[[package]]
1563−name = "lazy_static"
1564−version = "1.4.0"
1565−source = "registry+https://github.com/rust-lang/crates.io-index"
1566−checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
1567−
15681388[[package]]
15691389name = "libc"
1570−version = "0.2.147"
1390+version = "0.2.151"
15711391source = "registry+https://github.com/rust-lang/crates.io-index"
1572−checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
1392+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
15731393
15741394[[package]]
15751395name = "libloading"
1576−version = "0.8.0"
1396+version = "0.8.1"
15771397source = "registry+https://github.com/rust-lang/crates.io-index"
1578−checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb"
1398+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
15791399dependencies = [
15801400 "cfg-if",
15811401 "windows-sys 0.48.0",
@@ -1592,9 +1412,9 @@ dependencies = [
15921412
15931413[[package]]
15941414name = "linux-raw-sys"
1595−version = "0.3.4"
1415+version = "0.4.11"
15961416source = "registry+https://github.com/rust-lang/crates.io-index"
1597−checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
1417+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
15981418
15991419[[package]]
16001420name = "lock_api"
@@ -1608,15 +1428,15 @@ dependencies = [
16081428
16091429[[package]]
16101430name = "log"
1611−version = "0.4.19"
1431+version = "0.4.20"
16121432source = "registry+https://github.com/rust-lang/crates.io-index"
1613−checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
1433+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
16141434
16151435[[package]]
16161436name = "lsp-types"
1617−version = "0.94.0"
1437+version = "0.95.0"
16181438source = "registry+https://github.com/rust-lang/crates.io-index"
1619−checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
1439+checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
16201440dependencies = [
16211441 "bitflags 1.3.2",
16221442 "serde",
@@ -1627,41 +1447,26 @@ dependencies = [
16271447
16281448[[package]]
16291449name = "memchr"
1630−version = "2.5.0"
1450+version = "2.6.3"
16311451source = "registry+https://github.com/rust-lang/crates.io-index"
1632−checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
1452+checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
16331453
16341454[[package]]
16351455name = "memmap2"
1636−version = "0.5.10"
1637−source = "registry+https://github.com/rust-lang/crates.io-index"
1638−checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
1639−dependencies = [
1640− "libc",
1641−]
1642−
1643−[[package]]
1644−name = "memmap2"
1645−version = "0.7.1"
1456+version = "0.9.0"
16461457source = "registry+https://github.com/rust-lang/crates.io-index"
1647−checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6"
1458+checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
16481459dependencies = [
16491460 "libc",
16501461]
16511462
16521463[[package]]
1653−name = "minimal-lexical"
1654−version = "0.2.1"
1655−source = "registry+https://github.com/rust-lang/crates.io-index"
1656−checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
1657−
1658−[[package]]
1659−name = "miniz_oxide"
1660−version = "0.6.2"
1464+name = "memoffset"
1465+version = "0.9.0"
16611466source = "registry+https://github.com/rust-lang/crates.io-index"
1662−checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
1467+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
16631468dependencies = [
1664− "adler",
1469+ "autocfg",
16651470]
16661471
16671472[[package]]
@@ -1675,24 +1480,36 @@ dependencies = [
16751480
16761481[[package]]
16771482name = "mio"
1678−version = "0.8.6"
1483+version = "0.8.9"
16791484source = "registry+https://github.com/rust-lang/crates.io-index"
1680−checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
1485+checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
16811486dependencies = [
16821487 "libc",
16831488 "log",
16841489 "wasi",
1685− "windows-sys 0.45.0",
1490+ "windows-sys 0.48.0",
1491+]
1492+
1493+[[package]]
1494+name = "nucleo"
1495+version = "0.2.1"
1496+source = "registry+https://github.com/rust-lang/crates.io-index"
1497+checksum = "ae5331f4bcce475cf28cb29c95366c3091af4b0aa7703f1a6bc858f29718fdf3"
1498+dependencies = [
1499+ "nucleo-matcher",
1500+ "parking_lot",
1501+ "rayon",
16861502]
16871503
16881504[[package]]
1689−name = "nom"
1690−version = "7.1.3"
1505+name = "nucleo-matcher"
1506+version = "0.2.0"
16911507source = "registry+https://github.com/rust-lang/crates.io-index"
1692−checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
1508+checksum = "1b702b402fe286162d1f00b552a046ce74365d2ac473a2607ff36ba650f9bd57"
16931509dependencies = [
1510+ "cov-mark",
16941511 "memchr",
1695− "minimal-lexical",
1512+ "unicode-segmentation",
16961513]
16971514
16981515[[package]]
@@ -1710,7 +1527,7 @@ version = "1.15.0"
17101527source = "registry+https://github.com/rust-lang/crates.io-index"
17111528checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
17121529dependencies = [
1713− "hermit-abi 0.2.6",
1530+ "hermit-abi",
17141531 "libc",
17151532]
17161533
@@ -1734,9 +1551,20 @@ dependencies = [
17341551
17351552[[package]]
17361553name = "once_cell"
1737−version = "1.18.0"
1554+version = "1.19.0"
1555+source = "registry+https://github.com/rust-lang/crates.io-index"
1556+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
1557+
1558+[[package]]
1559+name = "open"
1560+version = "5.0.1"
17381561source = "registry+https://github.com/rust-lang/crates.io-index"
1739−checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
1562+checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349"
1563+dependencies = [
1564+ "is-wsl",
1565+ "libc",
1566+ "pathdiff",
1567+]
17401568
17411569[[package]]
17421570name = "parking_lot"
@@ -1761,17 +1589,23 @@ dependencies = [
17611589 "windows-sys 0.45.0",
17621590]
17631591
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+
17641598[[package]]
17651599name = "percent-encoding"
1766−version = "2.3.0"
1600+version = "2.3.1"
17671601source = "registry+https://github.com/rust-lang/crates.io-index"
1768−checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
1602+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
17691603
17701604[[package]]
17711605name = "pin-project-lite"
1772−version = "0.2.9"
1606+version = "0.2.12"
17731607source = "registry+https://github.com/rust-lang/crates.io-index"
1774−checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
1608+checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
17751609
17761610[[package]]
17771611name = "pin-utils"
@@ -1781,18 +1615,18 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
17811615
17821616[[package]]
17831617name = "proc-macro2"
1784−version = "1.0.63"
1618+version = "1.0.69"
17851619source = "registry+https://github.com/rust-lang/crates.io-index"
1786−checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
1620+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
17871621dependencies = [
17881622 "unicode-ident",
17891623]
17901624
17911625[[package]]
17921626name = "prodash"
1793−version = "25.0.0"
1627+version = "28.0.0"
17941628source = "registry+https://github.com/rust-lang/crates.io-index"
1795−checksum = "3236ce1618b6da4c7b618e0143c4d5b5dc190f75f81c49f248221382f7e9e9ae"
1629+checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
17961630
17971631[[package]]
17981632name = "pulldown-cmark"
@@ -1841,6 +1675,28 @@ dependencies = [
18411675 "getrandom",
18421676]
18431677
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+
18441700[[package]]
18451701name = "redox_syscall"
18461702version = "0.2.16"
@@ -1852,47 +1708,47 @@ dependencies = [
18521708
18531709[[package]]
18541710name = "redox_syscall"
1855−version = "0.3.5"
1711+version = "0.4.1"
18561712source = "registry+https://github.com/rust-lang/crates.io-index"
1857−checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
1713+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
18581714dependencies = [
18591715 "bitflags 1.3.2",
18601716]
18611717
18621718[[package]]
18631719name = "regex"
1864−version = "1.8.4"
1720+version = "1.10.2"
18651721source = "registry+https://github.com/rust-lang/crates.io-index"
1866−checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
1722+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
18671723dependencies = [
1868− "aho-corasick 1.0.1",
1724+ "aho-corasick",
18691725 "memchr",
1870− "regex-syntax 0.7.2",
1726+ "regex-automata",
1727+ "regex-syntax",
18711728]
18721729
18731730[[package]]
18741731name = "regex-automata"
1875−version = "0.1.10"
1876−source = "registry+https://github.com/rust-lang/crates.io-index"
1877−checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
1878−
1879−[[package]]
1880−name = "regex-syntax"
1881−version = "0.6.29"
1732+version = "0.4.3"
18821733source = "registry+https://github.com/rust-lang/crates.io-index"
1883−checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
1734+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
1735+dependencies = [
1736+ "aho-corasick",
1737+ "memchr",
1738+ "regex-syntax",
1739+]
18841740
18851741[[package]]
18861742name = "regex-syntax"
1887−version = "0.7.2"
1743+version = "0.8.2"
18881744source = "registry+https://github.com/rust-lang/crates.io-index"
1889−checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
1745+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
18901746
18911747[[package]]
18921748name = "ropey"
1893−version = "1.6.0"
1749+version = "1.6.1"
18941750source = "registry+https://github.com/rust-lang/crates.io-index"
1895−checksum = "53ce7a2c43a32e50d666e33c5a80251b31147bb4b49024bcab11fb6f20c671ed"
1751+checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
18961752dependencies = [
18971753 "smallvec",
18981754 "str_indices",
@@ -1906,16 +1762,15 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
19061762
19071763[[package]]
19081764name = "rustix"
1909−version = "0.37.15"
1765+version = "0.38.28"
19101766source = "registry+https://github.com/rust-lang/crates.io-index"
1911−checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece"
1767+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
19121768dependencies = [
1913− "bitflags 1.3.2",
1769+ "bitflags 2.4.1",
19141770 "errno",
1915− "io-lifetimes",
19161771 "libc",
19171772 "linux-raw-sys",
1918− "windows-sys 0.48.0",
1773+ "windows-sys 0.52.0",
19191774]
19201775
19211776[[package]]
@@ -1947,29 +1802,29 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
19471802
19481803[[package]]
19491804name = "serde"
1950−version = "1.0.166"
1805+version = "1.0.193"
19511806source = "registry+https://github.com/rust-lang/crates.io-index"
1952−checksum = "d01b7404f9d441d3ad40e6a636a7782c377d2abdbe4fa2440e2edcc2f4f10db8"
1807+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
19531808dependencies = [
19541809 "serde_derive",
19551810]
19561811
19571812[[package]]
19581813name = "serde_derive"
1959−version = "1.0.166"
1814+version = "1.0.193"
19601815source = "registry+https://github.com/rust-lang/crates.io-index"
1961−checksum = "5dd83d6dde2b6b2d466e14d9d1acce8816dedee94f735eac6395808b3483c6d6"
1816+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
19621817dependencies = [
19631818 "proc-macro2",
19641819 "quote",
1965− "syn 2.0.23",
1820+ "syn 2.0.38",
19661821]
19671822
19681823[[package]]
19691824name = "serde_json"
1970−version = "1.0.99"
1825+version = "1.0.108"
19711826source = "registry+https://github.com/rust-lang/crates.io-index"
1972−checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3"
1827+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
19731828dependencies = [
19741829 "itoa",
19751830 "ryu",
@@ -1984,7 +1839,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
19841839dependencies = [
19851840 "proc-macro2",
19861841 "quote",
1987− "syn 2.0.23",
1842+ "syn 2.0.38",
19881843]
19891844
19901845[[package]]
@@ -2004,9 +1859,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
20041859
20051860[[package]]
20061861name = "signal-hook"
2007−version = "0.3.15"
1862+version = "0.3.17"
20081863source = "registry+https://github.com/rust-lang/crates.io-index"
2009−checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
1864+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
20101865dependencies = [
20111866 "libc",
20121867 "signal-hook-registry",
@@ -2055,18 +1910,18 @@ dependencies = [
20551910
20561911[[package]]
20571912name = "slotmap"
2058−version = "1.0.6"
1913+version = "1.0.7"
20591914source = "registry+https://github.com/rust-lang/crates.io-index"
2060−checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
1915+checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
20611916dependencies = [
20621917 "version_check",
20631918]
20641919
20651920[[package]]
20661921name = "smallvec"
2067−version = "1.10.0"
1922+version = "1.11.2"
20681923source = "registry+https://github.com/rust-lang/crates.io-index"
2069−checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
1924+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
20701925
20711926[[package]]
20721927name = "smartstring"
@@ -2087,12 +1942,12 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
20871942
20881943[[package]]
20891944name = "socket2"
2090−version = "0.4.9"
1945+version = "0.5.5"
20911946source = "registry+https://github.com/rust-lang/crates.io-index"
2092−checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
1947+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
20931948dependencies = [
20941949 "libc",
2095− "winapi",
1950+ "windows-sys 0.48.0",
20961951]
20971952
20981953[[package]]
@@ -2101,12 +1956,6 @@ version = "1.1.0"
21011956source = "registry+https://github.com/rust-lang/crates.io-index"
21021957checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
21031958
2104−[[package]]
2105−name = "str-buf"
2106−version = "1.0.6"
2107−source = "registry+https://github.com/rust-lang/crates.io-index"
2108−checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
2109−
21101959[[package]]
21111960name = "str_indices"
21121961version = "0.4.1"
@@ -2126,9 +1975,9 @@ dependencies = [
21261975
21271976[[package]]
21281977name = "syn"
2129−version = "2.0.23"
1978+version = "2.0.38"
21301979source = "registry+https://github.com/rust-lang/crates.io-index"
2131−checksum = "59fb7d6d8281a51045d62b8eb3a7d1ce347b76f312af50cd3dc0af39c87c1737"
1980+checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
21321981dependencies = [
21331982 "proc-macro2",
21341983 "quote",
@@ -2137,14 +1986,13 @@ dependencies = [
21371986
21381987[[package]]
21391988name = "tempfile"
2140−version = "3.6.0"
1989+version = "3.8.1"
21411990source = "registry+https://github.com/rust-lang/crates.io-index"
2142−checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
1991+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
21431992dependencies = [
2144− "autocfg",
21451993 "cfg-if",
2146− "fastrand 1.9.0",
2147− "redox_syscall 0.3.5",
1994+ "fastrand",
1995+ "redox_syscall 0.4.1",
21481996 "rustix",
21491997 "windows-sys 0.48.0",
21501998]
@@ -2180,32 +2028,22 @@ dependencies = [
21802028
21812029[[package]]
21822030name = "thiserror"
2183−version = "1.0.40"
2031+version = "1.0.52"
21842032source = "registry+https://github.com/rust-lang/crates.io-index"
2185−checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
2033+checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
21862034dependencies = [
21872035 "thiserror-impl",
21882036]
21892037
21902038[[package]]
21912039name = "thiserror-impl"
2192−version = "1.0.40"
2040+version = "1.0.52"
21932041source = "registry+https://github.com/rust-lang/crates.io-index"
2194−checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
2042+checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
21952043dependencies = [
21962044 "proc-macro2",
21972045 "quote",
2198− "syn 2.0.23",
2199−]
2200−
2201−[[package]]
2202−name = "thread_local"
2203−version = "1.1.7"
2204−source = "registry+https://github.com/rust-lang/crates.io-index"
2205−checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
2206−dependencies = [
2207− "cfg-if",
2208− "once_cell",
2046+ "syn 2.0.38",
22092047]
22102048
22112049[[package]]
@@ -2219,9 +2057,9 @@ dependencies = [
22192057
22202058[[package]]
22212059name = "time"
2222−version = "0.3.20"
2060+version = "0.3.23"
22232061source = "registry+https://github.com/rust-lang/crates.io-index"
2224−checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
2062+checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
22252063dependencies = [
22262064 "itoa",
22272065 "libc",
@@ -2233,15 +2071,15 @@ dependencies = [
22332071
22342072[[package]]
22352073name = "time-core"
2236−version = "0.1.0"
2074+version = "0.1.1"
22372075source = "registry+https://github.com/rust-lang/crates.io-index"
2238−checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
2076+checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
22392077
22402078[[package]]
22412079name = "time-macros"
2242−version = "0.2.8"
2080+version = "0.2.10"
22432081source = "registry+https://github.com/rust-lang/crates.io-index"
2244−checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
2082+checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
22452083dependencies = [
22462084 "time-core",
22472085]
@@ -2263,11 +2101,10 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
22632101
22642102[[package]]
22652103name = "tokio"
2266−version = "1.29.1"
2104+version = "1.35.1"
22672105source = "registry+https://github.com/rust-lang/crates.io-index"
2268−checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da"
2106+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
22692107dependencies = [
2270− "autocfg",
22712108 "backtrace",
22722109 "bytes",
22732110 "libc",
@@ -2283,13 +2120,13 @@ dependencies = [
22832120
22842121[[package]]
22852122name = "tokio-macros"
2286−version = "2.1.0"
2123+version = "2.2.0"
22872124source = "registry+https://github.com/rust-lang/crates.io-index"
2288−checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
2125+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
22892126dependencies = [
22902127 "proc-macro2",
22912128 "quote",
2292− "syn 2.0.23",
2129+ "syn 2.0.38",
22932130]
22942131
22952132[[package]]
@@ -2305,9 +2142,9 @@ dependencies = [
23052142
23062143[[package]]
23072144name = "toml"
2308−version = "0.7.5"
2145+version = "0.7.6"
23092146source = "registry+https://github.com/rust-lang/crates.io-index"
2310−checksum = "1ebafdf5ad1220cb59e7d17cf4d2c72015297b75b19a10472f99b89225089240"
2147+checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
23112148dependencies = [
23122149 "serde",
23132150 "serde_spanned",
@@ -2326,22 +2163,21 @@ dependencies = [
23262163
23272164[[package]]
23282165name = "toml_edit"
2329−version = "0.19.11"
2166+version = "0.19.12"
23302167source = "registry+https://github.com/rust-lang/crates.io-index"
2331−checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7"
2168+checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
23322169dependencies = [
23332170 "indexmap",
23342171 "serde",
23352172 "serde_spanned",
23362173 "toml_datetime",
2337− "winnow",
2174+ "winnow 0.4.6",
23382175]
23392176
23402177[[package]]
23412178name = "tree-sitter"
23422179version = "0.20.10"
2343−source = "registry+https://github.com/rust-lang/crates.io-index"
2344−checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d"
2180+source = "git+https://github.com/tree-sitter/tree-sitter?rev=ab09ae20d640711174b8da8a654f6b3dec93da1a#ab09ae20d640711174b8da8a654f6b3dec93da1a"
23452181dependencies = [
23462182 "cc",
23472183 "regex",
@@ -2382,13 +2218,9 @@ checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
23822218
23832219[[package]]
23842220name = "unicode-linebreak"
2385−version = "0.1.4"
2221+version = "0.1.5"
23862222source = "registry+https://github.com/rust-lang/crates.io-index"
2387−checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
2388−dependencies = [
2389− "hashbrown 0.12.3",
2390− "regex",
2391−]
2223+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
23922224
23932225[[package]]
23942226name = "unicode-normalization"
@@ -2407,15 +2239,15 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
24072239
24082240[[package]]
24092241name = "unicode-width"
2410−version = "0.1.10"
2242+version = "0.1.11"
24112243source = "registry+https://github.com/rust-lang/crates.io-index"
2412−checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
2244+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
24132245
24142246[[package]]
24152247name = "url"
2416−version = "2.4.0"
2248+version = "2.5.0"
24172249source = "registry+https://github.com/rust-lang/crates.io-index"
2418−checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
2250+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
24192251dependencies = [
24202252 "form_urlencoded",
24212253 "idna",
@@ -2431,9 +2263,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
24312263
24322264[[package]]
24332265name = "walkdir"
2434−version = "2.3.3"
2266+version = "2.4.0"
24352267source = "registry+https://github.com/rust-lang/crates.io-index"
2436−checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
2268+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
24372269dependencies = [
24382270 "same-file",
24392271 "winapi-util",
@@ -2501,13 +2333,15 @@ checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
25012333
25022334[[package]]
25032335name = "which"
2504−version = "4.4.0"
2336+version = "5.0.0"
25052337source = "registry+https://github.com/rust-lang/crates.io-index"
2506−checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
2338+checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14"
25072339dependencies = [
25082340 "either",
2509− "libc",
2341+ "home",
25102342 "once_cell",
2343+ "rustix",
2344+ "windows-sys 0.48.0",
25112345]
25122346
25132347[[package]]
@@ -2550,6 +2384,25 @@ dependencies = [
25502384 "windows-targets 0.48.0",
25512385]
25522386
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+
25532406[[package]]
25542407name = "windows-sys"
25552408version = "0.45.0"
@@ -2568,6 +2421,15 @@ dependencies = [
25682421 "windows-targets 0.48.0",
25692422]
25702423
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+
25712433[[package]]
25722434name = "windows-targets"
25732435version = "0.42.2"
@@ -2598,6 +2460,21 @@ dependencies = [
25982460 "windows_x86_64_msvc 0.48.0",
25992461]
26002462
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+
26012478[[package]]
26022479name = "windows_aarch64_gnullvm"
26032480version = "0.42.2"
@@ -2610,6 +2487,12 @@ version = "0.48.0"
26102487source = "registry+https://github.com/rust-lang/crates.io-index"
26112488checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
26122489
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+
26132496[[package]]
26142497name = "windows_aarch64_msvc"
26152498version = "0.42.2"
@@ -2622,6 +2505,12 @@ version = "0.48.0"
26222505source = "registry+https://github.com/rust-lang/crates.io-index"
26232506checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
26242507
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+
26252514[[package]]
26262515name = "windows_i686_gnu"
26272516version = "0.42.2"
@@ -2634,6 +2523,12 @@ version = "0.48.0"
26342523source = "registry+https://github.com/rust-lang/crates.io-index"
26352524checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
26362525
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+
26372532[[package]]
26382533name = "windows_i686_msvc"
26392534version = "0.42.2"
@@ -2646,6 +2541,12 @@ version = "0.48.0"
26462541source = "registry+https://github.com/rust-lang/crates.io-index"
26472542checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
26482543
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+
26492550[[package]]
26502551name = "windows_x86_64_gnu"
26512552version = "0.42.2"
@@ -2658,6 +2559,12 @@ version = "0.48.0"
26582559source = "registry+https://github.com/rust-lang/crates.io-index"
26592560checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
26602561
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+
26612568[[package]]
26622569name = "windows_x86_64_gnullvm"
26632570version = "0.42.2"
@@ -2670,6 +2577,12 @@ version = "0.48.0"
26702577source = "registry+https://github.com/rust-lang/crates.io-index"
26712578checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
26722579
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+
26732586[[package]]
26742587name = "windows_x86_64_msvc"
26752588version = "0.42.2"
@@ -2682,6 +2595,12 @@ version = "0.48.0"
26822595source = "registry+https://github.com/rust-lang/crates.io-index"
26832596checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
26842597
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+
26852604[[package]]
26862605name = "winnow"
26872606version = "0.4.6"
@@ -2691,9 +2610,18 @@ dependencies = [
26912610 "memchr",
26922611]
26932612
2613+[[package]]
2614+name = "winnow"
2615+version = "0.5.28"
2616+source = "registry+https://github.com/rust-lang/crates.io-index"
2617+checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
2618+dependencies = [
2619+ "memchr",
2620+]
2621+
26942622[[package]]
26952623name = "xtask"
2696−version = "0.6.0"
2624+version = "23.10.0"
26972625dependencies = [
26982626 "helix-core",
26992627 "helix-loader",
@@ -2701,3 +2629,23 @@ dependencies = [
27012629 "helix-view",
27022630 "toml",
27032631]
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 @@
11[workspace]
2+resolver = "2"
23members = [
34 "helix-core",
45 "helix-view",
56 "helix-term",
67 "helix-tui",
78 "helix-lsp",
9+ "helix-event",
810 "helix-dap",
911 "helix-loader",
1012 "helix-vcs",
@@ -32,3 +34,17 @@ inherits = "test"
3234package.helix-core.opt-level = 2
3335package.helix-tui.opt-level = 2
3436package.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 @@
1818
1919![Screenshot](./screenshot.png)
2020
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.
2222
2323The editing model is very heavily based on Kakoune; during development I found
2424myself 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
6161
6262# Credits
6363
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+4
@@ -10,3 +10,7 @@ default-theme = "colibri"
1010preferred-dark-theme = "colibri"
1111git-repository-url = "https://github.com/helix-editor/helix"
1212edit-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+38−11
@@ -51,7 +51,7 @@ Its settings will be merged with the configuration directory `config.toml` and t
5151| `auto-completion` | Enable automatic pop up of auto-completion | `true` |
5252| `auto-format` | Enable automatic formatting on save | `true` |
5353| `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` |
5555| `preview-completion-insert` | Whether to apply completion item instantly when selected | `true` |
5656| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
5757| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
@@ -64,6 +64,9 @@ Its settings will be merged with the configuration directory `config.toml` and t
6464| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
6565| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
6666| `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`
6770
6871### `[editor.statusline]` Section
6972
@@ -89,9 +92,9 @@ The `[editor.statusline]` key takes the following sub-keys:
8992
9093| Key | Description | Default |
9194| --- | --- | --- |
92−| `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"]` |
9396| `center` | A list of elements aligned to the middle of the statusline | `[]` |
94−| `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"]` |
9598| `separator` | The character used to separate elements in the statusline | `"│"` |
9699| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
97100| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
@@ -108,6 +111,7 @@ The following statusline elements can be configured:
108111| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
109112| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
110113| `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 |
111115| `total-line-numbers` | The total line numbers of the opened file |
112116| `file-type` | The type of the opened file |
113117| `diagnostics` | The number of warnings and/or errors |
@@ -164,16 +168,31 @@ All git related options are only enabled in a git repository.
164168
165169| Key | Description | Default |
166170|--|--|---------|
167−|`hidden` | Enables ignoring hidden files | true
168−|`follow-symlinks` | Follow symlinks instead of ignoring them | true
169−|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | true
170−|`parents` | Enables reading ignore files from parent directories | true
171−|`ignore` | Enables reading `.ignore` files | true
172−|`git-ignore` | Enables reading `.gitignore` files | true
173−|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludefile` option | true
174−|`git-exclude` | Enables reading `.git/info/exclude` files | true
171+|`hidden` | Enables ignoring hidden files | `true`
172+|`follow-symlinks` | Follow symlinks instead of ignoring them | `true`
173+|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | `true`
174+|`parents` | Enables reading ignore files from parent directories | `true`
175+|`ignore` | Enables reading `.ignore` files | `true`
176+|`git-ignore` | Enables reading `.gitignore` files | `true`
177+|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludesfile` option | `true`
178+|`git-exclude` | Enables reading `.git/info/exclude` files | `true`
175179|`max-depth` | Set with an integer value for maximum depth to recurse | Defaults to `None`.
176180
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+
177196### `[editor.auto-pairs]` Section
178197
179198Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a
@@ -347,3 +366,11 @@ max-wrap = 25 # increase value to reduce forced mid-word wrapping
347366max-indent-retain = 0
348367wrap-indicator = "" # set wrap-indicator to "" to hide it
349368```
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+36−15
@@ -1,8 +1,9 @@
11| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
22| --- | --- | --- | --- | --- |
3+| agda | ✓ | | | |
34| astro | ✓ | | | |
45| awk | ✓ | ✓ | | `awk-language-server` |
5−| bash | ✓ | | ✓ | `bash-language-server` |
6+| bash | ✓ | ✓ | ✓ | `bash-language-server` |
67| bass | ✓ | | | `bass` |
78| beancount | ✓ | | | |
89| bibtex | ✓ | | | `texlab` |
@@ -10,13 +11,13 @@
1011| blueprint | ✓ | | | `blueprint-compiler` |
1112| c | ✓ | ✓ | ✓ | `clangd` |
1213| c-sharp | ✓ | ✓ | | `OmniSharp` |
13−| cabal | | | | |
14+| cabal | | | | `haskell-language-server-wrapper` |
1415| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
1516| capnp | ✓ | | ✓ | |
1617| clojure | ✓ | | | `clojure-lsp` |
1718| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
1819| comment | ✓ | | | |
19−| common-lisp | ✓ | | | `cl-lsp` |
20+| common-lisp | ✓ | | ✓ | `cl-lsp` |
2021| cpon | ✓ | | ✓ | |
2122| cpp | ✓ | ✓ | ✓ | `clangd` |
2223| crystal | ✓ | ✓ | | `crystalline` |
@@ -24,6 +25,7 @@
2425| cue | ✓ | | | `cuelsp` |
2526| d | ✓ | ✓ | ✓ | `serve-d` |
2627| dart | ✓ | | ✓ | `dart` |
28+| dbml | ✓ | | | |
2729| devicetree | ✓ | | | |
2830| dhall | ✓ | ✓ | | `dhall-lsp-server` |
2931| diff | ✓ | | | |
@@ -43,7 +45,10 @@
4345| fish | ✓ | ✓ | ✓ | |
4446| forth | ✓ | | | `forth-lsp` |
4547| fortran | ✓ | | ✓ | `fortls` |
48+| fsharp | ✓ | | | `fsautocomplete` |
49+| gas | ✓ | ✓ | | |
4650| gdscript | ✓ | ✓ | ✓ | |
51+| gemini | ✓ | | | |
4752| git-attributes | ✓ | | | |
4853| git-commit | ✓ | ✓ | | |
4954| git-config | ✓ | | | |
@@ -51,14 +56,16 @@
5156| git-rebase | ✓ | | | |
5257| gleam | ✓ | ✓ | | `gleam` |
5358| glsl | ✓ | ✓ | ✓ | |
54−| go | ✓ | ✓ | ✓ | `gopls` |
59+| gn | ✓ | | | |
60+| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
5561| godot-resource | ✓ | | | |
5662| gomod | ✓ | | | `gopls` |
5763| gotmpl | ✓ | | | `gopls` |
5864| gowork | ✓ | | | `gopls` |
59−| graphql | ✓ | | | |
65+| graphql | ✓ | | | `graphql-lsp` |
6066| hare | ✓ | | | |
6167| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
68+| haskell-persistent | ✓ | | | |
6269| hcl | ✓ | | ✓ | `terraform-ls` |
6370| heex | ✓ | ✓ | | `elixir-ls` |
6471| hosts | ✓ | | | |
@@ -67,15 +74,18 @@
6774| idris | | | | `idris2-lsp` |
6875| iex | ✓ | | | |
6976| ini | ✓ | | | |
70−| java | ✓ | ✓ | | `jdtls` |
77+| janet | ✓ | | | |
78+| java | ✓ | ✓ | ✓ | `jdtls` |
7179| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
80+| jinja | ✓ | | | |
7281| jsdoc | ✓ | | | |
7382| json | ✓ | | ✓ | `vscode-json-language-server` |
83+| json5 | ✓ | | | |
7484| jsonnet | ✓ | | | `jsonnet-language-server` |
7585| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
7686| julia | ✓ | ✓ | ✓ | `julia` |
7787| just | ✓ | ✓ | ✓ | |
78−| kdl | ✓ | | | |
88+| kdl | ✓ | ✓ | ✓ | |
7989| kotlin | ✓ | | | `kotlin-language-server` |
8090| latex | ✓ | ✓ | | `texlab` |
8191| lean | ✓ | | | `lean` |
@@ -83,6 +93,8 @@
8393| llvm | ✓ | ✓ | ✓ | |
8494| llvm-mir | ✓ | ✓ | ✓ | |
8595| llvm-mir-yaml | ✓ | | ✓ | |
96+| log | ✓ | | | |
97+| lpf | ✓ | | | |
8698| lua | ✓ | ✓ | ✓ | `lua-language-server` |
8799| make | ✓ | | | |
88100| markdoc | ✓ | | | `markdoc-ls` |
@@ -97,7 +109,8 @@
97109| nickel | ✓ | | ✓ | `nls` |
98110| nim | ✓ | ✓ | ✓ | `nimlangserver` |
99111| nix | ✓ | | | `nil` |
100−| nu | ✓ | | | |
112+| nu | ✓ | | | `nu` |
113+| nunjucks | ✓ | | | |
101114| ocaml | ✓ | | ✓ | `ocamllsp` |
102115| ocaml-interface | ✓ | | | `ocamllsp` |
103116| odin | ✓ | | ✓ | `ols` |
@@ -110,16 +123,17 @@
110123| perl | ✓ | ✓ | ✓ | `perlnavigator` |
111124| php | ✓ | ✓ | ✓ | `intelephense` |
112125| po | ✓ | ✓ | | |
126+| pod | ✓ | | | |
113127| ponylang | ✓ | ✓ | ✓ | |
114128| prisma | ✓ | | | `prisma-language-server` |
115129| prolog | | | | `swipl` |
116−| protobuf | ✓ | | ✓ | |
130+| protobuf | ✓ | | ✓ | `bufls`, `pb` |
117131| prql | ✓ | | | |
118−| purescript | ✓ | | | `purescript-language-server` |
132+| purescript | ✓ | ✓ | | `purescript-language-server` |
119133| python | ✓ | ✓ | ✓ | `pylsp` |
120134| qml | ✓ | | ✓ | `qmlls` |
121135| r | ✓ | | | `R` |
122−| racket | ✓ | | | `racket` |
136+| racket | ✓ | | ✓ | `racket` |
123137| regex | ✓ | | | |
124138| rego | ✓ | | | `regols` |
125139| rescript | ✓ | ✓ | | `rescript-language-server` |
@@ -131,30 +145,36 @@
131145| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
132146| sage | ✓ | ✓ | | |
133147| scala | ✓ | | ✓ | `metals` |
134−| scheme | ✓ | | | |
148+| scheme | ✓ | | ✓ | |
135149| scss | ✓ | | | `vscode-css-language-server` |
136150| slint | ✓ | | ✓ | `slint-lsp` |
151+| smali | ✓ | | ✓ | |
137152| smithy | ✓ | | | `cs` |
138153| sml | ✓ | | | |
139154| solidity | ✓ | | | `solc` |
140155| sql | ✓ | | | |
141156| sshclientconfig | ✓ | | | |
142157| starlark | ✓ | ✓ | | |
143−| svelte | ✓ | | | `svelteserver` |
158+| strace | ✓ | | | |
159+| svelte | ✓ | | ✓ | `svelteserver` |
144160| sway | ✓ | ✓ | ✓ | `forc` |
145161| swift | ✓ | | | `sourcekit-lsp` |
146162| t32 | ✓ | | | |
147163| tablegen | ✓ | ✓ | ✓ | |
148164| task | ✓ | | | |
165+| templ | ✓ | | | `templ` |
149166| tfvars | ✓ | | ✓ | `terraform-ls` |
167+| todotxt | ✓ | | | |
150168| toml | ✓ | | | `taplo` |
151169| tsq | ✓ | | | |
152170| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
153171| twig | ✓ | | | |
154172| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
173+| typst | ✓ | | | `typst-lsp` |
155174| ungrammar | ✓ | | | |
175+| unison | ✓ | | | |
156176| uxntal | ✓ | | | |
157−| v | ✓ | ✓ | ✓ | `v` |
177+| v | ✓ | ✓ | ✓ | `v-analyzer` |
158178| vala | ✓ | | | `vala-language-server` |
159179| verilog | ✓ | ✓ | | `svlangserver` |
160180| vhdl | ✓ | | | `vhdl_ls` |
@@ -165,8 +185,9 @@
165185| webc | ✓ | | | |
166186| wgsl | ✓ | | | `wgsl_analyzer` |
167187| wit | ✓ | | ✓ | |
188+| wren | ✓ | ✓ | ✓ | |
168189| xit | ✓ | | | |
169190| xml | ✓ | | ✓ | |
170−| yaml | ✓ | | ✓ | `yaml-language-server` |
191+| yaml | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
171192| yuck | ✓ | | | |
172193| zig | ✓ | ✓ | ✓ | `zls` |
book/src/generated/typable-cmd.md+5−1
@@ -17,13 +17,14 @@
1717| `: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) |
1818| `:new`, `:n` | Create a new scratch buffer. |
1919| `: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.) |
2121| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
2222| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
2323| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
2424| `:write-quit`, `:wq`, `:x` | Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) |
2525| `:write-quit!`, `:wq!`, `:x!` | Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) |
2626| `: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. |
2728| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. |
2829| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes). |
2930| `:quit-all`, `:qa` | Close all views. |
@@ -54,6 +55,7 @@
5455| `:lsp-restart` | Restarts the language servers used by the current doc |
5556| `:lsp-stop` | Stops the language servers that are used by the current doc |
5657| `: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. |
5759| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
5860| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
5961| `:debug-eval` | Evaluate expression in current debug context. |
@@ -82,3 +84,5 @@
8284| `:run-shell-command`, `:sh` | Run a shell command |
8385| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
8486| `: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.
36363. Refer to the
3737 [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
3838 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).
3940
4041> 💡 In Helix, the first matching query takes precedence when evaluating
4142> queries, which is different from other editors such as Neovim where the last
@@ -51,3 +52,4 @@ below.
5152 grammars.
5253- If a parser is causing a segfault, or you want to remove it, make sure to
5354 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 @@
11# Adding indent queries
22
3−Helix uses tree-sitter to correctly indent new lines. This requires
4−a tree-sitter grammar and an `indent.scm` query file placed in
5−`runtime/queries/{language}/indents.scm`. The indentation for a line
6−is calculated by traversing the syntax tree from the lowest node at the
7−beginning of the new line. Each of these nodes contributes to the total
8−indent when it is captured by the query (in what way depends on the name
9−of the capture).
3+Helix uses tree-sitter to correctly indent new lines. This requires a tree-
4+sitter grammar and an `indent.scm` query file placed in `runtime/queries/
5+{language}/indents.scm`. The indentation for a line is calculated by traversing
6+the syntax tree from the lowest node at the beginning of the new line (see
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 of
9+the capture.
1010
1111Note that it matters where these added indents begin. For example,
1212multiple 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).
1414
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.
1623
17−Added indents don't always apply to the whole node. For example, in most
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):
24+## Indent queries
2125
22−- `all`:
23−This scope applies to the whole captured node. This is only different from
24−`tail` when the captured node is the first node on its line.
26+When Helix is inserting a new line through `o`, `O`, or `<ret>`, to determine
27+the indent level for the new line, the query in `indents.scm` is run on the
28+document. The starting position of the query is the end of the line above where
29+a new line will be inserted.
2530
26−- `tail`:
27−This scope applies to everything except for the first line of the
28−captured node.
31+For `o`, the inserted line is the line below the cursor, so that starting
32+position of the query is the end of the current line.
2933
30−Every capture type has a default scope which should do the right thing
31−in most situations. When a different scope is required, this can be
32−changed by using a `#set!` declaration anywhere in the pattern:
33−```scm
34−(assignment_expression
35− right: (_) @indent
36− (#set! "scope" "all"))
34+```rust
35+fn need_hero(some_hero: Hero, life: Life) -> {
36+ matches!(some_hero, Hero { // ←─────────────────╮
37+ strong: true,//←╮ ↑ ↑ │
38+ fast: true, // │ │ ╰── query start │
39+ sure: true, // │ ╰───── cursor ├─ traversal
40+ soon: true, // ╰──────── new line inserted │ start node
41+ }) && // │
42+// ↑ │
43+// ╰───────────────────────────────────────────────╯
44+ some_hero > life
45+}
3746```
3847
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.
4050
41−- `@indent` (default scope `tail`):
42−Increase the indent level by 1. Multiple occurrences in the same line
43−don't stack. If there is at least one `@indent` and one `@outdent`
44−capture on the same line, the indent level isn't changed at all.
51+```rust
52+fn need_hero(some_hero: Hero, life: Life) -> { // ←─╮
53+ matches!(some_hero, Hero { // ←╮ ↑ │
54+ strong: true,// ↑ ╭───╯ │ │
55+ fast: true, // │ │ query start ─╯ │
56+ sure: true, // ╰───┼ cursor ├─ traversal
57+ soon: true, // ╰ new line inserted │ start node
58+ }) && // │
59+ some_hero > life // │
60+} // ←──────────────────────────────────────────────╯
61+```
4562
46−- `@outdent` (default scope `all`):
47−Decrease the indent level by 1. The same rules as for `@indent` apply.
63+From this starting node, the syntax tree is traversed up until the root node.
64+Each indent capture is collected along the way, and then combined according to
65+their [capture types](#capture-types) and [scopes](#scopes) to a final indent
66+level for the line.
4867
49−- `@extend`:
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.
68+### Capture types
5469
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.
5594- `@extend.prevent-once`:
56−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
58−extension of the next `@extend` capture. If multiple ancestors are captured,
59−only the extension of the innermost one is prevented. All other ancestors are unaffected
60−(regardless of whether the innermost ancestor would actually have been extended).
95+ Prevents the first extension of an ancestor of this node. For example, in Python
96+ a return expression always ends the block that it is in. Note that this only
97+ stops the extension of the next `@extend` capture. If multiple ancestors are
98+ captured, only the extension of the innermost one is prevented. All other
99+ ancestors are unaffected (regardless of whether the innermost ancestor would
100+ actually have been extended).
101+
102+#### `@indent` / `@outdent`
103+
104+Consider this example:
105+
106+```rust
107+fn shout(things: Vec<Thing>) {
108+ // ↑
109+ // ├───────────────────────╮ indent level
110+ // @indent ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
111+ // │
112+ let it_all = |out| { things.filter(|thing| { // │ 1
113+ // ↑ ↑ │
114+ // ├───────────────────────┼─────┼┄┄┄┄┄┄┄┄┄┄┄┄┄┄
115+ // @indent @indent │
116+ // │ 2
117+ thing.can_do_with(out) // │
118+ })}; // ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
119+ //↑↑↑ │ 1
120+} //╰┼┴──────────────────────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄
121+// 3x @outdent
122+```
123+
124+```scm
125+((block) @indent)
126+["}" ")"] @outdent
127+```
128+
129+Note how on the second line, we have two blocks begin on the same line. In this
130+case, since both captures occur on the same line, they are combined and only
131+result in a net increase of 1. Also note that the closing `}`s are part of the
132+`@indent` captures, but the 3 `@outdent`s also combine into 1 and result in that
133+line losing one indent level.
134+
135+#### `@extend` / `@extend.prevent-once`
136+
137+For an example of where `@extend` can be useful, consider Python, which is
138+whitespace-sensitive.
139+
140+```scm
141+]
142+ (parenthesized_expression)
143+ (function_definition)
144+ (class_definition)
145+] @indent
146+
147+```
148+
149+```python
150+class Hero:
151+ def __init__(self, strong, fast, sure, soon):# ←─╮
152+ self.is_strong = strong # │
153+ self.is_fast = fast # ╭─── query start │
154+ self.is_sure = sure # │ ╭─ cursor │
155+ self.is_soon = soon # │ │ │
156+ # ↑ ↑ │ │ │
157+ # │ ╰──────╯ │ │
158+ # ╰─────────────────────╯ │
159+ # ├─ traversal
160+ def need_hero(self, life): # │ start node
161+ return ( # │
162+ self.is_strong # │
163+ and self.is_fast # │
164+ and self.is_sure # │
165+ and self.is_soon # │
166+ and self > life # │
167+ ) # ←─────────────────────────────────────────╯
168+```
169+
170+Without braces to catch the scope of the function, the smallest descendant of
171+the cursor on a line feed ends up being the entire inside of the class. Because
172+of this, it will miss the entire function node and its indent capture, leading
173+to an indent level one too small.
174+
175+To address this case, `@extend` tells helix to "extend" the captured node's span
176+to the line feed and every consecutive line that has a greater indent level than
177+the line of the node.
178+
179+```scm
180+(parenthesized_expression) @indent
181+
182+]
183+ (function_definition)
184+ (class_definition)
185+] @indent @extend
186+
187+```
188+
189+```python
190+class Hero:
191+ def __init__(self, strong, fast, sure, soon):# ←─╮
192+ self.is_strong = strong # │
193+ self.is_fast = fast # ╭─── query start ├─ traversal
194+ self.is_sure = sure # │ ╭─ cursor │ start node
195+ self.is_soon = soon # │ │ ←───────────────╯
196+ # ↑ ↑ │ │
197+ # │ ╰──────╯ │
198+ # ╰─────────────────────╯
199+ def need_hero(self, life):
200+ return (
201+ self.is_strong
202+ and self.is_fast
203+ and self.is_sure
204+ and self.is_soon
205+ and self > life
206+ )
207+```
61208
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+```
62292
63293## Predicates
64294
65295In some cases, an S-expression cannot express exactly what pattern should be matched.
66296For that, tree-sitter allows for predicates to appear anywhere within a pattern,
67297similar to how `#set!` declarations work:
298+
68299```scm
69300(some_kind
70301 (child_kind) @indent
71302 (#predicate? arg1 arg2 ...)
72303)
73304```
305+
74306The number of arguments depends on the predicate that's used.
75307Each argument is either a capture (`@name`) or a string (`"some string"`).
76308The following predicates are supported by tree-sitter:
@@ -91,3 +323,47 @@ argument (a string).
91323
92324- `#same-line?`/`#not-same-line?`:
93325The 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 @@
11# Adding Injection Queries
22
33Writing 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
55are a few Helix specific extensions that allow for more control.
66
77And example of a simple query that would highlight all strings as bash in Nix:
book/src/install.md+41−6
@@ -9,9 +9,11 @@
99 - [Arch Linux extra](#arch-linux-extra)
1010 - [NixOS](#nixos)
1111 - [Flatpak](#flatpak)
12+ - [Snap](#snap)
1213 - [AppImage](#appimage)
1314- [macOS](#macos)
1415 - [Homebrew Core](#homebrew-core)
16+ - [MacPorts](#macports)
1517- [Windows](#windows)
1618 - [Winget](#winget)
1719 - [Scoop](#scoop)
@@ -63,10 +65,7 @@ sudo apt install helix
6365
6466### Fedora/RHEL
6567
66−Enable the `COPR` repository for Helix:
67−
6868```sh
69−sudo dnf copr enable varlad/helix
7069sudo dnf install helix
7170```
7271
@@ -104,6 +103,16 @@ flatpak install flathub com.helix_editor.Helix
104103flatpak run com.helix_editor.Helix
105104```
106105
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+
107116### AppImage
108117
109118Install Helix using the Linux [AppImage](https://appimage.org/) format.
@@ -122,6 +131,12 @@ chmod +x helix-*.AppImage # change permission for executable mode
122131brew install helix
123132```
124133
134+### MacPorts
135+
136+```sh
137+port install helix
138+```
139+
125140## Windows
126141
127142Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
@@ -213,7 +228,7 @@ Or, create a symbolic link:
213228ln -Ts $PWD/runtime ~/.config/helix/runtime
214229```
215230
216−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.
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.
217232
218233#### Windows
219234
@@ -246,12 +261,32 @@ following order:
2462611. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
247262 developing and testing helix only).
2482632. `runtime/` subdirectory of OS-dependent helix user config directory.
249−3. `$HELIX_RUNTIME`.
250−4. `runtime/` subdirectory of path to Helix executable.
264+3. `$HELIX_RUNTIME`
265+4. Distribution-specific fallback directory (set at compile time—not run time—
266+ with the `HELIX_DEFAULT_RUNTIME` environment variable)
267+5. `runtime/` subdirectory of path to Helix executable.
251268
252269This order also sets the priority for selecting which file will be used if multiple runtime
253270directories have files with the same name.
254271
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+
255290### Validating the installation
256291
257292To make sure everything is set up as expected you should run the Helix health
book/src/keymap.md+16−15
@@ -25,7 +25,7 @@
2525
2626## Normal mode
2727
28−Normal mode is the default mode when you launch helix. Return to it from other modes by typing `Escape`.
28+Normal mode is the default mode when you launch helix. You can return to it from other modes by pressing the `Escape` key.
2929
3030### Movement
3131
@@ -291,7 +291,7 @@ This layer is a kludge of mappings, mostly pickers.
291291| `w` | Enter [window mode](#window-mode) | N/A |
292292| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
293293| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
294−| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` |
294+| `y` | Yank selections to clipboard | `yank_to_clipboard` |
295295| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
296296| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
297297| `/` | Global search in workspace folder | `global_search` |
@@ -411,19 +411,20 @@ you to selectively add search terms to your selections.
411411
412412Keys to use within picker. Remapping currently not supported.
413413
414−| Key | Description |
415−| ----- | ------------- |
416−| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
417−| `Tab`, `Down`, `Ctrl-n` | Next entry |
418−| `PageUp`, `Ctrl-u` | Page up |
419−| `PageDown`, `Ctrl-d` | Page down |
420−| `Home` | Go to first entry |
421−| `End` | Go to last entry |
422−| `Enter` | Open selected |
423−| `Ctrl-s` | Open horizontally |
424−| `Ctrl-v` | Open vertically |
425−| `Ctrl-t` | Toggle preview |
426−| `Escape`, `Ctrl-c` | Close picker |
414+| Key | Description |
415+| ----- | ------------- |
416+| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
417+| `Tab`, `Down`, `Ctrl-n` | Next entry |
418+| `PageUp`, `Ctrl-u` | Page up |
419+| `PageDown`, `Ctrl-d` | Page down |
420+| `Home` | Go to first entry |
421+| `End` | Go to last entry |
422+| `Enter` | Open selected |
423+| `Alt-Enter` | Open selected in the background without closing the picker |
424+| `Ctrl-s` | Open horizontally |
425+| `Ctrl-v` | Open vertically |
426+| `Ctrl-t` | Toggle preview |
427+| `Escape`, `Ctrl-c` | Close picker |
427428
428429## Prompt
429430
book/src/languages.md+3−1
@@ -66,8 +66,10 @@ These configuration keys are available:
6666| `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) |
6767| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
6868| `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)
6970| `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` |
7071| `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.
7173
7274### File-type detection and the `file-types` key
7375
@@ -127,7 +129,7 @@ These are the available options for a language server.
127129| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
128130
129131A `format` sub-table within `config` can be used to pass extra formatting options to
130−[Document Formatting Requests](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-17.md#document-formatting-request--leftwards_arrow_with_hook).
132+[Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting).
131133For example, with typescript:
132134
133135```toml
book/src/themes.md+9−1
@@ -70,6 +70,7 @@ over it and is merged into the default palette.
7070
7171| Color Name |
7272| --- |
73+| `default` |
7374| `black` |
7475| `red` |
7576| `green` |
@@ -154,6 +155,7 @@ We use a similar set of scopes as
154155
155156- `type` - Types
156157 - `builtin` - Primitive types provided by the language (`int`, `usize`)
158+ - `parameter` - Generic type parameters (`T`)
157159 - `enum`
158160 - `variant`
159161- `constructor`
@@ -244,9 +246,12 @@ We use a similar set of scopes as
244246
245247- `diff` - version control changes
246248 - `plus` - additions
249+ - `gutter` - gutter indicator
247250 - `minus` - deletions
251+ - `gutter` - gutter indicator
248252 - `delta` - modifications
249253 - `moved` - renamed or moved files/changes
254+ - `gutter` - gutter indicator
250255
251256#### Interface
252257
@@ -291,11 +296,14 @@ These scopes are used for theming the editor interface:
291296| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
292297| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
293298| `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 |
294302| `ui.popup` | Documentation popups (e.g. Space + k) |
295303| `ui.popup.info` | Prompt for multiple key options |
296304| `ui.window` | Borderlines separating splits |
297305| `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. |
299307| `ui.text.focus` | The currently selected line in the picker |
300308| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
301309| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
book/src/usage.md+21−5
@@ -37,19 +37,35 @@ If a register is selected before invoking a change or delete command, the select
3737- `"hc` - Store the selection in register `h` and then change it (delete and enter insert mode).
3838- `"md` - Store the selection in register `m` and delete it.
3939
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:
4144
4245| Register character | Contains |
4346| --- | --- |
4447| `/` | Last search |
4548| `:` | Last executed command |
4649| `"` | Last yanked text |
47−| `_` | Black hole |
50+| `@` | Last recorded macro |
4851
49−The system clipboard is not directly supported by a special register. Instead, special commands and keybindings are provided. Refer to the
50−[key map](keymap.md#space-mode) for more details.
52+### Special registers
5153
52−The black hole register is a no-op register, meaning that no data will be read or written to it.
54+Some registers have special behavior when read from and written to.
55+
56+| Register character | When read | When written |
57+| --- | --- | --- |
58+| `_` | No values are returned | All values are discarded |
59+| `#` | Selection indices (first selection is `1`, second is `2`, etc.) | This register is not writable |
60+| `.` | Contents of the current selections | This register is not writable |
61+| `%` | Name of the current file | This register is not writable |
62+| `+` | Reads from the system clipboard | Joins and yanks to the system clipboard |
63+| `*` | Reads from the primary clipboard | Joins and yanks to the primary clipboard |
64+
65+When yanking multiple selections to the clipboard registers, the selections
66+are joined with newlines. Pasting from these registers will paste multiple
67+selections if the clipboard was last yanked to by the Helix session. Otherwise
68+the clipboard contents are pasted as one selection.
5369
5470## Surround
5571
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 @@
11<!DOCTYPE HTML>
2−<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
2+<html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
33 <head>
44 <!-- Book generated using mdBook -->
55 <meta charset="UTF-8">
66 <title>{{ title }}</title>
77 {{#if is_print }}
8− <meta name="robots" content="noindex" />
8+ <meta name="robots" content="noindex">
99 {{/if}}
1010 {{#if base_url}}
1111 <base href="{{ base_url }}">
@@ -15,10 +15,9 @@
1515 <!-- Custom HTML head -->
1616 {{> head}}
1717
18− <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
1918 <meta name="description" content="{{ description }}">
2019 <meta name="viewport" content="width=device-width, initial-scale=1">
21− <meta name="theme-color" content="#ffffff" />
20+ <meta name="theme-color" content="#ffffff">
2221
2322 {{#if favicon_svg}}
2423 <link rel="icon" href="{{ path_to_root }}favicon.svg">
@@ -35,8 +34,6 @@
3534
3635 <!-- Fonts -->
3736 <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">
4037 {{#if copy_fonts}}
4138 <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
4239 {{/if}}
@@ -53,18 +50,19 @@
5350
5451 {{#if mathjax_support}}
5552 <!-- 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>
5754 {{/if}}
5855 </head>
59− <body>
56+ <body class="sidebar-visible no-js">
57+ <div id="body-container">
6058 <!-- Provide site root to javascript -->
61− <script type="text/javascript">
59+ <script>
6260 var path_to_root = "{{ path_to_root }}";
6361 var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
6462 </script>
6563
6664 <!-- Work around some values being stored in localStorage wrapped in quotes -->
67− <script type="text/javascript">
65+ <script>
6866 try {
6967 var theme = localStorage.getItem('mdbook-theme');
7068 var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -80,27 +78,34 @@
8078 </script>
8179
8280 <!-- Set the theme before any content is loaded, prevents flash -->
83− <script type="text/javascript">
81+ <script>
8482 var theme;
8583 try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
8684 if (theme === null || theme === undefined) { theme = default_theme; }
8785 var html = document.querySelector('html');
88− html.classList.remove('no-js')
8986 html.classList.remove('{{ default_theme }}')
9087 html.classList.add(theme);
91− html.classList.add('js');
88+ var body = document.querySelector('body');
89+ body.classList.remove('no-js')
90+ body.classList.add('js');
9291 </script>
9392
93+ <input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
94+
9495 <!-- Hide / unhide sidebar before it is displayed -->
95− <script type="text/javascript">
96− var html = document.querySelector('html');
97− var sidebar = 'hidden';
96+ <script>
97+ var body = document.querySelector('body');
98+ var sidebar = null;
99+ var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
98100 if (document.body.clientWidth >= 1080) {
99101 try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
100102 sidebar = sidebar || 'visible';
103+ } else {
104+ sidebar = 'hidden';
101105 }
102− html.classList.remove('sidebar-visible');
103− html.classList.add("sidebar-" + sidebar);
106+ sidebar_toggle.checked = sidebar === 'visible';
107+ body.classList.remove('sidebar-visible');
108+ body.classList.add("sidebar-" + sidebar);
104109 </script>
105110
106111 <nav id="sidebar" class="sidebar" aria-label="Table of contents">
@@ -110,26 +115,48 @@
110115 <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
111116 </nav>
112117
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+
113140 <div id="page-wrapper" class="page-wrapper">
114141
115142 <div class="page">
116143 {{> header}}
117144 <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">
119146 <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">
121148 <i class="fa fa-bars"></i>
122− </button>
149+ </label>
123150 <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">
124151 <i class="fa fa-paint-brush"></i>
125152 </button>
126153 <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>
128− <li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
129− <li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
130− <li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
131− <li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
132− <li role="none"><button role="menuitem" class="theme" id="colibri">{{ theme_option "Colibri" }}</button></li>
154+ <li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
155+ <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
156+ <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
157+ <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
158+ <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
159+ <li role="none"><button role="menuitem" class="theme" id="colibri">Colibri</button></li>
133160 </ul>
134161 {{#if search_enabled}}
135162 <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 @@
151178 <i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
152179 </a>
153180 {{/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+
154187 </div>
155188 </div>
156189
157190 {{#if search_enabled}}
158191 <div id="search-wrapper" class="hidden">
159192 <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">
161194 </form>
162195 <div id="searchresults-outer" class="searchresults-outer hidden">
163196 <div id="searchresults-header" class="searchresults-header"></div>
@@ -168,7 +201,7 @@
168201 {{/if}}
169202
170203 <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
171− <script type="text/javascript">
204+ <script>
172205 document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
173206 document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
174207 Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -190,7 +223,7 @@
190223 {{/previous}}
191224
192225 {{#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">
194227 <i class="fa fa-angle-right"></i>
195228 </a>
196229 {{/next}}
@@ -208,7 +241,7 @@
208241 {{/previous}}
209242
210243 {{#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">
212245 <i class="fa fa-angle-right"></i>
213246 </a>
214247 {{/next}}
@@ -216,10 +249,12 @@
216249
217250 </div>
218251
219− {{#if livereload}}
252+ {{#if live_reload_endpoint}}
220253 <!-- Livereload script (if served using the cli tool) -->
221− <script type="text/javascript">
222− var socket = new WebSocket("{{{livereload}}}");
254+ <script>
255+ const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
256+ const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
257+ const socket = new WebSocket(wsAddress);
223258 socket.onmessage = function (event) {
224259 if (event.data === "reload") {
225260 socket.close();
@@ -235,7 +270,7 @@
235270
236271 {{#if google_analytics}}
237272 <!-- Google Analytics Tag -->
238− <script type="text/javascript">
273+ <script>
239274 var localAddrs = ["localhost", "127.0.0.1", ""];
240275
241276 // make sure we don't activate google analytics if the developer is
@@ -253,43 +288,43 @@
253288 {{/if}}
254289
255290 {{#if playground_line_numbers}}
256− <script type="text/javascript">
291+ <script>
257292 window.playground_line_numbers = true;
258293 </script>
259294 {{/if}}
260295
261296 {{#if playground_copyable}}
262− <script type="text/javascript">
297+ <script>
263298 window.playground_copyable = true;
264299 </script>
265300 {{/if}}
266301
267302 {{#if playground_js}}
268− <script src="{{ path_to_root }}ace.js" type="text/javascript" charset="utf-8"></script>
269− <script src="{{ path_to_root }}editor.js" type="text/javascript" charset="utf-8"></script>
270− <script src="{{ path_to_root }}mode-rust.js" type="text/javascript" charset="utf-8"></script>
271− <script src="{{ path_to_root }}theme-dawn.js" type="text/javascript" charset="utf-8"></script>
272− <script src="{{ path_to_root }}theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
303+ <script src="{{ path_to_root }}ace.js"></script>
304+ <script src="{{ path_to_root }}editor.js"></script>
305+ <script src="{{ path_to_root }}mode-rust.js"></script>
306+ <script src="{{ path_to_root }}theme-dawn.js"></script>
307+ <script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
273308 {{/if}}
274309
275310 {{#if search_js}}
276− <script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
277− <script src="{{ path_to_root }}mark.min.js" type="text/javascript" charset="utf-8"></script>
278− <script src="{{ path_to_root }}searcher.js" type="text/javascript" charset="utf-8"></script>
311+ <script src="{{ path_to_root }}elasticlunr.min.js"></script>
312+ <script src="{{ path_to_root }}mark.min.js"></script>
313+ <script src="{{ path_to_root }}searcher.js"></script>
279314 {{/if}}
280315
281− <script src="{{ path_to_root }}clipboard.min.js" type="text/javascript" charset="utf-8"></script>
282− <script src="{{ path_to_root }}highlight.js" type="text/javascript" charset="utf-8"></script>
283− <script src="{{ path_to_root }}book.js" type="text/javascript" charset="utf-8"></script>
316+ <script src="{{ path_to_root }}clipboard.min.js"></script>
317+ <script src="{{ path_to_root }}highlight.js"></script>
318+ <script src="{{ path_to_root }}book.js"></script>
284319
285320 <!-- Custom JS scripts -->
286321 {{#each additional_js}}
287− <script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
322+ <script src="{{ ../path_to_root }}{{this}}"></script>
288323 {{/each}}
289324
290325 {{#if is_print}}
291326 {{#if mathjax_support}}
292− <script type="text/javascript">
327+ <script>
293328 window.addEventListener('load', function() {
294329 MathJax.Hub.Register.StartupHook('End', function() {
295330 window.setTimeout(window.print, 100);
@@ -297,7 +332,7 @@
297332 });
298333 </script>
299334 {{else}}
300− <script type="text/javascript">
335+ <script>
301336 window.addEventListener('load', function() {
302337 window.setTimeout(window.print, 100);
303338 });
@@ -305,5 +340,6 @@
305340 {{/if}}
306341 {{/if}}
307342
343+ </div>
308344 </body>
309345</html>
contrib/Helix.appdata.xml+15
@@ -5,6 +5,7 @@
55 <project_license>MPL-2.0</project_license>
66 <name>Helix</name>
77 <summary>A post-modern text editor</summary>
8+ <summary xml:lang="ar">مُحَرِّرُ نُصُوصٍ سَابِقٌ لِعَهدِه</summary>
89
910 <description>
1011 <p>
@@ -17,6 +18,17 @@
1718 <li>Smart, incremental syntax highlighting and code editing via tree-sitter</li>
1819 </ul>
1920 </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>
2032
2133 <launchable type="desktop-id">Helix.desktop</launchable>
2234
@@ -36,6 +48,9 @@
3648 <content_rating type="oars-1.1" />
3749
3850 <releases>
51+ <release version="23.10" date="2023-10-24">
52+ <url>https://helix-editor.com/news/release-23-10-highlights/</url>
53+ </release>
3954 <release version="23.05" date="2023-05-18">
4055 <url>https://github.com/helix-editor/helix/releases/tag/23.05</url>
4156 </release>
contrib/Helix.desktop+3−1
@@ -1,6 +1,7 @@
11[Desktop Entry]
22Name=Helix
33GenericName=Text Editor
4+GenericName[ar]=مُحَرِّرُ نُصُوص
45GenericName[de]=Texteditor
56GenericName[fr]=Éditeur de texte
67GenericName[ru]=Текстовый редактор
@@ -9,7 +10,7 @@ GenericName[tr]=Metin Düzenleyici
910Comment=Edit text files
1011Comment[af]=Redigeer tekslêers
1112Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ
12−Comment[ar]=حرّر ملفات نصية
13+Comment[ar]=مُحَرِّرُ مِلَفَّاتٍ نَصِّيَّة
1314Comment[az]=Mətn fayllarını redaktə edin
1415Comment[be]=Рэдагаваньне тэкставых файлаў
1516Comment[bg]=Редактиране на текстови файлове
@@ -79,6 +80,7 @@ Exec=hx %F
7980Terminal=true
8081Type=Application
8182Keywords=Text;editor;
83+Keywords[ar]=نص;نصوص;محرر;
8284Keywords[fr]=Texte;éditeur;
8385Keywords[ru]=текст;текстовый редактор;
8486Keywords[sr]=Текст;едитор;
docs/CONTRIBUTING.md+10−3
@@ -29,9 +29,15 @@ files, run
2929cargo xtask docgen
3030```
3131
32−inside the project. We use [xtask][xtask] as an ad-hoc task runner and
33−thus do not require any dependencies other than `cargo` (You don't have
34−to `cargo install` anything either).
32+inside the project. We use [xtask][xtask] as an ad-hoc task runner.
33+
34+To preview the book itself, install [mdbook][mdbook]. Then, run
35+
36+```shell
37+mdbook serve book
38+```
39+
40+and visit [http://localhost:3000](http://localhost:3000).
3541
3642# Testing
3743
@@ -58,4 +64,5 @@ The current MSRV and future changes to the MSRV are listed in the [Firefox docum
5864[architecture.md]: ./architecture.md
5965[docs]: https://docs.helix-editor.com/
6066[xtask]: https://github.com/matklad/cargo-xtask
67+[mdbook]: https://rust-lang.github.io/mdBook/guide/installation.html
6168[helpers.rs]: ../helix-term/tests/test/helpers.rs
docs/releases.md+12−5
@@ -1,16 +1,23 @@
11## Checklist
22
33Helix releases are versioned in the Calendar Versioning scheme:
4−`YY.0M(.MICRO)`, for example, `22.05` for May of 2022. In these instructions
5−we'll use `<tag>` as a placeholder for the tag being published.
4+`YY.0M(.MICRO)`, for example, `22.05` for May of 2022, or in a patch release,
5+`22.05.1`. In these instructions we'll use `<tag>` as a placeholder for the tag
6+being published.
67
78* Merge the changelog PR
89* 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)
910* Tag and push
1011 * `git tag -s -m "<tag>" -a <tag> && git push`
1112 * Make sure to switch to master and pull first
12−* Edit the `VERSION` file and change the date to the next planned release
13− * Releases are planned to happen every two months, so `22.05` would change to `22.07`
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.
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`
1421* Wait for the Release CI to finish
1522 * It will automatically turn the git tag into a GitHub release when it uploads artifacts
1623* Edit the new release
@@ -57,4 +64,4 @@ versions for convenience:
5764> release. For the full log, check out the git log.
5865
5966Typically, 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.
flake.lock+21−237
@@ -1,126 +1,22 @@
11{
22 "nodes": {
33 "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": {
214 "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− ],
505 "nixpkgs": [
51− "nci",
526 "nixpkgs"
53− ],
54− "nixpkgsV1": "nixpkgsV1",
55− "poetry2nix": [
56− "nci"
57− ],
58− "pre-commit-hooks": [
59− "nci"
60− ],
61− "pruned-racket-catalog": [
62− "nci"
637 ]
648 },
659 "locked": {
66− "lastModified": 1683212002,
67− "narHash": "sha256-EObtqyQsv9v+inieRY5cvyCMCUI5zuU5qu+1axlJCPM=",
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",
117− "repo": "flake-compat",
118− "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
10+ "lastModified": 1701025348,
11+ "narHash": "sha256-42GHmYH+GF7VjwGSt+fVT1CQuNpGanJbNgVHTAZppUM=",
12+ "owner": "ipetkov",
13+ "repo": "crane",
14+ "rev": "42afaeb1a0325194a7cdb526332d2cb92fddd07b",
11915 "type": "github"
12016 },
12117 "original": {
122− "owner": "edolstra",
123− "repo": "flake-compat",
18+ "owner": "ipetkov",
19+ "repo": "crane",
12420 "type": "github"
12521 }
12622 },
@@ -129,11 +25,11 @@
12925 "systems": "systems"
13026 },
13127 "locked": {
132− "lastModified": 1681202837,
133− "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
28+ "lastModified": 1694529238,
29+ "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
13430 "owner": "numtide",
13531 "repo": "flake-utils",
136− "rev": "cfacdce06f30d2b68473a46042957675eebb3401",
32+ "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
13733 "type": "github"
13834 },
13935 "original": {
@@ -142,55 +38,13 @@
14238 "type": "github"
14339 }
14440 },
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− },
18741 "nixpkgs": {
18842 "locked": {
189− "lastModified": 1683408522,
190− "narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=",
43+ "lastModified": 1700794826,
44+ "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=",
19145 "owner": "nixos",
19246 "repo": "nixpkgs",
193− "rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7",
47+ "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8",
19448 "type": "github"
19549 },
19650 "original": {
@@ -200,99 +54,29 @@
20054 "type": "github"
20155 }
20256 },
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− },
27557 "root": {
27658 "inputs": {
277− "nci": "nci",
59+ "crane": "crane",
60+ "flake-utils": "flake-utils",
27861 "nixpkgs": "nixpkgs",
279− "parts": "parts_2",
28062 "rust-overlay": "rust-overlay"
28163 }
28264 },
28365 "rust-overlay": {
28466 "inputs": {
285− "flake-utils": "flake-utils",
67+ "flake-utils": [
68+ "flake-utils"
69+ ],
28670 "nixpkgs": [
28771 "nixpkgs"
28872 ]
28973 },
29074 "locked": {
291− "lastModified": 1683771545,
292− "narHash": "sha256-we0GYcKTo2jRQGmUGrzQ9VH0OYAUsJMCsK8UkF+vZUA=",
75+ "lastModified": 1701137803,
76+ "narHash": "sha256-0LcPAdql5IhQSUXJx3Zna0dYTgdIoYO7zUrsKgiBd04=",
29377 "owner": "oxalica",
29478 "repo": "rust-overlay",
295− "rev": "c57e210faf68e5d5386f18f1b17ad8365d25e4ed",
79+ "rev": "9dd940c967502f844eacea52a61e9596268d4f70",
29680 "type": "github"
29781 },
29882 "original": {
flake.nix+170−143
@@ -3,166 +3,193 @@
33
44 inputs = {
55 nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
6+ flake-utils.url = "github:numtide/flake-utils";
67 rust-overlay = {
78 url = "github:oxalica/rust-overlay";
8− inputs.nixpkgs.follows = "nixpkgs";
9+ inputs = {
10+ nixpkgs.follows = "nixpkgs";
11+ flake-utils.follows = "flake-utils";
12+ };
913 };
10− nci = {
11− url = "github:yusdacra/nix-cargo-integration";
14+ crane = {
15+ url = "github:ipetkov/crane";
1216 inputs.nixpkgs.follows = "nixpkgs";
13− inputs.rust-overlay.follows = "rust-overlay";
1417 };
15− parts.url = "github:hercules-ci/flake-parts";
1618 };
1719
18− outputs = inp: let
19− mkRootPath = rel:
20− builtins.path {
21− path = "${toString ./.}/${rel}";
22− name = rel;
23− };
24− filteredSource = let
25− pathsToIgnore = [
26− ".envrc"
27− ".ignore"
28− ".github"
29− ".gitignore"
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;
20+ outputs = {
21+ self,
22+ nixpkgs,
23+ crane,
24+ flake-utils,
25+ rust-overlay,
26+ ...
27+ }:
28+ flake-utils.lib.eachDefaultSystem (system: let
29+ pkgs = import nixpkgs {
30+ inherit system;
31+ overlays = [(import rust-overlay)];
6432 };
65− in
66− inp.parts.lib.mkFlake {inputs = inp;} {
67− imports = [inp.nci.flakeModule inp.parts.flakeModules.easyOverlay];
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
86− rm -r $out/grammars
87− ln -s ${grammars} $out/grammars
88− '';
89− helix-wrapped =
90− pkgs.runCommand
91− old.name
92− {
93− inherit (old) pname version;
94− meta = old.meta or {};
95− passthru =
96− (old.passthru or {})
97− // {
98− unwrapped = old;
99− };
100− nativeBuildInputs = [pkgs.makeWrapper];
101− makeWrapperArgs = config.makeWrapperArgs or [];
102− }
103− ''
104− cp -rs --no-preserve=mode,ownership ${old} $out
105− wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
106− '';
33+ mkRootPath = rel:
34+ builtins.path {
35+ path = "${toString ./.}/${rel}";
36+ name = rel;
37+ };
38+ filteredSource = let
39+ pathsToIgnore = [
40+ ".envrc"
41+ ".ignore"
42+ ".github"
43+ ".gitignore"
44+ "logo_dark.svg"
45+ "logo_light.svg"
46+ "rust-toolchain.toml"
47+ "rustfmt.toml"
48+ "runtime"
49+ "screenshot.png"
50+ "book"
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;
10768 in
108− helix-wrapped
109− // {
110− override = makeOverridableHelix old;
69+ lib.all (p: ! (lib.hasPrefix p relPath)) pathsToIgnore;
70+ in
71+ builtins.path {
72+ name = "helix-source";
73+ path = toString ./.;
74+ # filter out unnecessary paths
75+ filter = ignorePaths;
76+ };
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 {};
11191 passthru =
112− helix-wrapped.passthru
92+ (old.passthru or {})
11393 // {
114− wrapper = old: makeOverridableHelix old config;
94+ unwrapped = old;
11595 };
116− };
117− stdenv =
118− if pkgs.stdenv.isLinux
119− then pkgs.stdenv
120− else pkgs.clangStdenv;
121− rustFlagsEnv =
122− if stdenv.isLinux
123− then ''$RUSTFLAGS -C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment''
124− else "$RUSTFLAGS";
125− in {
126− nci.projects."helix-project".relPath = "";
127− nci.crates."helix-term" = {
128− overrides = {
129− add-meta.override = _: {meta.mainProgram = "hx";};
130− add-inputs.overrideAttrs = prev: {
131− buildInputs = (prev.buildInputs or []) ++ [stdenv.cc.cc.lib];
132− };
133− disable-grammar-builds = {
134− # disable fetching and building of tree-sitter grammars in the helix-term build.rs
135− HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
96+ nativeBuildInputs = [pkgs.makeWrapper];
97+ makeWrapperArgs = config.makeWrapperArgs or [];
98+ }
99+ ''
100+ cp -rs --no-preserve=mode,ownership ${old} $out
101+ wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
102+ '';
103+ in
104+ helix-wrapped
105+ // {
106+ override = makeOverridableHelix old;
107+ passthru =
108+ helix-wrapped.passthru
109+ // {
110+ wrapper = old: makeOverridableHelix old config;
136111 };
137− disable-tests = {checkPhase = ":";};
138− set-stdenv.override = _: {inherit stdenv;};
139− set-filtered-src.override = _: {src = filteredSource;};
140− };
141112 };
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+ };
142152
143− packages.helix-unwrapped = config.nci.outputs."helix-term".packages.release;
144− packages.helix-unwrapped-dev = config.nci.outputs."helix-term".packages.dev;
145− packages.helix = makeOverridableHelix config.packages.helix-unwrapped {};
146− packages.helix-dev = makeOverridableHelix config.packages.helix-unwrapped-dev {};
147− packages.default = config.packages.helix;
153+ checks = {
154+ # Build the crate itself
155+ inherit (self.packages.${system}) helix;
148156
149− overlayAttrs = {
150− inherit (config.packages) helix;
151− };
157+ clippy = craneLibMSRV.cargoClippy (commonArgs
158+ // {
159+ inherit cargoArtifacts;
160+ cargoClippyExtraArgs = "--all-targets -- --deny warnings";
161+ });
152162
153− devShells.default = config.nci.outputs."helix-project".devShell.overrideAttrs (old: {
154− nativeBuildInputs =
155− (old.nativeBuildInputs or [])
156− ++ (with pkgs; [lld_13 cargo-flamegraph rust-analyzer])
157− ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin)
158− ++ (lib.optional stdenv.isLinux pkgs.lldb)
159− ++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation);
160− shellHook = ''
161− export HELIX_RUNTIME="$PWD/runtime"
162− export RUST_BACKTRACE="1"
163− export RUSTFLAGS="${rustFlagsEnv}"
164− '';
165− });
163+ fmt = craneLibMSRV.cargoFmt commonArgs;
164+
165+ doc = craneLibMSRV.cargoDoc (commonArgs
166+ // {
167+ inherit cargoArtifacts;
168+ });
169+
170+ test = craneLibMSRV.cargoTest (commonArgs
171+ // {
172+ inherit cargoArtifacts;
173+ });
174+ };
175+
176+ devShells.default = pkgs.mkShell {
177+ inputsFrom = builtins.attrValues self.checks.${system};
178+ nativeBuildInputs = with pkgs;
179+ [lld_13 cargo-flamegraph rust-analyzer]
180+ ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin)
181+ ++ (lib.optional stdenv.isLinux pkgs.lldb)
182+ ++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation);
183+ shellHook = ''
184+ export HELIX_RUNTIME="$PWD/runtime"
185+ export RUST_BACKTRACE="1"
186+ export RUSTFLAGS="${rustFlagsEnv}"
187+ '';
188+ };
189+ })
190+ // {
191+ overlays.default = final: prev: {
192+ inherit (self.packages.${final.system}) helix;
166193 };
167194 };
168195
grammars.nix+24−21
@@ -5,6 +5,7 @@
55 runCommand,
66 yj,
77 includeGrammarIf ? _: true,
8+ grammarOverlays ? [],
89 ...
910}: let
1011 # HACK: nix < 2.6 has a bug in the toml parser, so we convert to JSON
@@ -48,22 +49,22 @@
4849 then sourceGitHub
4950 else sourceGit;
5051 in
51− stdenv.mkDerivation rec {
52+ stdenv.mkDerivation {
5253 # see https://github.com/NixOS/nixpkgs/blob/fbdd1a7c0bc29af5325e0d7dd70e804a972eb465/pkgs/development/tools/parsing/tree-sitter/grammar.nix
5354
5455 pname = "helix-tree-sitter-${grammar.name}";
5556 version = grammar.source.rev;
5657
57− src =
58− if builtins.hasAttr "subpath" grammar.source
59− then "${source}/${grammar.source.subpath}"
60− else source;
58+ src = source;
59+ sourceRoot = if builtins.hasAttr "subpath" grammar.source then
60+ "source/${grammar.source.subpath}"
61+ else
62+ "source";
6163
62− dontUnpack = true;
6364 dontConfigure = true;
6465
6566 FLAGS = [
66− "-I${src}/src"
67+ "-Isrc"
6768 "-g"
6869 "-O3"
6970 "-fPIC"
@@ -76,13 +77,13 @@
7677 buildPhase = ''
7778 runHook preBuild
7879
79− if [[ -e "$src/src/scanner.cc" ]]; then
80− $CXX -c "$src/src/scanner.cc" -o scanner.o $FLAGS
81− elif [[ -e "$src/src/scanner.c" ]]; then
82− $CC -c "$src/src/scanner.c" -o scanner.o $FLAGS
80+ if [[ -e src/scanner.cc ]]; then
81+ $CXX -c src/scanner.cc -o scanner.o $FLAGS
82+ elif [[ -e src/scanner.c ]]; then
83+ $CC -c src/scanner.c -o scanner.o $FLAGS
8384 fi
8485
85− $CC -c "$src/src/parser.c" -o parser.o $FLAGS
86+ $CC -c src/parser.c -o parser.o $FLAGS
8687 $CXX -shared -o $NAME.so *.o
8788
8889 ls -al
@@ -105,15 +106,17 @@
105106 '';
106107 };
107108 grammarsToBuild = builtins.filter includeGrammarIf gitGrammars;
108− builtGrammars =
109− builtins.map (grammar: {
110− inherit (grammar) name;
111− artifact = buildGrammar grammar;
112− })
113− grammarsToBuild;
114− grammarLinks =
115− builtins.map (grammar: "ln -s ${grammar.artifact}/${grammar.name}.so $out/${grammar.name}.so")
116− builtGrammars;
109+ builtGrammars = builtins.map (grammar: {
110+ inherit (grammar) name;
111+ value = buildGrammar grammar;
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);
117+ grammarLinks = lib.mapAttrsToList
118+ (name: artifact: "ln -s ${artifact}/${name}.so $out/${name}.so")
119+ (lib.filterAttrs (n: v: lib.isDerivation v) overlayedGrammars);
117120in
118121 runCommand "consolidated-helix-grammars" {} ''
119122 mkdir -p $out
helix-core/Cargo.toml+20−16
@@ -1,37 +1,38 @@
11[package]
22name = "helix-core"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
73description = "Helix editor core editing primitives"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
114include = ["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
1213
1314[features]
1415unicode-lines = ["ropey/unicode_lines"]
1516integration = []
1617
1718[dependencies]
18−helix-loader = { version = "0.6", path = "../helix-loader" }
19+helix-loader = { path = "../helix-loader" }
1920
20−ropey = { version = "1.6.0", default-features = false, features = ["simd"] }
21−smallvec = "1.10"
21+ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
22+smallvec = "1.11"
2223smartstring = "1.0.1"
2324unicode-segmentation = "1.10"
2425unicode-width = "0.1"
2526unicode-general-category = "0.6"
2627# slab = "0.4.2"
2728slotmap = "1.0"
28−tree-sitter = "0.20"
29−once_cell = "1.18"
29+tree-sitter.workspace = true
30+once_cell = "1.19"
3031arc-swap = "1"
3132regex = "1"
32−bitflags = "2.3"
33−ahash = "0.8.3"
34−hashbrown = { version = "0.14.0", features = ["raw"] }
33+bitflags = "2.4"
34+ahash = "0.8.6"
35+hashbrown = { version = "0.14.3", features = ["raw"] }
3536dunce = "1.0"
3637
3738log = "0.4"
@@ -48,6 +49,9 @@ chrono = { version = "0.4", default-features = false, features = ["alloc", "std"
4849etcetera = "0.8"
4950textwrap = "0.16.0"
5051
52+nucleo.workspace = true
53+parking_lot = "0.12"
54+
5155[dev-dependencies]
5256quickcheck = { version = "1", default-features = false }
53−indoc = "2.0.2"
57+indoc = "2.0.4"
helix-core/src/diagnostic.rs+4
@@ -39,6 +39,10 @@ pub enum DiagnosticTag {
3939#[derive(Debug, Clone)]
4040pub struct Diagnostic {
4141 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,
4246 pub line: usize,
4347 pub message: String,
4448 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> {
481481 let ptr = Box::into_raw(g.into_bytes().into_boxed_slice()) as *mut u8;
482482 GraphemeStr {
483483 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,
485485 phantom: PhantomData,
486486 }
487487 }
helix-core/src/history.rs+2−2
@@ -72,8 +72,8 @@ impl Default for History {
7272 revisions: vec![Revision {
7373 parent: 0,
7474 last_child: None,
75− transaction: Transaction::from(ChangeSet::new(&Rope::new())),
76− inversion: Transaction::from(ChangeSet::new(&Rope::new())),
75+ transaction: Transaction::from(ChangeSet::new("".into())),
76+ inversion: Transaction::from(ChangeSet::new("".into())),
7777 timestamp: Instant::now(),
7878 }],
7979 current: 0,
helix-core/src/indent.rs+586−127
@@ -1,13 +1,14 @@
1−use std::collections::HashMap;
1+use std::{borrow::Cow, collections::HashMap};
22
33use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
44
55use crate::{
66 chars::{char_is_line_ending, char_is_whitespace},
7− graphemes::tab_width_at,
8− syntax::{LanguageConfiguration, RopeProvider, Syntax},
7+ find_first_non_whitespace_char,
8+ graphemes::{grapheme_width, tab_width_at},
9+ syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
910 tree_sitter::Node,
10− Rope, RopeSlice,
11+ Position, Rope, RopeGraphemes, RopeSlice,
1112};
1213
1314/// Enum representing indentation style.
@@ -21,7 +22,7 @@ pub enum IndentStyle {
2122
2223// 16 spaces
2324const INDENTS: &str = " ";
24−const MAX_INDENT: u8 = 16;
25+pub const MAX_INDENT: u8 = 16;
2526
2627impl IndentStyle {
2728 /// Creates an `IndentStyle` from an indentation string.
@@ -196,6 +197,56 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: us
196197 len / indent_width
197198}
198199
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+
199250/// Computes for node and all ancestors whether they are the first node on their line.
200251/// The first entry in the return value represents the root node, the last one the node itself
201252fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bool> {
@@ -237,68 +288,140 @@ fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bo
237288/// This is usually constructed in one of 2 ways:
238289/// - Successively add indent captures to get the (added) indent from a single line
239290/// - Successively add the indent results for each line
240−#[derive(Default)]
241−pub struct Indentation {
242− /// The total indent (the number of indent levels) is defined as max(0, indent-outdent).
243− /// The string that this results in depends on the indent style (spaces or tabs, etc.)
291+/// The string that this indentation defines starts with the string contained in the align field (unless it is None), followed by:
292+/// - max(0, indent - outdent) tabs, if tabs are used for indentation
293+/// - max(0, indent - outdent)*indent_width spaces, if spaces are used for indentation
294+#[derive(Default, Debug, PartialEq, Eq, Clone)]
295+pub struct Indentation<'a> {
244296 indent: usize,
297+ indent_always: usize,
245298 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>>,
246303}
247−impl Indentation {
304+
305+impl<'a> Indentation<'a> {
248306 /// Add some other [Indentation] to this.
249− /// The added indent should be the total added indent from one line
250− fn add_line(&mut self, added: &Indentation) {
251− if added.indent > 0 && added.outdent == 0 {
252− self.indent += 1;
253− } else if added.outdent > 0 && added.indent == 0 {
254− self.outdent += 1;
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).
309+ fn add_line(&mut self, added: Indentation<'a>) {
310+ // Align overrides the indent from outer scopes.
311+ if self.align.is_some() {
312+ return;
313+ }
314+ if added.align.is_some() {
315+ self.align = added.align;
316+ return;
255317 }
318+ self.indent += added.indent;
319+ self.indent_always += added.indent_always;
320+ self.outdent += added.outdent;
321+ self.outdent_always += added.outdent_always;
256322 }
323+
257324 /// Add an indent capture to this indent.
258− /// All the captures that are added in this way should be on the same line.
259− fn add_capture(&mut self, added: IndentCaptureType) {
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).
328+ fn add_capture(&mut self, added: IndentCaptureType<'a>) {
260329 match added {
261330 IndentCaptureType::Indent => {
262− self.indent = 1;
331+ if self.indent_always == 0 {
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;
263340 }
264341 IndentCaptureType::Outdent => {
265− self.outdent = 1;
342+ if self.outdent_always == 0 {
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+ }
266354 }
267355 }
268356 }
269− fn as_string(&self, indent_style: &IndentStyle) -> String {
270− let indent_level = if self.indent >= self.outdent {
271− self.indent - self.outdent
357+ fn net_indent(&self) -> isize {
358+ (self.indent + self.indent_always) as isize
359+ - ((self.outdent + self.outdent_always) as isize)
360+ }
361+ /// Convert `self` into a string, taking into account the computed and actual indentation of some other line.
362+ fn relative_indent(
363+ &self,
364+ other_computed_indent: &Self,
365+ other_leading_whitespace: RopeSlice,
366+ indent_style: &IndentStyle,
367+ tab_width: usize,
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+ ))
272380 } else {
273− log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
274− 0
275− };
276− indent_style.as_str().repeat(indent_level)
381+ // If the alignment of both lines is different, we cannot compare their indentation in any meaningful way
382+ None
383+ }
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+ )
277393 }
278394}
279395
280396/// An indent definition which corresponds to a capture from the indent query
281−struct IndentCapture {
282− capture_type: IndentCaptureType,
397+#[derive(Debug)]
398+struct IndentCapture<'a> {
399+ capture_type: IndentCaptureType<'a>,
283400 scope: IndentScope,
284401}
285−#[derive(Clone, Copy)]
286−enum IndentCaptureType {
402+#[derive(Debug, Clone, PartialEq)]
403+enum IndentCaptureType<'a> {
287404 Indent,
405+ IndentAlways,
288406 Outdent,
407+ OutdentAlways,
408+ /// Alignment given as a string of whitespace
409+ Align(RopeSlice<'a>),
289410}
290−impl IndentCaptureType {
411+
412+impl<'a> IndentCaptureType<'a> {
291413 fn default_scope(&self) -> IndentScope {
292414 match self {
293− IndentCaptureType::Indent => IndentScope::Tail,
294− IndentCaptureType::Outdent => IndentScope::All,
415+ IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
416+ IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All,
417+ IndentCaptureType::Align(_) => IndentScope::All,
295418 }
296419 }
297420}
298421/// This defines which part of a node an [IndentCapture] applies to.
299422/// Each [IndentCaptureType] has a default scope, but the scope can be changed
300423/// with `#set!` property declarations.
301−#[derive(Clone, Copy)]
424+#[derive(Debug, Clone, Copy)]
302425enum IndentScope {
303426 /// The indent applies to the whole node
304427 All,
@@ -308,6 +431,7 @@ enum IndentScope {
308431
309432/// A capture from the indent query which does not define an indent but extends
310433/// the range of a node. This is used before the indent is calculated.
434+#[derive(Debug)]
311435enum ExtendCapture {
312436 Extend,
313437 PreventOnce,
@@ -316,24 +440,41 @@ enum ExtendCapture {
316440/// The result of running a tree-sitter indent query. This stores for
317441/// each node (identified by its ID) the relevant captures (already filtered
318442/// by predicates).
319−struct IndentQueryResult {
320− indent_captures: HashMap<usize, Vec<IndentCapture>>,
443+#[derive(Debug)]
444+struct IndentQueryResult<'a> {
445+ indent_captures: HashMap<usize, Vec<IndentCapture<'a>>>,
321446 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
322447}
323448
324−fn query_indents(
449+fn get_node_start_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
450+ let mut node_line = node.start_position().row;
451+ // Adjust for the new line that will be inserted
452+ if new_line_byte_pos.map_or(false, |pos| node.start_byte() >= pos) {
453+ node_line += 1;
454+ }
455+ node_line
456+}
457+fn get_node_end_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
458+ let mut node_line = node.end_position().row;
459+ // Adjust for the new line that will be inserted (with a strict inequality since end_byte is exclusive)
460+ if new_line_byte_pos.map_or(false, |pos| node.end_byte() > pos) {
461+ node_line += 1;
462+ }
463+ node_line
464+}
465+
466+fn query_indents<'a>(
325467 query: &Query,
326468 syntax: &Syntax,
327469 cursor: &mut QueryCursor,
328− text: RopeSlice,
470+ text: RopeSlice<'a>,
329471 range: std::ops::Range<usize>,
330− // Position of the (optional) newly inserted line break.
331− // Given as (line, byte_pos)
332− new_line_break: Option<(usize, usize)>,
333−) -> IndentQueryResult {
472+ new_line_byte_pos: Option<usize>,
473+) -> IndentQueryResult<'a> {
334474 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
335475 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
336476 cursor.set_byte_range(range);
477+
337478 // Iterate over all captures from the query
338479 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
339480 // Skip matches where not all custom predicates are fulfilled
@@ -360,21 +501,13 @@ fn query_indents(
360501 Some(QueryPredicateArg::Capture(capt1)),
361502 Some(QueryPredicateArg::Capture(capt2))
362503 ) => {
363− let get_line_num = |node: Node| {
364− let mut node_line = node.start_position().row;
365− // Adjust for the new line that will be inserted
366− if let Some((line, byte)) = new_line_break {
367− if node_line==line && node.start_byte()>=byte {
368− node_line += 1;
369− }
370− }
371− node_line
372− };
373504 let n1 = m.nodes_for_capture_index(*capt1).next();
374505 let n2 = m.nodes_for_capture_index(*capt2).next();
375506 match (n1, n2) {
376507 (Some(n1), Some(n2)) => {
377− 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;
378511 same_line==(pred.operator.as_ref()=="same-line?")
379512 }
380513 _ => true,
@@ -385,6 +518,23 @@ fn query_indents(
385518 }
386519 }
387520 }
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+ },
388538 _ => {
389539 panic!(
390540 "Invalid indent query: Unknown predicate (\"{}\")",
@@ -395,11 +545,28 @@ fn query_indents(
395545 }) {
396546 continue;
397547 }
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;
398553 for capture in m.captures {
399554 let capture_name = query.capture_names()[capture.index as usize].as_str();
400555 let capture_type = match capture_name {
401556 "indent" => IndentCaptureType::Indent,
557+ "indent.always" => IndentCaptureType::IndentAlways,
402558 "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+ }
403570 "extend" => {
404571 extend_captures
405572 .entry(capture.node.id())
@@ -449,17 +616,40 @@ fn query_indents(
449616 }
450617 }
451618 }
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+ }
452638 indent_captures
453− .entry(capture.node.id())
454− // Most entries only need to contain a single IndentCapture
639+ .entry(node_id)
455640 .or_insert_with(|| Vec::with_capacity(1))
456− .push(indent_capture);
641+ .push(capture);
457642 }
458643 }
459− IndentQueryResult {
644+
645+ let result = IndentQueryResult {
460646 indent_captures,
461647 extend_captures,
462− }
648+ };
649+
650+ log::trace!("indent result = {:?}", result);
651+
652+ result
463653}
464654
465655/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
@@ -529,61 +719,27 @@ fn extend_nodes<'a>(
529719 }
530720}
531721
532−/// Use the syntax tree to determine the indentation for a given position.
533−/// This can be used in 2 ways:
534−///
535−/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
536−/// - In this case, pos should be inside the first tree-sitter node on that line.
537−/// In most cases, this can just be the first non-whitespace on that line.
538−/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
539−/// after pos were moved to a new line.
540−///
541−/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
542−/// Each of these nodes produces some [Indentation] for:
543−///
544−/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
545−/// - The line after the node. This is defined by:
546−/// - The scope `tail`.
547−/// - The scope `all` if this node is not the first node on its line.
548−/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
549−/// The indents from different nodes for the same line are then combined.
550−/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
551−///
552−/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
553−/// ```ignore
554−/// some_function(|| {
555−/// // Both the function parameters as well as the contained block should be indented.
556−/// // Because they are on the same line, this only yields one indent level
557−/// });
558−/// ```
559−///
560−/// ```ignore
561−/// some_function(
562−/// param1,
563−/// || {
564−/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
565−/// },
566−/// );
567−/// ```
722+/// Prepare an indent query by computing:
723+/// - The node from which to start the query (this is non-trivial due to `@extend` captures)
724+/// - The indent captures for all relevant nodes.
568725#[allow(clippy::too_many_arguments)]
569−pub fn treesitter_indent_for_pos(
726+fn init_indent_query<'a, 'b>(
570727 query: &Query,
571− syntax: &Syntax,
572− indent_style: &IndentStyle,
728+ syntax: &'a Syntax,
729+ text: RopeSlice<'b>,
573730 tab_width: usize,
574731 indent_width: usize,
575− text: RopeSlice,
576732 line: usize,
577− pos: usize,
578− new_line: bool,
579−) -> Option<String> {
580− let byte_pos = text.char_to_byte(pos);
733+ byte_pos: usize,
734+ new_line_byte_pos: Option<usize>,
735+) -> Option<(Node<'a>, HashMap<usize, Vec<IndentCapture<'b>>>)> {
581736 // The innermost tree-sitter node which is considered for the indent
582737 // computation. It may change if some predeceding node is extended
583738 let mut node = syntax
584739 .tree()
585740 .root_node()
586741 .descendant_for_byte_range(byte_pos, byte_pos)?;
742+
587743 let (query_result, deepest_preceding) = {
588744 // The query range should intersect with all nodes directly preceding
589745 // the position of the indent query in case one of them is extended.
@@ -614,13 +770,12 @@ pub fn treesitter_indent_for_pos(
614770 &mut cursor,
615771 text,
616772 query_range,
617− new_line.then_some((line, byte_pos)),
773+ new_line_byte_pos,
618774 );
619775 ts_parser.cursors.push(cursor);
620776 (query_result, deepest_preceding)
621777 })
622778 };
623− let indent_captures = query_result.indent_captures;
624779 let extend_captures = query_result.extend_captures;
625780
626781 // Check for extend captures, potentially changing the node that the indent calculation starts with
@@ -635,6 +790,68 @@ pub fn treesitter_indent_for_pos(
635790 indent_width,
636791 );
637792 }
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+ )?;
638855 let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos));
639856
640857 let mut result = Indentation::default();
@@ -642,12 +859,16 @@ pub fn treesitter_indent_for_pos(
642859 // even if there are multiple "indent" nodes on the same line
643860 let mut indent_for_line = Indentation::default();
644861 let mut indent_for_line_below = Indentation::default();
862+
645863 loop {
646864 // This can safely be unwrapped because `first_in_line` contains
647865 // one entry for each ancestor of the node (which is what we iterate over)
648866 let is_first = *first_in_line.last().unwrap();
649− // Apply all indent definitions for this node
650− if let Some(definitions) = indent_captures.get(&node.id()) {
867+
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.
871+ if let Some(definitions) = indent_captures.remove(&node.id()) {
651872 for definition in definitions {
652873 match definition.scope {
653874 IndentScope::All => {
@@ -665,28 +886,22 @@ pub fn treesitter_indent_for_pos(
665886 }
666887
667888 if let Some(parent) = node.parent() {
668− let mut node_line = node.start_position().row;
669− let mut parent_line = parent.start_position().row;
670− if node_line == line && new_line {
671− // Also consider the line that will be inserted
672− if node.start_byte() >= byte_pos {
673− node_line += 1;
674− }
675− if parent.start_byte() >= byte_pos {
676− parent_line += 1;
677− }
678− };
889+ let node_line = get_node_start_line(node, new_line_byte_pos);
890+ let parent_line = get_node_start_line(parent, new_line_byte_pos);
891+
679892 if node_line != parent_line {
893+ // Don't add indent for the line below the line of the query
680894 if node_line < line + (new_line as usize) {
681− // Don't add indent for the line below the line of the query
682− result.add_line(&indent_for_line_below);
895+ result.add_line(indent_for_line_below);
683896 }
897+
684898 if node_line == parent_line + 1 {
685899 indent_for_line_below = indent_for_line;
686900 } else {
687− result.add_line(&indent_for_line);
901+ result.add_line(indent_for_line);
688902 indent_for_line_below = Indentation::default();
689903 }
904+
690905 indent_for_line = Indentation::default();
691906 }
692907
@@ -698,13 +913,13 @@ pub fn treesitter_indent_for_pos(
698913 if (node.start_position().row < line)
699914 || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
700915 {
701− result.add_line(&indent_for_line_below);
916+ result.add_line(indent_for_line_below);
702917 }
703− result.add_line(&indent_for_line);
918+ result.add_line(indent_for_line);
704919 break;
705920 }
706921 }
707− Some(result.as_string(indent_style))
922+ Some(result)
708923}
709924
710925/// Returns the indentation for a new line.
@@ -713,6 +928,7 @@ pub fn treesitter_indent_for_pos(
713928pub fn indent_for_newline(
714929 language_config: Option<&LanguageConfiguration>,
715930 syntax: Option<&Syntax>,
931+ indent_heuristic: &IndentationHeuristic,
716932 indent_style: &IndentStyle,
717933 tab_width: usize,
718934 text: RopeSlice,
@@ -721,14 +937,18 @@ pub fn indent_for_newline(
721937 current_line: usize,
722938) -> String {
723939 let indent_width = indent_style.indent_width(tab_width);
724− if let (Some(query), Some(syntax)) = (
940+ if let (
941+ IndentationHeuristic::TreeSitter | IndentationHeuristic::Hybrid,
942+ Some(query),
943+ Some(syntax),
944+ ) = (
945+ indent_heuristic,
725946 language_config.and_then(|config| config.indent_query()),
726947 syntax,
727948 ) {
728949 if let Some(indent) = treesitter_indent_for_pos(
729950 query,
730951 syntax,
731− indent_style,
732952 tab_width,
733953 indent_width,
734954 text,
@@ -736,9 +956,57 @@ pub fn indent_for_newline(
736956 line_before_end_pos,
737957 true,
738958 ) {
739− return indent;
959+ if *indent_heuristic == IndentationHeuristic::Hybrid {
960+ // We want to compute the indentation not only based on the
961+ // syntax tree but also on the actual indentation of a previous
962+ // line. This makes indentation computation more resilient to
963+ // incomplete queries, incomplete source code & differing indentation
964+ // styles for the same language.
965+ // However, using the indent of a previous line as a baseline may not
966+ // make sense, e.g. if it has a different alignment than the new line.
967+ // In order to prevent edge cases with long running times, we only try
968+ // a constant number of (non-empty) lines.
969+ const MAX_ATTEMPTS: usize = 4;
970+ let mut num_attempts = 0;
971+ for line_idx in (0..=line_before).rev() {
972+ let line = text.line(line_idx);
973+ let first_non_whitespace_char = match find_first_non_whitespace_char(line) {
974+ Some(i) => i,
975+ None => {
976+ continue;
977+ }
978+ };
979+ if let Some(indent) = (|| {
980+ let computed_indent = treesitter_indent_for_pos(
981+ query,
982+ syntax,
983+ tab_width,
984+ indent_width,
985+ text,
986+ line_idx,
987+ text.line_to_char(line_idx) + first_non_whitespace_char,
988+ false,
989+ )?;
990+ let leading_whitespace = line.slice(0..first_non_whitespace_char);
991+ indent.relative_indent(
992+ &computed_indent,
993+ leading_whitespace,
994+ indent_style,
995+ tab_width,
996+ )
997+ })() {
998+ return indent;
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);
7401007 };
7411008 }
1009+ // Fallback in case we either don't have indent queries or they failed for some reason
7421010 let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
7431011 indent_style.as_str().repeat(indent_level)
7441012}
@@ -810,4 +1078,195 @@ mod test {
8101078 2
8111079 );
8121080 }
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+ }
8131272}
helix-core/src/lib.rs+3−1
@@ -7,6 +7,7 @@ pub mod config;
77pub mod diagnostic;
88pub mod diff;
99pub mod doc_formatter;
10+pub mod fuzzy;
1011pub mod graphemes;
1112pub mod history;
1213pub mod increment;
@@ -18,7 +19,6 @@ pub mod movement;
1819pub mod object;
1920pub mod path;
2021mod position;
21−pub mod register;
2222pub mod search;
2323pub mod selection;
2424pub mod shellwords;
@@ -41,7 +41,9 @@ pub use helix_loader::find_workspace;
4141pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
4242 line.chars().position(|ch| !ch.is_whitespace())
4343}
44+mod rope_reader;
4445
46+pub use rope_reader::RopeReader;
4547pub use ropey::{self, str_utils, Rope, RopeBuilder, RopeSlice};
4648
4749// pub use tendril::StrTendril as Tendril;
helix-core/src/match_brackets.rs+6−2
@@ -106,12 +106,16 @@ fn find_pair(
106106 for close in
107107 iter::successors(node.next_sibling(), |node| node.next_sibling()).take(MATCH_LIMIT)
108108 {
109− let Some(open) = as_close_pair(doc, &close) else { continue; };
109+ let Some(open) = as_close_pair(doc, &close) else {
110+ continue;
111+ };
110112 if find_pair_end(doc, Some(node), open, Backward).is_some() {
111113 return doc.try_byte_to_char(close.start_byte()).ok();
112114 }
113115 }
114− let Some(parent) = node.parent() else { break; };
116+ let Some(parent) = node.parent() else {
117+ break;
118+ };
115119 node = parent;
116120 }
117121 let node = tree.root_node().named_descendant_for_byte_range(pos, pos)?;
helix-core/src/movement.rs+80−1
@@ -16,7 +16,7 @@ use crate::{
1616 syntax::LanguageConfiguration,
1717 text_annotations::TextAnnotations,
1818 textobject::TextObject,
19− visual_offset_from_block, Range, RopeSlice,
19+ visual_offset_from_block, Range, RopeSlice, Selection, Syntax,
2020};
2121
2222#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -556,6 +556,85 @@ pub fn goto_treesitter_object(
556556 last_range
557557}
558558
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+
559638#[cfg(test)]
560639mod test {
561640 use ropey::Rope;
helix-core/src/path.rs+5−7
@@ -85,23 +85,21 @@ pub fn get_normalized_path(path: &Path) -> PathBuf {
8585///
8686/// This function is used instead of `std::fs::canonicalize` because we don't want to verify
8787/// 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 {
8989 let path = expand_tilde(path);
9090 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)
9292 } else {
9393 path
9494 };
9595
96− Ok(get_normalized_path(path.as_path()))
96+ get_normalized_path(path.as_path())
9797}
9898
9999pub fn get_relative_path(path: &Path) -> PathBuf {
100100 let path = PathBuf::from(path);
101101 let path = if path.is_absolute() {
102− let cwdir = std::env::current_dir()
103− .map(|path| get_normalized_path(&path))
104− .expect("couldn't determine current directory");
102+ let cwdir = get_normalized_path(&helix_loader::current_working_dir());
105103 get_normalized_path(&path)
106104 .strip_prefix(cwdir)
107105 .map(PathBuf::from)
@@ -142,7 +140,7 @@ pub fn get_relative_path(path: &Path) -> PathBuf {
142140/// ```
143141///
144142pub 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();
146144 let path = path
147145 .as_ref()
148146 .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+10−2
@@ -630,11 +630,19 @@ impl Selection {
630630 self.transform(|range| Range::point(range.cursor(text)))
631631 }
632632
633− 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+ {
634638 self.ranges.iter().map(move |range| range.fragment(text))
635639 }
636640
637− 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+ {
638646 self.ranges.iter().map(move |range| range.slice(text))
639647 }
640648
helix-core/src/syntax.rs+88−80
@@ -4,7 +4,7 @@ use crate::{
44 diagnostic::Severity,
55 regex::Regex,
66 transaction::{ChangeSet, Operation},
7− Rope, RopeSlice, Tendril,
7+ RopeSlice, Tendril,
88};
99
1010use ahash::RandomState;
@@ -101,7 +101,8 @@ pub struct LanguageConfiguration {
101101 pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
102102 #[serde(default)]
103103 pub shebangs: Vec<String>, // interpreter(s) associated with language
104− pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
104+ #[serde(default)]
105+ pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
105106 pub comment_token: Option<String>,
106107 pub text_width: Option<usize>,
107108 pub soft_wrap: Option<SoftWrap>,
@@ -154,6 +155,8 @@ pub struct LanguageConfiguration {
154155 /// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
155156 /// Falling back to the current working directory if none are configured.
156157 pub workspace_lsp_roots: Option<Vec<PathBuf>>,
158+ #[serde(default)]
159+ pub persistent_diagnostic_sources: Vec<String>,
157160}
158161
159162#[derive(Debug, PartialEq, Eq, Hash)]
@@ -211,10 +214,7 @@ impl<'de> Deserialize<'de> for FileType {
211214 {
212215 match map.next_entry::<String, String>()? {
213216 Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
214− // FIXME: use `suffix.replace('/', std::path::MAIN_SEPARATOR_STR)`
215− // if MSRV is updated to 1.68
216− let mut separator = [0; 1];
217− suffix.replace('/', std::path::MAIN_SEPARATOR.encode_utf8(&mut separator))
217+ suffix.replace('/', std::path::MAIN_SEPARATOR_STR)
218218 })),
219219 Some((key, _value)) => Err(serde::de::Error::custom(format!(
220220 "unknown key in `file-types` list: {}",
@@ -444,6 +444,22 @@ pub struct IndentationConfiguration {
444444 pub unit: String,
445445}
446446
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+
447463/// Configuration for auto pairs
448464#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
449465#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
@@ -818,7 +834,10 @@ impl Loader {
818834 // TODO: content_regex handling conflict resolution
819835 }
820836
821− 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>> {
822841 let line = Cow::from(source.line(0));
823842 static SHEBANG_REGEX: Lazy<Regex> =
824843 Lazy::new(|| Regex::new(&["^", SHEBANG].concat()).unwrap());
@@ -928,7 +947,7 @@ fn byte_range_to_str(range: std::ops::Range<usize>, source: RopeSlice) -> Cow<st
928947
929948impl Syntax {
930949 pub fn new(
931− source: &Rope,
950+ source: RopeSlice,
932951 config: Arc<HighlightConfiguration>,
933952 loader: Arc<Loader>,
934953 ) -> Option<Self> {
@@ -959,7 +978,7 @@ impl Syntax {
959978 let res = syntax.update(source, source, &ChangeSet::new(source));
960979
961980 if res.is_err() {
962− 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:?}");
963982 return None;
964983 }
965984 Some(syntax)
@@ -967,8 +986,8 @@ impl Syntax {
967986
968987 pub fn update(
969988 &mut self,
970− old_source: &Rope,
971− source: &Rope,
989+ old_source: RopeSlice,
990+ source: RopeSlice,
972991 changeset: &ChangeSet,
973992 ) -> Result<(), Error> {
974993 let mut queue = VecDeque::new();
@@ -1135,12 +1154,38 @@ impl Syntax {
11351154 layer.tree().root_node(),
11361155 RopeProvider(source_slice),
11371156 );
1157+ let mut combined_injections = vec![
1158+ (None, Vec::new(), IncludedChildren::default());
1159+ layer.config.combined_injections_patterns.len()
1160+ ];
11381161 let mut injections = Vec::new();
1162+ let mut last_injection_end = 0;
11391163 for mat in matches {
11401164 let (injection_capture, content_node, included_children) = layer
11411165 .config
11421166 .injection_for_match(&layer.config.injections_query, &mat, source_slice);
11431167
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+
11441189 // Explicitly remove this match so that none of its other captures will remain
11451190 // in the stream of captures.
11461191 mat.remove();
@@ -1155,49 +1200,23 @@ impl Syntax {
11551200 intersect_ranges(&layer.ranges, &[content_node], included_children);
11561201
11571202 if !ranges.is_empty() {
1203+ if content_node.start_byte() < last_injection_end {
1204+ continue;
1205+ }
1206+ last_injection_end = content_node.end_byte();
11581207 injections.push((config, ranges));
11591208 }
11601209 }
11611210 }
11621211 }
11631212
1164− // Process combined injections.
1165− if let Some(combined_injections_query) = &layer.config.combined_injections_query {
1166− let mut injections_by_pattern_index =
1167− vec![
1168− (None, Vec::new(), IncludedChildren::default());
1169− combined_injections_query.pattern_count()
1170− ];
1171− let matches = cursor.matches(
1172− combined_injections_query,
1173− layer.tree().root_node(),
1174− RopeProvider(source_slice),
1175− );
1176− for mat in matches {
1177− let entry = &mut injections_by_pattern_index[mat.pattern_index];
1178− let (injection_capture, content_node, included_children) = layer
1179− .config
1180− .injection_for_match(combined_injections_query, &mat, source_slice);
1181− if injection_capture.is_some() {
1182− entry.0 = injection_capture;
1183− }
1184− if let Some(content_node) = content_node {
1185− entry.1.push(content_node);
1186− }
1187− entry.2 = included_children;
1188− }
1189− for (lang_name, content_nodes, included_children) in injections_by_pattern_index
1190− {
1191− if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) {
1192− if let Some(config) = (injection_callback)(&lang_name) {
1193− let ranges = intersect_ranges(
1194− &layer.ranges,
1195− &content_nodes,
1196− included_children,
1197− );
1198− if !ranges.is_empty() {
1199− injections.push((config, ranges));
1200− }
1213+ for (lang_name, content_nodes, included_children) in combined_injections {
1214+ if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) {
1215+ if let Some(config) = (injection_callback)(&lang_name) {
1216+ let ranges =
1217+ intersect_ranges(&layer.ranges, &content_nodes, included_children);
1218+ if !ranges.is_empty() {
1219+ injections.push((config, ranges));
12011220 }
12021221 }
12031222 }
@@ -1387,7 +1406,7 @@ impl LanguageLayer {
13871406 self.tree.as_ref().unwrap()
13881407 }
13891408
1390− fn parse(&mut self, parser: &mut Parser, source: &Rope) -> Result<(), Error> {
1409+ fn parse(&mut self, parser: &mut Parser, source: RopeSlice) -> Result<(), Error> {
13911410 parser
13921411 .set_included_ranges(&self.ranges)
13931412 .map_err(|_| Error::InvalidRanges)?;
@@ -1418,7 +1437,7 @@ impl LanguageLayer {
14181437}
14191438
14201439pub(crate) fn generate_edits(
1421− old_text: &Rope,
1440+ old_text: RopeSlice,
14221441 changeset: &ChangeSet,
14231442) -> Vec<tree_sitter::InputEdit> {
14241443 use Operation::*;
@@ -1434,7 +1453,7 @@ pub(crate) fn generate_edits(
14341453
14351454 // TODO; this is a lot easier with Change instead of Operation.
14361455
1437− fn point_at_pos(text: &Rope, pos: usize) -> (usize, Point) {
1456+ fn point_at_pos(text: RopeSlice, pos: usize) -> (usize, Point) {
14381457 let byte = text.char_to_byte(pos); // <- attempted to index past end
14391458 let line = text.char_to_line(pos);
14401459 let line_start_byte = text.line_to_byte(line);
@@ -1560,7 +1579,7 @@ pub struct HighlightConfiguration {
15601579 pub language: Grammar,
15611580 pub query: Query,
15621581 injections_query: Query,
1563− combined_injections_query: Option<Query>,
1582+ combined_injections_patterns: Vec<usize>,
15641583 highlights_pattern_index: usize,
15651584 highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
15661585 non_local_variable_patterns: Vec<bool>,
@@ -1611,7 +1630,7 @@ impl<'a> Iterator for ChunksBytes<'a> {
16111630}
16121631
16131632pub struct RopeProvider<'a>(pub RopeSlice<'a>);
1614−impl<'a> TextProvider<'a> for RopeProvider<'a> {
1633+impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> {
16151634 type I = ChunksBytes<'a>;
16161635
16171636 fn text(&mut self, node: Node) -> Self::I {
@@ -1625,7 +1644,7 @@ impl<'a> TextProvider<'a> for RopeProvider<'a> {
16251644struct HighlightIterLayer<'a> {
16261645 _tree: Option<Tree>,
16271646 cursor: QueryCursor,
1628− captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>>>>,
1647+ captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>, &'a [u8]>>>,
16291648 config: &'a HighlightConfiguration,
16301649 highlight_end_stack: Vec<usize>,
16311650 scope_stack: Vec<LocalScope<'a>>,
@@ -1676,26 +1695,15 @@ impl HighlightConfiguration {
16761695 }
16771696 }
16781697
1679− let mut injections_query = Query::new(language, injection_query)?;
1680−
1681− // Construct a separate query just for dealing with the 'combined injections'.
1682− // Disable the combined injection patterns in the main query.
1683− let mut combined_injections_query = Query::new(language, injection_query)?;
1684− let mut has_combined_queries = false;
1685− for pattern_index in 0..injections_query.pattern_count() {
1686− let settings = injections_query.property_settings(pattern_index);
1687− if settings.iter().any(|s| &*s.key == "injection.combined") {
1688− has_combined_queries = true;
1689− injections_query.disable_pattern(pattern_index);
1690− } else {
1691− combined_injections_query.disable_pattern(pattern_index);
1692− }
1693− }
1694− let combined_injections_query = if has_combined_queries {
1695− Some(combined_injections_query)
1696− } else {
1697− None
1698− };
1698+ let injections_query = Query::new(language, injection_query)?;
1699+ let combined_injections_patterns = (0..injections_query.pattern_count())
1700+ .filter(|&i| {
1701+ injections_query
1702+ .property_settings(i)
1703+ .iter()
1704+ .any(|s| &*s.key == "injection.combined")
1705+ })
1706+ .collect();
16991707
17001708 // Find all of the highlighting patterns that are disabled for nodes that
17011709 // have been identified as local variables.
@@ -1744,7 +1752,7 @@ impl HighlightConfiguration {
17441752 language,
17451753 query,
17461754 injections_query,
1747− combined_injections_query,
1755+ combined_injections_patterns,
17481756 highlights_pattern_index,
17491757 highlight_indices,
17501758 non_local_variable_patterns,
@@ -2540,7 +2548,7 @@ mod test {
25402548 let mut cursor = QueryCursor::new();
25412549
25422550 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2543− 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();
25442552
25452553 let root = syntax.tree().root_node();
25462554 let mut test = |capture, range| {
@@ -2614,7 +2622,7 @@ mod test {
26142622 fn main() {}
26152623 ",
26162624 );
2617− 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();
26182626 let tree = syntax.tree();
26192627 let root = tree.root_node();
26202628 assert_eq!(root.kind(), "source_file");
@@ -2641,7 +2649,7 @@ mod test {
26412649 &doc,
26422650 vec![(6, 11, Some("test".into())), (12, 17, None)].into_iter(),
26432651 );
2644− let edits = generate_edits(&doc, transaction.changes());
2652+ let edits = generate_edits(doc.slice(..), transaction.changes());
26452653 // transaction.apply(&mut state);
26462654
26472655 assert_eq!(
@@ -2670,7 +2678,7 @@ mod test {
26702678 let mut doc = Rope::from("fn test() {}");
26712679 let transaction =
26722680 Transaction::change(&doc, vec![(8, 8, Some("a: u32".into()))].into_iter());
2673− let edits = generate_edits(&doc, transaction.changes());
2681+ let edits = generate_edits(doc.slice(..), transaction.changes());
26742682 transaction.apply(&mut doc);
26752683
26762684 assert_eq!(doc, "fn test(a: u32) {}");
@@ -2704,7 +2712,7 @@ mod test {
27042712 let language = get_language(language_name).unwrap();
27052713
27062714 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2707− 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();
27082716
27092717 let root = syntax
27102718 .tree()
helix-core/src/transaction.rs+84−21
@@ -1,6 +1,7 @@
1+use ropey::RopeSlice;
12use smallvec::SmallVec;
23
3−use crate::{Range, Rope, Selection, Tendril};
4+use crate::{chars::char_is_word, Range, Rope, Selection, Tendril};
45use std::{borrow::Cow, iter::once};
56
67/// (from, to, replacement)
@@ -22,6 +23,30 @@ pub enum Operation {
2223pub enum Assoc {
2324 Before,
2425 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+ }
2550}
2651
2752#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -42,7 +67,7 @@ impl ChangeSet {
4267 }
4368
4469 #[must_use]
45− pub fn new(doc: &Rope) -> Self {
70+ pub fn new(doc: RopeSlice) -> Self {
4671 let len = doc.len_chars();
4772 Self {
4873 changes: Vec::new(),
@@ -388,7 +413,10 @@ impl ChangeSet {
388413 }
389414
390415 let Some((i, change)) = iter.next() else {
391− map!(|pos, _| (old_pos == pos).then_some(new_pos), self.changes.len());
416+ map!(
417+ |pos, _| (old_pos == pos).then_some(new_pos),
418+ self.changes.len()
419+ );
392420 break;
393421 };
394422
@@ -411,8 +439,6 @@ impl ChangeSet {
411439 map!(|pos, _| (old_end > pos).then_some(new_pos), i);
412440 }
413441 Insert(s) => {
414− let ins = s.chars().count();
415−
416442 // a subsequent delete means a replace, consume it
417443 if let Some((_, Delete(len))) = iter.peek() {
418444 iter.next();
@@ -420,13 +446,13 @@ impl ChangeSet {
420446 old_end = old_pos + len;
421447 // in range of replaced text
422448 map!(
423− |pos, assoc| (old_end > pos).then(|| {
449+ |pos, assoc: Assoc| (old_end > pos).then(|| {
424450 // at point or tracking before
425− if pos == old_pos || assoc == Assoc::Before {
451+ if pos == old_pos && assoc.stay_at_gaps() {
426452 new_pos
427453 } else {
428454 // place to end of insert
429− new_pos + ins
455+ new_pos + assoc.insert_offset(s)
430456 }
431457 }),
432458 i
@@ -434,20 +460,15 @@ impl ChangeSet {
434460 } else {
435461 // at insert point
436462 map!(
437− |pos, assoc| (old_pos == pos).then(|| {
463+ |pos, assoc: Assoc| (old_pos == pos).then(|| {
438464 // return position before inserted text
439− if assoc == Assoc::Before {
440− new_pos
441− } else {
442− // after text
443− new_pos + ins
444− }
465+ new_pos + assoc.insert_offset(s)
445466 }),
446467 i
447468 );
448469 }
449470
450− new_pos += ins;
471+ new_pos += s.chars().count();
451472 }
452473 }
453474 old_pos = old_end;
@@ -485,7 +506,7 @@ impl Transaction {
485506 /// Create a new, empty transaction.
486507 pub fn new(doc: &Rope) -> Self {
487508 Self {
488− changes: ChangeSet::new(doc),
509+ changes: ChangeSet::new(doc.slice(..)),
489510 selection: None,
490511 }
491512 }
@@ -880,6 +901,48 @@ mod test {
880901 let mut positions = [4, 2];
881902 cs.update_positions(positions.iter_mut().map(|pos| (pos, Assoc::After)));
882903 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);
883946 }
884947
885948 #[test]
@@ -946,9 +1009,9 @@ mod test {
9461009 #[test]
9471010 fn combine_with_empty() {
9481011 let empty = Rope::from("");
949− let a = ChangeSet::new(&empty);
1012+ let a = ChangeSet::new(empty.slice(..));
9501013
951− let mut b = ChangeSet::new(&empty);
1014+ let mut b = ChangeSet::new(empty.slice(..));
9521015 b.insert("a".into());
9531016
9541017 let changes = a.compose(b);
@@ -962,9 +1025,9 @@ mod test {
9621025 const TEST_CASE: &str = "Hello, これはヘリックスエディターです!";
9631026
9641027 let empty = Rope::from("");
965− let a = ChangeSet::new(&empty);
1028+ let a = ChangeSet::new(empty.slice(..));
9661029
967− let mut b = ChangeSet::new(&empty);
1030+ let mut b = ChangeSet::new(empty.slice(..));
9681031 b.insert(TEST_CASE.into());
9691032
9701033 let changes = a.compose(b);
helix-core/src/wrap.rs+3−1
@@ -1,7 +1,9 @@
11use smartstring::{LazyCompact, SmartString};
2+use textwrap::{Options, WordSplitter::NoHyphenation};
23
34/// Given a slice of text, return the text re-wrapped to fit it
45/// within the given width.
56pub fn reflow_hard_wrap(text: &str, text_width: usize) -> SmartString<LazyCompact> {
6− textwrap::refill(text, text_width).into()
7+ let options = Options::new(text_width).word_splitter(NoHyphenation);
8+ textwrap::refill(text, options).into()
79}
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 = " " }
1111[[grammar]]
1212name = "rust"
1313source = { 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 @@
11use helix_core::{
22 indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle},
3− syntax::Loader,
3+ syntax::{Configuration, Loader},
44 Syntax,
55};
6−use std::path::PathBuf;
6+use ropey::Rope;
7+use std::{ops::Range, path::PathBuf, process::Command};
78
89#[test]
910fn test_treesitter_indent_rust() {
10− test_treesitter_indent("rust.rs", "source.rust");
11+ standard_treesitter_test("rust.rs", "source.rust");
1112}
13+
1214#[test]
13−fn test_treesitter_indent_rust_2() {
14− test_treesitter_indent("indent.rs", "source.rust");
15− // TODO Use commands.rs as indentation test.
16− // Currently this fails because we can't align the parameters of a closure yet
17− // test_treesitter_indent("commands.rs", "source.rust");
15+fn test_treesitter_indent_cpp() {
16+ standard_treesitter_test("cpp.cpp", "source.cpp");
17+}
18+
19+#[test]
20+fn test_treesitter_indent_rust_helix() {
21+ // We pin a specific git revision to prevent unrelated changes from causing the indent tests to fail.
22+ // Ideally, someone updates this once in a while and fixes any errors that occur.
23+ let rev = "af382768cdaf89ff547dbd8f644a1bddd90e7c8f";
24+ let files = Command::new("git")
25+ .args([
26+ "ls-tree",
27+ "-r",
28+ "--name-only",
29+ "--full-tree",
30+ rev,
31+ "helix-term/src",
32+ ])
33+ .output()
34+ .unwrap();
35+ let files = String::from_utf8(files.stdout).unwrap();
36+
37+ let ignored_files = vec![
38+ // Contains many macros that tree-sitter does not parse in a meaningful way and is otherwise not very interesting
39+ "helix-term/src/health.rs",
40+ ];
41+
42+ for file in files.split_whitespace() {
43+ if ignored_files.contains(&file) {
44+ continue;
45+ }
46+ let ignored_lines: Vec<Range<usize>> = match file {
47+ "helix-term/src/application.rs" => vec![
48+ // We can't handle complicated indent rules inside macros (`json!` in this case) since
49+ // the tree-sitter grammar only parses them as `token_tree` and `identifier` nodes.
50+ 1045..1051,
51+ ],
52+ "helix-term/src/commands.rs" => vec![
53+ // This is broken because of the current handling of `call_expression`
54+ // (i.e. having an indent query for it but outdenting again in specific cases).
55+ // The indent query is needed to correctly handle multi-line arguments in function calls
56+ // inside indented `field_expression` nodes (which occurs fairly often).
57+ //
58+ // Once we have the `@indent.always` capture type, it might be possible to just have an indent
59+ // capture for the `arguments` field of a call expression. That could enable us to correctly
60+ // handle this.
61+ 2226..2230,
62+ ],
63+ "helix-term/src/commands/dap.rs" => vec![
64+ // Complex `format!` macro
65+ 46..52,
66+ ],
67+ "helix-term/src/commands/lsp.rs" => vec![
68+ // Macro
69+ 624..627,
70+ // Return type declaration of a closure. `cargo fmt` adds an additional space here,
71+ // which we cannot (yet) model with our indent queries.
72+ 878..879,
73+ // Same as in `helix-term/src/commands.rs`
74+ 1335..1343,
75+ ],
76+ "helix-term/src/config.rs" => vec![
77+ // Multiline string
78+ 146..152,
79+ ],
80+ "helix-term/src/keymap.rs" => vec![
81+ // Complex macro (see above)
82+ 456..470,
83+ // Multiline string without indent
84+ 563..567,
85+ ],
86+ "helix-term/src/main.rs" => vec![
87+ // Multiline string
88+ 44..70,
89+ ],
90+ "helix-term/src/ui/completion.rs" => vec![
91+ // Macro
92+ 218..232,
93+ ],
94+ "helix-term/src/ui/editor.rs" => vec![
95+ // The chained function calls here are not indented, probably because of the comment
96+ // in between. Since `cargo fmt` doesn't even attempt to format it, there's probably
97+ // no point in trying to indent this correctly.
98+ 342..350,
99+ ],
100+ "helix-term/src/ui/lsp.rs" => vec![
101+ // Macro
102+ 56..61,
103+ ],
104+ "helix-term/src/ui/statusline.rs" => vec![
105+ // Same as in `helix-term/src/commands.rs`
106+ 436..442,
107+ 450..456,
108+ ],
109+ _ => Vec::new(),
110+ };
111+
112+ let git_object = rev.to_string() + ":" + file;
113+ let content = Command::new("git")
114+ .args(["cat-file", "blob", &git_object])
115+ .output()
116+ .unwrap();
117+ let doc = Rope::from_reader(&mut content.stdout.as_slice()).unwrap();
118+ test_treesitter_indent(file, doc, "source.rust", ignored_lines);
119+ }
18120}
19121
20122#[test]
@@ -50,20 +152,41 @@ fn test_indent_level_for_line_with_spaces_and_tabs() {
50152 assert_eq!(indent_level, 2)
51153}
52154
53−fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
155+fn indent_tests_dir() -> PathBuf {
54156 let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
55157 test_dir.push("tests/data/indent");
158+ test_dir
159+}
56160
57− let mut test_file = test_dir.clone();
58− test_file.push(file_name);
59− let test_file = std::fs::File::open(test_file).unwrap();
161+fn indent_test_path(name: &str) -> PathBuf {
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);
176+ let test_file = std::fs::File::open(test_path).unwrap();
60177 let doc = ropey::Rope::from_reader(test_file).unwrap();
178+ test_treesitter_indent(file_name, doc, lang_scope, Vec::new())
179+}
61180
62− let mut config_file = test_dir;
63− config_file.push("languages.toml");
64− let config = std::fs::read_to_string(config_file).unwrap();
65− let config = toml::from_str(&config).unwrap();
66− let loader = Loader::new(config);
181+/// Test that all the lines in the given file are indented as expected.
182+/// ignored_lines is a list of (1-indexed) line ranges that are excluded from this test.
183+fn test_treesitter_indent(
184+ test_name: &str,
185+ doc: Rope,
186+ lang_scope: &str,
187+ ignored_lines: Vec<std::ops::Range<usize>>,
188+) {
189+ let loader = Loader::new(indent_tests_config());
67190
68191 // set runtime path so we can find the queries
69192 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) {
71194 std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
72195
73196 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);
74198 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();
77199 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();
78202
79203 for i in 0..doc.len_lines() {
80204 let line = text.line(i);
205+ if ignored_lines.iter().any(|range| range.contains(&(i + 1))) {
206+ continue;
207+ }
81208 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;
83210 let suggested_indent = treesitter_indent_for_pos(
84211 indent_query,
85212 &syntax,
86− &IndentStyle::Spaces(tab_and_indent_width as u8),
87− tab_and_indent_width,
88− tab_and_indent_width,
213+ tab_width,
214+ indent_style.indent_width(tab_width),
89215 text,
90216 i,
91217 text.line_to_char(i) + pos,
92218 false,
93219 )
94− .unwrap();
220+ .unwrap()
221+ .to_string(&indent_style, tab_width);
95222 assert!(
96223 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,
98226 i+1,
99227 line.slice(..line.len_chars()-1),
100228 suggested_indent,
helix-dap/Cargo.toml+11−9
@@ -1,25 +1,27 @@
11[package]
22name = "helix-dap"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2018"
6−license = "MPL-2.0"
73description = "DAP client implementation for Helix project"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
4+version.workspace = true
5+authors.workspace = true
6+edition.workspace = true
7+license.workspace = true
8+rust-version.workspace = true
9+categories.workspace = true
10+repository.workspace = true
11+homepage.workspace = true
1112
1213# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1314
1415[dependencies]
15−helix-core = { version = "0.6", path = "../helix-core" }
16+helix-core = { path = "../helix-core" }
17+
1618anyhow = "1.0"
1719log = "0.4"
1820serde = { version = "1.0", features = ["derive"] }
1921serde_json = "1.0"
2022thiserror = "1.0"
2123tokio = { 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"
2325
2426[dev-dependencies]
2527fern = "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−12
@@ -1,13 +1,14 @@
11[package]
22name = "helix-loader"
3−version = "0.6.0"
4−description = "A post-modern text editor."
5−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
6−edition = "2021"
7−license = "MPL-2.0"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
3+description = "Build bootstrapping for Helix crates"
4+version.workspace = true
5+authors.workspace = true
6+edition.workspace = true
7+license.workspace = true
8+rust-version.workspace = true
9+categories.workspace = true
10+repository.workspace = true
11+homepage.workspace = true
1112
1213[[bin]]
1314name = "hx-loader"
@@ -18,17 +19,18 @@ anyhow = "1"
1819serde = { version = "1.0", features = ["derive"] }
1920toml = "0.7"
2021etcetera = "0.8"
21−tree-sitter = "0.20"
22−once_cell = "1.18"
22+tree-sitter.workspace = true
23+once_cell = "1.19"
2324log = "0.4"
24−which = "4.4"
25+which = "5.0.0"
2526
2627# TODO: these two should be on !wasm32 only
2728
2829# cloning/compiling tree-sitter grammars
2930cc = { version = "1" }
3031threadpool = { version = "1.0" }
31−tempfile = "3.6.0"
32+tempfile = "3.8.1"
33+dunce = "1.0.4"
3234
3335[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
3436libloading = "0.8"
helix-loader/build.rs+20−6
@@ -2,7 +2,17 @@ use std::borrow::Cow;
22use std::path::Path;
33use std::process::Command;
44
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+}
616
717fn main() {
818 let git_hash = Command::new("git")
@@ -12,9 +22,10 @@ fn main() {
1222 .filter(|output| output.status.success())
1323 .and_then(|x| String::from_utf8(x.stdout).ok());
1424
25+ let calver = get_calver();
1526 let version: Cow<_> = match &git_hash {
16− Some(git_hash) => format!("{} ({})", VERSION, &git_hash[..8]).into(),
17− None => VERSION.into(),
27+ Some(git_hash) => format!("{} ({})", calver, &git_hash[..8]).into(),
28+ None => calver.into(),
1829 };
1930
2031 println!(
@@ -22,7 +33,6 @@ fn main() {
2233 std::env::var("TARGET").unwrap()
2334 );
2435
25− println!("cargo:rerun-if-changed=../VERSION");
2636 println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
2737
2838 if git_hash.is_none() {
@@ -40,7 +50,9 @@ fn main() {
4050 .ok()
4151 .filter(|output| output.status.success())
4252 .and_then(|x| String::from_utf8(x.stdout).ok())
43− else{ return; };
53+ else {
54+ return;
55+ };
4456 // If heads starts pointing at something else (different branch)
4557 // we need to return
4658 let head = Path::new(&git_dir).join("HEAD");
@@ -55,7 +67,9 @@ fn main() {
5567 .ok()
5668 .filter(|output| output.status.success())
5769 .and_then(|x| String::from_utf8(x.stdout).ok())
58− else{ return; };
70+ else {
71+ return;
72+ };
5973 let head_ref = Path::new(&git_dir).join(head_ref);
6074 if head_ref.exists() {
6175 println!("cargo:rerun-if-changed={}", head_ref.display());
helix-loader/src/lib.rs+78−17
@@ -3,29 +3,56 @@ pub mod grammar;
33
44use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
55use std::path::{Path, PathBuf};
6+use std::sync::RwLock;
67
78pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH");
89
10+static CWD: RwLock<Option<PathBuf>> = RwLock::new(None);
11+
912static RUNTIME_DIRS: once_cell::sync::Lazy<Vec<PathBuf>> =
1013 once_cell::sync::Lazy::new(prioritize_runtime_dirs);
1114
1215static CONFIG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
1316
14−pub fn initialize_config_file(specified_file: Option<PathBuf>) {
15− let config_file = specified_file.unwrap_or_else(|| {
16− let config_dir = config_dir();
17+static LOG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
1718
18− if !config_dir.exists() {
19− std::fs::create_dir_all(&config_dir).ok();
20− }
19+// Get the current working directory.
20+// This information is managed internally as the call to std::env::current_dir
21+// might fail if the cwd has been deleted.
22+pub fn current_working_dir() -> PathBuf {
23+ if let Some(path) = &*CWD.read().unwrap() {
24+ return path.clone();
25+ }
2126
22− config_dir.join("config.toml")
23− });
27+ let path = std::env::current_dir()
28+ .and_then(dunce::canonicalize)
29+ .expect("Couldn't determine current working directory");
30+ let mut cwd = CWD.write().unwrap();
31+ *cwd = Some(path.clone());
2432
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);
2647 CONFIG_FILE.set(config_file).ok();
2748}
2849
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+
2956/// A list of runtime directories from highest to lowest priority
3057///
3158/// The priority is:
@@ -33,7 +60,8 @@ pub fn initialize_config_file(specified_file: Option<PathBuf>) {
3360/// 1. sibling directory to `CARGO_MANIFEST_DIR` (if environment variable is set)
3461/// 2. subdirectory of user config directory (always included)
3562/// 3. `HELIX_RUNTIME` (if environment variable is set)
36−/// 4. subdirectory of path to helix executable (always included)
63+/// 4. `HELIX_DEFAULT_RUNTIME` (if environment variable is set *at build time*)
64+/// 5. subdirectory of path to helix executable (always included)
3765///
3866/// Postcondition: returns at least two paths (they might not exist).
3967fn prioritize_runtime_dirs() -> Vec<PathBuf> {
@@ -54,6 +82,14 @@ fn prioritize_runtime_dirs() -> Vec<PathBuf> {
5482 rt_dirs.push(dir.into());
5583 }
5684
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+
5793 // fallback to location of the executable being run
5894 // canonicalize the path in case the executable is symlinked
5995 let exe_rt_dir = std::env::current_exe()
@@ -122,10 +158,11 @@ pub fn cache_dir() -> PathBuf {
122158}
123159
124160pub fn config_file() -> PathBuf {
125− CONFIG_FILE
126− .get()
127− .map(|path| path.to_path_buf())
128− .unwrap_or_else(|| config_dir().join("config.toml"))
161+ CONFIG_FILE.get().map(|path| path.to_path_buf()).unwrap()
162+}
163+
164+pub fn log_file() -> PathBuf {
165+ LOG_FILE.get().map(|path| path.to_path_buf()).unwrap()
129166}
130167
131168pub fn workspace_config_file() -> PathBuf {
@@ -136,7 +173,7 @@ pub fn lang_config_file() -> PathBuf {
136173 config_dir().join("languages.toml")
137174}
138175
139−pub fn log_file() -> PathBuf {
176+pub fn default_log_file() -> PathBuf {
140177 cache_dir().join("helix.log")
141178}
142179
@@ -217,7 +254,7 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi
217254/// If no workspace was found returns (CWD, true).
218255/// Otherwise (workspace, false) is returned
219256pub 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();
221258 for ancestor in current_dir.ancestors() {
222259 if ancestor.join(".git").exists() || ancestor.join(".helix").exists() {
223260 return (ancestor.to_owned(), false);
@@ -227,13 +264,37 @@ pub fn find_workspace() -> (PathBuf, bool) {
227264 (current_dir, true)
228265}
229266
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+
230279#[cfg(test)]
231280mod merge_toml_tests {
232281 use std::str;
233282
234− use super::merge_toml_values;
283+ use super::{current_working_dir, merge_toml_values, set_current_working_dir};
235284 use toml::Value;
236285
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+
237298 #[test]
238299 fn language_toml_map_merges() {
239300 const USER: &str = r#"
helix-lsp/Cargo.toml+15−13
@@ -1,30 +1,32 @@
11[package]
22name = "helix-lsp"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
73description = "LSP client implementation for Helix project"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
4+version.workspace = true
5+authors.workspace = true
6+edition.workspace = true
7+license.workspace = true
8+rust-version.workspace = true
9+categories.workspace = true
10+repository.workspace = true
11+homepage.workspace = true
1112
1213# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1314
1415[dependencies]
15−helix-core = { version = "0.6", path = "../helix-core" }
16−helix-loader = { version = "0.6", path = "../helix-loader" }
17−helix-parsec = { version = "0.6", path = "../helix-parsec" }
16+helix-core = { path = "../helix-core" }
17+helix-loader = { path = "../helix-loader" }
18+helix-parsec = { path = "../helix-parsec" }
1819
1920anyhow = "1.0"
2021futures-executor = "0.3"
2122futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
23+globset = "0.4.14"
2224log = "0.4"
23−lsp-types = { version = "0.94" }
25+lsp-types = { version = "0.95" }
2426serde = { version = "1.0", features = ["derive"] }
2527serde_json = "1.0"
2628thiserror = "1.0"
27−tokio = { version = "1.29", 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"] }
2830tokio-stream = "0.1.14"
29−which = "4.4"
31+which = "5.0.0"
3032parking_lot = "0.12.1"
helix-lsp/src/client.rs+123−11
@@ -7,8 +7,9 @@ use crate::{
77use helix_core::{find_workspace, path, syntax::LanguageServerFeature, ChangeSet, Rope};
88use helix_loader::{self, VERSION_AND_GIT_HASH};
99use lsp::{
10− notification::DidChangeWorkspaceFolders, DidChangeWorkspaceFoldersParams, OneOf,
11− PositionEncodingKind, WorkspaceFolder, WorkspaceFoldersChangeEvent,
10+ notification::DidChangeWorkspaceFolders, CodeActionCapabilityResolveSupport,
11+ DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, WorkspaceFolder,
12+ WorkspaceFoldersChangeEvent,
1213};
1314use lsp_types as lsp;
1415use parking_lot::Mutex;
@@ -400,12 +401,22 @@ impl Client {
400401 &self,
401402 params: R::Params,
402403 ) -> 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>>
403415 where
404416 R::Params: serde::Serialize,
405417 {
406418 let server_tx = self.server_tx.clone();
407419 let id = self.next_request_id();
408− let timeout_secs = self.req_timeout;
409420
410421 async move {
411422 use std::time::Duration;
@@ -543,6 +554,15 @@ impl Client {
543554 normalizes_line_endings: Some(false),
544555 change_annotation_support: None,
545556 }),
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+ }),
546566 ..Default::default()
547567 }),
548568 text_document: Some(lsp::TextDocumentClientCapabilities {
@@ -609,6 +629,12 @@ impl Client {
609629 .collect(),
610630 },
611631 }),
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+ }),
612638 ..Default::default()
613639 }),
614640 publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
@@ -641,6 +667,7 @@ impl Client {
641667 version: Some(String::from(VERSION_AND_GIT_HASH)),
642668 }),
643669 locale: None, // TODO
670+ work_done_progress_params: lsp::WorkDoneProgressParams::default(),
644671 };
645672
646673 self.request::<lsp::request::Initialize>(params).await
@@ -689,6 +716,65 @@ impl Client {
689716 })
690717 }
691718
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+
692778 // -------------------------------------------------------------------------------------------
693779 // Text document
694780 // -------------------------------------------------------------------------------------------
@@ -884,20 +970,19 @@ impl Client {
884970 ) -> Option<impl Future<Output = Result<()>>> {
885971 let capabilities = self.capabilities.get().unwrap();
886972
887− let include_text = match &capabilities.text_document_sync {
888− Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
889− save: Some(options),
973+ let include_text = match &capabilities.text_document_sync.as_ref()? {
974+ lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
975+ save: options,
890976 ..
891− })) => match options {
977+ }) => match options.as_ref()? {
892978 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
893979 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
894980 include_text,
895981 }) => include_text.unwrap_or(false),
896− // Supported(false)
897− _ => return None,
982+ lsp::TextDocumentSyncSaveOptions::Supported(false) => return None,
898983 },
899− // unsupported
900− _ => return None,
984+ // see: https://github.com/microsoft/language-server-protocol/issues/288
985+ lsp::TextDocumentSyncCapability::Kind(..) => false,
901986 };
902987
903988 Some(self.notify::<lsp::notification::DidSaveTextDocument>(
@@ -954,6 +1039,24 @@ impl Client {
9541039 Some(self.call::<lsp::request::ResolveCompletionItem>(completion_item))
9551040 }
9561041
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+
9571060 pub fn text_document_signature_help(
9581061 &self,
9591062 text_document: lsp::TextDocumentIdentifier,
@@ -1428,4 +1531,13 @@ impl Client {
14281531
14291532 Some(self.call::<lsp::request::ExecuteCommand>(params))
14301533 }
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+ }
14311543}
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+45−30
@@ -1,4 +1,5 @@
11mod client;
2+pub mod file_event;
23pub mod jsonrpc;
34pub mod snippet;
45mod transport;
@@ -434,7 +435,7 @@ pub mod util {
434435 }
435436
436437 let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty());
437− let Some(tabstops) = tabstops else{
438+ let Some(tabstops) = tabstops else {
438439 // no tabstop normal mapping
439440 mapped_selection.push(range);
440441 continue;
@@ -444,36 +445,36 @@ pub mod util {
444445 // the tabstop closest to the range simply replaces `head` while anchor remains in place
445446 // the remaining tabstops receive their own single-width cursor
446447 if range.head < range.anchor {
447− let first_tabstop = tabstop_anchor + tabstops[0].1;
448+ let last_idx = tabstops.len() - 1;
449+ let last_tabstop = tabstop_anchor + tabstops[last_idx].0;
448450
449451 // if selection is forward but was moved to the right it is
450452 // contained entirely in the replacement text, just do a point
451453 // selection (fallback below)
452− if range.anchor >= first_tabstop {
453− let range = Range::new(range.anchor, first_tabstop);
454+ if range.anchor > last_tabstop {
455+ let range = Range::new(range.anchor, last_tabstop);
454456 mapped_selection.push(range);
455− let rem_tabstops = tabstops[1..]
457+ let rem_tabstops = tabstops[..last_idx]
456458 .iter()
457− .map(|tabstop| Range::point(tabstop_anchor + tabstop.1));
459+ .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
458460 mapped_selection.extend(rem_tabstops);
459461 continue;
460462 }
461463 } else {
462− let last_idx = tabstops.len() - 1;
463− let last_tabstop = tabstop_anchor + tabstops[last_idx].1;
464+ let first_tabstop = tabstop_anchor + tabstops[0].0;
464465
465466 // if selection is forward but was moved to the right it is
466467 // contained entirely in the replacement text, just do a point
467468 // selection (fallback below)
468− if range.anchor <= last_tabstop {
469+ if range.anchor < first_tabstop {
469470 // we can't properly compute the the next grapheme
470471 // here because the transaction hasn't been applied yet
471472 // that is not a problem because the range gets grapheme aligned anyway
472473 // tough so just adding one will always cause head to be grapheme
473474 // aligned correctly when applied to the document
474− let range = Range::new(range.anchor, last_tabstop + 1);
475+ let range = Range::new(range.anchor, first_tabstop + 1);
475476 mapped_selection.push(range);
476− let rem_tabstops = tabstops[..last_idx]
477+ let rem_tabstops = tabstops[1..]
477478 .iter()
478479 .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
479480 mapped_selection.extend(rem_tabstops);
@@ -547,6 +548,7 @@ pub enum MethodCall {
547548 WorkspaceFolders,
548549 WorkspaceConfiguration(lsp::ConfigurationParams),
549550 RegisterCapability(lsp::RegistrationParams),
551+ UnregisterCapability(lsp::UnregistrationParams),
550552}
551553
552554impl MethodCall {
@@ -570,6 +572,10 @@ impl MethodCall {
570572 let params: lsp::RegistrationParams = params.parse()?;
571573 Self::RegisterCapability(params)
572574 }
575+ lsp::request::UnregisterCapability::METHOD => {
576+ let params: lsp::UnregistrationParams = params.parse()?;
577+ Self::UnregisterCapability(params)
578+ }
573579 _ => {
574580 return Err(Error::Unhandled);
575581 }
@@ -629,6 +635,7 @@ pub struct Registry {
629635 syn_loader: Arc<helix_core::syntax::Loader>,
630636 counter: usize,
631637 pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
638+ pub file_event_handler: file_event::Handler,
632639}
633640
634641impl Registry {
@@ -638,6 +645,7 @@ impl Registry {
638645 syn_loader,
639646 counter: 0,
640647 incoming: SelectAll::new(),
648+ file_event_handler: file_event::Handler::new(),
641649 }
642650 }
643651
@@ -650,6 +658,7 @@ impl Registry {
650658 }
651659
652660 pub fn remove_by_id(&mut self, id: usize) {
661+ self.file_event_handler.remove_client(id);
653662 self.inner.retain(|_, language_servers| {
654663 language_servers.retain(|ls| id != ls.id());
655664 !language_servers.is_empty()
@@ -715,6 +724,7 @@ impl Registry {
715724 .unwrap();
716725
717726 for old_client in old_clients {
727+ self.file_event_handler.remove_client(old_client.id());
718728 tokio::spawn(async move {
719729 let _ = old_client.force_shutdown().await;
720730 });
@@ -731,6 +741,7 @@ impl Registry {
731741 pub fn stop(&mut self, name: &str) {
732742 if let Some(clients) = self.inner.remove(name) {
733743 for client in clients {
744+ self.file_event_handler.remove_client(client.id());
734745 tokio::spawn(async move {
735746 let _ = client.force_shutdown().await;
736747 });
@@ -738,36 +749,40 @@ impl Registry {
738749 }
739750 }
740751
741− pub fn get(
742− &mut self,
743− language_config: &LanguageConfiguration,
744− doc_path: Option<&std::path::PathBuf>,
745− root_dirs: &[PathBuf],
752+ pub fn get<'a>(
753+ &'a mut self,
754+ language_config: &'a LanguageConfiguration,
755+ doc_path: Option<&'a std::path::PathBuf>,
756+ root_dirs: &'a [PathBuf],
746757 enable_snippets: bool,
747− ) -> Result<HashMap<LanguageServerName, Arc<Client>>> {
748− language_config
749− .language_servers
750− .iter()
751− .map(|LanguageServerFeatures { name, .. }| {
758+ ) -> impl Iterator<Item = (LanguageServerName, Result<Arc<Client>>)> + 'a {
759+ language_config.language_servers.iter().map(
760+ move |LanguageServerFeatures { name, .. }| {
752761 if let Some(clients) = self.inner.get(name) {
753762 if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| {
754763 client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
755764 }) {
756− return Ok((name.to_owned(), client.clone()));
765+ return (name.to_owned(), Ok(client.clone()));
757766 }
758767 }
759− let client = self.start_client(
768+ match self.start_client(
760769 name.clone(),
761770 language_config,
762771 doc_path,
763772 root_dirs,
764773 enable_snippets,
765− )?;
766− let clients = self.inner.entry(name.clone()).or_default();
767− clients.push(client.clone());
768− Ok((name.clone(), client))
769− })
770− .collect()
774+ ) {
775+ Ok(client) => {
776+ self.inner
777+ .entry(name.to_owned())
778+ .or_default()
779+ .push(client.clone());
780+ (name.clone(), Ok(client))
781+ }
782+ Err(err) => (name.to_owned(), Err(err)),
783+ }
784+ },
785+ )
771786 }
772787
773788 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
@@ -931,7 +946,7 @@ pub fn find_lsp_workspace(
931946 let mut file = if file.is_absolute() {
932947 file.to_path_buf()
933948 } else {
934− let current_dir = std::env::current_dir().expect("unable to determine current directory");
949+ let current_dir = helix_loader::current_working_dir();
935950 current_dir.join(file)
936951 };
937952 file = path::get_normalized_path(&file);
helix-parsec/Cargo.toml+8−7
@@ -1,13 +1,14 @@
11[package]
22name = "helix-parsec"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
73description = "Parser combinators for Helix"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
114include = ["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
1213
1314[dependencies]
helix-term/Cargo.toml+33−25
@@ -1,16 +1,16 @@
11[package]
22name = "helix-term"
3−version = "0.6.0"
43description = "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"
114include = ["src/**/*", "README.md"]
125default-run = "hx"
13−rust-version = "1.65"
6+version.workspace = true
7+authors.workspace = true
8+edition.workspace = true
9+license.workspace = true
10+rust-version.workspace = true
11+categories.workspace = true
12+repository.workspace = true
13+homepage.workspace = true
1414
1515[features]
1616default = ["git"]
@@ -23,21 +23,22 @@ name = "hx"
2323path = "src/main.rs"
2424
2525[dependencies]
26−helix-core = { version = "0.6", path = "../helix-core" }
27−helix-view = { version = "0.6", path = "../helix-view" }
28−helix-lsp = { version = "0.6", path = "../helix-lsp" }
29−helix-dap = { version = "0.6", path = "../helix-dap" }
30−helix-vcs = { version = "0.6", path = "../helix-vcs" }
31−helix-loader = { version = "0.6", path = "../helix-loader" }
26+helix-core = { path = "../helix-core" }
27+helix-event = { path = "../helix-event" }
28+helix-view = { path = "../helix-view" }
29+helix-lsp = { path = "../helix-lsp" }
30+helix-dap = { path = "../helix-dap" }
31+helix-vcs = { path = "../helix-vcs" }
32+helix-loader = { path = "../helix-loader" }
3233
3334anyhow = "1"
34−once_cell = "1.18"
35+once_cell = "1.19"
3536
36−which = "4.4"
37+which = "5.0.0"
3738
3839tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
3940tui = { 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"] }
4142signal-hook = "0.3"
4243tokio-stream = "0.1"
4344futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
@@ -49,13 +50,17 @@ chrono = { version = "0.4", default-features = false, features = ["clock"] }
4950log = "0.4"
5051
5152# File picker
52−fuzzy-matcher = "0.3"
53+nucleo.workspace = true
5354ignore = "0.4"
5455# markdown doc rendering
5556pulldown-cmark = { version = "0.9", default-features = false }
5657# file type detection
5758content_inspector = "0.2.4"
5859
60+# opening URLs
61+open = "5.0.1"
62+url = "2.5.0"
63+
5964# config
6065toml = "0.7"
6166
@@ -63,17 +68,20 @@ serde_json = "1.0"
6368serde = { version = "1.0", features = ["derive"] }
6469
6570# ripgrep for global search
66−grep-regex = "0.1.11"
67−grep-searcher = "0.1.11"
71+grep-regex = "0.1.12"
72+grep-searcher = "0.1.13"
6873
6974[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
7075signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
71−libc = "0.2.147"
76+libc = "0.2.151"
77+
78+[target.'cfg(target_os = "macos")'.dependencies]
79+crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] }
7280
7381[build-dependencies]
74−helix-loader = { version = "0.6", path = "../helix-loader" }
82+helix-loader = { path = "../helix-loader" }
7583
7684[dev-dependencies]
77−smallvec = "1.10"
78−indoc = "2.0.2"
79−tempfile = "3.6.0"
85+smallvec = "1.11"
86+indoc = "2.0.4"
87+tempfile = "3.8.1"
helix-term/src/application.rs+175−106
@@ -1,11 +1,16 @@
11use arc_swap::{access::Map, ArcSwap};
22use futures_util::Stream;
33use helix_core::{
4+ chars::char_is_word,
45 diagnostic::{DiagnosticTag, NumberOrString},
56 path::get_relative_path,
67 pos_at_coords, syntax, Selection,
78};
8−use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
9+use helix_lsp::{
10+ lsp::{self, notification::Notification},
11+ util::lsp_pos_to_pos,
12+ LspProgressMap,
13+};
914use helix_view::{
1015 align_view,
1116 document::DocumentSavedEventResult,
@@ -29,13 +34,9 @@ use crate::{
2934};
3035
3136use log::{debug, error, warn};
32−use std::{
33− collections::btree_map::Entry,
34− io::{stdin, stdout},
35− path::Path,
36− sync::Arc,
37− time::{Duration, Instant},
38−};
37+#[cfg(not(feature = "integration"))]
38+use std::io::stdout;
39+use std::{collections::btree_map::Entry, io::stdin, path::Path, sync::Arc};
3940
4041use anyhow::{Context, Error};
4142
@@ -45,8 +46,6 @@ use {signal_hook::consts::signal, signal_hook_tokio::Signals};
4546#[cfg(windows)]
4647type Signals = futures_util::stream::Empty<()>;
4748
48−const LSP_DEADLINE: Duration = Duration::from_millis(16);
49−
5049#[cfg(not(feature = "integration"))]
5150use tui::backend::CrosstermBackend;
5251
@@ -76,7 +75,6 @@ pub struct Application {
7675 signals: Signals,
7776 jobs: Jobs,
7877 lsp_progress: LspProgressMap,
79− last_render: Instant,
8078}
8179
8280#[cfg(feature = "integration")]
@@ -163,17 +161,21 @@ impl Application {
163161 let path = helix_loader::runtime_file(Path::new("tutor"));
164162 editor.open(&path, Action::VerticalSplit)?;
165163 // 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);
167165 } else if !args.files.is_empty() {
168− let first = &args.files[0].0; // we know it's not empty
169− if first.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);
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 let Some((first, _)) = files_it.next_if(|(p, _)| p.is_dir()) {
170+ let picker = ui::file_picker(first, &config.load().editor);
173171 compositor.push(Box::new(overlaid(picker)));
174− } else {
175− let nr_of_files = args.files.len();
176− for (i, (file, pos)) in args.files.into_iter().enumerate() {
172+ }
173+
174+ // If there are any more files specified, open them
175+ if files_it.peek().is_some() {
176+ let mut nr_of_files = 0;
177+ for (file, pos) in files_it {
178+ nr_of_files += 1;
177179 if file.is_dir() {
178180 return Err(anyhow::anyhow!(
179181 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
@@ -185,7 +187,7 @@ impl Application {
185187 // option. If neither of those two arguments are passed
186188 // in, just load the files normally.
187189 let action = match args.split {
188− _ if i == 0 => Action::VerticalSplit,
190+ _ if nr_of_files == 1 => Action::VerticalSplit,
189191 Some(Layout::Vertical) => Action::VerticalSplit,
190192 Some(Layout::Horizontal) => Action::HorizontalSplit,
191193 None => Action::Load,
@@ -212,14 +214,11 @@ impl Application {
212214 // does not affect views without pos since it is at the top
213215 let (view, doc) = current!(editor);
214216 align_view(doc, view, Align::Center);
217+ } else {
218+ editor.new_file(Action::VerticalSplit);
215219 }
216220 } else if stdin().is_tty() || cfg!(feature = "integration") {
217221 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");
223222 } else {
224223 editor
225224 .new_file_from_stdin(Action::VerticalSplit)
@@ -253,29 +252,25 @@ impl Application {
253252 signals,
254253 jobs: Jobs::new(),
255254 lsp_progress: LspProgressMap::new(),
256− last_render: Instant::now(),
257255 };
258256
259257 Ok(app)
260258 }
261259
262260 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+
263266 let mut cx = crate::compositor::Context {
264267 editor: &mut self.editor,
265268 jobs: &mut self.jobs,
266269 scroll: None,
267270 };
268271
269− // Acquire mutable access to the redraw_handle lock
270− // to ensure that there are no tasks running that want to block rendering
271− drop(cx.editor.redraw_handle.1.write().await);
272+ helix_event::start_frame();
272273 cx.editor.needs_redraw = false;
273− {
274− // exhaust any leftover redraw notifications
275− let notify = cx.editor.redraw_handle.0.notified();
276− tokio::pin!(notify);
277− notify.enable();
278− }
279274
280275 let area = self
281276 .terminal
@@ -297,10 +292,9 @@ impl Application {
297292
298293 pub async fn event_loop<S>(&mut self, input_stream: &mut S)
299294 where
300− S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
295+ S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
301296 {
302297 self.render().await;
303− self.last_render = Instant::now();
304298
305299 loop {
306300 if !self.event_loop_until_idle(input_stream).await {
@@ -311,7 +305,7 @@ impl Application {
311305
312306 pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool
313307 where
314− S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
308+ S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
315309 {
316310 loop {
317311 if self.editor.should_close() {
@@ -564,16 +558,7 @@ impl Application {
564558 let bytes = doc_save_event.text.len_bytes();
565559
566560 if doc.path() != Some(&doc_save_event.path) {
567− if let Err(err) = doc.set_path(Some(&doc_save_event.path)) {
568− log::error!(
569− "error setting path for doc '{:?}': {}",
570− doc.path(),
571− err.to_string(),
572− );
573−
574− self.editor.set_error(err.to_string());
575− return;
576− }
561+ doc.set_path(Some(&doc_save_event.path));
577562
578563 let loader = self.editor.syn_loader.clone();
579564
@@ -609,12 +594,7 @@ impl Application {
609594 EditorEvent::LanguageServerMessage((id, call)) => {
610595 self.handle_language_server_message(call, id).await;
611596 // limit render calls for fast language server messages
612− let last = self.editor.language_servers.incoming.is_empty();
613−
614− if last || self.last_render.elapsed() > LSP_DEADLINE {
615− self.render().await;
616− self.last_render = Instant::now();
617− }
597+ helix_event::request_redraw();
618598 }
619599 EditorEvent::DebuggerEvent(payload) => {
620600 let needs_render = self.editor.handle_debugger_message(payload).await;
@@ -622,6 +602,9 @@ impl Application {
622602 self.render().await;
623603 }
624604 }
605+ EditorEvent::Redraw => {
606+ self.render().await;
607+ }
625608 EditorEvent::IdleTimer => {
626609 self.editor.clear_idle_timer();
627610 self.handle_idle_timeout().await;
@@ -636,10 +619,7 @@ impl Application {
636619 false
637620 }
638621
639− pub async fn handle_terminal_events(
640− &mut self,
641− event: Result<CrosstermEvent, crossterm::ErrorKind>,
642− ) {
622+ pub async fn handle_terminal_events(&mut self, event: std::io::Result<CrosstermEvent>) {
643623 let mut cx = crate::compositor::Context {
644624 editor: &mut self.editor,
645625 jobs: &mut self.jobs,
@@ -738,7 +718,7 @@ impl Application {
738718 ));
739719 }
740720 }
741− Notification::PublishDiagnostics(params) => {
721+ Notification::PublishDiagnostics(mut params) => {
742722 let path = match params.uri.to_file_path() {
743723 Ok(path) => path,
744724 Err(_) => {
@@ -752,31 +732,69 @@ impl Application {
752732 return;
753733 }
754734 let offset_encoding = language_server.offset_encoding();
755− let doc = self.editor.document_by_path_mut(&path).filter(|doc| {
756− if let Some(version) = params.version {
757− if version != doc.version() {
758− log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
759− return false;
735+ // have to inline the function because of borrow checking...
736+ let doc = self.editor.documents.values_mut()
737+ .find(|doc| doc.path().map(|p| p == &path).unwrap_or(false))
738+ .filter(|doc| {
739+ if let Some(version) = params.version {
740+ if version != doc.version() {
741+ log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
742+ return false;
743+ }
760744 }
761− }
762−
763− true
764− });
745+ true
746+ });
765747
766748 if let Some(doc) = doc {
767− let lang_conf = doc.language_config();
768− let text = doc.text();
749+ let lang_conf = doc.language.clone();
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+ }
769782
770− let diagnostics = params
771− .diagnostics
772− .iter()
773− .filter_map(|diagnostic| {
783+ let unchaged_diag_sources = &unchaged_diag_sources_;
784+ let diagnostics =
785+ params.diagnostics.iter().filter_map(move |diagnostic| {
774786 use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
775787 use lsp::DiagnosticSeverity;
776788
789+ if diagnostic.source.as_ref().map_or(false, |source| {
790+ unchaged_diag_sources.contains(source)
791+ }) {
792+ return None;
793+ }
794+
777795 // TODO: convert inside server
778796 let start = if let Some(start) = lsp_pos_to_pos(
779− text,
797+ &text,
780798 diagnostic.range.start,
781799 offset_encoding,
782800 ) {
@@ -787,14 +805,13 @@ impl Application {
787805 };
788806
789807 let end = if let Some(end) =
790− lsp_pos_to_pos(text, diagnostic.range.end, offset_encoding)
808+ lsp_pos_to_pos(&text, diagnostic.range.end, offset_encoding)
791809 {
792810 end
793811 } else {
794812 log::warn!("lsp position out of bounds - {:?}", diagnostic);
795813 return None;
796814 };
797−
798815 let severity =
799816 diagnostic.severity.map(|severity| match severity {
800817 DiagnosticSeverity::ERROR => Error,
@@ -807,7 +824,7 @@ impl Application {
807824 ),
808825 });
809826
810− if let Some(lang_conf) = lang_conf {
827+ if let Some(lang_conf) = &lang_conf {
811828 if let Some(severity) = severity {
812829 if severity < lang_conf.diagnostic_severity {
813830 return None;
@@ -846,8 +863,17 @@ impl Application {
846863 Vec::new()
847864 };
848865
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+
849872 Some(Diagnostic {
850873 range: Range { start, end },
874+ ends_at_word,
875+ starts_at_word,
876+ zero_width: start == end,
851877 line: diagnostic.range.start.line as usize,
852878 message: diagnostic.message.clone(),
853879 severity,
@@ -857,38 +883,33 @@ impl Application {
857883 data: diagnostic.data.clone(),
858884 language_server_id: server_id,
859885 })
860− })
861− .collect();
886+ });
862887
863− doc.replace_diagnostics(diagnostics, server_id);
888+ doc.replace_diagnostics(diagnostics, unchaged_diag_sources, server_id);
864889 }
865890
866− let mut diagnostics = params
867− .diagnostics
868− .into_iter()
869− .map(|d| (d, server_id))
870− .collect();
891+ let diagnostics = params.diagnostics.into_iter().map(|d| (d, server_id));
871892
872893 // Insert the original lsp::Diagnostics here because we may have no open document
873894 // for diagnosic message and so we can't calculate the exact position.
874895 // When using them later in the diagnostics picker, we calculate them on-demand.
875− match self.editor.diagnostics.entry(params.uri) {
896+ let diagnostics = match self.editor.diagnostics.entry(params.uri) {
876897 Entry::Occupied(o) => {
877898 let current_diagnostics = o.into_mut();
878899 // there may entries of other language servers, which is why we can't overwrite the whole entry
879900 current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
880− current_diagnostics.append(&mut diagnostics);
881− // Sort diagnostics first by severity and then by line numbers.
882− // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
901+ current_diagnostics.extend(diagnostics);
883902 current_diagnostics
884− .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
885− }
886− Entry::Vacant(v) => {
887− diagnostics
888− .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
889− v.insert(diagnostics);
903+ // Sort diagnostics first by severity and then by line numbers.
890904 }
905+ Entry::Vacant(v) => v.insert(diagnostics.collect()),
891906 };
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+ });
892913 }
893914 Notification::ShowMessage(params) => {
894915 log::warn!("unhandled window/showMessage: {:?}", params);
@@ -1096,17 +1117,65 @@ impl Application {
10961117 .collect();
10971118 Ok(json!(result))
10981119 }
1099− Ok(MethodCall::RegisterCapability(_params)) => {
1100− log::warn!("Ignoring a client/registerCapability request because dynamic capability registration is not enabled. Please report this upstream to the language server");
1101− // Language Servers based on the `vscode-languageserver-node` library often send
1102− // client/registerCapability even though we do not enable dynamic registration
1103− // for any capabilities. We should send a MethodNotFound JSONRPC error in this
1104− // case but that rejects the registration promise in the server which causes an
1105− // exit. So we work around this by ignoring the request and sending back an OK
1106− // response.
1120+ Ok(MethodCall::RegisterCapability(params)) => {
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+ _ => {
1149+ // Language Servers based on the `vscode-languageserver-node` library often send
1150+ // client/registerCapability even though we do not enable dynamic registration
1151+ // for most capabilities. We should send a MethodNotFound JSONRPC error in this
1152+ // case but that rejects the registration promise in the server which causes an
1153+ // exit. So we work around this by ignoring the request and sending back an OK
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+ }
11071160
11081161 Ok(serde_json::Value::Null)
11091162 }
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+ }
11101179 };
11111180
11121181 tokio::spawn(language_server!().reply(id, reply));
@@ -1132,7 +1201,7 @@ impl Application {
11321201
11331202 pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
11341203 where
1135− S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
1204+ S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
11361205 {
11371206 self.claim_term().await?;
11381207
helix-term/src/args.rs+29
@@ -17,12 +17,14 @@ pub struct Args {
1717 pub log_file: Option<PathBuf>,
1818 pub config_file: Option<PathBuf>,
1919 pub files: Vec<(PathBuf, Position)>,
20+ pub working_directory: Option<PathBuf>,
2021}
2122
2223impl Args {
2324 pub fn parse_args() -> Result<Args> {
2425 let mut args = Args::default();
2526 let mut argv = std::env::args().peekable();
27+ let mut line_number = 0;
2628
2729 argv.next(); // skip the program, we don't care about that
2830
@@ -59,6 +61,20 @@ impl Args {
5961 Some(path) => args.log_file = Some(path.into()),
6062 None => anyhow::bail!("--log must specify a path to write"),
6163 },
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+ },
6278 arg if arg.starts_with("--") => {
6379 anyhow::bail!("unexpected double dash argument: {}", arg)
6480 }
@@ -73,6 +89,13 @@ impl Args {
7389 }
7490 }
7591 }
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+ }
7699 arg => args.files.push(parse_file(arg)),
77100 }
78101 }
@@ -82,6 +105,12 @@ impl Args {
82105 args.files.push(parse_file(&arg));
83106 }
84107
108+ if let Some(file) = args.files.first_mut() {
109+ if line_number != 0 {
110+ file.1.row = line_number;
111+ }
112+ }
113+
85114 Ok(args)
86115 }
87116}
helix-term/src/commands.rs+647−396
@@ -29,12 +29,11 @@ use helix_core::{
2929 tree_sitter::Node,
3030 unicode::width::UnicodeWidthChar,
3131 visual_offset_from_block, Deletion, LineEnding, Position, Range, Rope, RopeGraphemes,
32− RopeSlice, Selection, SmallVec, Tendril, Transaction,
32+ RopeReader, RopeSlice, Selection, SmallVec, Tendril, Transaction,
3333};
3434use helix_view::{
35− clipboard::ClipboardType,
3635 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
37− editor::{Action, CompleteAction, Motion},
36+ editor::{Action, CompleteAction},
3837 info::Info,
3938 input::KeyEvent,
4039 keyboard::KeyCode,
@@ -44,7 +43,6 @@ use helix_view::{
4443};
4544
4645use anyhow::{anyhow, bail, ensure, Context as _};
47−use fuzzy_matcher::FuzzyMatcher;
4846use insert::*;
4947use movement::Movement;
5048
@@ -61,9 +59,14 @@ use crate::{
6159};
6260
6361use crate::job::{self, Jobs};
64−use futures_util::{stream::FuturesUnordered, StreamExt, TryStreamExt};
65−use std::{collections::HashMap, fmt, future::Future};
66−use std::{collections::HashSet, num::NonZeroUsize};
62+use futures_util::{stream::FuturesUnordered, TryStreamExt};
63+use std::{
64+ collections::{HashMap, HashSet},
65+ fmt,
66+ future::Future,
67+ io::Read,
68+ num::NonZeroUsize,
69+};
6770
6871use std::{
6972 borrow::Cow,
@@ -72,11 +75,11 @@ use std::{
7275
7376use once_cell::sync::Lazy;
7477use serde::de::{self, Deserialize, Deserializer};
78+use url::Url;
7579
7680use grep_regex::RegexMatcherBuilder;
7781use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
7882use ignore::{DirEntry, WalkBuilder, WalkState};
79−use tokio_stream::wrappers::UnboundedReceiverStream;
8083
8184pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
8285
@@ -248,6 +251,8 @@ impl MappableCommand {
248251 move_prev_long_word_start, "Move to start of previous long word",
249252 move_next_long_word_end, "Move to end of next long word",
250253 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",
251256 extend_next_word_start, "Extend to start of next word",
252257 extend_prev_word_start, "Extend to start of previous word",
253258 extend_next_word_end, "Extend to end of next word",
@@ -256,6 +261,8 @@ impl MappableCommand {
256261 extend_prev_long_word_start, "Extend to start of previous long word",
257262 extend_next_long_word_end, "Extend to end of next long word",
258263 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",
259266 find_till_char, "Move till next occurrence of char",
260267 find_next_char, "Move to next occurrence of char",
261268 extend_till_char, "Extend till next occurrence of char",
@@ -330,7 +337,7 @@ impl MappableCommand {
330337 goto_implementation, "Goto implementation",
331338 goto_file_start, "Goto line number <n> else file start",
332339 goto_file_end, "Goto file end",
333− goto_file, "Goto files in selection",
340+ goto_file, "Goto files/URLs in selection",
334341 goto_file_hsplit, "Goto files in selection (hsplit)",
335342 goto_file_vsplit, "Goto files in selection (vsplit)",
336343 goto_reference, "Goto references",
@@ -362,6 +369,7 @@ impl MappableCommand {
362369 extend_to_line_end, "Extend to line end",
363370 extend_to_line_end_newline, "Extend to line end",
364371 signature_help, "Show signature help",
372+ smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.",
365373 insert_tab, "Insert tab char",
366374 insert_newline, "Insert newline char",
367375 delete_char_backward, "Delete previous char",
@@ -376,6 +384,8 @@ impl MappableCommand {
376384 later, "Move forward in history",
377385 commit_undo_checkpoint, "Commit changes to new checkpoint",
378386 yank, "Yank selection",
387+ yank_to_clipboard, "Yank selections to clipboard",
388+ yank_to_primary_clipboard, "Yank selections to primary clipboard",
379389 yank_joined, "Join and yank selections",
380390 yank_joined_to_clipboard, "Join and yank selections to clipboard",
381391 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
@@ -980,6 +990,7 @@ fn align_selections(cx: &mut Context) {
980990
981991 let transaction = Transaction::change(doc.text(), changes.into_iter());
982992 doc.apply(&transaction, view.id);
993+ exit_select_mode(cx);
983994}
984995
985996fn goto_window(cx: &mut Context, align: Align) {
@@ -1099,8 +1110,7 @@ where
10991110 .transform(|range| move_fn(text, range, count, behavior));
11001111 doc.set_selection(view.id, selection);
11011112 };
1102− motion(cx.editor);
1103− cx.editor.last_motion = Some(Motion(Box::new(motion)));
1113+ cx.editor.apply_motion(motion)
11041114}
11051115
11061116fn goto_prev_paragraph(cx: &mut Context) {
@@ -1155,6 +1165,10 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11551165 let (view, doc) = current_ref!(cx.editor);
11561166 let text = doc.text();
11571167 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();
11581172 let mut paths: Vec<_> = selections
11591173 .iter()
11601174 .map(|r| text.slice(r.from()..r.to()).to_string())
@@ -1182,16 +1196,80 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11821196 .to_string(),
11831197 );
11841198 }
1199+
11851200 for sel in paths {
11861201 let p = sel.trim();
1187− if !p.is_empty() {
1188− if let Err(e) = cx.editor.open(&PathBuf::from(p), action) {
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)));
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) {
11891250 cx.editor.set_error(format!("Open file failed: {:?}", e));
11901251 }
11911252 }
11921253 }
11931254}
11941255
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+
11951273fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
11961274where
11971275 F: Fn(RopeSlice, Range, usize) -> Range,
@@ -1240,10 +1318,66 @@ fn extend_next_long_word_end(cx: &mut Context) {
12401318 extend_word_impl(cx, movement::move_next_long_word_end)
12411319}
12421320
1243−fn will_find_char<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool)
1244−where
1245− F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,
1246−{
1321+/// Separate branch to find_char designed only for `<ret>` char.
1322+//
1323+// This is necessary because the one document can have different line endings inside. And we
1324+// cannot predict what character to find when <ret> is pressed. On the current line it can be `lf`
1325+// but on the next line it can be `crlf`. That's why [`find_char_impl`] cannot be applied here.
1326+fn find_char_line_ending(
1327+ cx: &mut Context,
1328+ count: usize,
1329+ direction: Direction,
1330+ inclusive: bool,
1331+ extend: bool,
1332+) {
1333+ let (view, doc) = current!(cx.editor);
1334+ let text = doc.text().slice(..);
1335+
1336+ let selection = doc.selection(view.id).clone().transform(|range| {
1337+ let cursor = range.cursor(text);
1338+ let cursor_line = range.cursor_line(text);
1339+
1340+ // Finding the line where we're going to find <ret>. Depends mostly on
1341+ // `count`, but also takes into account edge cases where we're already at the end
1342+ // of a line or the beginning of a line
1343+ let find_on_line = match direction {
1344+ Direction::Forward => {
1345+ let on_edge = line_end_char_index(&text, cursor_line) == cursor;
1346+ let line = cursor_line + count - 1 + (on_edge as usize);
1347+ if line >= text.len_lines() - 1 {
1348+ return range;
1349+ } else {
1350+ line
1351+ }
1352+ }
1353+ Direction::Backward => {
1354+ let on_edge = text.line_to_char(cursor_line) == cursor && !inclusive;
1355+ let line = cursor_line as isize - (count as isize - 1 + on_edge as isize);
1356+ if line <= 0 {
1357+ return range;
1358+ } else {
1359+ line as usize
1360+ }
1361+ }
1362+ };
1363+
1364+ let pos = match (direction, inclusive) {
1365+ (Direction::Forward, true) => line_end_char_index(&text, find_on_line),
1366+ (Direction::Forward, false) => line_end_char_index(&text, find_on_line) - 1,
1367+ (Direction::Backward, true) => line_end_char_index(&text, find_on_line - 1),
1368+ (Direction::Backward, false) => text.line_to_char(find_on_line),
1369+ };
1370+
1371+ if extend {
1372+ range.put_cursor(text, pos, true)
1373+ } else {
1374+ Range::point(range.cursor(text)).put_cursor(text, pos, true)
1375+ }
1376+ });
1377+ doc.set_selection(view.id, selection);
1378+}
1379+
1380+fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bool) {
12471381 // TODO: count is reset to 1 before next key so we move it into the closure here.
12481382 // Would be nice to carry over.
12491383 let count = cx.count();
@@ -1256,13 +1390,9 @@ where
12561390 KeyEvent {
12571391 code: KeyCode::Enter,
12581392 ..
1259− } =>
1260− // TODO: this isn't quite correct when CRLF is involved.
1261− // This hack will work in most cases, since documents don't
1262− // usually mix line endings. But we should fix it eventually
1263− // anyway.
1264− {
1265− doc!(cx.editor).line_ending.as_str().chars().next().unwrap()
1393+ } => {
1394+ find_char_line_ending(cx, count, direction, inclusive, extend);
1395+ return;
12661396 }
12671397
12681398 KeyEvent {
@@ -1275,11 +1405,18 @@ where
12751405 } => ch,
12761406 _ => return,
12771407 };
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+ };
12781418
1279− find_char_impl(cx.editor, &search_fn, inclusive, extend, ch, count);
1280− cx.editor.last_motion = Some(Motion(Box::new(move |editor: &mut Editor| {
1281− find_char_impl(editor, &search_fn, inclusive, extend, ch, 1);
1282− })));
1419+ cx.editor.apply_motion(motion);
12831420 })
12841421}
12851422
@@ -1358,46 +1495,39 @@ fn find_prev_char_impl(
13581495}
13591496
13601497fn find_till_char(cx: &mut Context) {
1361− will_find_char(cx, find_next_char_impl, false, false)
1498+ find_char(cx, Direction::Forward, false, false);
13621499}
13631500
13641501fn find_next_char(cx: &mut Context) {
1365− will_find_char(cx, find_next_char_impl, true, false)
1502+ find_char(cx, Direction::Forward, true, false)
13661503}
13671504
13681505fn extend_till_char(cx: &mut Context) {
1369− will_find_char(cx, find_next_char_impl, false, true)
1506+ find_char(cx, Direction::Forward, false, true)
13701507}
13711508
13721509fn extend_next_char(cx: &mut Context) {
1373− will_find_char(cx, find_next_char_impl, true, true)
1510+ find_char(cx, Direction::Forward, true, true)
13741511}
13751512
13761513fn till_prev_char(cx: &mut Context) {
1377− will_find_char(cx, find_prev_char_impl, false, false)
1514+ find_char(cx, Direction::Backward, false, false)
13781515}
13791516
13801517fn find_prev_char(cx: &mut Context) {
1381− will_find_char(cx, find_prev_char_impl, true, false)
1518+ find_char(cx, Direction::Backward, true, false)
13821519}
13831520
13841521fn extend_till_prev_char(cx: &mut Context) {
1385− will_find_char(cx, find_prev_char_impl, false, true)
1522+ find_char(cx, Direction::Backward, false, true)
13861523}
13871524
13881525fn extend_prev_char(cx: &mut Context) {
1389− will_find_char(cx, find_prev_char_impl, true, true)
1526+ find_char(cx, Direction::Backward, true, true)
13901527}
13911528
13921529fn repeat_last_motion(cx: &mut Context) {
1393− let count = cx.count();
1394− let last_motion = cx.editor.last_motion.take();
1395− if let Some(m) = &last_motion {
1396− for _ in 0..count {
1397− m.run(cx.editor);
1398− }
1399− cx.editor.last_motion = last_motion;
1400− }
1530+ cx.editor.repeat_last_motion(cx.count())
14011531}
14021532
14031533fn replace(cx: &mut Context) {
@@ -1464,6 +1594,7 @@ where
14641594 });
14651595
14661596 doc.apply(&transaction, view.id);
1597+ exit_select_mode(cx);
14671598}
14681599
14691600fn switch_case(cx: &mut Context) {
@@ -1705,8 +1836,8 @@ fn select_regex(cx: &mut Context) {
17051836 "select:".into(),
17061837 Some(reg),
17071838 ui::completers::none,
1708− move |editor, regex, event| {
1709− let (view, doc) = current!(editor);
1839+ move |cx, regex, event| {
1840+ let (view, doc) = current!(cx.editor);
17101841 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
17111842 return;
17121843 }
@@ -1727,8 +1858,8 @@ fn split_selection(cx: &mut Context) {
17271858 "split:".into(),
17281859 Some(reg),
17291860 ui::completers::none,
1730− move |editor, regex, event| {
1731− let (view, doc) = current!(editor);
1861+ move |cx, regex, event| {
1862+ let (view, doc) = current!(cx.editor);
17321863 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
17331864 return;
17341865 }
@@ -1851,11 +1982,11 @@ fn search_impl(
18511982
18521983fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
18531984 let mut items = reg
1854− .and_then(|reg| cx.editor.registers.get(reg))
1855− .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect());
1985+ .and_then(|reg| cx.editor.registers.read(reg, cx.editor))
1986+ .map_or(Vec::new(), |reg| reg.take(200).collect());
18561987 items.sort_unstable();
18571988 items.dedup();
1858− items.into_iter().cloned().collect()
1989+ items.into_iter().map(|value| value.to_string()).collect()
18591990}
18601991
18611992fn search(cx: &mut Context) {
@@ -1892,12 +2023,14 @@ fn searcher(cx: &mut Context, direction: Direction) {
18922023 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
18932024 .collect()
18942025 },
1895− move |editor, regex, event| {
1896− if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
2026+ move |cx, regex, event| {
2027+ if event == PromptEvent::Validate {
2028+ cx.editor.registers.last_search_register = reg;
2029+ } else if event != PromptEvent::Update {
18972030 return;
18982031 }
18992032 search_impl(
1900− editor,
2033+ cx.editor,
19012034 &contents,
19022035 &regex,
19032036 Movement::Move,
@@ -1912,11 +2045,13 @@ fn searcher(cx: &mut Context, direction: Direction) {
19122045
19132046fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
19142047 let count = cx.count();
2048+ let register = cx
2049+ .register
2050+ .unwrap_or(cx.editor.registers.last_search_register);
19152051 let config = cx.editor.config();
19162052 let scrolloff = config.scrolloff;
1917− let (_, doc) = current!(cx.editor);
1918− let registers = &cx.editor.registers;
1919− 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);
19202055 let contents = doc.text().slice(..).to_string();
19212056 let search_config = &config.search;
19222057 let case_insensitive = if search_config.smart_case {
@@ -1925,7 +2060,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
19252060 false
19262061 };
19272062 let wrap_around = search_config.wrap_around;
1928− if let Ok(regex) = RegexBuilder::new(query)
2063+ if let Ok(regex) = RegexBuilder::new(&query)
19292064 .case_insensitive(case_insensitive)
19302065 .multi_line(true)
19312066 .build()
@@ -1965,6 +2100,7 @@ fn extend_search_prev(cx: &mut Context) {
19652100}
19662101
19672102fn search_selection(cx: &mut Context) {
2103+ let register = cx.register.unwrap_or('/');
19682104 let (view, doc) = current!(cx.editor);
19692105 let contents = doc.text().slice(..);
19702106
@@ -1977,13 +2113,24 @@ fn search_selection(cx: &mut Context) {
19772113 .collect::<Vec<_>>()
19782114 .join("|");
19792115
1980− let msg = format!("register '{}' set to '{}'", '/', &regex);
1981− cx.editor.registers.push('/', regex);
1982− cx.editor.set_status(msg);
2116+ let msg = format!("register '{}' set to '{}'", register, &regex);
2117+ match cx.editor.registers.push(register, regex) {
2118+ Ok(_) => {
2119+ cx.editor.registers.last_search_register = register;
2120+ cx.editor.set_status(msg)
2121+ }
2122+ Err(err) => cx.editor.set_error(err.to_string()),
2123+ }
19832124}
19842125
19852126fn make_search_word_bounded(cx: &mut Context) {
1986− let regex = match cx.editor.registers.last('/') {
2127+ // Defaults to the active search register instead `/` to be more ergonomic assuming most people
2128+ // would use this command following `search_selection`. This avoids selecting the register
2129+ // twice.
2130+ let register = cx
2131+ .register
2132+ .unwrap_or(cx.editor.registers.last_search_register);
2133+ let regex = match cx.editor.registers.first(register, cx.editor) {
19872134 Some(regex) => regex,
19882135 None => return,
19892136 };
@@ -2001,14 +2148,19 @@ fn make_search_word_bounded(cx: &mut Context) {
20012148 if !start_anchored {
20022149 new_regex.push_str("\\b");
20032150 }
2004− new_regex.push_str(regex);
2151+ new_regex.push_str(&regex);
20052152 if !end_anchored {
20062153 new_regex.push_str("\\b");
20072154 }
20082155
2009− let msg = format!("register '{}' set to '{}'", '/', &new_regex);
2010− cx.editor.registers.push('/', new_regex);
2011− cx.editor.set_status(msg);
2156+ let msg = format!("register '{}' set to '{}'", register, &new_regex);
2157+ match cx.editor.registers.push(register, new_regex) {
2158+ Ok(_) => {
2159+ cx.editor.registers.last_search_register = register;
2160+ cx.editor.set_status(msg)
2161+ }
2162+ Err(err) => cx.editor.set_error(err.to_string()),
2163+ }
20122164}
20132165
20142166fn global_search(cx: &mut Context) {
@@ -2047,13 +2199,11 @@ fn global_search(cx: &mut Context) {
20472199 }
20482200 }
20492201
2050− let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<FileResult>();
20512202 let config = cx.editor.config();
20522203 let smart_case = config.search.smart_case;
20532204 let file_picker_config = config.file_picker.clone();
20542205
20552206 let reg = cx.register.unwrap_or('/');
2056−
20572207 let completions = search_completions(cx, Some(reg));
20582208 ui::regex_prompt(
20592209 cx,
@@ -2066,43 +2216,67 @@ fn global_search(cx: &mut Context) {
20662216 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
20672217 .collect()
20682218 },
2069− move |_editor, regex, event| {
2219+ move |cx, regex, event| {
20702220 if event != PromptEvent::Validate {
20712221 return;
20722222 }
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();
20732231
20742232 if let Ok(matcher) = RegexMatcherBuilder::new()
20752233 .case_smart(smart_case)
20762234 .build(regex.as_str())
20772235 {
2078− let searcher = SearcherBuilder::new()
2079− .binary_detection(BinaryDetection::quit(b'\x00'))
2080− .build();
2236+ let search_root = helix_loader::current_working_dir();
2237+ if !search_root.exists() {
2238+ cx.editor
2239+ .set_error("Current working directory does not exist");
2240+ return;
2241+ }
2242+
2243+ let (picker, injector) = Picker::stream(current_path);
20812244
2082− let search_root = std::env::current_dir()
2083− .expect("Global search error: Failed to get current dir");
20842245 let dedup_symlinks = file_picker_config.deduplicate_links;
20852246 let absolute_root = search_root
20862247 .canonicalize()
20872248 .unwrap_or_else(|_| search_root.clone());
2088−
2089− WalkBuilder::new(search_root)
2090− .hidden(file_picker_config.hidden)
2091− .parents(file_picker_config.parents)
2092− .ignore(file_picker_config.ignore)
2093− .follow_links(file_picker_config.follow_symlinks)
2094− .git_ignore(file_picker_config.git_ignore)
2095− .git_global(file_picker_config.git_global)
2096− .git_exclude(file_picker_config.git_exclude)
2097− .max_depth(file_picker_config.max_depth)
2098− .filter_entry(move |entry| {
2099− filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2100− })
2101− .build_parallel()
2102− .run(|| {
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+
2256+ let mut walk_builder = WalkBuilder::new(search_root);
2257+
2258+ walk_builder
2259+ .hidden(file_picker_config.hidden)
2260+ .parents(file_picker_config.parents)
2261+ .ignore(file_picker_config.ignore)
2262+ .follow_links(file_picker_config.follow_symlinks)
2263+ .git_ignore(file_picker_config.git_ignore)
2264+ .git_global(file_picker_config.git_global)
2265+ .git_exclude(file_picker_config.git_exclude)
2266+ .max_depth(file_picker_config.max_depth)
2267+ .filter_entry(move |entry| {
2268+ filter_picker_entry(entry, &absolute_root, dedup_symlinks)
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+
2275+ walk_builder.build_parallel().run(|| {
21032276 let mut searcher = searcher.clone();
21042277 let matcher = matcher.clone();
2105− let all_matches_sx = all_matches_sx.clone();
2278+ let injector = injector_.clone();
2279+ let documents = &documents;
21062280 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
21072281 let entry = match entry {
21082282 Ok(entry) => entry,
@@ -2115,17 +2289,39 @@ fn global_search(cx: &mut Context) {
21152289 _ => return WalkState::Continue,
21162290 };
21172291
2118− let result = searcher.search_path(
2119− &matcher,
2120− entry.path(),
2121− sinks::UTF8(|line_num, _| {
2122− all_matches_sx
2123− .send(FileResult::new(entry.path(), line_num as usize - 1))
2124− .unwrap();
2125−
2126− Ok(true)
2127− }),
2128− );
2292+ let mut stop = false;
2293+ let sink = sinks::UTF8(|line_num, _| {
2294+ stop = injector
2295+ .push(FileResult::new(entry.path(), line_num as usize - 1))
2296+ .is_err();
2297+
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())
2304+ });
2305+
2306+ let result = if let Some((_, doc)) = doc {
2307+ // there is already a buffer for this file
2308+ // search the buffer instead of the file because it's faster
2309+ // and captures new edits without requiring a save
2310+ if searcher.multi_line_with_matcher(&matcher) {
2311+ // in this case a continous buffer is required
2312+ // convert the rope to a string
2313+ let text = doc.to_string();
2314+ searcher.search_slice(&matcher, text.as_bytes(), sink)
2315+ } else {
2316+ searcher.search_reader(
2317+ &matcher,
2318+ RopeReader::new(doc.slice(..)),
2319+ sink,
2320+ )
2321+ }
2322+ } else {
2323+ searcher.search_path(&matcher, entry.path(), sink)
2324+ };
21292325
21302326 if let Err(err) = result {
21312327 log::error!(
@@ -2134,65 +2330,66 @@ fn global_search(cx: &mut Context) {
21342330 err
21352331 );
21362332 }
2137− WalkState::Continue
2333+ if stop {
2334+ WalkState::Quit
2335+ } else {
2336+ WalkState::Continue
2337+ }
21382338 })
21392339 });
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+ })
21402387 } else {
21412388 // Otherwise do nothing
21422389 // log::warn!("Global Search Invalid Pattern")
21432390 }
21442391 },
21452392 );
2146−
2147− let current_path = doc_mut!(cx.editor).path().cloned();
2148−
2149− let show_picker = async move {
2150− let all_matches: Vec<FileResult> =
2151− UnboundedReceiverStream::new(all_matches_rx).collect().await;
2152− let call: job::Callback = Callback::EditorCompositor(Box::new(
2153− move |editor: &mut Editor, compositor: &mut Compositor| {
2154− if all_matches.is_empty() {
2155− editor.set_status("No matches found");
2156− return;
2157− }
2158−
2159− let picker = Picker::new(
2160− all_matches,
2161− current_path,
2162− move |cx, FileResult { path, line_num }, action| {
2163− match cx.editor.open(path, action) {
2164− Ok(_) => {}
2165− Err(e) => {
2166− cx.editor.set_error(format!(
2167− "Failed to open file '{}': {}",
2168− path.display(),
2169− e
2170− ));
2171− return;
2172− }
2173− }
2174−
2175− let line_num = *line_num;
2176− let (view, doc) = current!(cx.editor);
2177− let text = doc.text();
2178− if line_num >= text.len_lines() {
2179− cx.editor.set_error("The line you jumped to does not exist anymore because the file has changed.");
2180− return;
2181− }
2182− let start = text.line_to_char(line_num);
2183− let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2184−
2185− doc.set_selection(view.id, Selection::single(start, end));
2186− align_view(doc, view, Align::Center);
2187− }).with_preview(|_editor, FileResult { path, line_num }| {
2188− Some((path.clone().into(), Some((*line_num, *line_num))))
2189− });
2190− compositor.push(Box::new(overlaid(picker)));
2191− },
2192− ));
2193− Ok(call)
2194− };
2195− cx.jobs.callback(show_picker);
21962393}
21972394
21982395enum Extend {
@@ -2315,17 +2512,35 @@ enum Operation {
23152512 Change,
23162513}
23172514
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+
23182529fn delete_selection_impl(cx: &mut Context, op: Operation) {
23192530 let (view, doc) = current!(cx.editor);
23202531
23212532 let selection = doc.selection(view.id);
2533+ let only_whole_lines = selection_is_linewise(selection, doc.text());
23222534
23232535 if cx.register != Some('_') {
23242536 // first yank the selection
23252537 let text = doc.text().slice(..);
23262538 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
23272539 let reg_name = cx.register.unwrap_or('"');
2328− 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+ }
23292544 };
23302545
23312546 // then delete
@@ -2339,7 +2554,11 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
23392554 exit_select_mode(cx);
23402555 }
23412556 Operation::Change => {
2342− enter_insert_mode(cx);
2557+ if only_whole_lines {
2558+ open_above(cx);
2559+ } else {
2560+ enter_insert_mode(cx);
2561+ }
23432562 }
23442563 }
23452564}
@@ -2461,6 +2680,10 @@ fn insert_mode(cx: &mut Context) {
24612680 .transform(|range| Range::new(range.to(), range.from()));
24622681
24632682 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();
24642687}
24652688
24662689// inserts at the end of each selection
@@ -2497,6 +2720,10 @@ fn append_mode(cx: &mut Context) {
24972720
24982721fn file_picker(cx: &mut Context) {
24992722 let root = find_workspace().0;
2723+ if !root.exists() {
2724+ cx.editor.set_error("Workspace directory does not exist");
2725+ return;
2726+ }
25002727 let picker = ui::file_picker(root, &cx.editor.config());
25012728 cx.push_layer(Box::new(overlaid(picker)));
25022729}
@@ -2517,8 +2744,14 @@ fn file_picker_in_current_buffer_directory(cx: &mut Context) {
25172744 let picker = ui::file_picker(path, &cx.editor.config());
25182745 cx.push_layer(Box::new(overlaid(picker)));
25192746}
2747+
25202748fn file_picker_in_current_directory(cx: &mut Context) {
2521− 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+ }
25222755 let picker = ui::file_picker(cwd, &cx.editor.config());
25232756 cx.push_layer(Box::new(overlaid(picker)));
25242757}
@@ -2668,9 +2901,11 @@ fn jumplist_picker(cx: &mut Context) {
26682901 |cx, meta, action| {
26692902 cx.editor.switch(meta.id, action);
26702903 let config = cx.editor.config();
2671− let (view, doc) = current!(cx.editor);
2904+ let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id));
26722905 doc.set_selection(view.id, meta.selection.clone());
2673− view.ensure_cursor_in_view_center(doc, config.scrolloff);
2906+ if action.align_view(view, doc.id()) {
2907+ view.ensure_cursor_in_view_center(doc, config.scrolloff);
2908+ }
26742909 },
26752910 )
26762911 .with_preview(|editor, meta| {
@@ -2818,6 +3053,7 @@ fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) {
28183053 let indent = indent::indent_for_newline(
28193054 language_config,
28203055 syntax,
3056+ &doc.config.load().indent_heuristic,
28213057 &doc.indent_style,
28223058 tab_width,
28233059 text,
@@ -2922,6 +3158,7 @@ fn open(cx: &mut Context, open: Open) {
29223158 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
29233159 Open::Above => range.from(),
29243160 });
3161+
29253162 let new_line = match open {
29263163 // adjust position to the end of the line (next line - 1)
29273164 Open::Below => cursor_line + 1,
@@ -2929,13 +3166,15 @@ fn open(cx: &mut Context, open: Open) {
29293166 Open::Above => cursor_line,
29303167 };
29313168
3169+ let line_num = new_line.saturating_sub(1);
3170+
29323171 // Index to insert newlines after, as well as the char width
29333172 // to use to compensate for those inserted newlines.
29343173 let (line_end_index, line_end_offset_width) = if new_line == 0 {
29353174 (0, 0)
29363175 } else {
29373176 (
2938− line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
3177+ line_end_char_index(&text, line_num),
29393178 doc.line_ending.len_chars(),
29403179 )
29413180 };
@@ -2943,13 +3182,15 @@ fn open(cx: &mut Context, open: Open) {
29433182 let indent = indent::indent_for_newline(
29443183 doc.language_config(),
29453184 doc.syntax(),
3185+ &doc.config.load().indent_heuristic,
29463186 &doc.indent_style,
29473187 doc.tab_width(),
29483188 text,
2949− new_line.saturating_sub(1),
3189+ line_num,
29503190 line_end_index,
29513191 cursor_line,
29523192 );
3193+
29533194 let indent_len = indent.len();
29543195 let mut text = String::with_capacity(1 + indent_len);
29553196 text.push_str(doc.line_ending.as_str());
@@ -3248,8 +3489,7 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
32483489
32493490 doc.set_selection(view.id, selection)
32503491 };
3251− motion(cx.editor);
3252− cx.editor.last_motion = Some(Motion(Box::new(motion)));
3492+ cx.editor.apply_motion(motion);
32533493}
32543494
32553495/// Returns the [Range] for a [Hunk] in the given text.
@@ -3376,6 +3616,7 @@ pub mod insert {
33763616 }
33773617
33783618 use helix_core::auto_pairs;
3619+ use helix_view::editor::SmartTabConfig;
33793620
33803621 pub fn insert_char(cx: &mut Context, c: char) {
33813622 let (view, doc) = current_ref!(cx.editor);
@@ -3401,6 +3642,31 @@ pub mod insert {
34013642 }
34023643 }
34033644
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+
34043670 pub fn insert_tab(cx: &mut Context) {
34053671 let (view, doc) = current!(cx.editor);
34063672 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
@@ -3456,6 +3722,7 @@ pub mod insert {
34563722 let indent = indent::indent_for_newline(
34573723 doc.language_config(),
34583724 doc.syntax(),
3725+ &doc.config.load().indent_heuristic,
34593726 &doc.indent_style,
34603727 doc.tab_width(),
34613728 text,
@@ -3696,7 +3963,22 @@ fn commit_undo_checkpoint(cx: &mut Context) {
36963963// Yank / Paste
36973964
36983965fn yank(cx: &mut Context) {
3699− let (view, doc) = current!(cx.editor);
3966+ yank_impl(cx.editor, cx.register.unwrap_or('"'));
3967+ exit_select_mode(cx);
3968+}
3969+
3970+fn yank_to_clipboard(cx: &mut Context) {
3971+ yank_impl(cx.editor, '+');
3972+ exit_select_mode(cx);
3973+}
3974+
3975+fn yank_to_primary_clipboard(cx: &mut Context) {
3976+ yank_impl(cx.editor, '*');
3977+ exit_select_mode(cx);
3978+}
3979+
3980+fn yank_impl(editor: &mut Editor, register: char) {
3981+ let (view, doc) = current!(editor);
37003982 let text = doc.text().slice(..);
37013983
37023984 let values: Vec<String> = doc
@@ -3704,19 +3986,15 @@ fn yank(cx: &mut Context) {
37043986 .fragments(text)
37053987 .map(Cow::into_owned)
37063988 .collect();
3989+ let selections = values.len();
37073990
3708− let msg = format!(
3709− "yanked {} selection(s) to register {}",
3710− values.len(),
3711− cx.register.unwrap_or('"')
3712− );
3713−
3714− cx.editor
3715− .registers
3716− .write(cx.register.unwrap_or('"'), values);
3717−
3718− cx.editor.set_status(msg);
3719− exit_select_mode(cx);
3991+ match editor.registers.write(register, values) {
3992+ Ok(_) => editor.set_status(format!(
3993+ "yanked {selections} selection{} to register {register}",
3994+ if selections == 1 { "" } else { "s" }
3995+ )),
3996+ Err(err) => editor.set_error(err.to_string()),
3997+ }
37203998}
37213999
37224000fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
@@ -3724,6 +4002,7 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
37244002 let text = doc.text().slice(..);
37254003
37264004 let selection = doc.selection(view.id);
4005+ let selections = selection.len();
37274006 let joined = selection
37284007 .fragments(text)
37294008 .fold(String::new(), |mut acc, fragment| {
@@ -3734,104 +4013,52 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
37344013 acc
37354014 });
37364015
3737− let msg = format!(
3738− "joined and yanked {} selection(s) to register {}",
3739− selection.len(),
3740− register,
3741− );
3742−
3743− editor.registers.write(register, vec![joined]);
3744− editor.set_status(msg);
4016+ match editor.registers.write(register, vec![joined]) {
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+ }
37454023}
37464024
37474025fn yank_joined(cx: &mut Context) {
3748− let line_ending = doc!(cx.editor).line_ending;
3749− let register = cx.register.unwrap_or('"');
3750− yank_joined_impl(cx.editor, line_ending.as_str(), register);
4026+ let separator = doc!(cx.editor).line_ending.as_str();
4027+ yank_joined_impl(cx.editor, separator, cx.register.unwrap_or('"'));
37514028 exit_select_mode(cx);
37524029}
37534030
3754−fn yank_joined_to_clipboard_impl(
3755− editor: &mut Editor,
3756− separator: &str,
3757− clipboard_type: ClipboardType,
3758−) -> anyhow::Result<()> {
3759− let (view, doc) = current!(editor);
3760− let text = doc.text().slice(..);
3761−
3762− let values: Vec<String> = doc
3763− .selection(view.id)
3764− .fragments(text)
3765− .map(Cow::into_owned)
3766− .collect();
3767−
3768− let clipboard_text = match clipboard_type {
3769− ClipboardType::Clipboard => "system clipboard",
3770− ClipboardType::Selection => "primary clipboard",
3771− };
3772−
3773− let msg = format!(
3774− "joined and yanked {} selection(s) to {}",
3775− values.len(),
3776− clipboard_text,
3777− );
3778−
3779− let joined = values.join(separator);
3780−
3781− editor
3782− .clipboard_provider
3783− .set_contents(joined, clipboard_type)
3784− .context("Couldn't set system clipboard content")?;
3785−
3786− editor.set_status(msg);
3787−
3788− Ok(())
4031+fn yank_joined_to_clipboard(cx: &mut Context) {
4032+ let line_ending = doc!(cx.editor).line_ending;
4033+ yank_joined_impl(cx.editor, line_ending.as_str(), '+');
4034+ exit_select_mode(cx);
37894035}
37904036
3791−fn yank_joined_to_clipboard(cx: &mut Context) {
4037+fn yank_joined_to_primary_clipboard(cx: &mut Context) {
37924038 let line_ending = doc!(cx.editor).line_ending;
3793− let _ =
3794− yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Clipboard);
4039+ yank_joined_impl(cx.editor, line_ending.as_str(), '*');
37954040 exit_select_mode(cx);
37964041}
37974042
3798−fn yank_main_selection_to_clipboard_impl(
3799− editor: &mut Editor,
3800− clipboard_type: ClipboardType,
3801−) -> anyhow::Result<()> {
4043+fn yank_primary_selection_impl(editor: &mut Editor, register: char) {
38024044 let (view, doc) = current!(editor);
38034045 let text = doc.text().slice(..);
38044046
3805− let message_text = match clipboard_type {
3806− ClipboardType::Clipboard => "yanked main selection to system clipboard",
3807− ClipboardType::Selection => "yanked main selection to primary clipboard",
3808− };
3809−
3810− let value = doc.selection(view.id).primary().fragment(text);
4047+ let selection = doc.selection(view.id).primary().fragment(text).to_string();
38114048
3812− if let Err(e) = editor
3813− .clipboard_provider
3814− .set_contents(value.into_owned(), clipboard_type)
3815− {
3816− bail!("Couldn't set system clipboard content: {}", e);
4049+ match editor.registers.write(register, vec![selection]) {
4050+ Ok(_) => editor.set_status(format!("yanked primary selection to register {register}",)),
4051+ Err(err) => editor.set_error(err.to_string()),
38174052 }
3818−
3819− editor.set_status(message_text);
3820− Ok(())
38214053}
38224054
38234055fn yank_main_selection_to_clipboard(cx: &mut Context) {
3824− let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard);
3825−}
3826−
3827−fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3828− let line_ending = doc!(cx.editor).line_ending;
3829− let _ =
3830− yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Selection);
4056+ yank_primary_selection_impl(cx.editor, '+');
4057+ exit_select_mode(cx);
38314058}
38324059
38334060fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
3834− let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection);
4061+ yank_primary_selection_impl(cx.editor, '*');
38354062 exit_select_mode(cx);
38364063}
38374064
@@ -3929,146 +4156,99 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
39294156 };
39304157 let (view, doc) = current!(cx.editor);
39314158 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
3932−}
3933−
3934−fn paste_clipboard_impl(
3935− editor: &mut Editor,
3936− action: Paste,
3937− clipboard_type: ClipboardType,
3938− count: usize,
3939−) -> anyhow::Result<()> {
3940− let (view, doc) = current!(editor);
3941− match editor.clipboard_provider.get_contents(clipboard_type) {
3942− Ok(contents) => {
3943− paste_impl(&[contents], doc, view, action, count, editor.mode);
3944− Ok(())
3945− }
3946− Err(e) => Err(e.context("Couldn't get system clipboard contents")),
3947− }
4159+ exit_select_mode(cx);
39484160}
39494161
39504162fn paste_clipboard_after(cx: &mut Context) {
3951− let _ = paste_clipboard_impl(
3952− cx.editor,
3953− Paste::After,
3954− ClipboardType::Clipboard,
3955− cx.count(),
3956− );
4163+ paste(cx.editor, '+', Paste::After, cx.count());
4164+ exit_select_mode(cx);
39574165}
39584166
39594167fn paste_clipboard_before(cx: &mut Context) {
3960− let _ = paste_clipboard_impl(
3961− cx.editor,
3962− Paste::Before,
3963− ClipboardType::Clipboard,
3964− cx.count(),
3965− );
4168+ paste(cx.editor, '+', Paste::Before, cx.count());
4169+ exit_select_mode(cx);
39664170}
39674171
39684172fn paste_primary_clipboard_after(cx: &mut Context) {
3969− let _ = paste_clipboard_impl(
3970− cx.editor,
3971− Paste::After,
3972− ClipboardType::Selection,
3973− cx.count(),
3974− );
4173+ paste(cx.editor, '*', Paste::After, cx.count());
4174+ exit_select_mode(cx);
39754175}
39764176
39774177fn paste_primary_clipboard_before(cx: &mut Context) {
3978− let _ = paste_clipboard_impl(
3979− cx.editor,
3980− Paste::Before,
3981− ClipboardType::Selection,
3982− cx.count(),
3983− );
4178+ paste(cx.editor, '*', Paste::Before, cx.count());
4179+ exit_select_mode(cx);
39844180}
39854181
39864182fn replace_with_yanked(cx: &mut Context) {
3987− let count = cx.count();
3988− let reg_name = cx.register.unwrap_or('"');
3989− let (view, doc) = current!(cx.editor);
3990− let registers = &mut cx.editor.registers;
3991−
3992− if let Some(values) = registers.read(reg_name) {
3993− if !values.is_empty() {
3994− let repeat = std::iter::repeat(
3995− values
3996− .last()
3997− .map(|value| Tendril::from(&value.repeat(count)))
3998− .unwrap(),
3999− );
4000− let mut values = values
4001− .iter()
4002− .map(|value| Tendril::from(&value.repeat(count)))
4003− .chain(repeat);
4004− let selection = doc.selection(view.id);
4005− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4006− if !range.is_empty() {
4007− (range.from(), range.to(), Some(values.next().unwrap()))
4008− } else {
4009− (range.from(), range.to(), None)
4010− }
4011− });
4012−
4013− doc.apply(&transaction, view.id);
4014− exit_select_mode(cx);
4015− }
4016− }
4183+ replace_with_yanked_impl(cx.editor, cx.register.unwrap_or('"'), cx.count());
4184+ exit_select_mode(cx);
40174185}
40184186
4019−fn replace_selections_with_clipboard_impl(
4020− cx: &mut Context,
4021− clipboard_type: ClipboardType,
4022−) -> anyhow::Result<()> {
4023− let count = cx.count();
4024− let (view, doc) = current!(cx.editor);
4025−
4026− match cx.editor.clipboard_provider.get_contents(clipboard_type) {
4027− Ok(contents) => {
4028− let selection = doc.selection(view.id);
4029− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4030− (
4031− range.from(),
4032− range.to(),
4033− Some(contents.repeat(count).as_str().into()),
4034− )
4035− });
4187+fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
4188+ let Some(values) = editor.registers
4189+ .read(register, editor)
4190+ .filter(|values| values.len() > 0) else { return };
4191+ let values: Vec<_> = values.map(|value| value.to_string()).collect();
40364192
4037− doc.apply(&transaction, view.id);
4038− doc.append_changes_to_history(view);
4193+ let (view, doc) = current!(editor);
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)
40394210 }
4040− Err(e) => return Err(e.context("Couldn't get system clipboard contents")),
4041− }
4211+ });
40424212
4043− exit_select_mode(cx);
4044− Ok(())
4213+ doc.apply(&transaction, view.id);
40454214}
40464215
40474216fn replace_selections_with_clipboard(cx: &mut Context) {
4048− let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard);
4217+ replace_with_yanked_impl(cx.editor, '+', cx.count());
4218+ exit_select_mode(cx);
40494219}
40504220
40514221fn replace_selections_with_primary_clipboard(cx: &mut Context) {
4052− let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Selection);
4222+ replace_with_yanked_impl(cx.editor, '*', cx.count());
4223+ exit_select_mode(cx);
40534224}
40544225
4055−fn paste(cx: &mut Context, pos: Paste) {
4056− let count = cx.count();
4057− let reg_name = cx.register.unwrap_or('"');
4058− let (view, doc) = current!(cx.editor);
4059− let registers = &mut cx.editor.registers;
4226+fn paste(editor: &mut Editor, register: char, pos: Paste, count: usize) {
4227+ let Some(values) = editor.registers.read(register, editor) else { return };
4228+ let values: Vec<_> = values.map(|value| value.to_string()).collect();
40604229
4061− if let Some(values) = registers.read(reg_name) {
4062− paste_impl(values, doc, view, pos, count, cx.editor.mode);
4063− }
4230+ let (view, doc) = current!(editor);
4231+ paste_impl(&values, doc, view, pos, count, editor.mode);
40644232}
40654233
40664234fn paste_after(cx: &mut Context) {
4067− paste(cx, Paste::After)
4235+ paste(
4236+ cx.editor,
4237+ cx.register.unwrap_or('"'),
4238+ Paste::After,
4239+ cx.count(),
4240+ );
4241+ exit_select_mode(cx);
40684242}
40694243
40704244fn paste_before(cx: &mut Context) {
4071− paste(cx, Paste::Before)
4245+ paste(
4246+ cx.editor,
4247+ cx.register.unwrap_or('"'),
4248+ Paste::Before,
4249+ cx.count(),
4250+ );
4251+ exit_select_mode(cx);
40724252}
40734253
40744254fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
@@ -4107,6 +4287,7 @@ fn indent(cx: &mut Context) {
41074287 }),
41084288 );
41094289 doc.apply(&transaction, view.id);
4290+ exit_select_mode(cx);
41104291}
41114292
41124293fn unindent(cx: &mut Context) {
@@ -4146,6 +4327,7 @@ fn unindent(cx: &mut Context) {
41464327 let transaction = Transaction::change(doc.text(), changes.into_iter());
41474328
41484329 doc.apply(&transaction, view.id);
4330+ exit_select_mode(cx);
41494331}
41504332
41514333fn format_selections(cx: &mut Context) {
@@ -4175,7 +4357,7 @@ fn format_selections(cx: &mut Context) {
41754357 })
41764358 else {
41774359 cx.editor
4178− .set_error("No configured language server does not support range formatting");
4360+ .set_error("No configured language server supports range formatting");
41794361 return;
41804362 };
41814363
@@ -4212,10 +4394,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
42124394 use movement::skip_while;
42134395 let (view, doc) = current!(cx.editor);
42144396 let text = doc.text();
4215− let slice = doc.text().slice(..);
4397+ let slice = text.slice(..);
42164398
42174399 let mut changes = Vec::new();
4218− let fragment = Tendril::from(" ");
42194400
42204401 for selection in doc.selection(view.id) {
42214402 let (start, mut end) = selection.line_range(slice);
@@ -4231,9 +4412,13 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
42314412 let mut end = text.line_to_char(line + 1);
42324413 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
42334414
4234− // need to skip from start, not end
4235− let change = (start, end, Some(fragment.clone()));
4236− changes.push(change);
4415+ let separator = if end == line_end_char_index(&slice, line + 1) {
4416+ // the joining line contains only space-characters => don't include a whitespace when joining
4417+ None
4418+ } else {
4419+ Some(Tendril::from(" "))
4420+ };
4421+ changes.push((start, end, separator));
42374422 }
42384423 }
42394424
@@ -4245,9 +4430,6 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
42454430 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
42464431 changes.dedup();
42474432
4248− // TODO: joining multiple empty lines should be replaced by a single space.
4249− // need to merge change ranges that touch
4250−
42514433 // select inserted spaces
42524434 let transaction = if select_space {
42534435 let ranges: SmallVec<_> = changes
@@ -4259,9 +4441,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
42594441 })
42604442 .collect();
42614443 let selection = Selection::new(ranges, 0);
4262− Transaction::change(doc.text(), changes.into_iter()).with_selection(selection)
4444+ Transaction::change(text, changes.into_iter()).with_selection(selection)
42634445 } else {
4264− Transaction::change(doc.text(), changes.into_iter())
4446+ Transaction::change(text, changes.into_iter())
42654447 };
42664448
42674449 doc.apply(&transaction, view.id);
@@ -4275,8 +4457,8 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
42754457 if remove { "remove:" } else { "keep:" }.into(),
42764458 Some(reg),
42774459 ui::completers::none,
4278− move |editor, regex, event| {
4279− let (view, doc) = current!(editor);
4460+ move |cx, regex, event| {
4461+ let (view, doc) = current!(cx.editor);
42804462 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
42814463 return;
42824464 }
@@ -4584,8 +4766,7 @@ fn expand_selection(cx: &mut Context) {
45844766 }
45854767 }
45864768 };
4587− motion(cx.editor);
4588− cx.editor.last_motion = Some(Motion(Box::new(motion)));
4769+ cx.editor.apply_motion(motion);
45894770}
45904771
45914772fn shrink_selection(cx: &mut Context) {
@@ -4609,8 +4790,7 @@ fn shrink_selection(cx: &mut Context) {
46094790 doc.set_selection(view.id, selection);
46104791 }
46114792 };
4612− motion(cx.editor);
4613− cx.editor.last_motion = Some(Motion(Box::new(motion)));
4793+ cx.editor.apply_motion(motion);
46144794}
46154795
46164796fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
@@ -4628,8 +4808,7 @@ where
46284808 doc.set_selection(view.id, selection);
46294809 }
46304810 };
4631− motion(cx.editor);
4632− cx.editor.last_motion = Some(Motion(Box::new(motion)));
4811+ cx.editor.apply_motion(motion);
46334812}
46344813
46354814fn select_next_sibling(cx: &mut Context) {
@@ -4640,6 +4819,49 @@ fn select_prev_sibling(cx: &mut Context) {
46404819 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
46414820}
46424821
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+
46434865fn match_brackets(cx: &mut Context) {
46444866 let (view, doc) = current!(cx.editor);
46454867 let is_select = cx.editor.mode == Mode::Select;
@@ -4753,17 +4975,19 @@ fn transpose_view(cx: &mut Context) {
47534975 cx.editor.transpose_view()
47544976}
47554977
4756−// split helper, clear it later
4757−fn split(cx: &mut Context, action: Action) {
4758− let (view, doc) = current!(cx.editor);
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).
4981+fn split(editor: &mut Editor, action: Action) {
4982+ let (view, doc) = current!(editor);
47594983 let id = doc.id();
47604984 let selection = doc.selection(view.id).clone();
47614985 let offset = view.offset;
47624986
4763− cx.editor.switch(id, action);
4987+ editor.switch(id, action);
47644988
47654989 // match the selection in the previous view
4766− let (view, doc) = current!(cx.editor);
4990+ let (view, doc) = current!(editor);
47674991 doc.set_selection(view.id, selection);
47684992 // match the view scroll offset (switch doesn't handle this fully
47694993 // since the selection is only matched after the split)
@@ -4771,7 +4995,7 @@ fn split(cx: &mut Context, action: Action) {
47714995}
47724996
47734997fn hsplit(cx: &mut Context) {
4774− split(cx, Action::HorizontalSplit);
4998+ split(cx.editor, Action::HorizontalSplit);
47754999}
47765000
47775001fn hsplit_new(cx: &mut Context) {
@@ -4779,7 +5003,7 @@ fn hsplit_new(cx: &mut Context) {
47795003}
47805004
47815005fn vsplit(cx: &mut Context) {
4782− split(cx, Action::VerticalSplit);
5006+ split(cx.editor, Action::VerticalSplit);
47835007}
47845008
47855009fn vsplit_new(cx: &mut Context) {
@@ -4828,7 +5052,12 @@ fn insert_register(cx: &mut Context) {
48285052 if let Some(ch) = event.char() {
48295053 cx.editor.autoinfo = None;
48305054 cx.register = Some(ch);
4831− paste(cx, Paste::Cursor);
5055+ paste(
5056+ cx.editor,
5057+ cx.register.unwrap_or('"'),
5058+ Paste::Cursor,
5059+ cx.count(),
5060+ );
48325061 }
48335062 })
48345063}
@@ -4912,8 +5141,7 @@ fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direct
49125141 editor.set_status("Syntax-tree is not available in current buffer");
49135142 }
49145143 };
4915− motion(cx.editor);
4916− cx.editor.last_motion = Some(Motion(Box::new(motion)));
5144+ cx.editor.apply_motion(motion);
49175145}
49185146
49195147fn goto_next_function(cx: &mut Context) {
@@ -5034,8 +5262,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
50345262 });
50355263 doc.set_selection(view.id, selection);
50365264 };
5037− textobject(cx.editor);
5038− cx.editor.last_motion = Some(Motion(Box::new(textobject)));
5265+ cx.editor.apply_motion(textobject);
50395266 }
50405267 });
50415268
@@ -5125,11 +5352,18 @@ fn surround_replace(cx: &mut Context) {
51255352 }
51265353 };
51275354
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+
51285362 cx.on_next_key(move |cx, event| {
51295363 let (view, doc) = current!(cx.editor);
51305364 let to = match event.char() {
51315365 Some(to) => to,
5132− None => return,
5366+ None => return doc.set_selection(view.id, selection),
51335367 };
51345368 let (open, close) = surround::get_pair(to);
51355369 let transaction = Transaction::change(
@@ -5140,6 +5374,7 @@ fn surround_replace(cx: &mut Context) {
51405374 (pos, pos + 1, Some(t))
51415375 }),
51425376 );
5377+ doc.set_selection(view.id, selection);
51435378 doc.apply(&transaction, view.id);
51445379 exit_select_mode(cx);
51455380 });
@@ -5374,12 +5609,18 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
53745609 };
53755610
53765611 // These `usize`s cannot underflow because selection ranges cannot overlap.
5377− // Once the MSRV is 1.66.0 (mixed_integer_ops is stabilized), we can use checked
5378− // arithmetic to assert this.
5379− let anchor = (to as isize + offset - deleted_len as isize) as usize;
5612+ let anchor = to
5613+ .checked_add_signed(offset)
5614+ .expect("Selection ranges cannot overlap")
5615+ .checked_sub(deleted_len)
5616+ .expect("Selection ranges cannot overlap");
53805617 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
53815618 ranges.push(new_range);
5382− 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");
53835624
53845625 changes.push((from, to, Some(output)));
53855626 }
@@ -5401,7 +5642,7 @@ fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBeha
54015642 cx,
54025643 prompt,
54035644 Some('|'),
5404− ui::completers::none,
5645+ ui::completers::filename,
54055646 move |cx, input: &str, event: PromptEvent| {
54065647 if event != PromptEvent::Validate {
54075648 return;
@@ -5518,6 +5759,7 @@ fn increment_impl(cx: &mut Context, increment_direction: IncrementDirection) {
55185759 let transaction = Transaction::change(doc.text(), changes.into_iter());
55195760 let transaction = transaction.with_selection(new_selection);
55205761 doc.apply(&transaction, view.id);
5762+ exit_select_mode(cx);
55215763 }
55225764}
55235765
@@ -5536,9 +5778,12 @@ fn record_macro(cx: &mut Context) {
55365778 }
55375779 })
55385780 .collect::<String>();
5539− cx.editor.registers.write(reg, vec![s]);
5540− cx.editor
5541− .set_status(format!("Recorded to register [{}]", reg));
5781+ match cx.editor.registers.write(reg, vec![s]) {
5782+ Ok(_) => cx
5783+ .editor
5784+ .set_status(format!("Recorded to register [{}]", reg)),
5785+ Err(err) => cx.editor.set_error(err.to_string()),
5786+ }
55425787 } else {
55435788 let reg = cx.register.take().unwrap_or('@');
55445789 cx.editor.macro_recording = Some((reg, Vec::new()));
@@ -5558,8 +5803,14 @@ fn replay_macro(cx: &mut Context) {
55585803 return;
55595804 }
55605805
5561− let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
5562− match helix_view::input::parse_macro(keys_str) {
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+ {
5813+ match helix_view::input::parse_macro(&keys) {
55635814 Ok(keys) => keys,
55645815 Err(err) => {
55655816 cx.editor.set_error(format!("Invalid macro: {}", err));
helix-term/src/commands/dap.rs+14−5
@@ -8,7 +8,7 @@ use dap::{StackFrame, Thread, ThreadStates};
88use helix_core::syntax::{DebugArgumentValue, DebugConfigCompletion, DebugTemplate};
99use helix_dap::{self as dap, Client};
1010use helix_lsp::block_on;
11−use helix_view::editor::Breakpoint;
11+use helix_view::{editor::Breakpoint, graphics::Margin};
1212
1313use serde_json::{to_value, Value};
1414use tokio_stream::wrappers::UnboundedReceiverStream;
@@ -217,7 +217,7 @@ pub fn dap_start_impl(
217217 }
218218 }
219219
220− args.insert("cwd", to_value(std::env::current_dir().unwrap())?);
220+ args.insert("cwd", to_value(helix_loader::current_working_dir())?);
221221
222222 let args = to_value(args).unwrap();
223223
@@ -339,8 +339,12 @@ fn debug_parameter_prompt(
339339 .to_owned();
340340
341341 let completer = match field_type {
342− "filename" => ui::completers::filename,
343− "directory" => ui::completers::directory,
342+ "filename" => |editor: &Editor, input: &str| {
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+ },
344348 _ => ui::completers::none,
345349 };
346350
@@ -577,7 +581,12 @@ pub fn dap_variables(cx: &mut Context) {
577581 }
578582
579583 let contents = Text::from(tui::text::Text::from(variables));
580− let popup = Popup::new("dap-variables", contents);
584+ let margin = if cx.editor.popup_border() {
585+ Margin::all(1)
586+ } else {
587+ Margin::none()
588+ };
589+ let popup = Popup::new("dap-variables", contents).margin(margin);
581590 cx.replace_or_push_layer("dap-variables", popup);
582591}
583592
helix-term/src/commands/lsp.rs+60−62
@@ -23,6 +23,7 @@ use helix_core::{
2323use helix_view::{
2424 document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
2525 editor::Action,
26+ graphics::Margin,
2627 theme::Style,
2728 Document, View,
2829};
@@ -49,7 +50,7 @@ use std::{
4950/// If there is no configured language server that supports the feature, this displays a status message.
5051/// Using this macro in a context where the editor automatically queries the LSP
5152/// (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.
5354#[macro_export]
5455macro_rules! language_server_with_feature {
5556 ($editor:expr, $doc:expr, $feature:expr) => {{
@@ -195,7 +196,6 @@ fn location_to_file_location(location: &lsp::Location) -> FileLocation {
195196 (path.into(), line)
196197}
197198
198−// TODO: share with symbol picker(symbol.location)
199199fn jump_to_location(
200200 editor: &mut Editor,
201201 location: &lsp::Location,
@@ -213,15 +213,16 @@ fn jump_to_location(
213213 return;
214214 }
215215 };
216− match editor.open(&path, action) {
217− Ok(_) => (),
216+
217+ let doc = match editor.open(&path, action) {
218+ Ok(id) => doc_mut!(editor, &id),
218219 Err(err) => {
219220 let err = format!("failed to open path: {:?}: {:?}", location.uri, err);
220221 editor.set_error(err);
221222 return;
222223 }
223− }
224− let (view, doc) = current!(editor);
224+ };
225+ let view = view_mut!(editor);
225226 // TODO: convert inside server
226227 let new_range =
227228 if let Some(new_range) = lsp_range_to_range(doc.text(), location.range, offset_encoding) {
@@ -233,45 +234,22 @@ fn jump_to_location(
233234 // we flip the range so that the cursor sits on the start of the symbol
234235 // (for example start of the function).
235236 doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
236− align_view(doc, view, Align::Center);
237+ if action.align_view(view, doc.id()) {
238+ align_view(doc, view, Align::Center);
239+ }
237240}
238241
239242type SymbolPicker = Picker<SymbolInformationItem>;
240243
241244fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url>) -> SymbolPicker {
242245 // TODO: drop current_path comparison and instead use workspace: bool flag?
243− Picker::new(symbols, current_path.clone(), move |cx, item, action| {
244− let (view, doc) = current!(cx.editor);
245− push_jump(view, doc);
246−
247− if current_path.as_ref() != Some(&item.symbol.location.uri) {
248− let uri = &item.symbol.location.uri;
249− let path = match uri.to_file_path() {
250− Ok(path) => path,
251− Err(_) => {
252− let err = format!("unable to convert URI to filepath: {}", uri);
253− cx.editor.set_error(err);
254− return;
255− }
256− };
257− if let Err(err) = cx.editor.open(&path, action) {
258− let err = format!("failed to open document: {}: {}", uri, err);
259− log::error!("{}", err);
260− cx.editor.set_error(err);
261− return;
262− }
263− }
264−
265− let (view, doc) = current!(cx.editor);
266−
267− if let Some(range) =
268− lsp_range_to_range(doc.text(), item.symbol.location.range, item.offset_encoding)
269− {
270− // we flip the range so that the cursor sits on the start of the symbol
271− // (for example start of the function).
272− doc.set_selection(view.id, Selection::single(range.head, range.anchor));
273− align_view(doc, view, Align::Center);
274− }
246+ Picker::new(symbols, current_path, move |cx, item, action| {
247+ jump_to_location(
248+ cx.editor,
249+ &item.symbol.location,
250+ item.offset_encoding,
251+ action,
252+ );
275253 })
276254 .with_preview(move |_editor, item| Some(location_to_file_location(&item.symbol.location)))
277255 .truncate_start(false)
@@ -286,7 +264,7 @@ enum DiagnosticsFormat {
286264fn diag_picker(
287265 cx: &Context,
288266 diagnostics: BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize)>>,
289− current_path: Option<lsp::Url>,
267+ _current_path: Option<lsp::Url>,
290268 format: DiagnosticsFormat,
291269) -> Picker<PickerDiagnostic> {
292270 // TODO: drop current_path comparison and instead use workspace: bool flag?
@@ -324,22 +302,12 @@ fn diag_picker(
324302 offset_encoding,
325303 },
326304 action| {
327− if current_path.as_ref() == Some(url) {
328− let (view, doc) = current!(cx.editor);
329− push_jump(view, doc);
330− } else {
331− let path = url.to_file_path().unwrap();
332− cx.editor.open(&path, action).expect("editor.open failed");
333− }
334−
335− let (view, doc) = current!(cx.editor);
336−
337− if let Some(range) = lsp_range_to_range(doc.text(), diag.range, *offset_encoding) {
338− // we flip the range so that the cursor sits on the start of the symbol
339− // (for example start of the function).
340− doc.set_selection(view.id, Selection::single(range.head, range.anchor));
341− align_view(doc, view, Align::Center);
342− }
305+ jump_to_location(
306+ cx.editor,
307+ &lsp::Location::new(url.clone(), diag.range),
308+ *offset_encoding,
309+ action,
310+ )
343311 },
344312 )
345313 .with_preview(move |_editor, PickerDiagnostic { url, diag, .. }| {
@@ -730,7 +698,8 @@ pub fn code_action(cx: &mut Context) {
730698
731699 // always present here
732700 let action = action.unwrap();
733− let Some(language_server) = editor.language_server_by_id(action.language_server_id) else {
701+ let Some(language_server) = editor.language_server_by_id(action.language_server_id)
702+ else {
734703 editor.set_error("Language Server disappeared");
735704 return;
736705 };
@@ -743,7 +712,25 @@ pub fn code_action(cx: &mut Context) {
743712 }
744713 lsp::CodeActionOrCommand::CodeAction(code_action) => {
745714 log::debug!("code action: {:?}", code_action);
746− if let Some(ref workspace_edit) = code_action.edit {
715+ // we support lsp "codeAction/resolve" for `edit` and `command` fields
716+ let mut resolved_code_action = None;
717+ if code_action.edit.is_none() || code_action.command.is_none() {
718+ if let Some(future) =
719+ language_server.resolve_code_action(code_action.clone())
720+ {
721+ if let Ok(response) = helix_lsp::block_on(future) {
722+ if let Ok(code_action) =
723+ serde_json::from_value::<CodeAction>(response)
724+ {
725+ resolved_code_action = Some(code_action);
726+ }
727+ }
728+ }
729+ }
730+ let resolved_code_action =
731+ resolved_code_action.as_ref().unwrap_or(code_action);
732+
733+ if let Some(ref workspace_edit) = resolved_code_action.edit {
747734 log::debug!("edit: {:?}", workspace_edit);
748735 let _ = apply_workspace_edit(editor, offset_encoding, workspace_edit);
749736 }
@@ -758,7 +745,16 @@ pub fn code_action(cx: &mut Context) {
758745 });
759746 picker.move_down(); // pre-select the first item
760747
761− let popup = Popup::new("code-action", picker).with_scrollbar(false);
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)
755+ .with_scrollbar(false)
756+ .margin(margin);
757+
762758 compositor.replace_or_push("code-action", popup);
763759 };
764760
@@ -1033,7 +1029,7 @@ fn goto_impl(
10331029 locations: Vec<lsp::Location>,
10341030 offset_encoding: OffsetEncoding,
10351031) {
1036− let cwdir = std::env::current_dir().unwrap_or_default();
1032+ let cwdir = helix_loader::current_working_dir();
10371033
10381034 match locations.as_slice() {
10391035 [location] => {
@@ -1173,7 +1169,8 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
11731169 // Do not show the message if signature help was invoked
11741170 // automatically on backspace, trigger characters, etc.
11751171 if invoked == SignatureHelpInvoked::Manual {
1176− cx.editor.set_error("No configured language server supports signature-help");
1172+ cx.editor
1173+ .set_error("No configured language server supports signature-help");
11771174 }
11781175 return;
11791176 };
@@ -1398,7 +1395,8 @@ pub fn rename_symbol(cx: &mut Context) {
13981395 .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
13991396 .find(|ls| language_server_id.map_or(true, |id| id == ls.id()))
14001397 else {
1401− cx.editor.set_error("No configured language server supports symbol renaming");
1398+ cx.editor
1399+ .set_error("No configured language server supports symbol renaming");
14021400 return;
14031401 };
14041402
helix-term/src/commands/typed.rs+927−699
@@ -5,7 +5,10 @@ use crate::job::Job;
55
66use super::*;
77
8−use helix_core::{encoding, shellwords::Shellwords};
8+use helix_core::fuzzy::fuzzy_match;
9+use helix_core::indent::MAX_INDENT;
10+use helix_core::{encoding, line_ending, path::get_canonicalized_path, shellwords::Shellwords};
11+use helix_lsp::{OffsetEncoding, Url};
912use helix_view::document::DEFAULT_LANGUAGE_NAME;
1013use helix_view::editor::{Action, CloseError, ConfigEvent};
1114use serde_json::Value;
@@ -329,12 +332,16 @@ fn write_impl(
329332 path: Option<&Cow<str>>,
330333 force: bool,
331334) -> anyhow::Result<()> {
332− let editor_auto_fmt = cx.editor.config().auto_format;
335+ let config = cx.editor.config();
333336 let jobs = &mut cx.jobs;
334337 let (view, doc) = current!(cx.editor);
335338 let path = path.map(AsRef::as_ref);
336339
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 {
338345 doc.auto_format().map(|fmt| {
339346 let callback = make_format_callback(
340347 doc.id(),
@@ -358,6 +365,16 @@ fn write_impl(
358365 Ok(())
359366}
360367
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+
361378fn write(
362379 cx: &mut compositor::Context,
363380 args: &[Cow<str>],
@@ -460,8 +477,7 @@ fn set_indent_style(
460477 cx.editor.set_status(match style {
461478 Tabs => "tabs".to_owned(),
462479 Spaces(1) => "1 space".to_owned(),
463− Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
464− _ => unreachable!(), // Shouldn't happen.
480+ Spaces(n) => format!("{} spaces", n),
465481 });
466482 return Ok(());
467483 }
@@ -473,7 +489,7 @@ fn set_indent_style(
473489 Some(arg) => arg
474490 .parse::<u8>()
475491 .ok()
476− .filter(|n| (1..=8).contains(n))
492+ .filter(|n| (1..=MAX_INDENT).contains(n))
477493 .map(Spaces),
478494 _ => None,
479495 };
@@ -558,7 +574,6 @@ fn set_line_ending(
558574
559575 Ok(())
560576}
561−
562577fn earlier(
563578 cx: &mut compositor::Context,
564579 args: &[Cow<str>],
@@ -657,11 +672,10 @@ pub fn write_all_impl(
657672 write_scratch: bool,
658673) -> anyhow::Result<()> {
659674 let mut errors: Vec<&'static str> = Vec::new();
660− let auto_format = cx.editor.config().auto_format;
675+ let config = cx.editor.config();
661676 let jobs = &mut cx.jobs;
662677 let current_view = view!(cx.editor);
663678
664− // save all documents
665679 let saves: Vec<_> = cx
666680 .editor
667681 .documents
@@ -672,7 +686,7 @@ pub fn write_all_impl(
672686 }
673687 if doc.path().is_none() {
674688 if write_scratch {
675− errors.push("cannot write a buffer without a filename\n");
689+ errors.push("cannot write a buffer without a filename");
676690 }
677691 return None;
678692 }
@@ -692,32 +706,35 @@ pub fn write_all_impl(
692706 current_view.id
693707 };
694708
695− let fmt = if auto_format {
696− doc.auto_format().map(|fmt| {
697− let callback = make_format_callback(
698− doc.id(),
699− doc.version(),
700− target_view,
701− fmt,
702− Some((None, force)),
703− );
704− jobs.add(Job::with_callback(callback).wait_before_exiting());
705− })
706− } else {
707− None
708− };
709+ Some((doc.id(), target_view))
710+ })
711+ .collect();
709712
710− if fmt.is_none() {
711− return Some(doc.id());
712− }
713+ for (doc_id, target_view) in saves {
714+ let doc = doc_mut!(cx.editor, &doc_id);
715+
716+ if config.insert_final_newline {
717+ insert_final_newline(doc, view_mut!(cx.editor, target_view));
718+ }
713719
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 {
714732 None
715− })
716− .collect();
733+ };
717734
718− // manually call save for the rest of docs that don't have a formatter
719− for id in saves {
720− cx.editor.save::<PathBuf>(id, None, force)?;
735+ if fmt.is_none() {
736+ cx.editor.save::<PathBuf>(doc_id, None, force)?;
737+ }
721738 }
722739
723740 if !errors.is_empty() && !force {
@@ -739,6 +756,18 @@ fn write_all(
739756 write_all_impl(cx, false, true)
740757}
741758
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+
742771fn write_all_quit(
743772 cx: &mut compositor::Context,
744773 _args: &[Cow<str>],
@@ -892,7 +921,8 @@ fn yank_main_selection_to_clipboard(
892921 return Ok(());
893922 }
894923
895− yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard)
924+ yank_primary_selection_impl(cx.editor, '+');
925+ Ok(())
896926}
897927
898928fn yank_joined(
@@ -926,7 +956,8 @@ fn yank_joined_to_clipboard(
926956 let doc = doc!(cx.editor);
927957 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
928958 let separator = args.first().unwrap_or(&default_sep);
929− yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
959+ yank_joined_impl(cx.editor, separator, '+');
960+ Ok(())
930961}
931962
932963fn yank_main_selection_to_primary_clipboard(
@@ -938,7 +969,8 @@ fn yank_main_selection_to_primary_clipboard(
938969 return Ok(());
939970 }
940971
941− yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection)
972+ yank_primary_selection_impl(cx.editor, '*');
973+ Ok(())
942974}
943975
944976fn yank_joined_to_primary_clipboard(
@@ -953,7 +985,8 @@ fn yank_joined_to_primary_clipboard(
953985 let doc = doc!(cx.editor);
954986 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
955987 let separator = args.first().unwrap_or(&default_sep);
956− yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
988+ yank_joined_impl(cx.editor, separator, '*');
989+ Ok(())
957990}
958991
959992fn paste_clipboard_after(
@@ -965,7 +998,8 @@ fn paste_clipboard_after(
965998 return Ok(());
966999 }
9671000
968− paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard, 1)
1001+ paste(cx.editor, '+', Paste::After, 1);
1002+ Ok(())
9691003}
9701004
9711005fn paste_clipboard_before(
@@ -977,7 +1011,8 @@ fn paste_clipboard_before(
9771011 return Ok(());
9781012 }
9791013
980− paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Clipboard, 1)
1014+ paste(cx.editor, '+', Paste::Before, 1);
1015+ Ok(())
9811016}
9821017
9831018fn paste_primary_clipboard_after(
@@ -989,7 +1024,8 @@ fn paste_primary_clipboard_after(
9891024 return Ok(());
9901025 }
9911026
992− paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection, 1)
1027+ paste(cx.editor, '*', Paste::After, 1);
1028+ Ok(())
9931029}
9941030
9951031fn paste_primary_clipboard_before(
@@ -1001,30 +1037,8 @@ fn paste_primary_clipboard_before(
10011037 return Ok(());
10021038 }
10031039
1004− paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Selection, 1)
1005−}
1006−
1007−fn replace_selections_with_clipboard_impl(
1008− cx: &mut compositor::Context,
1009− clipboard_type: ClipboardType,
1010−) -> anyhow::Result<()> {
1011− let scrolloff = cx.editor.config().scrolloff;
1012− let (view, doc) = current!(cx.editor);
1013−
1014− match cx.editor.clipboard_provider.get_contents(clipboard_type) {
1015− Ok(contents) => {
1016− let selection = doc.selection(view.id);
1017− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
1018− (range.from(), range.to(), Some(contents.as_str().into()))
1019− });
1020−
1021− doc.apply(&transaction, view.id);
1022− doc.append_changes_to_history(view);
1023− view.ensure_cursor_in_view(doc, scrolloff);
1024− Ok(())
1025− }
1026− Err(e) => Err(e.context("Couldn't get system clipboard contents")),
1027− }
1040+ paste(cx.editor, '*', Paste::Before, 1);
1041+ Ok(())
10281042}
10291043
10301044fn replace_selections_with_clipboard(
@@ -1036,7 +1050,8 @@ fn replace_selections_with_clipboard(
10361050 return Ok(());
10371051 }
10381052
1039− replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
1053+ replace_with_yanked_impl(cx.editor, '+', 1);
1054+ Ok(())
10401055}
10411056
10421057fn replace_selections_with_primary_clipboard(
@@ -1048,7 +1063,8 @@ fn replace_selections_with_primary_clipboard(
10481063 return Ok(());
10491064 }
10501065
1051− replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
1066+ replace_with_yanked_impl(cx.editor, '*', 1);
1067+ Ok(())
10521068}
10531069
10541070fn show_clipboard_provider(
@@ -1061,7 +1077,7 @@ fn show_clipboard_provider(
10611077 }
10621078
10631079 cx.editor
1064− .set_status(cx.editor.clipboard_provider.name().to_string());
1080+ .set_status(cx.editor.registers.clipboard_provider_name().to_string());
10651081 Ok(())
10661082}
10671083
@@ -1081,14 +1097,11 @@ fn change_current_directory(
10811097 .as_ref(),
10821098 );
10831099
1084− if let Err(e) = std::env::set_current_dir(dir) {
1085− bail!("Couldn't change the current working directory: {}", e);
1086− }
1100+ helix_loader::set_current_working_dir(dir)?;
10871101
1088− let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
10891102 cx.editor.set_status(format!(
10901103 "Current working directory is now {}",
1091− cwd.display()
1104+ helix_loader::current_working_dir().display()
10921105 ));
10931106 Ok(())
10941107}
@@ -1102,9 +1115,14 @@ fn show_current_directory(
11021115 return Ok(());
11031116 }
11041117
1105− let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1106− cx.editor
1107− .set_status(format!("Current working directory is {}", cwd.display()));
1118+ let cwd = helix_loader::current_working_dir();
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+ }
11081126 Ok(())
11091127}
11101128
@@ -1264,12 +1282,17 @@ fn reload(
12641282 }
12651283
12661284 let scrolloff = cx.editor.config().scrolloff;
1267− let redraw_handle = cx.editor.redraw_handle.clone();
12681285 let (view, doc) = current!(cx.editor);
1269− doc.reload(view, &cx.editor.diff_providers, redraw_handle)
1270− .map(|_| {
1271− view.ensure_cursor_in_view(doc, scrolloff);
1272− })
1286+ doc.reload(view, &cx.editor.diff_providers).map(|_| {
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());
1294+ }
1295+ Ok(())
12731296}
12741297
12751298fn reload_all(
@@ -1308,8 +1331,13 @@ fn reload_all(
13081331 // Ensure that the view is synced with the document's history.
13091332 view.sync_changes(doc);
13101333
1311− let redraw_handle = cx.editor.redraw_handle.clone();
1312− 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+ }
13131341
13141342 for view_id in view_ids {
13151343 let view = view_mut!(cx.editor, view_id);
@@ -1358,9 +1386,8 @@ fn lsp_workspace_command(
13581386 .map(|options| (ls.id(), options))
13591387 })
13601388 else {
1361− cx.editor.set_status(
1362− "No active language servers for this document support workspace commands",
1363− );
1389+ cx.editor
1390+ .set_status("No active language servers for this document support workspace commands");
13641391 return Ok(());
13651392 };
13661393
@@ -1516,6 +1543,84 @@ fn tree_sitter_scopes(
15161543 Ok(())
15171544}
15181545
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+
15191624fn vsplit(
15201625 cx: &mut compositor::Context,
15211626 args: &[Cow<str>],
@@ -1525,10 +1630,8 @@ fn vsplit(
15251630 return Ok(());
15261631 }
15271632
1528− let id = view!(cx.editor).doc;
1529−
15301633 if args.is_empty() {
1531− cx.editor.switch(id, Action::VerticalSplit);
1634+ split(cx.editor, Action::VerticalSplit);
15321635 } else {
15331636 for arg in args {
15341637 cx.editor
@@ -1548,10 +1651,8 @@ fn hsplit(
15481651 return Ok(());
15491652 }
15501653
1551− let id = view!(cx.editor).doc;
1552−
15531654 if args.is_empty() {
1554− cx.editor.switch(id, Action::HorizontalSplit);
1655+ split(cx.editor, Action::HorizontalSplit);
15551656 } else {
15561657 for arg in args {
15571658 cx.editor
@@ -1666,7 +1767,7 @@ fn tutor(
16661767 let path = helix_loader::runtime_file(Path::new("tutor"));
16671768 cx.editor.open(&path, Action::Replace)?;
16681769 // Unset path to prevent accidentally saving to the original tutor file.
1669− doc_mut!(cx.editor).set_path(None)?;
1770+ doc_mut!(cx.editor).set_path(None);
16701771 Ok(())
16711772}
16721773
@@ -1843,14 +1944,29 @@ fn toggle_option(
18431944 .to_string(),
18441945 )
18451946 }
1846− Value::Null | Value::Object(_) | Value::Array(_) | Value::Number(_) => {
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(_) => {
18471963 anyhow::bail!("Configuration {key} does not support toggle yet")
18481964 }
18491965 };
18501966
18511967 let status = format!("'{key}' is now set to {value}");
18521968 let config = serde_json::from_value(config)
1853− .map_err(|_| anyhow::anyhow!("Could not parse field: `{:?}`", &args))?;
1969+ .map_err(|err| anyhow::anyhow!("Cannot parse `{:?}`, {}", &args, err))?;
18541970
18551971 cx.editor
18561972 .config_events
@@ -2259,599 +2375,721 @@ fn clear_register(
22592375 format!("Invalid register {}", args[0])
22602376 );
22612377 let register = args[0].chars().next().unwrap_or_default();
2262− match cx.editor.registers.remove(register) {
2263− Some(_) => cx
2264− .editor
2265− .set_status(format!("Register {} cleared", register)),
2266− None => cx
2267− .editor
2268− .set_error(format!("Register {} not found", register)),
2378+ if cx.editor.registers.remove(register) {
2379+ cx.editor
2380+ .set_status(format!("Register {} cleared", register));
2381+ } else {
2382+ cx.editor
2383+ .set_error(format!("Register {} not found", register));
22692384 }
22702385 Ok(())
22712386}
22722387
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+
22732485pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2274− TypableCommand {
2275− name: "quit",
2276− aliases: &["q"],
2277− doc: "Close the current view.",
2278− fun: quit,
2279− signature: CommandSignature::none(),
2280− },
2281− TypableCommand {
2282− name: "quit!",
2283− aliases: &["q!"],
2284− doc: "Force close the current view, ignoring unsaved changes.",
2285− fun: force_quit,
2286− signature: CommandSignature::none(),
2287− },
2288− TypableCommand {
2289− name: "open",
2290− aliases: &["o"],
2291− doc: "Open a file from disk into the current view.",
2292− fun: open,
2293− signature: CommandSignature::all(completers::filename),
2294− },
2295− TypableCommand {
2296− name: "buffer-close",
2297− aliases: &["bc", "bclose"],
2298− doc: "Close the current buffer.",
2299− fun: buffer_close,
2300− signature: CommandSignature::all(completers::buffer),
2301− },
2302− TypableCommand {
2303− name: "buffer-close!",
2304− aliases: &["bc!", "bclose!"],
2305− doc: "Close the current buffer forcefully, ignoring unsaved changes.",
2306− fun: force_buffer_close,
2307− signature: CommandSignature::all(completers::buffer)
2308− },
2309− TypableCommand {
2310− name: "buffer-close-others",
2311− aliases: &["bco", "bcloseother"],
2312− doc: "Close all buffers but the currently focused one.",
2313− fun: buffer_close_others,
2314− signature: CommandSignature::none(),
2315− },
2316− TypableCommand {
2317− name: "buffer-close-others!",
2318− aliases: &["bco!", "bcloseother!"],
2319− doc: "Force close all buffers but the currently focused one.",
2320− fun: force_buffer_close_others,
2321− signature: CommandSignature::none(),
2322− },
2323− TypableCommand {
2324− name: "buffer-close-all",
2325− aliases: &["bca", "bcloseall"],
2326− doc: "Close all buffers without quitting.",
2327− fun: buffer_close_all,
2328− signature: CommandSignature::none(),
2329− },
2330− TypableCommand {
2331− name: "buffer-close-all!",
2332− aliases: &["bca!", "bcloseall!"],
2333− doc: "Force close all buffers ignoring unsaved changes without quitting.",
2334− fun: force_buffer_close_all,
2335− signature: CommandSignature::none(),
2336− },
2337− TypableCommand {
2338− name: "buffer-next",
2339− aliases: &["bn", "bnext"],
2340− doc: "Goto next buffer.",
2341− fun: buffer_next,
2342− signature: CommandSignature::none(),
2343− },
2344− TypableCommand {
2345− name: "buffer-previous",
2346− aliases: &["bp", "bprev"],
2347− doc: "Goto previous buffer.",
2348− fun: buffer_previous,
2349− signature: CommandSignature::none(),
2350− },
2351− TypableCommand {
2352− name: "write",
2353− aliases: &["w"],
2354− doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
2355− fun: write,
2356− signature: CommandSignature::positional(&[completers::filename]),
2357− },
2358− TypableCommand {
2359− name: "write!",
2360− aliases: &["w!"],
2361− doc: "Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt)",
2362− fun: force_write,
2363− signature: CommandSignature::positional(&[completers::filename]),
2364− },
2365− TypableCommand {
2366− name: "write-buffer-close",
2367− aliases: &["wbc"],
2368− doc: "Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt)",
2369− fun: write_buffer_close,
2370− signature: CommandSignature::positional(&[completers::filename]),
2371− },
2372− TypableCommand {
2373− name: "write-buffer-close!",
2374− aliases: &["wbc!"],
2375− doc: "Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt)",
2376− fun: force_write_buffer_close,
2377− signature: CommandSignature::positional(&[completers::filename]),
2378− },
2379− TypableCommand {
2380− name: "new",
2381− aliases: &["n"],
2382− doc: "Create a new scratch buffer.",
2383− fun: new_file,
2384− // TODO: This seems to complete with a filename, but doesn't use that filename to
2385− // set the path of the newly created buffer.
2386− signature: CommandSignature::positional(&[completers::filename]),
2387− },
2388− TypableCommand {
2389− name: "format",
2390− aliases: &["fmt"],
2391− doc: "Format the file using the LSP formatter.",
2392− fun: format,
2393− signature: CommandSignature::none(),
2394− },
2395− TypableCommand {
2396− name: "indent-style",
2397− aliases: &[],
2398− doc: "Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)",
2399− fun: set_indent_style,
2400− signature: CommandSignature::none(),
2401− },
2402− TypableCommand {
2403− name: "line-ending",
2404− aliases: &[],
2405− #[cfg(not(feature = "unicode-lines"))]
2406− doc: "Set the document's default line ending. Options: crlf, lf.",
2407− #[cfg(feature = "unicode-lines")]
2408− doc: "Set the document's default line ending. Options: crlf, lf, cr, ff, nel.",
2409− fun: set_line_ending,
2410− signature: CommandSignature::none(),
2411− },
2412− TypableCommand {
2413− name: "earlier",
2414− aliases: &["ear"],
2415− doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
2416− fun: earlier,
2417− signature: CommandSignature::none(),
2418− },
2419− TypableCommand {
2420− name: "later",
2421− aliases: &["lat"],
2422− doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
2423− fun: later,
2424− signature: CommandSignature::none(),
2425− },
2426− TypableCommand {
2427− name: "write-quit",
2428− aliases: &["wq", "x"],
2429− doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
2430− fun: write_quit,
2431− signature: CommandSignature::positional(&[completers::filename]),
2432− },
2433− TypableCommand {
2434− name: "write-quit!",
2435− aliases: &["wq!", "x!"],
2436− doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
2437− fun: force_write_quit,
2438− signature: CommandSignature::positional(&[completers::filename]),
2439− },
2440− TypableCommand {
2441− name: "write-all",
2442− aliases: &["wa"],
2443− doc: "Write changes from all buffers to disk.",
2444− fun: write_all,
2445− signature: CommandSignature::none(),
2446− },
2447− TypableCommand {
2448− name: "write-quit-all",
2449− aliases: &["wqa", "xa"],
2450− doc: "Write changes from all buffers to disk and close all views.",
2451− fun: write_all_quit,
2452− signature: CommandSignature::none(),
2453− },
2454− TypableCommand {
2455− name: "write-quit-all!",
2456− aliases: &["wqa!", "xa!"],
2457− doc: "Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes).",
2458− fun: force_write_all_quit,
2459− signature: CommandSignature::none(),
2460− },
2461− TypableCommand {
2462− name: "quit-all",
2463− aliases: &["qa"],
2464− doc: "Close all views.",
2465− fun: quit_all,
2466− signature: CommandSignature::none(),
2467− },
2468− TypableCommand {
2469− name: "quit-all!",
2470− aliases: &["qa!"],
2471− doc: "Force close all views ignoring unsaved changes.",
2472− fun: force_quit_all,
2473− signature: CommandSignature::none(),
2474− },
2475− TypableCommand {
2476− name: "cquit",
2477− aliases: &["cq"],
2478− doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).",
2479− fun: cquit,
2480− signature: CommandSignature::none(),
2481− },
2482− TypableCommand {
2483− name: "cquit!",
2484− aliases: &["cq!"],
2485− doc: "Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).",
2486− fun: force_cquit,
2487− signature: CommandSignature::none(),
2488− },
2489− TypableCommand {
2490− name: "theme",
2491− aliases: &[],
2492− doc: "Change the editor theme (show current theme if no name specified).",
2493− fun: theme,
2494− signature: CommandSignature::positional(&[completers::theme]),
2495− },
2496− TypableCommand {
2497− name: "yank-join",
2498− aliases: &[],
2499− doc: "Yank joined selections. A separator can be provided as first argument. Default value is newline.",
2500− fun: yank_joined,
2501− signature: CommandSignature::none(),
2502− },
2503− TypableCommand {
2504− name: "clipboard-yank",
2505− aliases: &[],
2506− doc: "Yank main selection into system clipboard.",
2507− fun: yank_main_selection_to_clipboard,
2508− signature: CommandSignature::none(),
2509− },
2510− TypableCommand {
2511− name: "clipboard-yank-join",
2512− aliases: &[],
2513− 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.
2514− fun: yank_joined_to_clipboard,
2515− signature: CommandSignature::none(),
2516− },
2517− TypableCommand {
2518− name: "primary-clipboard-yank",
2519− aliases: &[],
2520− doc: "Yank main selection into system primary clipboard.",
2521− fun: yank_main_selection_to_primary_clipboard,
2522− signature: CommandSignature::none(),
2523− },
2524− TypableCommand {
2525− name: "primary-clipboard-yank-join",
2526− aliases: &[],
2527− 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.
2528− fun: yank_joined_to_primary_clipboard,
2529− signature: CommandSignature::none(),
2530− },
2531− TypableCommand {
2532− name: "clipboard-paste-after",
2533− aliases: &[],
2534− doc: "Paste system clipboard after selections.",
2535− fun: paste_clipboard_after,
2536− signature: CommandSignature::none(),
2537− },
2538− TypableCommand {
2539− name: "clipboard-paste-before",
2540− aliases: &[],
2541− doc: "Paste system clipboard before selections.",
2542− fun: paste_clipboard_before,
2543− signature: CommandSignature::none(),
2544− },
2545− TypableCommand {
2546− name: "clipboard-paste-replace",
2547− aliases: &[],
2548− doc: "Replace selections with content of system clipboard.",
2549− fun: replace_selections_with_clipboard,
2550− signature: CommandSignature::none(),
2551− },
2552− TypableCommand {
2553− name: "primary-clipboard-paste-after",
2554− aliases: &[],
2555− doc: "Paste primary clipboard after selections.",
2556− fun: paste_primary_clipboard_after,
2557− signature: CommandSignature::none(),
2558− },
2559− TypableCommand {
2560− name: "primary-clipboard-paste-before",
2561− aliases: &[],
2562− doc: "Paste primary clipboard before selections.",
2563− fun: paste_primary_clipboard_before,
2564− signature: CommandSignature::none(),
2565− },
2566− TypableCommand {
2567− name: "primary-clipboard-paste-replace",
2568− aliases: &[],
2569− doc: "Replace selections with content of system primary clipboard.",
2570− fun: replace_selections_with_primary_clipboard,
2571− signature: CommandSignature::none(),
2572− },
2573− TypableCommand {
2574− name: "show-clipboard-provider",
2575− aliases: &[],
2576− doc: "Show clipboard provider name in status bar.",
2577− fun: show_clipboard_provider,
2578− signature: CommandSignature::none(),
2579− },
2580− TypableCommand {
2581− name: "change-current-directory",
2582− aliases: &["cd"],
2583− doc: "Change the current working directory.",
2584− fun: change_current_directory,
2585− signature: CommandSignature::positional(&[completers::directory]),
2586− },
2587− TypableCommand {
2588− name: "show-directory",
2589− aliases: &["pwd"],
2590− doc: "Show the current working directory.",
2591− fun: show_current_directory,
2592− signature: CommandSignature::none(),
2593− },
2594− TypableCommand {
2595− name: "encoding",
2596− aliases: &[],
2597− doc: "Set encoding. Based on `https://encoding.spec.whatwg.org`.",
2598− fun: set_encoding,
2599− signature: CommandSignature::none(),
2600− },
2601− TypableCommand {
2602− name: "character-info",
2603− aliases: &["char"],
2604− doc: "Get info about the character under the primary cursor.",
2605− fun: get_character_info,
2606− signature: CommandSignature::none(),
2607− },
2608− TypableCommand {
2609− name: "reload",
2610− aliases: &["rl"],
2611− doc: "Discard changes and reload from the source file.",
2612− fun: reload,
2613− signature: CommandSignature::none(),
2614− },
2615− TypableCommand {
2616− name: "reload-all",
2617− aliases: &["rla"],
2618− doc: "Discard changes and reload all documents from the source files.",
2619− fun: reload_all,
2620− signature: CommandSignature::none(),
2621− },
2622− TypableCommand {
2623− name: "update",
2624− aliases: &["u"],
2625− doc: "Write changes only if the file has been modified.",
2626− fun: update,
2627− signature: CommandSignature::none(),
2628− },
2629− TypableCommand {
2630− name: "lsp-workspace-command",
2631− aliases: &[],
2632− doc: "Open workspace command picker",
2633− fun: lsp_workspace_command,
2634− signature: CommandSignature::positional(&[completers::lsp_workspace_command]),
2635− },
2636− TypableCommand {
2637− name: "lsp-restart",
2638− aliases: &[],
2639− doc: "Restarts the language servers used by the current doc",
2640− fun: lsp_restart,
2641− signature: CommandSignature::none(),
2642− },
2643− TypableCommand {
2644− name: "lsp-stop",
2645− aliases: &[],
2646− doc: "Stops the language servers that are used by the current doc",
2647− fun: lsp_stop,
2648− signature: CommandSignature::none(),
2649− },
2650− TypableCommand {
2651− name: "tree-sitter-scopes",
2652− aliases: &[],
2653− doc: "Display tree sitter scopes, primarily for theming and development.",
2654− fun: tree_sitter_scopes,
2655− signature: CommandSignature::none(),
2656− },
2657− TypableCommand {
2658− name: "debug-start",
2659− aliases: &["dbg"],
2660− doc: "Start a debug session from a given template with given parameters.",
2661− fun: debug_start,
2662− signature: CommandSignature::none(),
2663− },
2664− TypableCommand {
2665− name: "debug-remote",
2666− aliases: &["dbg-tcp"],
2667− doc: "Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.",
2668− fun: debug_remote,
2669− signature: CommandSignature::none(),
2670− },
2671− TypableCommand {
2672− name: "debug-eval",
2673− aliases: &[],
2674− doc: "Evaluate expression in current debug context.",
2675− fun: debug_eval,
2676− signature: CommandSignature::none(),
2677− },
2678− TypableCommand {
2679− name: "vsplit",
2680− aliases: &["vs"],
2681− doc: "Open the file in a vertical split.",
2682− fun: vsplit,
2683− signature: CommandSignature::all(completers::filename)
2684− },
2685− TypableCommand {
2686− name: "vsplit-new",
2687− aliases: &["vnew"],
2688− doc: "Open a scratch buffer in a vertical split.",
2689− fun: vsplit_new,
2690− signature: CommandSignature::none(),
2691− },
2692− TypableCommand {
2693− name: "hsplit",
2694− aliases: &["hs", "sp"],
2695− doc: "Open the file in a horizontal split.",
2696− fun: hsplit,
2697− signature: CommandSignature::all(completers::filename)
2698− },
2699− TypableCommand {
2700− name: "hsplit-new",
2701− aliases: &["hnew"],
2702− doc: "Open a scratch buffer in a horizontal split.",
2703− fun: hsplit_new,
2704− signature: CommandSignature::none(),
2705− },
2706− TypableCommand {
2707− name: "tutor",
2708− aliases: &[],
2709− doc: "Open the tutorial.",
2710− fun: tutor,
2711− signature: CommandSignature::none(),
2712− },
2713− TypableCommand {
2714− name: "goto",
2715− aliases: &["g"],
2716− doc: "Goto line number.",
2717− fun: goto_line_number,
2718− signature: CommandSignature::none(),
2719− },
2720− TypableCommand {
2721− name: "set-language",
2722− aliases: &["lang"],
2723− doc: "Set the language of current buffer (show current language if no value specified).",
2724− fun: language,
2725− signature: CommandSignature::positional(&[completers::language]),
2726− },
2727− TypableCommand {
2728− name: "set-option",
2729− aliases: &["set"],
2730− doc: "Set a config option at runtime.\nFor example to disable smart case search, use `:set search.smart-case false`.",
2731− fun: set_option,
2732− // TODO: Add support for completion of the options value(s), when appropriate.
2733− signature: CommandSignature::positional(&[completers::setting]),
2734− },
2735− TypableCommand {
2736− name: "toggle-option",
2737− aliases: &["toggle"],
2738− doc: "Toggle a boolean config option at runtime.\nFor example to toggle smart case search, use `:toggle search.smart-case`.",
2739− fun: toggle_option,
2740− signature: CommandSignature::positional(&[completers::setting]),
2741− },
2742− TypableCommand {
2743− name: "get-option",
2744− aliases: &["get"],
2745− doc: "Get the current value of a config option.",
2746− fun: get_option,
2747− signature: CommandSignature::positional(&[completers::setting]),
2748− },
2749− TypableCommand {
2750− name: "sort",
2751− aliases: &[],
2752− doc: "Sort ranges in selection.",
2753− fun: sort,
2754− signature: CommandSignature::none(),
2755− },
2756− TypableCommand {
2757− name: "rsort",
2758− aliases: &[],
2759− doc: "Sort ranges in selection in reverse order.",
2760− fun: sort_reverse,
2761− signature: CommandSignature::none(),
2762− },
2763− TypableCommand {
2764− name: "reflow",
2765− aliases: &[],
2766− doc: "Hard-wrap the current selection of lines to a given width.",
2767− fun: reflow,
2768− signature: CommandSignature::none(),
2769− },
2770− TypableCommand {
2771− name: "tree-sitter-subtree",
2772− aliases: &["ts-subtree"],
2773− doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
2774− fun: tree_sitter_subtree,
2775− signature: CommandSignature::none(),
2776− },
2777− TypableCommand {
2778− name: "config-reload",
2779− aliases: &[],
2780− doc: "Refresh user config.",
2781− fun: refresh_config,
2782− signature: CommandSignature::none(),
2783− },
2784− TypableCommand {
2785− name: "config-open",
2786− aliases: &[],
2787− doc: "Open the user config.toml file.",
2788− fun: open_config,
2789− signature: CommandSignature::none(),
2790− },
2791− TypableCommand {
2792− name: "config-open-workspace",
2793− aliases: &[],
2794− doc: "Open the workspace config.toml file.",
2795− fun: open_workspace_config,
2796− signature: CommandSignature::none(),
2797− },
2798− TypableCommand {
2799− name: "log-open",
2800− aliases: &[],
2801− doc: "Open the helix log file.",
2802− fun: open_log,
2803− signature: CommandSignature::none(),
2804− },
2805− TypableCommand {
2806− name: "insert-output",
2807− aliases: &[],
2808− doc: "Run shell command, inserting output before each selection.",
2809− fun: insert_output,
2810− signature: CommandSignature::none(),
2811− },
2812− TypableCommand {
2813− name: "append-output",
2814− aliases: &[],
2815− doc: "Run shell command, appending output after each selection.",
2816− fun: append_output,
2817− signature: CommandSignature::none(),
2818− },
2819− TypableCommand {
2820− name: "pipe",
2821− aliases: &[],
2822− doc: "Pipe each selection to the shell command.",
2823− fun: pipe,
2824− signature: CommandSignature::none(),
2825− },
2826− TypableCommand {
2827− name: "pipe-to",
2828− aliases: &[],
2829− doc: "Pipe each selection to the shell command, ignoring output.",
2830− fun: pipe_to,
2831− signature: CommandSignature::none(),
2832− },
2833− TypableCommand {
2834− name: "run-shell-command",
2835− aliases: &["sh"],
2836− doc: "Run a shell command",
2837− fun: run_shell_command,
2838− signature: CommandSignature::all(completers::filename)
2839− },
2840− TypableCommand {
2841− name: "reset-diff-change",
2842− aliases: &["diffget", "diffg"],
2843− doc: "Reset the diff change at the cursor position.",
2844− fun: reset_diff_change,
2845− signature: CommandSignature::none(),
2846− },
2847− TypableCommand {
2848− name: "clear-register",
2849− aliases: &[],
2850− doc: "Clear given register. If no argument is provided, clear all registers.",
2851− fun: clear_register,
2852− signature: CommandSignature::none(),
2853− },
2854− ];
2486+ TypableCommand {
2487+ name: "quit",
2488+ aliases: &["q"],
2489+ doc: "Close the current view.",
2490+ fun: quit,
2491+ signature: CommandSignature::none(),
2492+ },
2493+ TypableCommand {
2494+ name: "quit!",
2495+ aliases: &["q!"],
2496+ doc: "Force close the current view, ignoring unsaved changes.",
2497+ fun: force_quit,
2498+ signature: CommandSignature::none(),
2499+ },
2500+ TypableCommand {
2501+ name: "open",
2502+ aliases: &["o"],
2503+ doc: "Open a file from disk into the current view.",
2504+ fun: open,
2505+ signature: CommandSignature::all(completers::filename),
2506+ },
2507+ TypableCommand {
2508+ name: "buffer-close",
2509+ aliases: &["bc", "bclose"],
2510+ doc: "Close the current buffer.",
2511+ fun: buffer_close,
2512+ signature: CommandSignature::all(completers::buffer),
2513+ },
2514+ TypableCommand {
2515+ name: "buffer-close!",
2516+ aliases: &["bc!", "bclose!"],
2517+ doc: "Close the current buffer forcefully, ignoring unsaved changes.",
2518+ fun: force_buffer_close,
2519+ signature: CommandSignature::all(completers::buffer)
2520+ },
2521+ TypableCommand {
2522+ name: "buffer-close-others",
2523+ aliases: &["bco", "bcloseother"],
2524+ doc: "Close all buffers but the currently focused one.",
2525+ fun: buffer_close_others,
2526+ signature: CommandSignature::none(),
2527+ },
2528+ TypableCommand {
2529+ name: "buffer-close-others!",
2530+ aliases: &["bco!", "bcloseother!"],
2531+ doc: "Force close all buffers but the currently focused one.",
2532+ fun: force_buffer_close_others,
2533+ signature: CommandSignature::none(),
2534+ },
2535+ TypableCommand {
2536+ name: "buffer-close-all",
2537+ aliases: &["bca", "bcloseall"],
2538+ doc: "Close all buffers without quitting.",
2539+ fun: buffer_close_all,
2540+ signature: CommandSignature::none(),
2541+ },
2542+ TypableCommand {
2543+ name: "buffer-close-all!",
2544+ aliases: &["bca!", "bcloseall!"],
2545+ doc: "Force close all buffers ignoring unsaved changes without quitting.",
2546+ fun: force_buffer_close_all,
2547+ signature: CommandSignature::none(),
2548+ },
2549+ TypableCommand {
2550+ name: "buffer-next",
2551+ aliases: &["bn", "bnext"],
2552+ doc: "Goto next buffer.",
2553+ fun: buffer_next,
2554+ signature: CommandSignature::none(),
2555+ },
2556+ TypableCommand {
2557+ name: "buffer-previous",
2558+ aliases: &["bp", "bprev"],
2559+ doc: "Goto previous buffer.",
2560+ fun: buffer_previous,
2561+ signature: CommandSignature::none(),
2562+ },
2563+ TypableCommand {
2564+ name: "write",
2565+ aliases: &["w"],
2566+ doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
2567+ fun: write,
2568+ signature: CommandSignature::positional(&[completers::filename]),
2569+ },
2570+ TypableCommand {
2571+ name: "write!",
2572+ aliases: &["w!"],
2573+ doc: "Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt)",
2574+ fun: force_write,
2575+ signature: CommandSignature::positional(&[completers::filename]),
2576+ },
2577+ TypableCommand {
2578+ name: "write-buffer-close",
2579+ aliases: &["wbc"],
2580+ doc: "Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt)",
2581+ fun: write_buffer_close,
2582+ signature: CommandSignature::positional(&[completers::filename]),
2583+ },
2584+ TypableCommand {
2585+ name: "write-buffer-close!",
2586+ aliases: &["wbc!"],
2587+ doc: "Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt)",
2588+ fun: force_write_buffer_close,
2589+ signature: CommandSignature::positional(&[completers::filename]),
2590+ },
2591+ TypableCommand {
2592+ name: "new",
2593+ aliases: &["n"],
2594+ doc: "Create a new scratch buffer.",
2595+ fun: new_file,
2596+ signature: CommandSignature::none(),
2597+ },
2598+ TypableCommand {
2599+ name: "format",
2600+ aliases: &["fmt"],
2601+ doc: "Format the file using the LSP formatter.",
2602+ fun: format,
2603+ signature: CommandSignature::none(),
2604+ },
2605+ TypableCommand {
2606+ name: "indent-style",
2607+ aliases: &[],
2608+ doc: "Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.)",
2609+ fun: set_indent_style,
2610+ signature: CommandSignature::none(),
2611+ },
2612+ TypableCommand {
2613+ name: "line-ending",
2614+ aliases: &[],
2615+ #[cfg(not(feature = "unicode-lines"))]
2616+ doc: "Set the document's default line ending. Options: crlf, lf.",
2617+ #[cfg(feature = "unicode-lines")]
2618+ doc: "Set the document's default line ending. Options: crlf, lf, cr, ff, nel.",
2619+ fun: set_line_ending,
2620+ signature: CommandSignature::none(),
2621+ },
2622+ TypableCommand {
2623+ name: "earlier",
2624+ aliases: &["ear"],
2625+ doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
2626+ fun: earlier,
2627+ signature: CommandSignature::none(),
2628+ },
2629+ TypableCommand {
2630+ name: "later",
2631+ aliases: &["lat"],
2632+ doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
2633+ fun: later,
2634+ signature: CommandSignature::none(),
2635+ },
2636+ TypableCommand {
2637+ name: "write-quit",
2638+ aliases: &["wq", "x"],
2639+ doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
2640+ fun: write_quit,
2641+ signature: CommandSignature::positional(&[completers::filename]),
2642+ },
2643+ TypableCommand {
2644+ name: "write-quit!",
2645+ aliases: &["wq!", "x!"],
2646+ doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
2647+ fun: force_write_quit,
2648+ signature: CommandSignature::positional(&[completers::filename]),
2649+ },
2650+ TypableCommand {
2651+ name: "write-all",
2652+ aliases: &["wa"],
2653+ doc: "Write changes from all buffers to disk.",
2654+ fun: write_all,
2655+ signature: CommandSignature::none(),
2656+ },
2657+ TypableCommand {
2658+ name: "write-all!",
2659+ aliases: &["wa!"],
2660+ doc: "Forcefully write changes from all buffers to disk creating necessary subdirectories.",
2661+ fun: force_write_all,
2662+ signature: CommandSignature::none(),
2663+ },
2664+ TypableCommand {
2665+ name: "write-quit-all",
2666+ aliases: &["wqa", "xa"],
2667+ doc: "Write changes from all buffers to disk and close all views.",
2668+ fun: write_all_quit,
2669+ signature: CommandSignature::none(),
2670+ },
2671+ TypableCommand {
2672+ name: "write-quit-all!",
2673+ aliases: &["wqa!", "xa!"],
2674+ doc: "Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes).",
2675+ fun: force_write_all_quit,
2676+ signature: CommandSignature::none(),
2677+ },
2678+ TypableCommand {
2679+ name: "quit-all",
2680+ aliases: &["qa"],
2681+ doc: "Close all views.",
2682+ fun: quit_all,
2683+ signature: CommandSignature::none(),
2684+ },
2685+ TypableCommand {
2686+ name: "quit-all!",
2687+ aliases: &["qa!"],
2688+ doc: "Force close all views ignoring unsaved changes.",
2689+ fun: force_quit_all,
2690+ signature: CommandSignature::none(),
2691+ },
2692+ TypableCommand {
2693+ name: "cquit",
2694+ aliases: &["cq"],
2695+ doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).",
2696+ fun: cquit,
2697+ signature: CommandSignature::none(),
2698+ },
2699+ TypableCommand {
2700+ name: "cquit!",
2701+ aliases: &["cq!"],
2702+ doc: "Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).",
2703+ fun: force_cquit,
2704+ signature: CommandSignature::none(),
2705+ },
2706+ TypableCommand {
2707+ name: "theme",
2708+ aliases: &[],
2709+ doc: "Change the editor theme (show current theme if no name specified).",
2710+ fun: theme,
2711+ signature: CommandSignature::positional(&[completers::theme]),
2712+ },
2713+ TypableCommand {
2714+ name: "yank-join",
2715+ aliases: &[],
2716+ doc: "Yank joined selections. A separator can be provided as first argument. Default value is newline.",
2717+ fun: yank_joined,
2718+ signature: CommandSignature::none(),
2719+ },
2720+ TypableCommand {
2721+ name: "clipboard-yank",
2722+ aliases: &[],
2723+ doc: "Yank main selection into system clipboard.",
2724+ fun: yank_main_selection_to_clipboard,
2725+ signature: CommandSignature::none(),
2726+ },
2727+ TypableCommand {
2728+ name: "clipboard-yank-join",
2729+ aliases: &[],
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.
2731+ fun: yank_joined_to_clipboard,
2732+ signature: CommandSignature::none(),
2733+ },
2734+ TypableCommand {
2735+ name: "primary-clipboard-yank",
2736+ aliases: &[],
2737+ doc: "Yank main selection into system primary clipboard.",
2738+ fun: yank_main_selection_to_primary_clipboard,
2739+ signature: CommandSignature::none(),
2740+ },
2741+ TypableCommand {
2742+ name: "primary-clipboard-yank-join",
2743+ aliases: &[],
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.
2745+ fun: yank_joined_to_primary_clipboard,
2746+ signature: CommandSignature::none(),
2747+ },
2748+ TypableCommand {
2749+ name: "clipboard-paste-after",
2750+ aliases: &[],
2751+ doc: "Paste system clipboard after selections.",
2752+ fun: paste_clipboard_after,
2753+ signature: CommandSignature::none(),
2754+ },
2755+ TypableCommand {
2756+ name: "clipboard-paste-before",
2757+ aliases: &[],
2758+ doc: "Paste system clipboard before selections.",
2759+ fun: paste_clipboard_before,
2760+ signature: CommandSignature::none(),
2761+ },
2762+ TypableCommand {
2763+ name: "clipboard-paste-replace",
2764+ aliases: &[],
2765+ doc: "Replace selections with content of system clipboard.",
2766+ fun: replace_selections_with_clipboard,
2767+ signature: CommandSignature::none(),
2768+ },
2769+ TypableCommand {
2770+ name: "primary-clipboard-paste-after",
2771+ aliases: &[],
2772+ doc: "Paste primary clipboard after selections.",
2773+ fun: paste_primary_clipboard_after,
2774+ signature: CommandSignature::none(),
2775+ },
2776+ TypableCommand {
2777+ name: "primary-clipboard-paste-before",
2778+ aliases: &[],
2779+ doc: "Paste primary clipboard before selections.",
2780+ fun: paste_primary_clipboard_before,
2781+ signature: CommandSignature::none(),
2782+ },
2783+ TypableCommand {
2784+ name: "primary-clipboard-paste-replace",
2785+ aliases: &[],
2786+ doc: "Replace selections with content of system primary clipboard.",
2787+ fun: replace_selections_with_primary_clipboard,
2788+ signature: CommandSignature::none(),
2789+ },
2790+ TypableCommand {
2791+ name: "show-clipboard-provider",
2792+ aliases: &[],
2793+ doc: "Show clipboard provider name in status bar.",
2794+ fun: show_clipboard_provider,
2795+ signature: CommandSignature::none(),
2796+ },
2797+ TypableCommand {
2798+ name: "change-current-directory",
2799+ aliases: &["cd"],
2800+ doc: "Change the current working directory.",
2801+ fun: change_current_directory,
2802+ signature: CommandSignature::positional(&[completers::directory]),
2803+ },
2804+ TypableCommand {
2805+ name: "show-directory",
2806+ aliases: &["pwd"],
2807+ doc: "Show the current working directory.",
2808+ fun: show_current_directory,
2809+ signature: CommandSignature::none(),
2810+ },
2811+ TypableCommand {
2812+ name: "encoding",
2813+ aliases: &[],
2814+ doc: "Set encoding. Based on `https://encoding.spec.whatwg.org`.",
2815+ fun: set_encoding,
2816+ signature: CommandSignature::none(),
2817+ },
2818+ TypableCommand {
2819+ name: "character-info",
2820+ aliases: &["char"],
2821+ doc: "Get info about the character under the primary cursor.",
2822+ fun: get_character_info,
2823+ signature: CommandSignature::none(),
2824+ },
2825+ TypableCommand {
2826+ name: "reload",
2827+ aliases: &["rl"],
2828+ doc: "Discard changes and reload from the source file.",
2829+ fun: reload,
2830+ signature: CommandSignature::none(),
2831+ },
2832+ TypableCommand {
2833+ name: "reload-all",
2834+ aliases: &["rla"],
2835+ doc: "Discard changes and reload all documents from the source files.",
2836+ fun: reload_all,
2837+ signature: CommandSignature::none(),
2838+ },
2839+ TypableCommand {
2840+ name: "update",
2841+ aliases: &["u"],
2842+ doc: "Write changes only if the file has been modified.",
2843+ fun: update,
2844+ signature: CommandSignature::none(),
2845+ },
2846+ TypableCommand {
2847+ name: "lsp-workspace-command",
2848+ aliases: &[],
2849+ doc: "Open workspace command picker",
2850+ fun: lsp_workspace_command,
2851+ signature: CommandSignature::positional(&[completers::lsp_workspace_command]),
2852+ },
2853+ TypableCommand {
2854+ name: "lsp-restart",
2855+ aliases: &[],
2856+ doc: "Restarts the language servers used by the current doc",
2857+ fun: lsp_restart,
2858+ signature: CommandSignature::none(),
2859+ },
2860+ TypableCommand {
2861+ name: "lsp-stop",
2862+ aliases: &[],
2863+ doc: "Stops the language servers that are used by the current doc",
2864+ fun: lsp_stop,
2865+ signature: CommandSignature::none(),
2866+ },
2867+ TypableCommand {
2868+ name: "tree-sitter-scopes",
2869+ aliases: &[],
2870+ doc: "Display tree sitter scopes, primarily for theming and development.",
2871+ fun: tree_sitter_scopes,
2872+ signature: CommandSignature::none(),
2873+ },
2874+ TypableCommand {
2875+ name: "tree-sitter-highlight-name",
2876+ aliases: &[],
2877+ doc: "Display name of tree-sitter highlight scope under the cursor.",
2878+ fun: tree_sitter_highlight_name,
2879+ signature: CommandSignature::none(),
2880+ },
2881+ TypableCommand {
2882+ name: "debug-start",
2883+ aliases: &["dbg"],
2884+ doc: "Start a debug session from a given template with given parameters.",
2885+ fun: debug_start,
2886+ signature: CommandSignature::none(),
2887+ },
2888+ TypableCommand {
2889+ name: "debug-remote",
2890+ aliases: &["dbg-tcp"],
2891+ doc: "Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.",
2892+ fun: debug_remote,
2893+ signature: CommandSignature::none(),
2894+ },
2895+ TypableCommand {
2896+ name: "debug-eval",
2897+ aliases: &[],
2898+ doc: "Evaluate expression in current debug context.",
2899+ fun: debug_eval,
2900+ signature: CommandSignature::none(),
2901+ },
2902+ TypableCommand {
2903+ name: "vsplit",
2904+ aliases: &["vs"],
2905+ doc: "Open the file in a vertical split.",
2906+ fun: vsplit,
2907+ signature: CommandSignature::all(completers::filename)
2908+ },
2909+ TypableCommand {
2910+ name: "vsplit-new",
2911+ aliases: &["vnew"],
2912+ doc: "Open a scratch buffer in a vertical split.",
2913+ fun: vsplit_new,
2914+ signature: CommandSignature::none(),
2915+ },
2916+ TypableCommand {
2917+ name: "hsplit",
2918+ aliases: &["hs", "sp"],
2919+ doc: "Open the file in a horizontal split.",
2920+ fun: hsplit,
2921+ signature: CommandSignature::all(completers::filename)
2922+ },
2923+ TypableCommand {
2924+ name: "hsplit-new",
2925+ aliases: &["hnew"],
2926+ doc: "Open a scratch buffer in a horizontal split.",
2927+ fun: hsplit_new,
2928+ signature: CommandSignature::none(),
2929+ },
2930+ TypableCommand {
2931+ name: "tutor",
2932+ aliases: &[],
2933+ doc: "Open the tutorial.",
2934+ fun: tutor,
2935+ signature: CommandSignature::none(),
2936+ },
2937+ TypableCommand {
2938+ name: "goto",
2939+ aliases: &["g"],
2940+ doc: "Goto line number.",
2941+ fun: goto_line_number,
2942+ signature: CommandSignature::none(),
2943+ },
2944+ TypableCommand {
2945+ name: "set-language",
2946+ aliases: &["lang"],
2947+ doc: "Set the language of current buffer (show current language if no value specified).",
2948+ fun: language,
2949+ signature: CommandSignature::positional(&[completers::language]),
2950+ },
2951+ TypableCommand {
2952+ name: "set-option",
2953+ aliases: &["set"],
2954+ doc: "Set a config option at runtime.\nFor example to disable smart case search, use `:set search.smart-case false`.",
2955+ fun: set_option,
2956+ // TODO: Add support for completion of the options value(s), when appropriate.
2957+ signature: CommandSignature::positional(&[completers::setting]),
2958+ },
2959+ TypableCommand {
2960+ name: "toggle-option",
2961+ aliases: &["toggle"],
2962+ doc: "Toggle a boolean config option at runtime.\nFor example to toggle smart case search, use `:toggle search.smart-case`.",
2963+ fun: toggle_option,
2964+ signature: CommandSignature::positional(&[completers::setting]),
2965+ },
2966+ TypableCommand {
2967+ name: "get-option",
2968+ aliases: &["get"],
2969+ doc: "Get the current value of a config option.",
2970+ fun: get_option,
2971+ signature: CommandSignature::positional(&[completers::setting]),
2972+ },
2973+ TypableCommand {
2974+ name: "sort",
2975+ aliases: &[],
2976+ doc: "Sort ranges in selection.",
2977+ fun: sort,
2978+ signature: CommandSignature::none(),
2979+ },
2980+ TypableCommand {
2981+ name: "rsort",
2982+ aliases: &[],
2983+ doc: "Sort ranges in selection in reverse order.",
2984+ fun: sort_reverse,
2985+ signature: CommandSignature::none(),
2986+ },
2987+ TypableCommand {
2988+ name: "reflow",
2989+ aliases: &[],
2990+ doc: "Hard-wrap the current selection of lines to a given width.",
2991+ fun: reflow,
2992+ signature: CommandSignature::none(),
2993+ },
2994+ TypableCommand {
2995+ name: "tree-sitter-subtree",
2996+ aliases: &["ts-subtree"],
2997+ doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
2998+ fun: tree_sitter_subtree,
2999+ signature: CommandSignature::none(),
3000+ },
3001+ TypableCommand {
3002+ name: "config-reload",
3003+ aliases: &[],
3004+ doc: "Refresh user config.",
3005+ fun: refresh_config,
3006+ signature: CommandSignature::none(),
3007+ },
3008+ TypableCommand {
3009+ name: "config-open",
3010+ aliases: &[],
3011+ doc: "Open the user config.toml file.",
3012+ fun: open_config,
3013+ signature: CommandSignature::none(),
3014+ },
3015+ TypableCommand {
3016+ name: "config-open-workspace",
3017+ aliases: &[],
3018+ doc: "Open the workspace config.toml file.",
3019+ fun: open_workspace_config,
3020+ signature: CommandSignature::none(),
3021+ },
3022+ TypableCommand {
3023+ name: "log-open",
3024+ aliases: &[],
3025+ doc: "Open the helix log file.",
3026+ fun: open_log,
3027+ signature: CommandSignature::none(),
3028+ },
3029+ TypableCommand {
3030+ name: "insert-output",
3031+ aliases: &[],
3032+ doc: "Run shell command, inserting output before each selection.",
3033+ fun: insert_output,
3034+ signature: CommandSignature::none(),
3035+ },
3036+ TypableCommand {
3037+ name: "append-output",
3038+ aliases: &[],
3039+ doc: "Run shell command, appending output after each selection.",
3040+ fun: append_output,
3041+ signature: CommandSignature::none(),
3042+ },
3043+ TypableCommand {
3044+ name: "pipe",
3045+ aliases: &[],
3046+ doc: "Pipe each selection to the shell command.",
3047+ fun: pipe,
3048+ signature: CommandSignature::none(),
3049+ },
3050+ TypableCommand {
3051+ name: "pipe-to",
3052+ aliases: &[],
3053+ doc: "Pipe each selection to the shell command, ignoring output.",
3054+ fun: pipe_to,
3055+ signature: CommandSignature::none(),
3056+ },
3057+ TypableCommand {
3058+ name: "run-shell-command",
3059+ aliases: &["sh"],
3060+ doc: "Run a shell command",
3061+ fun: run_shell_command,
3062+ signature: CommandSignature::all(completers::filename)
3063+ },
3064+ TypableCommand {
3065+ name: "reset-diff-change",
3066+ aliases: &["diffget", "diffg"],
3067+ doc: "Reset the diff change at the cursor position.",
3068+ fun: reset_diff_change,
3069+ signature: CommandSignature::none(),
3070+ },
3071+ TypableCommand {
3072+ name: "clear-register",
3073+ aliases: &[],
3074+ doc: "Clear given register. If no argument is provided, clear all registers.",
3075+ fun: clear_register,
3076+ signature: CommandSignature::none(),
3077+ },
3078+ TypableCommand {
3079+ name: "redraw",
3080+ aliases: &[],
3081+ doc: "Clear and re-render the whole UI",
3082+ fun: redraw,
3083+ signature: CommandSignature::none(),
3084+ },
3085+ TypableCommand {
3086+ name: "move",
3087+ aliases: &[],
3088+ doc: "Move the current buffer and its corresponding file to a different path",
3089+ fun: move_buffer,
3090+ signature: CommandSignature::positional(&[completers::filename]),
3091+ },
3092+];
28553093
28563094pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
28573095 Lazy::new(|| {
@@ -2870,28 +3108,18 @@ pub(super) fn command_mode(cx: &mut Context) {
28703108 ":".into(),
28713109 Some(':'),
28723110 |editor: &Editor, input: &str| {
2873− static FUZZY_MATCHER: Lazy<fuzzy_matcher::skim::SkimMatcherV2> =
2874− Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default);
2875−
28763111 let shellwords = Shellwords::from(input);
28773112 let words = shellwords.words();
28783113
28793114 if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) {
2880− // If the command has not been finished yet, complete commands.
2881− let mut matches: Vec<_> = typed::TYPABLE_COMMAND_LIST
2882− .iter()
2883− .filter_map(|command| {
2884− FUZZY_MATCHER
2885− .fuzzy_match(command.name, input)
2886− .map(|score| (command.name, score))
2887− })
2888− .collect();
2889−
2890− matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score));
2891− matches
2892− .into_iter()
2893− .map(|(name, _)| (0.., name.into()))
2894− .collect()
3115+ fuzzy_match(
3116+ input,
3117+ TYPABLE_COMMAND_LIST.iter().map(|command| command.name),
3118+ false,
3119+ )
3120+ .into_iter()
3121+ .map(|(name, _)| (0.., name.into()))
3122+ .collect()
28953123 } else {
28963124 // Otherwise, use the command's completer and the last shellword
28973125 // as completion input.
helix-term/src/compositor.rs+12
@@ -16,6 +16,7 @@ pub enum EventResult {
1616}
1717
1818use crate::job::Jobs;
19+use crate::ui::picker;
1920use helix_view::Editor;
2021
2122pub use helix_view::input::Event;
@@ -79,6 +80,7 @@ pub struct Compositor {
7980 area: Rect,
8081
8182 pub(crate) last_picker: Option<Box<dyn Component>>,
83+ pub(crate) full_redraw: bool,
8284}
8385
8486impl Compositor {
@@ -87,6 +89,7 @@ impl Compositor {
8789 layers: Vec::new(),
8890 area,
8991 last_picker: None,
92+ full_redraw: false,
9093 }
9194 }
9295
@@ -100,6 +103,11 @@ impl Compositor {
100103
101104 /// Add a layer to be rendered in front of all existing layers.
102105 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+ }
103111 let size = self.size();
104112 // trigger required_size on init
105113 layer.required_size((size.width, size.height));
@@ -200,6 +208,10 @@ impl Compositor {
200208 .find(|component| component.id() == Some(id))
201209 .and_then(|component| component.as_any_mut().downcast_mut())
202210 }
211+
212+ pub fn need_full_redraw(&mut self) {
213+ self.full_redraw = true;
214+ }
203215}
204216
205217// View casting, taken straight from Cursive
helix-term/src/config.rs+1
@@ -109,6 +109,7 @@ impl Config {
109109 )?,
110110 }
111111 }
112+
112113 // these are just two io errors return the one for the global config
113114 (Err(err), Err(_)) => return Err(err),
114115 };
helix-term/src/health.rs+44−16
@@ -181,8 +181,8 @@ pub fn languages_all() -> std::io::Result<()> {
181181 .language
182182 .sort_unstable_by_key(|l| l.language_id.clone());
183183
184− let check_binary = |cmd: Option<String>| match cmd {
185− Some(cmd) => match which::which(&cmd) {
184+ let check_binary = |cmd: Option<&str>| match cmd {
185+ Some(cmd) => match which::which(cmd) {
186186 Ok(_) => column(&format!("✓ {}", cmd), Color::Green),
187187 Err(_) => column(&format!("✘ {}", cmd), Color::Red),
188188 },
@@ -192,17 +192,15 @@ pub fn languages_all() -> std::io::Result<()> {
192192 for lang in &syn_loader_conf.language {
193193 column(&lang.language_id, Color::Reset);
194194
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| {
198196 syn_loader_conf
199197 .language_server
200198 .get(&ls.name)
201− .map(|config| config.command.clone())
199+ .map(|config| config.command.as_str())
202200 });
203− check_binary(lsp);
201+ check_binary(cmds.next());
204202
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());
206204 check_binary(dap);
207205
208206 for ts_feat in TsFeature::all() {
@@ -213,6 +211,12 @@ pub fn languages_all() -> std::io::Result<()> {
213211 }
214212
215213 writeln!(stdout)?;
214+
215+ for cmd in cmds {
216+ column("", Color::Reset);
217+ check_binary(Some(cmd));
218+ writeln!(stdout)?;
219+ }
216220 }
217221
218222 Ok(())
@@ -268,15 +272,12 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
268272 }
269273 };
270274
271− // TODO multiple language servers
272− probe_protocol(
275+ probe_protocols(
273276 "language server",
274− lang.language_servers.first().and_then(|ls| {
275− syn_loader_conf
276− .language_server
277− .get(&ls.name)
278− .map(|config| config.command.clone())
279− }),
277+ lang.language_servers
278+ .iter()
279+ .filter_map(|ls| syn_loader_conf.language_server.get(&ls.name))
280+ .map(|config| config.command.as_str()),
280281 )?;
281282
282283 probe_protocol(
@@ -291,6 +292,33 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
291292 Ok(())
292293}
293294
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+
294322/// Display diagnostics about LSP and DAP.
295323fn probe_protocol(protocol_name: &str, server_cmd: Option<String>) -> std::io::Result<()> {
296324 let stdout = std::io::stdout();
helix-term/src/keymap.rs+14−14
@@ -401,14 +401,14 @@ mod tests {
401401 fn merge_partial_keys() {
402402 let keymap = hashmap! {
403403 Mode::Normal => keymap!({ "Normal mode"
404− "i" => normal_mode,
405− "无" => insert_mode,
406− "z" => jump_backward,
407− "g" => { "Merge into goto mode"
408− "$" => goto_line_end,
409− "g" => delete_char_forward,
410− },
411− })
404+ "i" => normal_mode,
405+ "无" => insert_mode,
406+ "z" => jump_backward,
407+ "g" => { "Merge into goto mode"
408+ "$" => goto_line_end,
409+ "g" => delete_char_forward,
410+ },
411+ })
412412 };
413413 let mut merged_keyamp = default();
414414 merge_keys(&mut merged_keyamp, keymap.clone());
@@ -474,13 +474,13 @@ mod tests {
474474 fn order_should_be_set() {
475475 let keymap = hashmap! {
476476 Mode::Normal => keymap!({ "Normal mode"
477− "space" => { ""
478− "s" => { ""
479− "v" => vsplit,
480− "c" => hsplit,
481− },
477+ "space" => { ""
478+ "s" => { ""
479+ "v" => vsplit,
480+ "c" => hsplit,
482481 },
483− })
482+ },
483+ })
484484 };
485485 let mut merged_keyamp = default();
486486 merge_keys(&mut merged_keyamp, keymap.clone());
helix-term/src/keymap/default.rs+8−2
@@ -88,6 +88,8 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
8888 "A-i" | "A-down" => shrink_selection,
8989 "A-p" | "A-left" => select_prev_sibling,
9090 "A-n" | "A-right" => select_next_sibling,
91+ "A-e" => move_parent_node_end,
92+ "A-b" => move_parent_node_start,
9193
9294 "%" => select_all,
9395 "x" => extend_line_below,
@@ -265,7 +267,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
265267 "C-v" | "v" => vsplit_new,
266268 },
267269 },
268− "y" => yank_joined_to_clipboard,
270+ "y" => yank_to_clipboard,
269271 "Y" => yank_main_selection_to_clipboard,
270272 "p" => paste_clipboard_after,
271273 "P" => paste_clipboard_before,
@@ -336,6 +338,9 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
336338 "B" => extend_prev_long_word_start,
337339 "E" => extend_next_long_word_end,
338340
341+ "A-e" => extend_parent_node_end,
342+ "A-b" => extend_parent_node_start,
343+
339344 "n" => extend_search_next,
340345 "N" => extend_search_prev,
341346
@@ -368,7 +373,8 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
368373 "C-h" | "backspace" | "S-backspace" => delete_char_backward,
369374 "C-d" | "del" => delete_char_forward,
370375 "C-j" | "ret" => insert_newline,
371− "tab" => insert_tab,
376+ "tab" => smart_tab,
377+ "S-tab" => insert_tab,
372378
373379 "up" => move_visual_line_up,
374380 "down" => move_visual_line_down,
helix-term/src/main.rs+21−17
@@ -4,9 +4,8 @@ use helix_loader::VERSION_AND_GIT_HASH;
44use helix_term::application::Application;
55use helix_term::args::Args;
66use helix_term::config::{Config, ConfigLoadError};
7−use std::path::PathBuf;
87
9−fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
8+fn setup_logging(verbosity: u64) -> Result<()> {
109 let mut base_config = fern::Dispatch::new();
1110
1211 base_config = match verbosity {
@@ -27,7 +26,7 @@ fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
2726 message
2827 ))
2928 })
30− .chain(fern::log_file(logpath)?);
29+ .chain(fern::log_file(helix_loader::log_file())?);
3130
3231 base_config.chain(file_config).apply()?;
3332
@@ -41,12 +40,6 @@ fn main() -> Result<()> {
4140
4241#[tokio::main]
4342async 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−
5043 let help = format!(
5144 "\
5245{} {}
@@ -73,15 +66,20 @@ FLAGS:
7366 -V, --version Prints version information
7467 --vsplit Splits all given files vertically into different windows
7568 --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
7671",
7772 env!("CARGO_PKG_NAME"),
7873 VERSION_AND_GIT_HASH,
7974 env!("CARGO_PKG_AUTHORS"),
8075 env!("CARGO_PKG_DESCRIPTION"),
81− logpath.display(),
76+ helix_loader::default_log_file().display(),
8277 );
8378
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());
8583
8684 // Help has a higher priority and should be handled separately.
8785 if args.display_help {
@@ -116,15 +114,21 @@ FLAGS:
116114 return Ok(0);
117115 }
118116
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")?;
121118
122− let config_dir = helix_loader::config_dir();
123− if !config_dir.exists() {
124− std::fs::create_dir_all(&config_dir).ok();
119+ // Before setting the working directory, resolve all the paths in args.files
120+ for (path, _) in args.files.iter_mut() {
121+ *path = helix_core::path::get_canonicalized_path(path);
125122 }
126123
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+ }
128132
129133 let config = match Config::load_default() {
130134 Ok(config) => config,
helix-term/src/ui/completion.rs+32−4
@@ -2,6 +2,7 @@ use crate::compositor::{Component, Context, Event, EventResult};
22use helix_view::{
33 document::SavePoint,
44 editor::CompleteAction,
5+ graphics::Margin,
56 theme::{Modifier, Style},
67 ViewId,
78};
@@ -144,7 +145,9 @@ impl Completion {
144145 }
145146 };
146147
147− let Some(range) = util::lsp_range_to_range(doc.text(), edit.range, offset_encoding) else{
148+ let Some(range) =
149+ util::lsp_range_to_range(doc.text(), edit.range, offset_encoding)
150+ else {
148151 return Transaction::new(doc.text());
149152 };
150153
@@ -292,6 +295,8 @@ impl Completion {
292295 };
293296 // if more text was entered, remove it
294297 doc.restore(view, &savepoint, true);
298+ // save an undo checkpoint before the completion
299+ doc.append_changes_to_history(view);
295300 let transaction = item_to_transaction(
296301 doc,
297302 view.id,
@@ -322,9 +327,18 @@ impl Completion {
322327 }
323328 };
324329 });
330+
331+ let margin = if editor.menu_border() {
332+ Margin::vertical(1)
333+ } else {
334+ Margin::none()
335+ };
336+
325337 let popup = Popup::new(Self::ID, menu)
326338 .with_scrollbar(false)
327− .ignore_escape_key(true);
339+ .ignore_escape_key(true)
340+ .margin(margin);
341+
328342 let mut completion = Self {
329343 popup,
330344 start_offset,
@@ -411,10 +425,18 @@ impl Completion {
411425 _ => return false,
412426 };
413427
414− let Some(language_server) = cx.editor.language_server_by_id(current_item.language_server_id) else { return false; };
428+ let Some(language_server) = cx
429+ .editor
430+ .language_server_by_id(current_item.language_server_id)
431+ else {
432+ return false;
433+ };
415434
416435 // This method should not block the compositor so we handle the response asynchronously.
417− let Some(future) = language_server.resolve_completion_item(current_item.item.clone()) else { return false; };
436+ let Some(future) = language_server.resolve_completion_item(current_item.item.clone())
437+ else {
438+ return false;
439+ };
418440
419441 cx.callback(
420442 future,
@@ -557,6 +579,12 @@ impl Component for Completion {
557579 // clear area
558580 let background = cx.editor.theme.get("ui.popup");
559581 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+
560588 markdown_doc.render(doc_area, surface, cx);
561589 }
562590}
helix-term/src/ui/document.rs+60−24
@@ -97,7 +97,8 @@ pub fn render_document(
9797 doc: &Document,
9898 offset: ViewPosition,
9999 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>,
101102 theme: &Theme,
102103 line_decoration: &mut [Box<dyn LineDecoration + '_>],
103104 translated_positions: &mut [TranslatedPosition],
@@ -109,7 +110,8 @@ pub fn render_document(
109110 offset,
110111 &doc.text_format(viewport.width, Some(theme)),
111112 doc_annotations,
112− highlight_iter,
113+ syntax_highlight_iter,
114+ overlay_highlight_iter,
113115 theme,
114116 line_decoration,
115117 translated_positions,
@@ -157,7 +159,8 @@ pub fn render_text<'t>(
157159 offset: ViewPosition,
158160 text_fmt: &TextFormat,
159161 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>,
161164 theme: &Theme,
162165 line_decorations: &mut [Box<dyn LineDecoration + '_>],
163166 translated_positions: &mut [TranslatedPosition],
@@ -178,10 +181,16 @@ pub fn render_text<'t>(
178181
179182 let (mut formatter, mut first_visible_char_idx) =
180183 DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, offset.anchor);
181− let mut styles = StyleIter {
184+ let mut syntax_styles = StyleIter {
182185 text_style: renderer.text_style,
183186 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,
185194 theme,
186195 };
187196
@@ -193,7 +202,10 @@ pub fn render_text<'t>(
193202 };
194203 let mut is_in_indent_area = true;
195204 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
197209 .next()
198210 .unwrap_or_else(|| (Style::default(), usize::MAX));
199211
@@ -221,9 +233,16 @@ pub fn render_text<'t>(
221233
222234 // skip any graphemes on visual lines before the block start
223235 if pos.row < row_off {
224− if char_pos >= style_span.1 {
225− style_span = if let Some(style_span) = styles.next() {
226− style_span
236+ if char_pos >= syntax_style_span.1 {
237+ syntax_style_span = if let Some(syntax_style_span) = syntax_styles.next() {
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
227246 } else {
228247 break;
229248 }
@@ -260,8 +279,15 @@ pub fn render_text<'t>(
260279 }
261280
262281 // acquire the correct grapheme style
263− if char_pos >= style_span.1 {
264− style_span = styles.next().unwrap_or((Style::default(), usize::MAX));
282+ if char_pos >= syntax_style_span.1 {
283+ syntax_style_span = syntax_styles
284+ .next()
285+ .unwrap_or((Style::default(), usize::MAX));
286+ }
287+ if char_pos >= overlay_style_span.1 {
288+ overlay_style_span = overlay_styles
289+ .next()
290+ .unwrap_or((Style::default(), usize::MAX));
265291 }
266292 char_pos += grapheme.doc_chars();
267293
@@ -275,22 +301,25 @@ pub fn render_text<'t>(
275301 pos,
276302 );
277303
278− let grapheme_style = if let GraphemeSource::VirtualText { highlight } = grapheme.source {
279− let style = renderer.text_style;
280− if let Some(highlight) = highlight {
281− style.patch(theme.highlight(highlight.0))
304+ let (syntax_style, overlay_style) =
305+ if let GraphemeSource::VirtualText { highlight } = grapheme.source {
306+ let mut style = renderer.text_style;
307+ if let Some(highlight) = highlight {
308+ style = style.patch(theme.highlight(highlight.0))
309+ }
310+ (style, Style::default())
282311 } else {
283− style
284− }
285− } else {
286− style_span.0
287− };
312+ (syntax_style_span.0, overlay_style_span.0)
313+ };
288314
289− let virt = grapheme.is_virtual();
315+ let is_virtual = grapheme.is_virtual();
290316 renderer.draw_grapheme(
291317 grapheme.grapheme,
292− grapheme_style,
293− virt,
318+ GraphemeStyle {
319+ syntax_style,
320+ overlay_style,
321+ },
322+ is_virtual,
294323 &mut last_line_indent_level,
295324 &mut is_in_indent_area,
296325 pos,
@@ -322,6 +351,11 @@ pub struct TextRenderer<'a> {
322351 pub viewport: Rect,
323352}
324353
354+pub struct GraphemeStyle {
355+ syntax_style: Style,
356+ overlay_style: Style,
357+}
358+
325359impl<'a> TextRenderer<'a> {
326360 pub fn new(
327361 surface: &'a mut Surface,
@@ -395,7 +429,7 @@ impl<'a> TextRenderer<'a> {
395429 pub fn draw_grapheme(
396430 &mut self,
397431 grapheme: Grapheme,
398− mut style: Style,
432+ grapheme_style: GraphemeStyle,
399433 is_virtual: bool,
400434 last_indent_level: &mut usize,
401435 is_in_indent_area: &mut bool,
@@ -405,9 +439,11 @@ impl<'a> TextRenderer<'a> {
405439 let is_whitespace = grapheme.is_whitespace();
406440
407441 // TODO is it correct to apply the whitespace style to all unicode white spaces?
442+ let mut style = grapheme_style.syntax_style;
408443 if is_whitespace {
409444 style = style.patch(self.whitespace_style);
410445 }
446+ style = style.patch(grapheme_style.overlay_style);
411447
412448 let width = grapheme.width();
413449 let space = if is_virtual { " " } else { &self.space };
helix-term/src/ui/editor.rs+102−54
@@ -43,6 +43,8 @@ pub struct EditorView {
4343 pub(crate) last_insert: (commands::MappableCommand, Vec<InsertEvent>),
4444 pub(crate) completion: Option<Completion>,
4545 spinners: ProgressSpinners,
46+ /// Tracks if the terminal window is focused by reaction to terminal focus events
47+ terminal_focused: bool,
4648}
4749
4850#[derive(Debug, Clone)]
@@ -71,6 +73,7 @@ impl EditorView {
7173 last_insert: (commands::MappableCommand::normal_mode, Vec::new()),
7274 completion: None,
7375 spinners: ProgressSpinners::default(),
76+ terminal_focused: true,
7477 }
7578 }
7679
@@ -121,16 +124,20 @@ impl EditorView {
121124 line_decorations.push(Box::new(line_decoration));
122125 }
123126
124− let mut highlights =
127+ let syntax_highlights =
125128 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(
127133 doc,
128134 view.offset.anchor,
129135 inner.height,
130136 &text_annotations,
131137 );
132− if !overlay_highlights.is_empty() {
133− highlights = Box::new(syntax::merge(highlights, overlay_highlights));
138+ if !overlay_syntax_highlights.is_empty() {
139+ overlay_highlights =
140+ Box::new(syntax::merge(overlay_highlights, overlay_syntax_highlights));
134141 }
135142
136143 for diagnostic in Self::doc_diagnostics_highlights(doc, theme) {
@@ -139,39 +146,41 @@ impl EditorView {
139146 if diagnostic.is_empty() {
140147 continue;
141148 }
142− highlights = Box::new(syntax::merge(highlights, diagnostic));
149+ overlay_highlights = Box::new(syntax::merge(overlay_highlights, diagnostic));
143150 }
144151
145− let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
152+ if is_focused {
146153 let highlights = syntax::merge(
147− highlights,
154+ overlay_highlights,
148155 Self::doc_selection_highlights(
149156 editor.mode(),
150157 doc,
151158 view,
152159 theme,
153160 &config.cursor_shape,
161+ self.terminal_focused,
154162 ),
155163 );
156164 let focused_view_elements = Self::highlight_focused_view_elements(view, doc, theme);
157165 if focused_view_elements.is_empty() {
158− Box::new(highlights)
166+ overlay_highlights = Box::new(highlights)
159167 } else {
160− Box::new(syntax::merge(highlights, focused_view_elements))
168+ overlay_highlights = Box::new(syntax::merge(highlights, focused_view_elements))
161169 }
162− } else {
163− Box::new(highlights)
164− };
170+ }
165171
166− Self::render_gutter(
167− editor,
168− doc,
169− view,
170− view.area,
171− theme,
172− is_focused,
173− &mut line_decorations,
174− );
172+ let gutter_overflow = view.gutter_offset(doc) == 0;
173+ if !gutter_overflow {
174+ Self::render_gutter(
175+ editor,
176+ doc,
177+ view,
178+ view.area,
179+ theme,
180+ is_focused & self.terminal_focused,
181+ &mut line_decorations,
182+ );
183+ }
175184
176185 if is_focused {
177186 let cursor = doc
@@ -191,7 +200,8 @@ impl EditorView {
191200 doc,
192201 view.offset,
193202 &text_annotations,
194− highlights,
203+ syntax_highlights,
204+ overlay_highlights,
195205 theme,
196206 &mut line_decorations,
197207 &mut translated_positions,
@@ -251,27 +261,39 @@ impl EditorView {
251261 .for_each(|area| surface.set_style(area, ruler_theme))
252262 }
253263
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(
255280 doc: &Document,
256281 anchor: usize,
257282 height: u16,
258− text_annotations: &TextAnnotations,
259− ) -> Vec<(usize, std::ops::Range<usize>)> {
283+ ) -> Box<dyn Iterator<Item = HighlightEvent>> {
260284 let text = doc.text().slice(..);
261285 let row = text.char_to_line(anchor.min(text.len_chars()));
262286
263− let range = {
264− // Calculate viewport byte ranges:
265− // Saturating subs to make it inclusive zero indexing.
266− let last_line = text.len_lines().saturating_sub(1);
267− let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
268− let start = text.line_to_byte(row.min(last_line));
269− let end = text.line_to_byte(last_visible_line + 1);
270−
271− start..end
272− };
273−
274− text_annotations.collect_overlay_highlights(range)
287+ // Calculate viewport byte ranges:
288+ // Saturating subs to make it inclusive zero indexing.
289+ let range = Self::viewport_byte_range(text, row, height);
290+ Box::new(
291+ [HighlightEvent::Source {
292+ start: text.byte_to_char(range.start),
293+ end: text.byte_to_char(range.end),
294+ }]
295+ .into_iter(),
296+ )
275297 }
276298
277299 /// Get syntax highlights for a document in a view represented by the first line
@@ -286,16 +308,7 @@ impl EditorView {
286308 let text = doc.text().slice(..);
287309 let row = text.char_to_line(anchor.min(text.len_chars()));
288310
289− let range = {
290− // Calculate viewport byte ranges:
291− // Saturating subs to make it inclusive zero indexing.
292− let last_line = text.len_lines().saturating_sub(1);
293− let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
294− let start = text.line_to_byte(row.min(last_line));
295− let end = text.line_to_byte(last_visible_line + 1);
296−
297− start..end
298− };
311+ let range = Self::viewport_byte_range(text, row, height);
299312
300313 match doc.syntax() {
301314 Some(syntax) => {
@@ -328,6 +341,20 @@ impl EditorView {
328341 }
329342 }
330343
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+
331358 /// Get highlight spans for document diagnostics
332359 pub fn doc_diagnostics_highlights(
333360 doc: &Document,
@@ -359,7 +386,7 @@ impl EditorView {
359386 let mut warning_vec = Vec::new();
360387 let mut error_vec = Vec::new();
361388
362− for diagnostic in doc.diagnostics() {
389+ for diagnostic in doc.shown_diagnostics() {
363390 // Separate diagnostics into different Vecs by severity.
364391 let (vec, scope) = match diagnostic.severity {
365392 Some(Severity::Info) => (&mut info_vec, info),
@@ -394,6 +421,7 @@ impl EditorView {
394421 view: &View,
395422 theme: &Theme,
396423 cursor_shape_config: &CursorShapeConfig,
424+ is_terminal_focused: bool,
397425 ) -> Vec<(usize, std::ops::Range<usize>)> {
398426 let text = doc.text().slice(..);
399427 let selection = doc.selection(view.id);
@@ -441,7 +469,7 @@ impl EditorView {
441469
442470 // Special-case: cursor at end of the rope.
443471 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) {
445473 // Bar and underline cursors are drawn by the terminal
446474 // BUG: If the editor area loses focus while having a bar or
447475 // underline cursor (eg. when a regex prompt has focus) then
@@ -464,13 +492,17 @@ impl EditorView {
464492 cursor_start
465493 };
466494 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) {
468498 spans.push((cursor_scope, cursor_start..range.head));
469499 }
470500 } else {
471501 // Reverse case.
472502 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) {
474506 spans.push((cursor_scope, range.head..cursor_end));
475507 }
476508 // non block cursors look like they exclude the cursor
@@ -1067,6 +1099,7 @@ impl EditorView {
10671099 let editor = &mut cxt.editor;
10681100
10691101 if let Some((pos, view_id)) = pos_and_view(editor, row, column, true) {
1102+ let prev_view_id = view!(editor).id;
10701103 let doc = doc_mut!(editor, &view!(editor, view_id).doc);
10711104
10721105 if modifiers == KeyModifiers::ALT {
@@ -1076,6 +1109,10 @@ impl EditorView {
10761109 doc.set_selection(view_id, Selection::point(pos));
10771110 }
10781111
1112+ if view_id != prev_view_id {
1113+ self.clear_completion(editor);
1114+ }
1115+
10791116 editor.focus(view_id);
10801117 editor.ensure_cursor_in_view(view_id);
10811118
@@ -1369,13 +1406,17 @@ impl Component for EditorView {
13691406
13701407 Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
13711408 Event::IdleTimeout => self.handle_idle_timeout(&mut cx),
1372− Event::FocusGained => EventResult::Ignored(None),
1409+ Event::FocusGained => {
1410+ self.terminal_focused = true;
1411+ EventResult::Consumed(None)
1412+ }
13731413 Event::FocusLost => {
13741414 if context.editor.config().auto_save {
13751415 if let Err(e) = commands::typed::write_all_impl(context, false, false) {
13761416 context.editor.set_error(format!("{}", e));
13771417 }
13781418 }
1419+ self.terminal_focused = false;
13791420 EventResult::Consumed(None)
13801421 }
13811422 }
@@ -1485,8 +1526,15 @@ impl Component for EditorView {
14851526
14861527 fn cursor(&self, _area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
14871528 match editor.cursor() {
1488− // All block cursors are drawn manually
1489− (pos, CursorKind::Block) => (pos, CursorKind::Hidden),
1529+ // all block cursors are drawn manually
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+ }
14901538 cursor => cursor,
14911539 }
14921540 }
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 {
6262 });
6363
6464 let sig_text = crate::ui::markdown::highlighted_code_block(
65− self.signature.clone(),
65+ &self.signature,
6666 &self.language,
6767 Some(&cx.editor.theme),
6868 Arc::clone(&self.config_loader),
@@ -92,7 +92,9 @@ impl Component for SignatureHelp {
9292 Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
9393 };
9494 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()));
9698 let sig_doc_para = Paragraph::new(sig_doc)
9799 .wrap(Wrap { trim: false })
98100 .scroll((cx.scroll.unwrap_or_default() as u16, 0));
@@ -109,7 +111,7 @@ impl Component for SignatureHelp {
109111 let max_text_width = (viewport.0 - PADDING).min(120);
110112
111113 let signature_text = crate::ui::markdown::highlighted_code_block(
112− self.signature.clone(),
114+ &self.signature,
113115 &self.language,
114116 None,
115117 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};
1010
1111use helix_core::{
1212 syntax::{self, HighlightEvent, InjectionLanguageMarker, Syntax},
13− Rope,
13+ RopeSlice,
1414};
1515use helix_view::{
1616 graphics::{Margin, Rect, Style},
17+ theme::Modifier,
1718 Theme,
1819};
1920
20−fn styled_multiline_text<'a>(text: String, style: Style) -> Text<'a> {
21+fn styled_multiline_text<'a>(text: &str, style: Style) -> Text<'a> {
2122 let spans: Vec<_> = text
2223 .lines()
2324 .map(|line| Span::styled(line.to_string(), style))
@@ -27,7 +28,7 @@ fn styled_multiline_text<'a>(text: String, style: Style) -> Text<'a> {
2728}
2829
2930pub fn highlighted_code_block<'a>(
30− text: String,
31+ text: &str,
3132 language: &str,
3233 theme: Option<&Theme>,
3334 config_loader: Arc<syntax::Loader>,
@@ -45,13 +46,13 @@ pub fn highlighted_code_block<'a>(
4546 None => return styled_multiline_text(text, code_style),
4647 };
4748
48− let rope = Rope::from(text.as_ref());
49+ let ropeslice = RopeSlice::from(text);
4950 let syntax = config_loader
5051 .language_configuration_for_injection_string(&InjectionLanguageMarker::Name(
5152 language.into(),
5253 ))
5354 .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)));
5556
5657 let syntax = match syntax {
5758 Some(s) => s,
@@ -59,7 +60,7 @@ pub fn highlighted_code_block<'a>(
5960 };
6061
6162 let highlight_iter = syntax
62− .highlight_iter(rope.slice(..), None, None)
63+ .highlight_iter(ropeslice, None, None)
6364 .map(|e| e.unwrap());
6465 let highlight_iter: Box<dyn Iterator<Item = HighlightEvent>> =
6566 if let Some(spans) = additional_highlight_spans {
@@ -183,7 +184,9 @@ impl Markdown {
183184 // Transform text in `<code>` blocks into `Event::Code`
184185 let mut in_code = false;
185186 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+ {
187190 in_code = true;
188191 None
189192 }
@@ -267,7 +270,7 @@ impl Markdown {
267270 CodeBlockKind::Indented => "",
268271 };
269272 let tui_text = highlighted_code_block(
270− text.to_string(),
273+ &text,
271274 language,
272275 theme,
273276 Arc::clone(&self.config_loader),
@@ -275,17 +278,21 @@ impl Markdown {
275278 );
276279 lines.extend(tui_text.lines.into_iter());
277280 } else {
278− let style = if let Some(Tag::Heading(level, ..)) = tags.last() {
279− match level {
281+ let style = match tags.last() {
282+ Some(Tag::Heading(level, ..)) => match level {
280283 HeadingLevel::H1 => heading_styles[0],
281284 HeadingLevel::H2 => heading_styles[1],
282285 HeadingLevel::H3 => heading_styles[2],
283286 HeadingLevel::H4 => heading_styles[3],
284287 HeadingLevel::H5 => heading_styles[4],
285288 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)
286294 }
287− } else {
288− text_style
295+ _ => text_style,
289296 };
290297 spans.push(Span::styled(text, style));
291298 }
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};
22
33use crate::{
44 compositor::{Callback, Component, Compositor, Context, Event, EventResult},
55 ctrl, key, shift,
66};
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+};
814
915pub use tui::widgets::{Cell, Row};
1016
11−use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
12−use fuzzy_matcher::FuzzyMatcher;
13−
14−use helix_view::{graphics::Rect, Editor};
17+use helix_view::{
18+ editor::SmartTabConfig,
19+ graphics::{Margin, Rect},
20+ Editor,
21+};
1522use tui::layout::Constraint;
1623
17−pub trait Item {
24+pub trait Item: Sync + Send + 'static {
1825 /// Additional editor state that is used for label calculation.
19− type Data;
26+ type Data: Sync + Send + 'static;
2027
2128 fn format(&self, data: &Self::Data) -> Row;
2229
@@ -51,9 +58,8 @@ pub struct Menu<T: Item> {
5158
5259 cursor: Option<usize>,
5360
54− matcher: Box<Matcher>,
5561 /// (index, score)
56− matches: Vec<(usize, i64)>,
62+ matches: Vec<(u32, u32)>,
5763
5864 widths: Vec<Constraint>,
5965
@@ -75,11 +81,10 @@ impl<T: Item> Menu<T> {
7581 editor_data: <T as Item>::Data,
7682 callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
7783 ) -> 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();
7985 Self {
8086 options,
8187 editor_data,
82− matcher: Box::new(Matcher::default().ignore_case()),
8388 matches,
8489 cursor: None,
8590 widths: Vec::new(),
@@ -94,20 +99,19 @@ impl<T: Item> Menu<T> {
9499 pub fn score(&mut self, pattern: &str) {
95100 // reuse the matches allocation
96101 self.matches.clear();
97− self.matches.extend(
98− self.options
99− .iter()
100− .enumerate()
101− .filter_map(|(index, option)| {
102− let text = option.filter_text(&self.editor_data);
103− // TODO: using fuzzy_indices could give us the char idx for match highlighting
104− self.matcher
105− .fuzzy_match(&text, pattern)
106− .map(|score| (index, score))
107− }),
108− );
109− // Order of equal elements needs to be preserved as LSP preselected items come in order of high to low priority
110− self.matches.sort_by_key(|(_, score)| -score);
102+ let mut matcher = MATCHER.lock();
103+ matcher.config = Config::DEFAULT;
104+ let pattern = Atom::new(pattern, CaseMatching::Ignore, AtomKind::Fuzzy, false);
105+ let mut buf = Vec::new();
106+ let matches = self.options.iter().enumerate().filter_map(|(i, option)| {
107+ let text = option.filter_text(&self.editor_data);
108+ pattern
109+ .score(Utf32Str::new(&text, &mut buf), &mut matcher)
110+ .map(|score| (i as u32, score as u32))
111+ });
112+ self.matches.extend(matches);
113+ self.matches
114+ .sort_unstable_by_key(|&(i, score)| (Reverse(score), i));
111115
112116 // reset cursor position
113117 self.cursor = None;
@@ -201,7 +205,7 @@ impl<T: Item> Menu<T> {
201205 self.cursor.and_then(|cursor| {
202206 self.matches
203207 .get(cursor)
204− .map(|(index, _score)| &self.options[*index])
208+ .map(|(index, _score)| &self.options[*index as usize])
205209 })
206210 }
207211
@@ -209,7 +213,7 @@ impl<T: Item> Menu<T> {
209213 self.cursor.and_then(|cursor| {
210214 self.matches
211215 .get(cursor)
212− .map(|(index, _score)| &mut self.options[*index])
216+ .map(|(index, _score)| &mut self.options[*index as usize])
213217 })
214218 }
215219
@@ -247,6 +251,21 @@ impl<T: Item + 'static> Component for Menu<T> {
247251 compositor.pop();
248252 }));
249253
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+
250269 match event {
251270 // esc or ctrl-c aborts the completion and closes the menu
252271 key!(Esc) | ctrl!('c') => {
@@ -310,6 +329,15 @@ impl<T: Item + 'static> Component for Menu<T> {
310329 let selected = theme.get("ui.menu.selected");
311330 surface.clear_with(area, style);
312331
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+
313341 let scroll = self.scroll;
314342
315343 let options: Vec<_> = self
@@ -317,7 +345,7 @@ impl<T: Item + 'static> Component for Menu<T> {
317345 .iter()
318346 .map(|(index, _score)| {
319347 // (index, self.options.get(*index).unwrap()) // get_unchecked
320− &self.options[*index] // get_unchecked
348+ &self.options[*index as usize] // get_unchecked
321349 })
322350 .collect();
323351
@@ -350,15 +378,19 @@ impl<T: Item + 'static> Component for Menu<T> {
350378 false,
351379 );
352380
353− if let Some(cursor) = self.cursor {
354− let offset_from_top = cursor - scroll;
355− let left = &mut surface[(area.left(), area.y + offset_from_top as u16)];
356− left.set_style(selected);
357− let right = &mut surface[(
358− area.right().saturating_sub(1),
359− area.y + offset_from_top as u16,
360− )];
361− right.set_style(selected);
381+ let render_borders = cx.editor.menu_border();
382+
383+ if !render_borders {
384+ if let Some(cursor) = self.cursor {
385+ let offset_from_top = cursor - scroll;
386+ let left = &mut surface[(area.left(), area.y + offset_from_top as u16)];
387+ left.set_style(selected);
388+ let right = &mut surface[(
389+ area.right().saturating_sub(1),
390+ area.y + offset_from_top as u16,
391+ )];
392+ right.set_style(selected);
393+ }
362394 }
363395
364396 let fits = len <= win_height;
@@ -373,12 +405,13 @@ impl<T: Item + 'static> Component for Menu<T> {
373405 for i in 0..win_height {
374406 cell = &mut surface[(area.right() - 1, area.top() + i as u16)];
375407
376− cell.set_symbol("▐"); // right half block
408+ let half_block = if render_borders { "▌" } else { "▐" };
377409
378410 if scroll_line <= i && i < scroll_line + scroll_height {
379411 // Draw scroll thumb
412+ cell.set_symbol(half_block);
380413 cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
381− } else {
414+ } else if !render_borders {
382415 // Draw scroll track
383416 cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
384417 }
helix-term/src/ui/mod.rs+91−150
@@ -1,13 +1,12 @@
11mod completion;
22mod document;
33pub(crate) mod editor;
4−mod fuzzy_match;
54mod info;
65pub mod lsp;
76mod markdown;
87pub mod menu;
98pub mod overlay;
10−mod picker;
9+pub mod picker;
1110pub mod popup;
1211mod prompt;
1312mod spinner;
@@ -64,7 +63,7 @@ pub fn regex_prompt(
6463 prompt: std::borrow::Cow<'static, str>,
6564 history_register: Option<char>,
6665 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,
6867) {
6968 let (view, doc) = current!(cx.editor);
7069 let doc_id = view.doc;
@@ -111,7 +110,7 @@ pub fn regex_prompt(
111110 view.jumps.push((doc_id, snapshot.clone()));
112111 }
113112
114− fun(cx.editor, regex, event);
113+ fun(cx, regex, event);
115114
116115 let (view, doc) = current!(cx.editor);
117116 view.ensure_cursor_in_view(doc, config.scrolloff);
@@ -142,16 +141,14 @@ pub fn regex_prompt(
142141 };
143142
144143 cx.jobs.callback(callback);
145− } else {
146− // Update
147− // TODO: mark command line as error
148144 }
149145 }
150146 }
151147 }
152148 }
153149 },
154− );
150+ )
151+ .with_language("regex", std::sync::Arc::clone(&cx.editor.syn_loader));
155152 // Calculate initial completion
156153 prompt.recalculate_completion(cx.editor);
157154 // prompt
@@ -176,9 +173,13 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
176173 .git_ignore(config.file_picker.git_ignore)
177174 .git_global(config.file_picker.git_global)
178175 .git_exclude(config.file_picker.git_exclude)
176+ .sort_by_file_name(|name1, name2| name1.cmp(name2))
179177 .max_depth(config.file_picker.max_depth)
180178 .filter_entry(move |entry| filter_picker_entry(entry, &absolute_root, dedup_symlinks));
181179
180+ walk_builder.add_custom_ignore_filename(helix_loader::config_dir().join("ignore"));
181+ walk_builder.add_custom_ignore_filename(".helix/ignore");
182+
182183 // We want to exclude files that the editor can't handle yet
183184 let mut type_builder = TypesBuilder::new();
184185 type_builder
@@ -192,32 +193,16 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
192193 .build()
193194 .expect("failed to build excluded_types");
194195 walk_builder.types(excluded_types);
195−
196− // We want files along with their modification date for sorting
197− let files = walk_builder.build().filter_map(|entry| {
196+ let mut files = walk_builder.build().filter_map(|entry| {
198197 let entry = entry.ok()?;
199− // This is faster than entry.path().is_dir() since it uses cached fs::Metadata fetched by ignore/walkdir
200− if entry.file_type()?.is_file() {
201− Some(entry.into_path())
202− } else {
203− None
198+ if !entry.file_type()?.is_file() {
199+ return None;
204200 }
201+ Some(entry.into_path())
205202 });
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−
218203 log::debug!("file_picker init {:?}", Instant::now().duration_since(now));
219204
220− Picker::new(files, root, move |cx, path: &PathBuf, action| {
205+ let picker = Picker::new(Vec::new(), root, move |cx, path: &PathBuf, action| {
221206 if let Err(e) = cx.editor.open(path, action) {
222207 let err = if let Some(err) = e.source() {
223208 format!("{}", err)
@@ -227,20 +212,41 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
227212 cx.editor.set_error(err);
228213 }
229214 })
230− .with_preview(|_editor, path| Some((path.clone().into(), None)))
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+ }
235+ }
236+ });
237+ }
238+ picker
231239}
232240
233241pub mod completers {
234242 use crate::ui::prompt::Completion;
235− use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
236− use fuzzy_matcher::FuzzyMatcher;
243+ use helix_core::fuzzy::fuzzy_match;
237244 use helix_core::syntax::LanguageServerFeature;
238245 use helix_view::document::SCRATCH_BUFFER_NAME;
239246 use helix_view::theme;
240247 use helix_view::{editor::Config, Editor};
241248 use once_cell::sync::Lazy;
242249 use std::borrow::Cow;
243− use std::cmp::Reverse;
244250
245251 pub type Completer = fn(&Editor, &str) -> Vec<Completion>;
246252
@@ -249,31 +255,16 @@ pub mod completers {
249255 }
250256
251257 pub fn buffer(editor: &Editor, input: &str) -> Vec<Completion> {
252− let mut names: Vec<_> = editor
253− .documents
254− .values()
255− .map(|doc| {
256− let name = doc
257− .relative_path()
258− .map(|p| p.display().to_string())
259− .unwrap_or_else(|| String::from(SCRATCH_BUFFER_NAME));
260− ((0..), Cow::from(name))
261− })
262− .collect();
263−
264− let matcher = Matcher::default();
265−
266− let mut matches: Vec<_> = names
267− .into_iter()
268− .filter_map(|(_range, name)| {
269− matcher.fuzzy_match(&name, input).map(|score| (name, score))
270− })
271− .collect();
272−
273− matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
274− names = matches.into_iter().map(|(name, _)| ((0..), name)).collect();
258+ let names = editor.documents.values().map(|doc| {
259+ doc.relative_path()
260+ .map(|p| p.display().to_string().into())
261+ .unwrap_or_else(|| Cow::from(SCRATCH_BUFFER_NAME))
262+ });
275263
276− names
264+ fuzzy_match(input, names, true)
265+ .into_iter()
266+ .map(|(name, _)| ((0..), name))
267+ .collect()
277268 }
278269
279270 pub fn theme(_editor: &Editor, input: &str) -> Vec<Completion> {
@@ -286,26 +277,10 @@ pub mod completers {
286277 names.sort();
287278 names.dedup();
288279
289− let mut names: Vec<_> = names
280+ fuzzy_match(input, names, false)
290281 .into_iter()
291− .map(|name| ((0..), Cow::from(name)))
292− .collect();
293−
294− let matcher = Matcher::default();
295−
296− let mut matches: Vec<_> = names
297− .into_iter()
298− .filter_map(|(_range, name)| {
299− matcher.fuzzy_match(&name, input).map(|score| (name, score))
300− })
301− .collect();
302−
303− matches.sort_unstable_by(|(name1, score1), (name2, score2)| {
304− (Reverse(*score1), name1).cmp(&(Reverse(*score2), name2))
305− });
306− names = matches.into_iter().map(|(name, _)| ((0..), name)).collect();
307−
308− names
282+ .map(|(name, _)| ((0..), name.into()))
283+ .collect()
309284 }
310285
311286 /// Recursive function to get all keys from this value and add them to vec
@@ -332,22 +307,22 @@ pub mod completers {
332307 keys
333308 });
334309
335− let matcher = Matcher::default();
336−
337− let mut matches: Vec<_> = KEYS
338− .iter()
339− .filter_map(|name| matcher.fuzzy_match(name, input).map(|score| (name, score)))
340− .collect();
341−
342− matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
343− matches
310+ fuzzy_match(input, &*KEYS, false)
344311 .into_iter()
345312 .map(|(name, _)| ((0..), name.into()))
346313 .collect()
347314 }
348315
349316 pub fn filename(editor: &Editor, input: &str) -> Vec<Completion> {
350− 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| {
351326 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
352327
353328 if is_dir {
@@ -359,8 +334,6 @@ pub mod completers {
359334 }
360335
361336 pub fn language(editor: &Editor, input: &str) -> Vec<Completion> {
362− let matcher = Matcher::default();
363−
364337 let text: String = "text".into();
365338
366339 let language_ids = editor
@@ -369,27 +342,13 @@ pub mod completers {
369342 .map(|config| &config.language_id)
370343 .chain(std::iter::once(&text));
371344
372− let mut matches: Vec<_> = language_ids
373− .filter_map(|language_id| {
374− matcher
375− .fuzzy_match(language_id, input)
376− .map(|score| (language_id, score))
377− })
378− .collect();
379−
380− matches.sort_unstable_by(|(language1, score1), (language2, score2)| {
381− (Reverse(*score1), language1).cmp(&(Reverse(*score2), language2))
382− });
383−
384− matches
345+ fuzzy_match(input, language_ids, false)
385346 .into_iter()
386− .map(|(language, _score)| ((0..), language.clone().into()))
347+ .map(|(name, _)| ((0..), name.to_owned().into()))
387348 .collect()
388349 }
389350
390351 pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
391− let matcher = Matcher::default();
392−
393352 let Some(options) = doc!(editor)
394353 .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
395354 .find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
@@ -397,28 +356,22 @@ pub mod completers {
397356 return vec![];
398357 };
399358
400− let mut matches: Vec<_> = options
401− .commands
402− .iter()
403− .filter_map(|command| {
404− matcher
405− .fuzzy_match(command, input)
406− .map(|score| (command, score))
407− })
408− .collect();
409−
410− matches.sort_unstable_by(|(command1, score1), (command2, score2)| {
411− (Reverse(*score1), command1).cmp(&(Reverse(*score2), command2))
412− });
413−
414− matches
359+ fuzzy_match(input, &options.commands, false)
415360 .into_iter()
416− .map(|(command, _score)| ((0..), command.clone().into()))
361+ .map(|(name, _)| ((0..), name.to_owned().into()))
417362 .collect()
418363 }
419364
420365 pub fn directory(editor: &Editor, input: &str) -> Vec<Completion> {
421− 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| {
422375 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
423376
424377 if is_dir {
@@ -441,7 +394,12 @@ pub mod completers {
441394 }
442395
443396 // TODO: we could return an iter/lazy thing so it can fetch as many as it needs.
444− 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>
445403 where
446404 F: Fn(&ignore::DirEntry) -> FileMatch,
447405 {
@@ -472,7 +430,7 @@ pub mod completers {
472430 match path.parent() {
473431 Some(path) if !path.as_os_str().is_empty() => path.to_path_buf(),
474432 // Path::new("h")'s parent is Some("")...
475− _ => std::env::current_dir().expect("couldn't determine current directory"),
433+ _ => helix_loader::current_working_dir(),
476434 }
477435 };
478436
@@ -481,9 +439,10 @@ pub mod completers {
481439
482440 let end = input.len()..;
483441
484− let mut files: Vec<_> = WalkBuilder::new(&dir)
442+ let files = WalkBuilder::new(&dir)
485443 .hidden(false)
486444 .follow_links(false) // We're scanning over depth 1
445+ .git_ignore(git_ignore)
487446 .max_depth(Some(1))
488447 .build()
489448 .filter_map(|file| {
@@ -512,43 +471,25 @@ pub mod completers {
512471 path.push("");
513472 }
514473
515− let path = path.to_str()?.to_owned();
516− Some((end.clone(), Cow::from(path)))
474+ let path = path.into_os_string().into_string().ok()?;
475+ Some(Cow::from(path))
517476 })
518477 }) // TODO: unwrap or skip
519− .filter(|(_, path)| !path.is_empty()) // TODO
520− .collect();
478+ .filter(|path| !path.is_empty());
521479
522480 // if empty, return a list of dirs and files in current dir
523481 if let Some(file_name) = file_name {
524− let matcher = Matcher::default();
525−
526− // inefficient, but we need to calculate the scores, filter out None, then sort.
527− let mut matches: Vec<_> = files
528− .into_iter()
529− .filter_map(|(_range, file)| {
530− matcher
531− .fuzzy_match(&file, &file_name)
532− .map(|score| (file, score))
533− })
534− .collect();
535−
536482 let range = (input.len().saturating_sub(file_name.len()))..;
537−
538− matches.sort_unstable_by(|(file1, score1), (file2, score2)| {
539− (Reverse(*score1), file1).cmp(&(Reverse(*score2), file2))
540− });
541−
542− files = matches
483+ fuzzy_match(&file_name, files, true)
543484 .into_iter()
544− .map(|(file, _)| (range.clone(), file))
545− .collect();
485+ .map(|(name, _)| (range.clone(), name))
486+ .collect()
546487
547488 // TODO: complete to longest common match
548489 } else {
490+ let mut files: Vec<_> = files.map(|file| (end.clone(), file)).collect();
549491 files.sort_unstable_by(|(_, path1), (_, path2)| path1.cmp(path2));
492+ files
550493 }
551−
552− files
553494 }
554495}
helix-term/src/ui/picker.rs+362−325
@@ -7,11 +7,12 @@ use crate::{
77 ui::{
88 self,
99 document::{render_document, LineDecoration, LinePos, TextRenderer},
10− fuzzy_match::FuzzyQuery,
1110 EditorView,
1211 },
1312};
1413use futures_util::{future::BoxFuture, FutureExt};
14+use nucleo::pattern::CaseMatching;
15+use nucleo::{Config, Nucleo, Utf32String};
1516use tui::{
1617 buffer::Buffer as Surface,
1718 layout::Constraint,
@@ -19,16 +20,23 @@ use tui::{
1920 widgets::{Block, BorderType, Borders, Cell, Table},
2021};
2122
22−use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
2323use tui::widgets::Widget;
2424
25−use std::cmp::{self, Ordering};
26−use std::{collections::HashMap, io::Read, path::PathBuf};
25+use std::{
26+ collections::HashMap,
27+ io::Read,
28+ path::PathBuf,
29+ sync::{
30+ atomic::{self, AtomicBool},
31+ Arc,
32+ },
33+};
2734
2835use crate::ui::{Prompt, PromptEvent};
2936use helix_core::{
30− movement::Direction, text_annotations::TextAnnotations,
31− unicode::segmentation::UnicodeSegmentation, Position, Syntax,
37+ char_idx_at_visual_offset, fuzzy::MATCHER, movement::Direction,
38+ text_annotations::TextAnnotations, unicode::segmentation::UnicodeSegmentation, Position,
39+ Syntax,
3240};
3341use helix_view::{
3442 editor::Action,
@@ -38,6 +46,7 @@ use helix_view::{
3846 Document, DocumentId, Editor,
3947};
4048
49+pub const ID: &str = "picker";
4150use super::{menu::Item, overlay::Overlay};
4251
4352pub const MIN_AREA_WIDTH_FOR_PREVIEW: u16 = 72;
@@ -51,12 +60,12 @@ pub enum PathOrId {
5160}
5261
5362impl PathOrId {
54− fn get_canonicalized(self) -> std::io::Result<Self> {
63+ fn get_canonicalized(self) -> Self {
5564 use PathOrId::*;
56− Ok(match self {
57− Path(path) => Path(helix_core::path::get_canonicalized_path(&path)?),
65+ match self {
66+ Path(path) => Path(helix_core::path::get_canonicalized_path(&path)),
5867 Id(id) => Id(id),
59− })
68+ }
6069 }
6170}
6271
@@ -103,9 +112,9 @@ impl Preview<'_, '_> {
103112 /// Alternate text to show for the preview.
104113 fn placeholder(&self) -> &str {
105114 match *self {
106− Self::EditorDocument(_) => "<File preview>",
115+ Self::EditorDocument(_) => "<Invalid file location>",
107116 Self::Cached(preview) => match preview {
108− CachedPreview::Document(_) => "<File preview>",
117+ CachedPreview::Document(_) => "<Invalid file location>",
109118 CachedPreview::Binary => "<Binary file>",
110119 CachedPreview::LargeFile => "<File too large to preview>",
111120 CachedPreview::NotFound => "<File not found>",
@@ -114,20 +123,71 @@ impl Preview<'_, '_> {
114123 }
115124}
116125
126+fn item_to_nucleo<T: Item>(item: T, editor_data: &T::Data) -> Option<(T, Utf32String)> {
127+ let row = item.format(editor_data);
128+ let mut cells = row.cells.iter();
129+ let mut text = String::with_capacity(row.cell_text().map(|cell| cell.len()).sum());
130+ let cell = cells.next()?;
131+ if let Some(cell) = cell.content.lines.first() {
132+ for span in &cell.0 {
133+ text.push_str(&span.content);
134+ }
135+ }
136+
137+ for cell in cells {
138+ text.push(' ');
139+ if let Some(cell) = cell.content.lines.first() {
140+ for span in &cell.0 {
141+ text.push_str(&span.content);
142+ }
143+ }
144+ }
145+ Some((item, text.into()))
146+}
147+
148+pub struct Injector<T: Item> {
149+ dst: nucleo::Injector<T>,
150+ editor_data: Arc<T::Data>,
151+ shutown: Arc<AtomicBool>,
152+}
153+
154+impl<T: Item> Clone for Injector<T> {
155+ fn clone(&self) -> Self {
156+ Injector {
157+ dst: self.dst.clone(),
158+ editor_data: self.editor_data.clone(),
159+ shutown: self.shutown.clone(),
160+ }
161+ }
162+}
163+
164+pub struct InjectorShutdown;
165+
166+impl<T: Item> Injector<T> {
167+ pub fn push(&self, item: T) -> Result<(), InjectorShutdown> {
168+ if self.shutown.load(atomic::Ordering::Relaxed) {
169+ return Err(InjectorShutdown);
170+ }
171+
172+ if let Some((item, matcher_text)) = item_to_nucleo(item, &self.editor_data) {
173+ self.dst.push(item, |dst| dst[0] = matcher_text);
174+ }
175+ Ok(())
176+ }
177+}
178+
117179pub struct Picker<T: Item> {
118− options: Vec<T>,
119− editor_data: T::Data,
120− // filter: String,
121− matcher: Box<Matcher>,
122− matches: Vec<PickerMatch>,
180+ editor_data: Arc<T::Data>,
181+ shutdown: Arc<AtomicBool>,
182+ matcher: Nucleo<T>,
123183
124184 /// Current height of the completions box
125185 completion_height: u16,
126186
127− cursor: usize,
128− // pattern: String,
187+ cursor: u32,
129188 prompt: Prompt,
130− previous_pattern: (String, FuzzyQuery),
189+ previous_pattern: String,
190+
131191 /// Whether to show the preview panel (default true)
132192 show_preview: bool,
133193 /// Constraints for tabular formatting
@@ -144,10 +204,59 @@ pub struct Picker<T: Item> {
144204}
145205
146206impl<T: Item + 'static> Picker<T> {
207+ pub fn stream(editor_data: T::Data) -> (Nucleo<T>, Injector<T>) {
208+ let matcher = Nucleo::new(
209+ Config::DEFAULT,
210+ Arc::new(helix_event::request_redraw),
211+ None,
212+ 1,
213+ );
214+ let streamer = Injector {
215+ dst: matcher.injector(),
216+ editor_data: Arc::new(editor_data),
217+ shutown: Arc::new(AtomicBool::new(false)),
218+ };
219+ (matcher, streamer)
220+ }
221+
147222 pub fn new(
148223 options: Vec<T>,
149224 editor_data: T::Data,
150225 callback_fn: impl Fn(&mut Context, &T, Action) + 'static,
226+ ) -> Self {
227+ let matcher = Nucleo::new(
228+ Config::DEFAULT,
229+ Arc::new(helix_event::request_redraw),
230+ None,
231+ 1,
232+ );
233+ let injector = matcher.injector();
234+ for item in options {
235+ if let Some((item, matcher_text)) = item_to_nucleo(item, &editor_data) {
236+ injector.push(item, |dst| dst[0] = matcher_text);
237+ }
238+ }
239+ Self::with(
240+ matcher,
241+ Arc::new(editor_data),
242+ Arc::new(AtomicBool::new(false)),
243+ callback_fn,
244+ )
245+ }
246+
247+ pub fn with_stream(
248+ matcher: Nucleo<T>,
249+ injector: Injector<T>,
250+ callback_fn: impl Fn(&mut Context, &T, Action) + 'static,
251+ ) -> Self {
252+ Self::with(matcher, injector.editor_data, injector.shutown, callback_fn)
253+ }
254+
255+ fn with(
256+ matcher: Nucleo<T>,
257+ editor_data: Arc<T::Data>,
258+ shutdown: Arc<AtomicBool>,
259+ callback_fn: impl Fn(&mut Context, &T, Action) + 'static,
151260 ) -> Self {
152261 let prompt = Prompt::new(
153262 "".into(),
@@ -156,14 +265,13 @@ impl<T: Item + 'static> Picker<T> {
156265 |_editor: &mut Context, _pattern: &str, _event: PromptEvent| {},
157266 );
158267
159− let mut picker = Self {
160− options,
268+ Self {
269+ matcher,
161270 editor_data,
162− matcher: Box::default(),
163− matches: Vec::new(),
271+ shutdown,
164272 cursor: 0,
165273 prompt,
166− previous_pattern: (String::new(), FuzzyQuery::default()),
274+ previous_pattern: String::new(),
167275 truncate_start: true,
168276 show_preview: true,
169277 callback_fn: Box::new(callback_fn),
@@ -172,24 +280,15 @@ impl<T: Item + 'static> Picker<T> {
172280 preview_cache: HashMap::new(),
173281 read_buffer: Vec::with_capacity(1024),
174282 file_fn: None,
175− };
176−
177− picker.calculate_column_widths();
178−
179− // scoring on empty input
180− // TODO: just reuse score()
181− picker
182− .matches
183− .extend(picker.options.iter().enumerate().map(|(index, option)| {
184− let text = option.filter_text(&picker.editor_data);
185− PickerMatch {
186− index,
187− score: 0,
188− len: text.chars().count(),
189− }
190− }));
283+ }
284+ }
191285
192− picker
286+ pub fn injector(&self) -> Injector<T> {
287+ Injector {
288+ dst: self.matcher.injector(),
289+ editor_data: self.editor_data.clone(),
290+ shutown: self.shutdown.clone(),
291+ }
193292 }
194293
195294 pub fn truncate_start(mut self, truncate_start: bool) -> Self {
@@ -202,122 +301,25 @@ impl<T: Item + 'static> Picker<T> {
202301 preview_fn: impl Fn(&Editor, &T) -> Option<FileLocation> + 'static,
203302 ) -> Self {
204303 self.file_fn = Some(Box::new(preview_fn));
304+ // assumption: if we have a preview we are matching paths... If this is ever
305+ // not true this could be a separate builder function
306+ self.matcher.update_config(Config::DEFAULT.match_paths());
205307 self
206308 }
207309
208310 pub fn set_options(&mut self, new_options: Vec<T>) {
209− self.options = new_options;
210− self.cursor = 0;
211− self.force_score();
212− self.calculate_column_widths();
213− }
214−
215− /// Calculate the width constraints using the maximum widths of each column
216− /// for the current options.
217− fn calculate_column_widths(&mut self) {
218− let n = self
219− .options
220− .first()
221− .map(|option| option.format(&self.editor_data).cells.len())
222− .unwrap_or_default();
223− let max_lens = self.options.iter().fold(vec![0; n], |mut acc, option| {
224− let row = option.format(&self.editor_data);
225− // maintain max for each column
226− for (acc, cell) in acc.iter_mut().zip(row.cells.iter()) {
227− let width = cell.content.width();
228− if width > *acc {
229− *acc = width;
230− }
311+ self.matcher.restart(false);
312+ let injector = self.matcher.injector();
313+ for item in new_options {
314+ if let Some((item, matcher_text)) = item_to_nucleo(item, &self.editor_data) {
315+ injector.push(item, |dst| dst[0] = matcher_text);
231316 }
232− acc
233− });
234− self.widths = max_lens
235− .into_iter()
236− .map(|len| Constraint::Length(len as u16))
237− .collect();
238− }
239−
240− pub fn score(&mut self) {
241− let pattern = self.prompt.line();
242−
243− if pattern == &self.previous_pattern.0 {
244− return;
245317 }
246−
247− let (query, is_refined) = self
248− .previous_pattern
249− .1
250− .refine(pattern, &self.previous_pattern.0);
251−
252− if pattern.is_empty() {
253− // Fast path for no pattern.
254− self.matches.clear();
255− self.matches
256− .extend(self.options.iter().enumerate().map(|(index, option)| {
257− let text = option.filter_text(&self.editor_data);
258− PickerMatch {
259− index,
260− score: 0,
261− len: text.chars().count(),
262− }
263− }));
264− } else if is_refined {
265− // optimization: if the pattern is a more specific version of the previous one
266− // then we can score the filtered set.
267− self.matches.retain_mut(|pmatch| {
268− let option = &self.options[pmatch.index];
269− let text = option.sort_text(&self.editor_data);
270−
271− match query.fuzzy_match(&text, &self.matcher) {
272− Some(s) => {
273− // Update the score
274− pmatch.score = s;
275− true
276− }
277− None => false,
278− }
279− });
280−
281− self.matches.sort_unstable();
282− } else {
283− self.force_score();
284− }
285−
286− // reset cursor position
287− self.cursor = 0;
288− let pattern = self.prompt.line();
289− self.previous_pattern.0.clone_from(pattern);
290− self.previous_pattern.1 = query;
291− }
292−
293− pub fn force_score(&mut self) {
294− let pattern = self.prompt.line();
295−
296− let query = FuzzyQuery::new(pattern);
297− self.matches.clear();
298− self.matches.extend(
299− self.options
300− .iter()
301− .enumerate()
302− .filter_map(|(index, option)| {
303− let text = option.filter_text(&self.editor_data);
304−
305− query
306− .fuzzy_match(&text, &self.matcher)
307− .map(|score| PickerMatch {
308− index,
309− score,
310− len: text.chars().count(),
311− })
312− }),
313− );
314−
315− self.matches.sort_unstable();
316318 }
317319
318320 /// Move the cursor by a number of lines, either down (`Forward`) or up (`Backward`)
319− pub fn move_by(&mut self, amount: usize, direction: Direction) {
320− let len = self.matches.len();
321+ pub fn move_by(&mut self, amount: u32, direction: Direction) {
322+ let len = self.matcher.snapshot().matched_item_count();
321323
322324 if len == 0 {
323325 // No results, can't move.
@@ -336,12 +338,12 @@ impl<T: Item + 'static> Picker<T> {
336338
337339 /// Move the cursor down by exactly one page. After the last page comes the first page.
338340 pub fn page_up(&mut self) {
339− self.move_by(self.completion_height as usize, Direction::Backward);
341+ self.move_by(self.completion_height as u32, Direction::Backward);
340342 }
341343
342344 /// Move the cursor up by exactly one page. After the first page comes the last page.
343345 pub fn page_down(&mut self) {
344− self.move_by(self.completion_height as usize, Direction::Forward);
346+ self.move_by(self.completion_height as u32, Direction::Forward);
345347 }
346348
347349 /// Move the cursor to the first entry
@@ -351,13 +353,18 @@ impl<T: Item + 'static> Picker<T> {
351353
352354 /// Move the cursor to the last entry
353355 pub fn to_end(&mut self) {
354− self.cursor = self.matches.len().saturating_sub(1);
356+ self.cursor = self
357+ .matcher
358+ .snapshot()
359+ .matched_item_count()
360+ .saturating_sub(1);
355361 }
356362
357363 pub fn selection(&self) -> Option<&T> {
358− self.matches
359− .get(self.cursor)
360− .map(|pmatch| &self.options[pmatch.index])
364+ self.matcher
365+ .snapshot()
366+ .get_matched_item(self.cursor)
367+ .map(|item| item.data)
361368 }
362369
363370 pub fn toggle_preview(&mut self) {
@@ -366,8 +373,17 @@ impl<T: Item + 'static> Picker<T> {
366373
367374 fn prompt_handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
368375 if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) {
369− // TODO: recalculate only if pattern changed
370− self.score();
376+ let pattern = self.prompt.line();
377+ // TODO: better track how the pattern has changed
378+ if pattern != &self.previous_pattern {
379+ self.matcher.pattern.reparse(
380+ 0,
381+ pattern,
382+ CaseMatching::Smart,
383+ pattern.starts_with(&self.previous_pattern),
384+ );
385+ self.previous_pattern = pattern.clone();
386+ }
371387 }
372388 EventResult::Consumed(None)
373389 }
@@ -375,7 +391,7 @@ impl<T: Item + 'static> Picker<T> {
375391 fn current_file(&self, editor: &Editor) -> Option<FileLocation> {
376392 self.selection()
377393 .and_then(|current| (self.file_fn.as_ref()?)(editor, current))
378− .and_then(|(path_or_id, line)| path_or_id.get_canonicalized().ok().zip(Some(line)))
394+ .map(|(path_or_id, line)| (path_or_id.get_canonicalized(), line))
379395 }
380396
381397 /// Get (cached) preview for a given path. If a document corresponding
@@ -411,12 +427,9 @@ impl<T: Item + 'static> Picker<T> {
411427 (size, _) if size > MAX_FILE_SIZE_FOR_PREVIEW => {
412428 CachedPreview::LargeFile
413429 }
414− _ => {
415− // TODO: enable syntax highlighting; blocked by async rendering
416− Document::open(path, None, None, editor.config.clone())
417− .map(|doc| CachedPreview::Document(Box::new(doc)))
418− .unwrap_or(CachedPreview::NotFound)
419− }
430+ _ => Document::open(path, None, None, editor.config.clone())
431+ .map(|doc| CachedPreview::Document(Box::new(doc)))
432+ .unwrap_or(CachedPreview::NotFound),
420433 },
421434 )
422435 .unwrap_or(CachedPreview::NotFound);
@@ -432,7 +445,7 @@ impl<T: Item + 'static> Picker<T> {
432445
433446 fn handle_idle_timeout(&mut self, cx: &mut Context) -> EventResult {
434447 let Some((current_file, _)) = self.current_file(cx.editor) else {
435− return EventResult::Consumed(None)
448+ return EventResult::Consumed(None);
436449 };
437450
438451 // Try to find a document in the cache
@@ -453,17 +466,24 @@ impl<T: Item + 'static> Picker<T> {
453466 let text = doc.text().clone();
454467 let loader = cx.editor.syn_loader.clone();
455468 let job = tokio::task::spawn_blocking(move || {
456− let syntax = language_config
457− .highlight_config(&loader.scopes())
458− .and_then(|highlight_config| Syntax::new(&text, highlight_config, loader));
469+ let syntax = language_config.highlight_config(&loader.scopes()).and_then(
470+ |highlight_config| Syntax::new(text.slice(..), highlight_config, loader),
471+ );
459472 let callback = move |editor: &mut Editor, compositor: &mut Compositor| {
460473 let Some(syntax) = syntax else {
461474 log::info!("highlighting picker item failed");
462− return
475+ return;
476+ };
477+ let picker = match compositor.find::<Overlay<Self>>() {
478+ Some(Overlay { content, .. }) => Some(content),
479+ None => compositor
480+ .find::<Overlay<DynamicPicker<T>>>()
481+ .map(|overlay| &mut overlay.content.file_picker),
463482 };
464− let Some(Overlay { content: picker, .. }) = compositor.find::<Overlay<Self>>() else {
483+ let Some(picker) = picker
484+ else {
465485 log::info!("picker closed before syntax highlighting finished");
466− return
486+ return;
467487 };
468488 // Try to find a document in the cache
469489 let doc = match current_file {
@@ -492,6 +512,14 @@ impl<T: Item + 'static> Picker<T> {
492512 }
493513
494514 fn render_picker(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
515+ let status = self.matcher.tick(10);
516+ let snapshot = self.matcher.snapshot();
517+ if status.changed {
518+ self.cursor = self
519+ .cursor
520+ .min(snapshot.matched_item_count().saturating_sub(1))
521+ }
522+
495523 let text_style = cx.editor.theme.get("ui.text");
496524 let selected = cx.editor.theme.get("ui.text.focus");
497525 let highlight_style = cx.editor.theme.get("special").add_modifier(Modifier::BOLD);
@@ -512,8 +540,15 @@ impl<T: Item + 'static> Picker<T> {
512540 // -- Render the input bar:
513541
514542 let area = inner.clip_left(1).with_height(1);
543+ // render the prompt first since it will clear its background
544+ self.prompt.render(area, surface, cx);
515545
516− let count = format!("{}/{}", self.matches.len(), self.options.len());
546+ let count = format!(
547+ "{}{}/{}",
548+ if status.running { "(running) " } else { "" },
549+ snapshot.matched_item_count(),
550+ snapshot.item_count(),
551+ );
517552 surface.set_stringn(
518553 (area.x + area.width).saturating_sub(count.len() as u16 + 1),
519554 area.y,
@@ -522,8 +557,6 @@ impl<T: Item + 'static> Picker<T> {
522557 text_style,
523558 );
524559
525− self.prompt.render(area, surface, cx);
526−
527560 // -- Separator
528561 let sep_style = cx.editor.theme.get("ui.background.separator");
529562 let borders = BorderType::line_symbols(BorderType::Plain);
@@ -536,106 +569,89 @@ impl<T: Item + 'static> Picker<T> {
536569 // -- Render the contents:
537570 // subtract area of prompt from top
538571 let inner = inner.clip_top(2);
539−
540− let rows = inner.height;
541− let offset = self.cursor - (self.cursor % std::cmp::max(1, rows as usize));
572+ let rows = inner.height as u32;
573+ let offset = self.cursor - (self.cursor % std::cmp::max(1, rows));
542574 let cursor = self.cursor.saturating_sub(offset);
575+ let end = offset
576+ .saturating_add(rows)
577+ .min(snapshot.matched_item_count());
578+ let mut indices = Vec::new();
579+ let mut matcher = MATCHER.lock();
580+ matcher.config = Config::DEFAULT;
581+ if self.file_fn.is_some() {
582+ matcher.config.set_match_paths()
583+ }
543584
544− let options = self
545− .matches
546− .iter()
547− .skip(offset)
548− .take(rows as usize)
549− .map(|pmatch| &self.options[pmatch.index])
550− .map(|option| option.format(&self.editor_data))
551− .map(|mut row| {
552− const TEMP_CELL_SEP: &str = " ";
553−
554− let line = row.cell_text().fold(String::new(), |mut s, frag| {
555− s.push_str(&frag);
556− s.push_str(TEMP_CELL_SEP);
557− s
558− });
559−
560− // Items are filtered by using the text returned by menu::Item::filter_text
561− // but we do highlighting here using the text in Row and therefore there
562− // might be inconsistencies. This is the best we can do since only the
563− // text in Row is displayed to the end user.
564− let (_score, highlights) = FuzzyQuery::new(self.prompt.line())
565− .fuzzy_indices(&line, &self.matcher)
566− .unwrap_or_default();
567−
568− let highlight_byte_ranges: Vec<_> = line
569− .char_indices()
570− .enumerate()
571− .filter_map(|(char_idx, (byte_offset, ch))| {
572− highlights
573− .contains(&char_idx)
574− .then(|| byte_offset..byte_offset + ch.len_utf8())
575− })
576− .collect();
577−
578− // The starting byte index of the current (iterating) cell
579− let mut cell_start_byte_offset = 0;
580− for cell in row.cells.iter_mut() {
581− let spans = match cell.content.lines.get(0) {
582− Some(s) => s,
583− None => {
584− cell_start_byte_offset += TEMP_CELL_SEP.len();
585− continue;
586− }
587− };
585+ let options = snapshot.matched_items(offset..end).map(|item| {
586+ snapshot.pattern().column_pattern(0).indices(
587+ item.matcher_columns[0].slice(..),
588+ &mut matcher,
589+ &mut indices,
590+ );
591+ indices.sort_unstable();
592+ indices.dedup();
593+ let mut row = item.data.format(&self.editor_data);
594+
595+ let mut grapheme_idx = 0u32;
596+ let mut indices = indices.drain(..);
597+ let mut next_highlight_idx = indices.next().unwrap_or(u32::MAX);
598+ if self.widths.len() < row.cells.len() {
599+ self.widths.resize(row.cells.len(), Constraint::Length(0));
600+ }
601+ let mut widths = self.widths.iter_mut();
602+ for cell in &mut row.cells {
603+ let Some(Constraint::Length(max_width)) = widths.next() else {
604+ unreachable!();
605+ };
588606
589− let mut cell_len = 0;
590−
591− let graphemes_with_style: Vec<_> = spans
592− .0
593− .iter()
594− .flat_map(|span| {
595− span.content
596− .grapheme_indices(true)
597− .zip(std::iter::repeat(span.style))
598− })
599− .map(|((grapheme_byte_offset, grapheme), style)| {
600− cell_len += grapheme.len();
601− let start = cell_start_byte_offset;
602−
603− let grapheme_byte_range =
604− grapheme_byte_offset..grapheme_byte_offset + grapheme.len();
605−
606− if highlight_byte_ranges.iter().any(|hl_rng| {
607− hl_rng.start >= start + grapheme_byte_range.start
608− && hl_rng.end <= start + grapheme_byte_range.end
609− }) {
610− (grapheme, style.patch(highlight_style))
611− } else {
612− (grapheme, style)
613− }
614− })
615− .collect();
616−
617− let mut span_list: Vec<(String, Style)> = Vec::new();
618− for (grapheme, style) in graphemes_with_style {
619− if span_list.last().map(|(_, sty)| sty) == Some(&style) {
620− let (string, _) = span_list.last_mut().unwrap();
621− string.push_str(grapheme);
607+ // merge index highlights on top of existing hightlights
608+ let mut span_list = Vec::new();
609+ let mut current_span = String::new();
610+ let mut current_style = Style::default();
611+ let mut width = 0;
612+
613+ let spans: &[Span] = cell.content.lines.first().map_or(&[], |it| it.0.as_slice());
614+ for span in spans {
615+ // this looks like a bug on first glance, we are iterating
616+ // graphemes but treating them as char indices. The reason that
617+ // this is correct is that nucleo will only ever consider the first char
618+ // of a grapheme (and discard the rest of the grapheme) so the indices
619+ // returned by nucleo are essentially grapheme indecies
620+ for grapheme in span.content.graphemes(true) {
621+ let style = if grapheme_idx == next_highlight_idx {
622+ next_highlight_idx = indices.next().unwrap_or(u32::MAX);
623+ span.style.patch(highlight_style)
622624 } else {
623− span_list.push((String::from(grapheme), style))
625+ span.style
626+ };
627+ if style != current_style {
628+ if !current_span.is_empty() {
629+ span_list.push(Span::styled(current_span, current_style))
630+ }
631+ current_span = String::new();
632+ current_style = style;
624633 }
634+ current_span.push_str(grapheme);
635+ grapheme_idx += 1;
625636 }
637+ width += span.width();
638+ }
626639
627− let spans: Vec<Span> = span_list
628− .into_iter()
629− .map(|(string, style)| Span::styled(string, style))
630− .collect();
631− let spans: Spans = spans.into();
632− *cell = Cell::from(spans);
640+ span_list.push(Span::styled(current_span, current_style));
641+ if width as u16 > *max_width {
642+ *max_width = width as u16;
643+ }
644+ *cell = Cell::from(Spans::from(span_list));
633645
634− cell_start_byte_offset += cell_len + TEMP_CELL_SEP.len();
646+ // spacer
647+ if grapheme_idx == next_highlight_idx {
648+ next_highlight_idx = indices.next().unwrap_or(u32::MAX);
635649 }
650+ grapheme_idx += 1;
651+ }
636652
637− row
638− });
653+ row
654+ });
639655
640656 let table = Table::new(options)
641657 .style(text_style)
@@ -651,7 +667,7 @@ impl<T: Item + 'static> Picker<T> {
651667 surface,
652668 &mut TableState {
653669 offset: 0,
654− selected: Some(cursor),
670+ selected: Some(cursor as usize),
655671 },
656672 self.truncate_start,
657673 );
@@ -677,8 +693,14 @@ impl<T: Item + 'static> Picker<T> {
677693 if let Some((path, range)) = self.current_file(cx.editor) {
678694 let preview = self.get_preview(path, cx.editor);
679695 let doc = match preview.document() {
680− Some(doc) => doc,
681− None => {
696+ Some(doc)
697+ if range.map_or(true, |(start, end)| {
698+ start <= end && end <= doc.text().len_lines()
699+ }) =>
700+ {
701+ doc
702+ }
703+ _ => {
682704 let alt_text = preview.placeholder();
683705 let x = inner.x + inner.width.saturating_sub(alt_text.len() as u16) / 2;
684706 let y = inner.y + inner.height / 2;
@@ -687,32 +709,47 @@ impl<T: Item + 'static> Picker<T> {
687709 }
688710 };
689711
690− // align to middle
691− let first_line = range
692− .map(|(start, end)| {
693− let height = end.saturating_sub(start) + 1;
694− let middle = start + (height.saturating_sub(1) / 2);
695− middle.saturating_sub(inner.height as usize / 2).min(start)
696− })
697− .unwrap_or(0);
698−
699− let offset = ViewPosition {
700− anchor: doc.text().line_to_char(first_line),
701− horizontal_offset: 0,
702− vertical_offset: 0,
703− };
712+ let mut offset = ViewPosition::default();
713+ if let Some((start_line, end_line)) = range {
714+ let height = end_line - start_line;
715+ let text = doc.text().slice(..);
716+ let start = text.line_to_char(start_line);
717+ let middle = text.line_to_char(start_line + height / 2);
718+ if height < inner.height as usize {
719+ let text_fmt = doc.text_format(inner.width, None);
720+ let annotations = TextAnnotations::default();
721+ (offset.anchor, offset.vertical_offset) = char_idx_at_visual_offset(
722+ text,
723+ middle,
724+ // align to middle
725+ -(inner.height as isize / 2),
726+ 0,
727+ &text_fmt,
728+ &annotations,
729+ );
730+ if start < offset.anchor {
731+ offset.anchor = start;
732+ offset.vertical_offset = 0;
733+ }
734+ } else {
735+ offset.anchor = start;
736+ }
737+ }
704738
705− let mut highlights = EditorView::doc_syntax_highlights(
739+ let syntax_highlights = EditorView::doc_syntax_highlights(
706740 doc,
707741 offset.anchor,
708742 area.height,
709743 &cx.editor.theme,
710744 );
745+
746+ let mut overlay_highlights =
747+ EditorView::empty_highlight_iter(doc, offset.anchor, area.height);
711748 for spans in EditorView::doc_diagnostics_highlights(doc, &cx.editor.theme) {
712749 if spans.is_empty() {
713750 continue;
714751 }
715− highlights = Box::new(helix_core::syntax::merge(highlights, spans));
752+ overlay_highlights = Box::new(helix_core::syntax::merge(overlay_highlights, spans));
716753 }
717754 let mut decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
718755
@@ -743,7 +780,8 @@ impl<T: Item + 'static> Picker<T> {
743780 offset,
744781 // TODO: compute text annotations asynchronously here (like inlay hints)
745782 &TextAnnotations::default(),
746− highlights,
783+ syntax_highlights,
784+ overlay_highlights,
747785 &cx.editor.theme,
748786 &mut decorations,
749787 &mut [],
@@ -752,7 +790,7 @@ impl<T: Item + 'static> Picker<T> {
752790 }
753791}
754792
755−impl<T: Item + 'static> Component for Picker<T> {
793+impl<T: Item + 'static + Send + Sync> Component for Picker<T> {
756794 fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
757795 // +---------+ +---------+
758796 // |prompt | |preview |
@@ -761,7 +799,8 @@ impl<T: Item + 'static> Component for Picker<T> {
761799 // | | | |
762800 // +---------+ +---------+
763801
764− let render_preview = self.show_preview && area.width > MIN_AREA_WIDTH_FOR_PREVIEW;
802+ let render_preview =
803+ self.show_preview && self.file_fn.is_some() && area.width > MIN_AREA_WIDTH_FOR_PREVIEW;
765804
766805 let picker_width = if render_preview {
767806 area.width / 2
@@ -791,11 +830,28 @@ impl<T: Item + 'static> Component for Picker<T> {
791830 _ => return EventResult::Ignored(None),
792831 };
793832
794− let close_fn =
795− EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _ctx| {
796− // remove the layer
797− compositor.last_picker = compositor.pop();
798− })));
833+ let close_fn = |picker: &mut Self| {
834+ // if the picker is very large don't store it as last_picker to avoid
835+ // excessive memory consumption
836+ let callback: compositor::Callback = if picker.matcher.snapshot().item_count() > 100_000
837+ {
838+ Box::new(|compositor: &mut Compositor, _ctx| {
839+ // remove the layer
840+ compositor.pop();
841+ })
842+ } else {
843+ // stop streaming in new items in the background, really we should
844+ // be restarting the stream somehow once the picker gets
845+ // reopened instead (like for an FS crawl) that would also remove the
846+ // need for the special case above but that is pretty tricky
847+ picker.shutdown.store(true, atomic::Ordering::Relaxed);
848+ Box::new(|compositor: &mut Compositor, _ctx| {
849+ // remove the layer
850+ compositor.last_picker = compositor.pop();
851+ })
852+ };
853+ EventResult::Consumed(Some(callback))
854+ };
799855
800856 // So that idle timeout retriggers
801857 ctx.editor.reset_idle_timer();
@@ -819,9 +875,7 @@ impl<T: Item + 'static> Component for Picker<T> {
819875 key!(End) => {
820876 self.to_end();
821877 }
822− key!(Esc) | ctrl!('c') => {
823− return close_fn;
824− }
878+ key!(Esc) | ctrl!('c') => return close_fn(self),
825879 alt!(Enter) => {
826880 if let Some(option) = self.selection() {
827881 (self.callback_fn)(ctx, option, Action::Load);
@@ -831,19 +885,19 @@ impl<T: Item + 'static> Component for Picker<T> {
831885 if let Some(option) = self.selection() {
832886 (self.callback_fn)(ctx, option, Action::Replace);
833887 }
834− return close_fn;
888+ return close_fn(self);
835889 }
836890 ctrl!('s') => {
837891 if let Some(option) = self.selection() {
838892 (self.callback_fn)(ctx, option, Action::HorizontalSplit);
839893 }
840− return close_fn;
894+ return close_fn(self);
841895 }
842896 ctrl!('v') => {
843897 if let Some(option) = self.selection() {
844898 (self.callback_fn)(ctx, option, Action::VerticalSplit);
845899 }
846− return close_fn;
900+ return close_fn(self);
847901 }
848902 ctrl!('t') => {
849903 self.toggle_preview();
@@ -871,30 +925,15 @@ impl<T: Item + 'static> Component for Picker<T> {
871925 self.completion_height = height.saturating_sub(4);
872926 Some((width, height))
873927 }
874−}
875−
876−#[derive(PartialEq, Eq, Debug)]
877−struct PickerMatch {
878− score: i64,
879− index: usize,
880− len: usize,
881−}
882−
883−impl PickerMatch {
884− fn key(&self) -> impl Ord {
885− (cmp::Reverse(self.score), self.len, self.index)
886− }
887−}
888928
889−impl PartialOrd for PickerMatch {
890− fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
891− Some(self.cmp(other))
929+ fn id(&self) -> Option<&'static str> {
930+ Some(ID)
892931 }
893932}
894−
895−impl Ord for PickerMatch {
896− fn cmp(&self, other: &Self) -> Ordering {
897− self.key().cmp(&other.key())
933+impl<T: Item> Drop for Picker<T> {
934+ fn drop(&mut self) {
935+ // ensure we cancel any ongoing background threads streaming into the picker
936+ self.shutdown.store(true, atomic::Ordering::Relaxed)
898937 }
899938}
900939
@@ -907,15 +946,13 @@ pub type DynQueryCallback<T> =
907946
908947/// A picker that updates its contents via a callback whenever the
909948/// query string changes. Useful for live grep, workspace symbols, etc.
910−pub struct DynamicPicker<T: ui::menu::Item + Send> {
949+pub struct DynamicPicker<T: ui::menu::Item + Send + Sync> {
911950 file_picker: Picker<T>,
912951 query_callback: DynQueryCallback<T>,
913952 query: String,
914953}
915954
916−impl<T: ui::menu::Item + Send> DynamicPicker<T> {
917− pub const ID: &'static str = "dynamic-picker";
918−
955+impl<T: ui::menu::Item + Send + Sync> DynamicPicker<T> {
919956 pub fn new(file_picker: Picker<T>, query_callback: DynQueryCallback<T>) -> Self {
920957 Self {
921958 file_picker,
@@ -925,7 +962,7 @@ impl<T: ui::menu::Item + Send> DynamicPicker<T> {
925962 }
926963}
927964
928−impl<T: Item + Send + 'static> Component for DynamicPicker<T> {
965+impl<T: Item + Send + Sync + 'static> Component for DynamicPicker<T> {
929966 fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
930967 self.file_picker.render(area, surface, cx);
931968 }
@@ -947,7 +984,7 @@ impl<T: Item + Send + 'static> Component for DynamicPicker<T> {
947984 let callback = Callback::EditorCompositor(Box::new(move |editor, compositor| {
948985 // Wrapping of pickers in overlay is done outside the picker code,
949986 // so this is fragile and will break if wrapped in some other widget.
950− let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(Self::ID) {
987+ let picker = match compositor.find_id::<Overlay<DynamicPicker<T>>>(ID) {
951988 Some(overlay) => &mut overlay.content.file_picker,
952989 None => return,
953990 };
@@ -968,6 +1005,6 @@ impl<T: Item + Send + 'static> Component for DynamicPicker<T> {
9681005 }
9691006
9701007 fn id(&self) -> Option<&'static str> {
971− Some(Self::ID)
1008+ Some(ID)
9721009 }
9731010}
helix-term/src/ui/popup.rs+27−7
@@ -3,7 +3,10 @@ use crate::{
33 compositor::{Callback, Component, Context, Event, EventResult},
44 ctrl, key,
55};
6−use tui::buffer::Buffer as Surface;
6+use tui::{
7+ buffer::Buffer as Surface,
8+ widgets::{Block, Borders, Widget},
9+};
710
811use helix_core::Position;
912use helix_view::{
@@ -252,13 +255,29 @@ impl<T: Component> Component for Popup<T> {
252255 let background = cx.editor.theme.get("ui.popup");
253256 surface.clear_with(area, background);
254257
255− let inner = area.inner(&self.margin);
258+ let render_borders = cx.editor.popup_border();
259+
260+ let inner = if self
261+ .contents
262+ .type_name()
263+ .starts_with("helix_term::ui::menu::Menu")
264+ {
265+ area
266+ } else {
267+ area.inner(&self.margin)
268+ };
269+
270+ let border = usize::from(render_borders);
271+ if render_borders {
272+ Widget::render(Block::default().borders(Borders::ALL), area, surface);
273+ }
274+
256275 self.contents.render(inner, surface, cx);
257276
258277 // render scrollbar if contents do not fit
259278 if self.has_scrollbar {
260− let win_height = inner.height as usize;
261− let len = self.child_size.1 as usize;
279+ let win_height = (inner.height as usize).saturating_sub(2 * border);
280+ let len = (self.child_size.1 as usize).saturating_sub(2 * border);
262281 let fits = len <= win_height;
263282 let scroll = self.scroll;
264283 let scroll_style = cx.editor.theme.get("ui.menu.scroll");
@@ -274,14 +293,15 @@ impl<T: Component> Component for Popup<T> {
274293
275294 let mut cell;
276295 for i in 0..win_height {
277− cell = &mut surface[(inner.right() - 1, inner.top() + i as u16)];
296+ cell = &mut surface[(inner.right() - 1, inner.top() + (border + i) as u16)];
278297
279− cell.set_symbol("▐"); // right half block
298+ let half_block = if render_borders { "▌" } else { "▐" };
280299
281300 if scroll_line <= i && i < scroll_line + scroll_height {
282301 // Draw scroll thumb
302+ cell.set_symbol(half_block);
283303 cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
284− } else {
304+ } else if !render_borders {
285305 // Draw scroll track
286306 cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
287307 }
helix-term/src/ui/prompt.rs+55−48
@@ -1,7 +1,9 @@
11use crate::compositor::{Component, Compositor, Context, Event, EventResult};
22use crate::{alt, ctrl, key, shift, ui};
3+use helix_core::syntax;
34use helix_view::input::KeyEvent;
45use helix_view::keyboard::KeyCode;
6+use std::sync::Arc;
57use std::{borrow::Cow, ops::RangeFrom};
68use tui::buffer::Buffer as Surface;
79use tui::widgets::{Block, Borders, Widget};
@@ -32,6 +34,7 @@ pub struct Prompt {
3234 callback_fn: CallbackFn,
3335 pub doc_fn: DocFn,
3436 next_char_handler: Option<PromptCharHandler>,
37+ language: Option<(&'static str, Arc<syntax::Loader>)>,
3538}
3639
3740#[derive(Clone, Copy, PartialEq, Eq)]
@@ -83,6 +86,7 @@ impl Prompt {
8386 callback_fn: Box::new(callback_fn),
8487 doc_fn: Box::new(|_| None),
8588 next_char_handler: None,
89+ language: None,
8690 }
8791 }
8892
@@ -94,6 +98,11 @@ impl Prompt {
9498 self
9599 }
96100
101+ pub fn with_language(mut self, language: &'static str, loader: Arc<syntax::Loader>) -> Self {
102+ self.language = Some((language, loader));
103+ self
104+ }
105+
97106 pub fn line(&self) -> &String {
98107 &self.line
99108 }
@@ -297,8 +306,8 @@ impl Prompt {
297306 direction: CompletionDirection,
298307 ) {
299308 (self.callback_fn)(cx, &self.line, PromptEvent::Abort);
300− let values = match cx.editor.registers.read(register) {
301− Some(values) if !values.is_empty() => values,
309+ let mut values = match cx.editor.registers.read(register, cx.editor) {
310+ Some(values) if values.len() > 0 => values.rev(),
302311 _ => return,
303312 };
304313
@@ -306,13 +315,16 @@ impl Prompt {
306315
307316 let index = match direction {
308317 CompletionDirection::Forward => self.history_pos.map_or(0, |i| i + 1),
309− CompletionDirection::Backward => {
310− self.history_pos.unwrap_or(values.len()).saturating_sub(1)
311− }
318+ CompletionDirection::Backward => self
319+ .history_pos
320+ .unwrap_or_else(|| values.len())
321+ .saturating_sub(1),
312322 }
313323 .min(end);
314324
315− self.line = values[index].clone();
325+ self.line = values.nth(index).unwrap().to_string();
326+ // Appease the borrow checker.
327+ drop(values);
316328
317329 self.history_pos = Some(index);
318330
@@ -356,6 +368,7 @@ impl Prompt {
356368 let completion_color = theme.get("ui.menu");
357369 let selected_color = theme.get("ui.menu.selected");
358370 let suggestion_color = theme.get("ui.text.inactive");
371+ let background = theme.get("ui.background");
359372 // completion
360373
361374 let max_len = self
@@ -451,33 +464,32 @@ impl Prompt {
451464 }
452465
453466 let line = area.height - 1;
467+ surface.clear_with(area.clip_top(line), background);
454468 // render buffer text
455469 surface.set_string(area.x, area.y + line, &self.prompt, prompt_color);
456470
457− let (input, is_suggestion): (Cow<str>, bool) = if self.line.is_empty() {
458− // latest value in the register list
459− match self
471+ let line_area = area.clip_left(self.prompt.len() as u16).clip_top(line);
472+ if self.line.is_empty() {
473+ // Show the most recently entered value as a suggestion.
474+ if let Some(suggestion) = self
460475 .history_register
461− .and_then(|reg| cx.editor.registers.last(reg))
462− .map(|entry| entry.into())
476+ .and_then(|reg| cx.editor.registers.first(reg, cx.editor))
463477 {
464− Some(value) => (value, true),
465− None => (Cow::from(""), false),
478+ surface.set_string(line_area.x, line_area.y, suggestion, suggestion_color);
466479 }
480+ } else if let Some((language, loader)) = self.language.as_ref() {
481+ let mut text: ui::text::Text = crate::ui::markdown::highlighted_code_block(
482+ &self.line,
483+ language,
484+ Some(&cx.editor.theme),
485+ loader.clone(),
486+ None,
487+ )
488+ .into();
489+ text.render(line_area, surface, cx);
467490 } else {
468− (self.line.as_str().into(), false)
469− };
470−
471− surface.set_string(
472− area.x + self.prompt.len() as u16,
473− area.y + line,
474− &input,
475− if is_suggestion {
476− suggestion_color
477− } else {
478− prompt_color
479− },
480− );
491+ surface.set_string(line_area.x, line_area.y, self.line.clone(), prompt_color);
492+ }
481493 }
482494}
483495
@@ -558,25 +570,29 @@ impl Component for Prompt {
558570 } else {
559571 let last_item = self
560572 .history_register
561− .and_then(|reg| cx.editor.registers.last(reg).cloned())
562− .map(|entry| entry.into())
563− .unwrap_or_else(|| Cow::from(""));
573+ .and_then(|reg| cx.editor.registers.first(reg, cx.editor))
574+ .map(|entry| entry.to_string())
575+ .unwrap_or_else(|| String::from(""));
564576
565577 // handle executing with last command in history if nothing entered
566− let input: Cow<str> = if self.line.is_empty() {
567− last_item
578+ let input = if self.line.is_empty() {
579+ &last_item
568580 } else {
569581 if last_item != self.line {
570582 // store in history
571583 if let Some(register) = self.history_register {
572− cx.editor.registers.push(register, self.line.clone());
584+ if let Err(err) =
585+ cx.editor.registers.push(register, self.line.clone())
586+ {
587+ cx.editor.set_error(err.to_string());
588+ }
573589 };
574590 }
575591
576− self.line.as_str().into()
592+ &self.line
577593 };
578594
579− (self.callback_fn)(cx, &input, PromptEvent::Validate);
595+ (self.callback_fn)(cx, input, PromptEvent::Validate);
580596
581597 return close_fn;
582598 }
@@ -608,25 +624,16 @@ impl Component for Prompt {
608624 self.completion = cx
609625 .editor
610626 .registers
611− .inner()
612− .iter()
613− .map(|(ch, reg)| {
614− let content = reg
615− .read()
616− .get(0)
617− .and_then(|s| s.lines().next().to_owned())
618− .unwrap_or_default();
619− (0.., format!("{} {}", ch, &content).into())
620− })
627+ .iter_preview()
628+ .map(|(ch, preview)| (0.., format!("{} {}", ch, &preview).into()))
621629 .collect();
622630 self.next_char_handler = Some(Box::new(|prompt, c, context| {
623631 prompt.insert_str(
624− context
632+ &context
625633 .editor
626634 .registers
627− .read(c)
628− .and_then(|r| r.first())
629− .map_or("", |r| r.as_str()),
635+ .first(c, context.editor)
636+ .unwrap_or_default(),
630637 context.editor,
631638 );
632639 }));
helix-term/src/ui/statusline.rs+14
@@ -145,6 +145,7 @@ where
145145 helix_view::editor::StatusLineElement::FileModificationIndicator => {
146146 render_file_modification_indicator
147147 }
148+ helix_view::editor::StatusLineElement::ReadOnlyIndicator => render_read_only_indicator,
148149 helix_view::editor::StatusLineElement::FileEncoding => render_file_encoding,
149150 helix_view::editor::StatusLineElement::FileLineEnding => render_file_line_ending,
150151 helix_view::editor::StatusLineElement::FileType => render_file_type,
@@ -442,6 +443,19 @@ where
442443 write(context, title, None);
443444}
444445
446+fn render_read_only_indicator<F>(context: &mut RenderContext, write: F)
447+where
448+ F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
449+{
450+ let title = if context.doc.readonly {
451+ " [readonly] "
452+ } else {
453+ ""
454+ }
455+ .to_string();
456+ write(context, title, None);
457+}
458+
445459fn render_file_base_name<F>(context: &mut RenderContext, write: F)
446460where
447461 F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
helix-term/tests/integration.rs+1
@@ -18,6 +18,7 @@ mod test {
1818 mod auto_indent;
1919 mod auto_pairs;
2020 mod commands;
21+ mod languages;
2122 mod movement;
2223 mod prompt;
2324 mod splits;
helix-term/tests/test/commands.rs+47
@@ -2,6 +2,7 @@ use helix_term::application::Application;
22
33use super::*;
44
5+mod movement;
56mod write;
67
78#[tokio::test(flavor = "multi_thread")]
@@ -479,3 +480,49 @@ fn bar() {#(\n|)#\
479480
480481 Ok(())
481482}
483+
484+#[tokio::test(flavor = "multi_thread")]
485+async fn test_join_selections() -> anyhow::Result<()> {
486+ // normal join
487+ test((
488+ platform_line(indoc! {"\
489+ #[a|]#bc
490+ def
491+ "}),
492+ "J",
493+ platform_line(indoc! {"\
494+ #[a|]#bc def
495+ "}),
496+ ))
497+ .await?;
498+
499+ // join with empty line
500+ test((
501+ platform_line(indoc! {"\
502+ #[a|]#bc
503+
504+ def
505+ "}),
506+ "JJ",
507+ platform_line(indoc! {"\
508+ #[a|]#bc def
509+ "}),
510+ ))
511+ .await?;
512+
513+ // join with additional space in non-empty line
514+ test((
515+ platform_line(indoc! {"\
516+ #[a|]#bc
517+
518+ def
519+ "}),
520+ "JJ",
521+ platform_line(indoc! {"\
522+ #[a|]#bc def
523+ "}),
524+ ))
525+ .await?;
526+
527+ Ok(())
528+}
helix-term/tests/test/commands/movement.rsnew file+452
@@ -0,0 +1,452 @@
1+use super::*;
2+
3+#[tokio::test(flavor = "multi_thread")]
4+async fn test_move_parent_node_end() -> anyhow::Result<()> {
5+ let tests = vec![
6+ // single cursor stays single cursor, first goes to end of current
7+ // node, then parent
8+ (
9+ helpers::platform_line(indoc! {r##"
10+ fn foo() {
11+ let result = if true {
12+ "yes"
13+ } else {
14+ "no#["|]#
15+ }
16+ }
17+ "##}),
18+ "<A-e>",
19+ helpers::platform_line(indoc! {"\
20+ fn foo() {
21+ let result = if true {
22+ \"yes\"
23+ } else {
24+ \"no\"#[\n|]#
25+ }
26+ }
27+ "}),
28+ ),
29+ (
30+ helpers::platform_line(indoc! {"\
31+ fn foo() {
32+ let result = if true {
33+ \"yes\"
34+ } else {
35+ \"no\"#[\n|]#
36+ }
37+ }
38+ "}),
39+ "<A-e>",
40+ helpers::platform_line(indoc! {"\
41+ fn foo() {
42+ let result = if true {
43+ \"yes\"
44+ } else {
45+ \"no\"
46+ }#[\n|]#
47+ }
48+ "}),
49+ ),
50+ // select mode extends
51+ (
52+ helpers::platform_line(indoc! {r##"
53+ fn foo() {
54+ let result = if true {
55+ "yes"
56+ } else {
57+ #["no"|]#
58+ }
59+ }
60+ "##}),
61+ "v<A-e><A-e>",
62+ helpers::platform_line(indoc! {"\
63+ fn foo() {
64+ let result = if true {
65+ \"yes\"
66+ } else {
67+ #[\"no\"
68+ }\n|]#
69+ }
70+ "}),
71+ ),
72+ ];
73+
74+ for test in tests {
75+ test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?;
76+ }
77+
78+ Ok(())
79+}
80+
81+#[tokio::test(flavor = "multi_thread")]
82+async fn test_move_parent_node_start() -> anyhow::Result<()> {
83+ let tests = vec![
84+ // single cursor stays single cursor, first goes to end of current
85+ // node, then parent
86+ (
87+ helpers::platform_line(indoc! {r##"
88+ fn foo() {
89+ let result = if true {
90+ "yes"
91+ } else {
92+ "no#["|]#
93+ }
94+ }
95+ "##}),
96+ "<A-b>",
97+ helpers::platform_line(indoc! {"\
98+ fn foo() {
99+ let result = if true {
100+ \"yes\"
101+ } else {
102+ #[\"|]#no\"
103+ }
104+ }
105+ "}),
106+ ),
107+ (
108+ helpers::platform_line(indoc! {"\
109+ fn foo() {
110+ let result = if true {
111+ \"yes\"
112+ } else {
113+ \"no\"#[\n|]#
114+ }
115+ }
116+ "}),
117+ "<A-b>",
118+ helpers::platform_line(indoc! {"\
119+ fn foo() {
120+ let result = if true {
121+ \"yes\"
122+ } else #[{|]#
123+ \"no\"
124+ }
125+ }
126+ "}),
127+ ),
128+ (
129+ helpers::platform_line(indoc! {"\
130+ fn foo() {
131+ let result = if true {
132+ \"yes\"
133+ } else #[{|]#
134+ \"no\"
135+ }
136+ }
137+ "}),
138+ "<A-b>",
139+ helpers::platform_line(indoc! {"\
140+ fn foo() {
141+ let result = if true {
142+ \"yes\"
143+ } #[e|]#lse {
144+ \"no\"
145+ }
146+ }
147+ "}),
148+ ),
149+ // select mode extends
150+ (
151+ helpers::platform_line(indoc! {r##"
152+ fn foo() {
153+ let result = if true {
154+ "yes"
155+ } else {
156+ #["no"|]#
157+ }
158+ }
159+ "##}),
160+ "v<A-b><A-b>",
161+ helpers::platform_line(indoc! {"\
162+ fn foo() {
163+ let result = if true {
164+ \"yes\"
165+ } else #[|{
166+ ]#\"no\"
167+ }
168+ }
169+ "}),
170+ ),
171+ (
172+ helpers::platform_line(indoc! {r##"
173+ fn foo() {
174+ let result = if true {
175+ "yes"
176+ } else {
177+ #["no"|]#
178+ }
179+ }
180+ "##}),
181+ "v<A-b><A-b><A-b>",
182+ helpers::platform_line(indoc! {"\
183+ fn foo() {
184+ let result = if true {
185+ \"yes\"
186+ } #[|else {
187+ ]#\"no\"
188+ }
189+ }
190+ "}),
191+ ),
192+ ];
193+
194+ for test in tests {
195+ test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?;
196+ }
197+
198+ Ok(())
199+}
200+
201+#[tokio::test(flavor = "multi_thread")]
202+async fn test_smart_tab_move_parent_node_end() -> anyhow::Result<()> {
203+ let tests = vec![
204+ // single cursor stays single cursor, first goes to end of current
205+ // node, then parent
206+ (
207+ helpers::platform_line(indoc! {r##"
208+ fn foo() {
209+ let result = if true {
210+ "yes"
211+ } else {
212+ "no#["|]#
213+ }
214+ }
215+ "##}),
216+ "i<tab>",
217+ helpers::platform_line(indoc! {"\
218+ fn foo() {
219+ let result = if true {
220+ \"yes\"
221+ } else {
222+ \"no\"#[|\n]#
223+ }
224+ }
225+ "}),
226+ ),
227+ (
228+ helpers::platform_line(indoc! {"\
229+ fn foo() {
230+ let result = if true {
231+ \"yes\"
232+ } else {
233+ \"no\"#[\n|]#
234+ }
235+ }
236+ "}),
237+ "i<tab>",
238+ helpers::platform_line(indoc! {"\
239+ fn foo() {
240+ let result = if true {
241+ \"yes\"
242+ } else {
243+ \"no\"
244+ }#[|\n]#
245+ }
246+ "}),
247+ ),
248+ // appending to the end of a line should still look at the current
249+ // line, not the next one
250+ (
251+ helpers::platform_line(indoc! {"\
252+ fn foo() {
253+ let result = if true {
254+ \"yes\"
255+ } else {
256+ \"no#[\"|]#
257+ }
258+ }
259+ "}),
260+ "a<tab>",
261+ helpers::platform_line(indoc! {"\
262+ fn foo() {
263+ let result = if true {
264+ \"yes\"
265+ } else {
266+ \"no\"
267+ }#[\n|]#
268+ }
269+ "}),
270+ ),
271+ // before cursor is all whitespace, so insert tab
272+ (
273+ helpers::platform_line(indoc! {"\
274+ fn foo() {
275+ let result = if true {
276+ \"yes\"
277+ } else {
278+ #[\"no\"|]#
279+ }
280+ }
281+ "}),
282+ "i<tab>",
283+ helpers::platform_line(indoc! {"\
284+ fn foo() {
285+ let result = if true {
286+ \"yes\"
287+ } else {
288+ #[|\"no\"]#
289+ }
290+ }
291+ "}),
292+ ),
293+ // if selection spans multiple lines, it should still only look at the
294+ // line on which the head is
295+ (
296+ helpers::platform_line(indoc! {"\
297+ fn foo() {
298+ let result = if true {
299+ #[\"yes\"
300+ } else {
301+ \"no\"|]#
302+ }
303+ }
304+ "}),
305+ "a<tab>",
306+ helpers::platform_line(indoc! {"\
307+ fn foo() {
308+ let result = if true {
309+ \"yes\"
310+ } else {
311+ \"no\"
312+ }#[\n|]#
313+ }
314+ "}),
315+ ),
316+ (
317+ helpers::platform_line(indoc! {"\
318+ fn foo() {
319+ let result = if true {
320+ #[\"yes\"
321+ } else {
322+ \"no\"|]#
323+ }
324+ }
325+ "}),
326+ "i<tab>",
327+ helpers::platform_line(indoc! {"\
328+ fn foo() {
329+ let result = if true {
330+ #[|\"yes\"
331+ } else {
332+ \"no\"]#
333+ }
334+ }
335+ "}),
336+ ),
337+ (
338+ helpers::platform_line(indoc! {"\
339+ fn foo() {
340+ #[l|]#et result = if true {
341+ #(\"yes\"
342+ } else {
343+ \"no\"|)#
344+ }
345+ }
346+ "}),
347+ "i<tab>",
348+ helpers::platform_line(indoc! {"\
349+ fn foo() {
350+ #[|l]#et result = if true {
351+ #(|\"yes\"
352+ } else {
353+ \"no\")#
354+ }
355+ }
356+ "}),
357+ ),
358+ (
359+ helpers::platform_line(indoc! {"\
360+ fn foo() {
361+ let result = if true {
362+ \"yes\"#[\n|]#
363+ } else {
364+ \"no\"#(\n|)#
365+ }
366+ }
367+ "}),
368+ "i<tab>",
369+ helpers::platform_line(indoc! {"\
370+ fn foo() {
371+ let result = if true {
372+ \"yes\"
373+ }#[| ]#else {
374+ \"no\"
375+ }#(|\n)#
376+ }
377+ "}),
378+ ),
379+ (
380+ helpers::platform_line(indoc! {"\
381+ fn foo() {
382+ let result = if true {
383+ #[\"yes\"|]#
384+ } else {
385+ #(\"no\"|)#
386+ }
387+ }
388+ "}),
389+ "i<tab>",
390+ helpers::platform_line(indoc! {"\
391+ fn foo() {
392+ let result = if true {
393+ #[|\"yes\"]#
394+ } else {
395+ #(|\"no\")#
396+ }
397+ }
398+ "}),
399+ ),
400+ // if any cursors are not preceded by all whitespace, then do the
401+ // smart_tab action
402+ (
403+ helpers::platform_line(indoc! {"\
404+ fn foo() {
405+ let result = if true {
406+ #[\"yes\"\n|]#
407+ } else {
408+ \"no#(\"\n|)#
409+ }
410+ }
411+ "}),
412+ "i<tab>",
413+ helpers::platform_line(indoc! {"\
414+ fn foo() {
415+ let result = if true {
416+ \"yes\"
417+ }#[| ]#else {
418+ \"no\"
419+ }#(|\n)#
420+ }
421+ "}),
422+ ),
423+ // Ctrl-tab always inserts a tab
424+ (
425+ helpers::platform_line(indoc! {"\
426+ fn foo() {
427+ let result = if true {
428+ #[\"yes\"\n|]#
429+ } else {
430+ \"no#(\"\n|)#
431+ }
432+ }
433+ "}),
434+ "i<S-tab>",
435+ helpers::platform_line(indoc! {"\
436+ fn foo() {
437+ let result = if true {
438+ #[|\"yes\"\n]#
439+ } else {
440+ \"no #(|\"\n)#
441+ }
442+ }
443+ "}),
444+ ),
445+ ];
446+
447+ for test in tests {
448+ test_with_config(AppBuilder::new().with_file("foo.rs", None), test).await?;
449+ }
450+
451+ Ok(())
452+}
helix-term/tests/test/commands/write.rs+122−3
@@ -93,7 +93,7 @@ async fn test_buffer_close_concurrent() -> anyhow::Result<()> {
9393 )
9494 .await?;
9595
96− helpers::assert_file_has_content(file.as_file_mut(), &RANGE.end().to_string())?;
96+ helpers::assert_file_has_content(file.as_file_mut(), &platform_line(&RANGE.end().to_string()))?;
9797
9898 Ok(())
9999}
@@ -209,7 +209,7 @@ async fn test_write_concurrent() -> anyhow::Result<()> {
209209
210210 let mut file_content = String::new();
211211 file.as_file_mut().read_to_string(&mut file_content)?;
212− assert_eq!(RANGE.end().to_string(), file_content);
212+ assert_eq!(platform_line(&RANGE.end().to_string()), file_content);
213213
214214 Ok(())
215215}
@@ -424,13 +424,132 @@ async fn test_write_utf_bom_file() -> anyhow::Result<()> {
424424 Ok(())
425425}
426426
427+#[tokio::test(flavor = "multi_thread")]
428+async fn test_write_insert_final_newline_added_if_missing() -> anyhow::Result<()> {
429+ let mut file = tempfile::NamedTempFile::new()?;
430+ let mut app = helpers::AppBuilder::new()
431+ .with_file(file.path(), None)
432+ .with_input_text("#[h|]#ave you tried chamomile tea?")
433+ .build()?;
434+
435+ test_key_sequence(&mut app, Some(":w<ret>"), None, false).await?;
436+
437+ helpers::assert_file_has_content(
438+ file.as_file_mut(),
439+ &helpers::platform_line("have you tried chamomile tea?\n"),
440+ )?;
441+
442+ Ok(())
443+}
444+
445+#[tokio::test(flavor = "multi_thread")]
446+async fn test_write_insert_final_newline_unchanged_if_not_missing() -> anyhow::Result<()> {
447+ let mut file = tempfile::NamedTempFile::new()?;
448+ let mut app = helpers::AppBuilder::new()
449+ .with_file(file.path(), None)
450+ .with_input_text(&helpers::platform_line("#[t|]#en minutes, please\n"))
451+ .build()?;
452+
453+ test_key_sequence(&mut app, Some(":w<ret>"), None, false).await?;
454+
455+ helpers::assert_file_has_content(
456+ file.as_file_mut(),
457+ &helpers::platform_line("ten minutes, please\n"),
458+ )?;
459+
460+ Ok(())
461+}
462+
463+#[tokio::test(flavor = "multi_thread")]
464+async fn test_write_insert_final_newline_unchanged_if_missing_and_false() -> anyhow::Result<()> {
465+ let mut file = tempfile::NamedTempFile::new()?;
466+ let mut app = helpers::AppBuilder::new()
467+ .with_config(Config {
468+ editor: helix_view::editor::Config {
469+ insert_final_newline: false,
470+ ..Default::default()
471+ },
472+ ..Default::default()
473+ })
474+ .with_file(file.path(), None)
475+ .with_input_text("#[t|]#he quiet rain continued through the night")
476+ .build()?;
477+
478+ test_key_sequence(&mut app, Some(":w<ret>"), None, false).await?;
479+
480+ helpers::assert_file_has_content(
481+ file.as_file_mut(),
482+ "the quiet rain continued through the night",
483+ )?;
484+
485+ Ok(())
486+}
487+
488+#[tokio::test(flavor = "multi_thread")]
489+async fn test_write_all_insert_final_newline_add_if_missing_and_modified() -> anyhow::Result<()> {
490+ let mut file1 = tempfile::NamedTempFile::new()?;
491+ let mut file2 = tempfile::NamedTempFile::new()?;
492+ let mut app = helpers::AppBuilder::new()
493+ .with_file(file1.path(), None)
494+ .with_input_text("#[w|]#e don't serve time travelers here")
495+ .build()?;
496+
497+ test_key_sequence(
498+ &mut app,
499+ Some(&format!(
500+ ":o {}<ret>ia time traveler walks into a bar<esc>:wa<ret>",
501+ file2.path().to_string_lossy()
502+ )),
503+ None,
504+ false,
505+ )
506+ .await?;
507+
508+ helpers::assert_file_has_content(
509+ file1.as_file_mut(),
510+ &helpers::platform_line("we don't serve time travelers here\n"),
511+ )?;
512+
513+ helpers::assert_file_has_content(
514+ file2.as_file_mut(),
515+ &helpers::platform_line("a time traveler walks into a bar\n"),
516+ )?;
517+
518+ Ok(())
519+}
520+
521+#[tokio::test(flavor = "multi_thread")]
522+async fn test_write_all_insert_final_newline_do_not_add_if_unmodified() -> anyhow::Result<()> {
523+ let mut file = tempfile::NamedTempFile::new()?;
524+ let mut app = helpers::AppBuilder::new()
525+ .with_file(file.path(), None)
526+ .build()?;
527+
528+ file.write_all(b"i lost on Jeopardy!")?;
529+ file.rewind()?;
530+
531+ test_key_sequence(&mut app, Some(":wa<ret>"), None, false).await?;
532+
533+ helpers::assert_file_has_content(file.as_file_mut(), "i lost on Jeopardy!")?;
534+
535+ Ok(())
536+}
537+
427538async fn edit_file_with_content(file_content: &[u8]) -> anyhow::Result<()> {
428539 let mut file = tempfile::NamedTempFile::new()?;
429540
430541 file.as_file_mut().write_all(&file_content)?;
431542
432543 helpers::test_key_sequence(
433− &mut helpers::AppBuilder::new().build()?,
544+ &mut helpers::AppBuilder::new()
545+ .with_config(Config {
546+ editor: helix_view::editor::Config {
547+ insert_final_newline: false,
548+ ..Default::default()
549+ },
550+ ..Default::default()
551+ })
552+ .build()?,
434553 Some(&format!(":o {}<ret>:x<ret>", file.path().to_string_lossy())),
435554 None,
436555 true,
helix-term/tests/test/helpers.rs+9−1
@@ -320,6 +320,14 @@ impl AppBuilder {
320320 }
321321
322322 pub fn build(self) -> anyhow::Result<Application> {
323+ if let Some(path) = &self.args.working_directory {
324+ bail!("Changing the working directory to {path:?} is not yet supported for integration tests");
325+ }
326+
327+ if let Some((path, _)) = self.args.files.first().filter(|p| p.0.is_dir()) {
328+ bail!("Having the directory {path:?} in args.files[0] is not yet supported for integration tests");
329+ }
330+
323331 let mut app = Application::new(self.args, self.config, self.syn_conf)?;
324332
325333 if let Some((text, selection)) = self.input {
@@ -350,7 +358,7 @@ pub fn assert_file_has_content(file: &mut File, content: &str) -> anyhow::Result
350358
351359 let mut file_content = String::new();
352360 file.read_to_string(&mut file_content)?;
353− assert_eq!(content, file_content);
361+ assert_eq!(file_content, content);
354362
355363 Ok(())
356364}
helix-term/tests/test/languages/go.rsnew file+41
@@ -0,0 +1,41 @@
1+use super::*;
2+
3+#[tokio::test(flavor = "multi_thread")]
4+async fn auto_indent() -> anyhow::Result<()> {
5+ let app = || AppBuilder::new().with_file("foo.go", None);
6+
7+ let enter_tests = [
8+ (
9+ helpers::platform_line(indoc! {r##"
10+ type Test struct {#[}|]#
11+ "##}),
12+ "i<ret>",
13+ helpers::platform_line(indoc! {"\
14+ type Test struct {
15+ \t#[|\n]#
16+ }
17+ "}),
18+ ),
19+ (
20+ helpers::platform_line(indoc! {"\
21+ func main() {
22+ \tswitch nil {#[}|]#
23+ }
24+ "}),
25+ "i<ret>",
26+ helpers::platform_line(indoc! {"\
27+ func main() {
28+ \tswitch nil {
29+ \t\t#[|\n]#
30+ \t}
31+ }
32+ "}),
33+ ),
34+ ];
35+
36+ for test in enter_tests {
37+ test_with_config(app(), test).await?;
38+ }
39+
40+ Ok(())
41+}
helix-term/tests/test/languages/mod.rsnew file+4
@@ -0,0 +1,4 @@
1+use super::*;
2+
3+mod go;
4+mod yaml;
helix-term/tests/test/languages/yaml.rsnew file+819
@@ -0,0 +1,819 @@
1+use super::*;
2+
3+#[tokio::test(flavor = "multi_thread")]
4+async fn auto_indent() -> anyhow::Result<()> {
5+ let app = || AppBuilder::new().with_file("foo.yaml", None);
6+
7+ let below_tests = [
8+ (
9+ helpers::platform_line(indoc! {r##"
10+ #[t|]#op:
11+ baz: foo
12+ bazi:
13+ more: yes
14+ why: because
15+ quux:
16+ - 1
17+ - 2
18+ bax: foox
19+ fook:
20+ "##}),
21+ "o",
22+ helpers::platform_line(indoc! {"\
23+ top:
24+ #[\n|]#
25+ baz: foo
26+ bazi:
27+ more: yes
28+ why: because
29+ quux:
30+ - 1
31+ - 2
32+ bax: foox
33+ fook:
34+ "}),
35+ ),
36+ (
37+ helpers::platform_line(indoc! {r##"
38+ top:
39+ b#[a|]#z: foo
40+ bazi:
41+ more: yes
42+ why: because
43+ quux:
44+ - 1
45+ - 2
46+ bax: foox
47+ fook:
48+ "##}),
49+ "o",
50+ helpers::platform_line(indoc! {"\
51+ top:
52+ baz: foo
53+ #[\n|]#
54+ bazi:
55+ more: yes
56+ why: because
57+ quux:
58+ - 1
59+ - 2
60+ bax: foox
61+ fook:
62+ "}),
63+ ),
64+ (
65+ helpers::platform_line(indoc! {r##"
66+ top:
67+ baz: foo
68+ bazi#[:|]#
69+ more: yes
70+ why: because
71+ quux:
72+ - 1
73+ - 2
74+ bax: foox
75+ fook:
76+ "##}),
77+ "o",
78+ helpers::platform_line(indoc! {"\
79+ top:
80+ baz: foo
81+ bazi:
82+ #[\n|]#
83+ more: yes
84+ why: because
85+ quux:
86+ - 1
87+ - 2
88+ bax: foox
89+ fook:
90+ "}),
91+ ),
92+ (
93+ helpers::platform_line(indoc! {r##"
94+ top:
95+ baz: foo
96+ bazi:
97+ more: #[yes|]#
98+ why: because
99+ quux:
100+ - 1
101+ - 2
102+ bax: foox
103+ fook:
104+ "##}),
105+ "o",
106+ helpers::platform_line(indoc! {"\
107+ top:
108+ baz: foo
109+ bazi:
110+ more: yes
111+ #[\n|]#
112+ why: because
113+ quux:
114+ - 1
115+ - 2
116+ bax: foox
117+ fook:
118+ "}),
119+ ),
120+ (
121+ helpers::platform_line(indoc! {r##"
122+ top:
123+ baz: foo
124+ bazi:
125+ more: yes
126+ why: becaus#[e|]#
127+ quux:
128+ - 1
129+ - 2
130+ bax: foox
131+ fook:
132+ "##}),
133+ "o",
134+ helpers::platform_line(indoc! {"\
135+ top:
136+ baz: foo
137+ bazi:
138+ more: yes
139+ why: because
140+ #[\n|]#
141+ quux:
142+ - 1
143+ - 2
144+ bax: foox
145+ fook:
146+ "}),
147+ ),
148+ (
149+ helpers::platform_line(indoc! {"\
150+ top:
151+ baz: foo
152+ bazi:
153+ more: yes
154+ why: because
155+ quux:#[\n|]#
156+ - 1
157+ - 2
158+ bax: foox
159+ fook:
160+ "}),
161+ "o",
162+ helpers::platform_line(indoc! {"\
163+ top:
164+ baz: foo
165+ bazi:
166+ more: yes
167+ why: because
168+ quux:
169+ #[\n|]#
170+ - 1
171+ - 2
172+ bax: foox
173+ fook:
174+ "}),
175+ ),
176+ (
177+ helpers::platform_line(indoc! {"\
178+ top:
179+ baz: foo
180+ bazi:
181+ more: yes
182+ why: because
183+ quux:
184+ - 1#[\n|]#
185+ - 2
186+ bax: foox
187+ fook:
188+ "}),
189+ "o",
190+ helpers::platform_line(indoc! {"\
191+ top:
192+ baz: foo
193+ bazi:
194+ more: yes
195+ why: because
196+ quux:
197+ - 1
198+ #[\n|]#
199+ - 2
200+ bax: foox
201+ fook:
202+ "}),
203+ ),
204+ (
205+ helpers::platform_line(indoc! {"\
206+ top:
207+ baz: foo
208+ bazi:
209+ more: yes
210+ why: because
211+ quux:
212+ - 1
213+ - 2
214+ bax: foox
215+ fook:#[\n|]#
216+ "}),
217+ "o",
218+ helpers::platform_line(indoc! {"\
219+ top:
220+ baz: foo
221+ bazi:
222+ more: yes
223+ why: because
224+ quux:
225+ - 1
226+ - 2
227+ bax: foox
228+ fook:
229+ #[\n|]#
230+ "}),
231+ ),
232+ (
233+ helpers::platform_line(indoc! {"\
234+ top:
235+ baz: foo
236+ bax: |
237+ some
238+ multi
239+ line
240+ string#[\n|]#
241+ fook:
242+ "}),
243+ "o",
244+ helpers::platform_line(indoc! {"\
245+ top:
246+ baz: foo
247+ bax: |
248+ some
249+ multi
250+ line
251+ string
252+ #[\n|]#
253+ fook:
254+ "}),
255+ ),
256+ (
257+ helpers::platform_line(indoc! {"\
258+ top:
259+ baz: foo
260+ bax: >
261+ some
262+ multi
263+ line#[\n|]#
264+ string
265+ fook:
266+ "}),
267+ "o",
268+ helpers::platform_line(indoc! {"\
269+ top:
270+ baz: foo
271+ bax: >
272+ some
273+ multi
274+ line
275+ #[\n|]#
276+ string
277+ fook:
278+ "}),
279+ ),
280+ (
281+ helpers::platform_line(indoc! {"\
282+ top:
283+ baz: foo
284+ bax: >#[\n|]#
285+ fook:
286+ "}),
287+ "o",
288+ helpers::platform_line(indoc! {"\
289+ top:
290+ baz: foo
291+ bax: >
292+ #[\n|]#
293+ fook:
294+ "}),
295+ ),
296+ (
297+ helpers::platform_line(indoc! {"\
298+ - top:#[\n|]#
299+ baz: foo
300+ bax: foox
301+ fook:
302+ "}),
303+ "o",
304+ helpers::platform_line(indoc! {"\
305+ - top:
306+ #[\n|]#
307+ baz: foo
308+ bax: foox
309+ fook:
310+ "}),
311+ ),
312+ (
313+ helpers::platform_line(indoc! {"\
314+ - top:
315+ baz: foo#[\n|]#
316+ bax: foox
317+ fook:
318+ "}),
319+ "o",
320+ helpers::platform_line(indoc! {"\
321+ - top:
322+ baz: foo
323+ #[\n|]#
324+ bax: foox
325+ fook:
326+ "}),
327+ ),
328+ (
329+ helpers::platform_line(indoc! {"\
330+ - top:
331+ baz: foo
332+ bax: foox#[\n|]#
333+ fook:
334+ "}),
335+ "o",
336+ helpers::platform_line(indoc! {"\
337+ - top:
338+ baz: foo
339+ bax: foox
340+ #[\n|]#
341+ fook:
342+ "}),
343+ ),
344+ (
345+ helpers::platform_line(indoc! {"\
346+ top:
347+ baz:
348+ - one: two#[\n|]#
349+ three: four
350+ - top:
351+ baz: foo
352+ bax: foox
353+ "}),
354+ "o",
355+ helpers::platform_line(indoc! {"\
356+ top:
357+ baz:
358+ - one: two
359+ #[\n|]#
360+ three: four
361+ - top:
362+ baz: foo
363+ bax: foox
364+ "}),
365+ ),
366+ // yaml map without a key
367+ (
368+ helpers::platform_line(indoc! {"\
369+ top:#[\n|]#
370+ "}),
371+ "o",
372+ helpers::platform_line(indoc! {"\
373+ top:
374+ #[\n|]#
375+ "}),
376+ ),
377+ (
378+ helpers::platform_line(indoc! {"\
379+ top#[:|]#
380+ bottom: withvalue
381+ "}),
382+ "o",
383+ helpers::platform_line(indoc! {"\
384+ top:
385+ #[\n|]#
386+ bottom: withvalue
387+ "}),
388+ ),
389+ (
390+ helpers::platform_line(indoc! {"\
391+ bottom: withvalue
392+ top#[:|]#
393+ "}),
394+ "o",
395+ helpers::platform_line(indoc! {"\
396+ bottom: withvalue
397+ top:
398+ #[\n|]#
399+ "}),
400+ ),
401+ ];
402+
403+ for test in below_tests {
404+ test_with_config(app(), test).await?;
405+ }
406+
407+ let above_tests = [
408+ (
409+ helpers::platform_line(indoc! {r##"
410+ #[t|]#op:
411+ baz: foo
412+ bazi:
413+ more: yes
414+ why: because
415+ quux:
416+ - 1
417+ - 2
418+ bax: foox
419+ fook:
420+ "##}),
421+ "O",
422+ helpers::platform_line(indoc! {"\
423+ #[\n|]#
424+ top:
425+ baz: foo
426+ bazi:
427+ more: yes
428+ why: because
429+ quux:
430+ - 1
431+ - 2
432+ bax: foox
433+ fook:
434+ "}),
435+ ),
436+ (
437+ helpers::platform_line(indoc! {r##"
438+ top:
439+ b#[a|]#z: foo
440+ bazi:
441+ more: yes
442+ why: because
443+ quux:
444+ - 1
445+ - 2
446+ bax: foox
447+ fook:
448+ "##}),
449+ "O",
450+ helpers::platform_line(indoc! {"\
451+ top:
452+ #[\n|]#
453+ baz: foo
454+ bazi:
455+ more: yes
456+ why: because
457+ quux:
458+ - 1
459+ - 2
460+ bax: foox
461+ fook:
462+ "}),
463+ ),
464+ (
465+ helpers::platform_line(indoc! {r##"
466+ top:
467+ baz: foo
468+ bazi#[:|]#
469+ more: yes
470+ why: because
471+ quux:
472+ - 1
473+ - 2
474+ bax: foox
475+ fook:
476+ "##}),
477+ "O",
478+ helpers::platform_line(indoc! {"\
479+ top:
480+ baz: foo
481+ #[\n|]#
482+ bazi:
483+ more: yes
484+ why: because
485+ quux:
486+ - 1
487+ - 2
488+ bax: foox
489+ fook:
490+ "}),
491+ ),
492+ (
493+ helpers::platform_line(indoc! {r##"
494+ top:
495+ baz: foo
496+ bazi:
497+ more: #[yes|]#
498+ why: because
499+ quux:
500+ - 1
501+ - 2
502+ bax: foox
503+ fook:
504+ "##}),
505+ "O",
506+ helpers::platform_line(indoc! {"\
507+ top:
508+ baz: foo
509+ bazi:
510+ #[\n|]#
511+ more: yes
512+ why: because
513+ quux:
514+ - 1
515+ - 2
516+ bax: foox
517+ fook:
518+ "}),
519+ ),
520+ (
521+ helpers::platform_line(indoc! {r##"
522+ top:
523+ baz: foo
524+ bazi:
525+ more: yes
526+ why: becaus#[e|]#
527+ quux:
528+ - 1
529+ - 2
530+ bax: foox
531+ fook:
532+ "##}),
533+ "O",
534+ helpers::platform_line(indoc! {"\
535+ top:
536+ baz: foo
537+ bazi:
538+ more: yes
539+ #[\n|]#
540+ why: because
541+ quux:
542+ - 1
543+ - 2
544+ bax: foox
545+ fook:
546+ "}),
547+ ),
548+ (
549+ helpers::platform_line(indoc! {"\
550+ top:
551+ baz: foo
552+ bazi:
553+ more: yes
554+ why: because
555+ quux:#[\n|]#
556+ - 1
557+ - 2
558+ bax: foox
559+ fook:
560+ "}),
561+ "O",
562+ helpers::platform_line(indoc! {"\
563+ top:
564+ baz: foo
565+ bazi:
566+ more: yes
567+ why: because
568+ #[\n|]#
569+ quux:
570+ - 1
571+ - 2
572+ bax: foox
573+ fook:
574+ "}),
575+ ),
576+ (
577+ helpers::platform_line(indoc! {"\
578+ top:
579+ baz: foo
580+ bazi:
581+ more: yes
582+ why: because
583+ quux:
584+ - 1#[\n|]#
585+ - 2
586+ bax: foox
587+ fook:
588+ "}),
589+ "O",
590+ helpers::platform_line(indoc! {"\
591+ top:
592+ baz: foo
593+ bazi:
594+ more: yes
595+ why: because
596+ quux:
597+ #[\n|]#
598+ - 1
599+ - 2
600+ bax: foox
601+ fook:
602+ "}),
603+ ),
604+ (
605+ helpers::platform_line(indoc! {"\
606+ top:
607+ baz: foo
608+ bazi:
609+ more: yes
610+ why: because
611+ quux:
612+ - 1
613+ - 2
614+ bax: foox
615+ fook:#[\n|]#
616+ "}),
617+ "O",
618+ helpers::platform_line(indoc! {"\
619+ top:
620+ baz: foo
621+ bazi:
622+ more: yes
623+ why: because
624+ quux:
625+ - 1
626+ - 2
627+ bax: foox
628+ #[\n|]#
629+ fook:
630+ "}),
631+ ),
632+ (
633+ helpers::platform_line(indoc! {"\
634+ top:
635+ baz: foo
636+ bax: |
637+ some
638+ multi
639+ line
640+ string#[\n|]#
641+ fook:
642+ "}),
643+ "O",
644+ helpers::platform_line(indoc! {"\
645+ top:
646+ baz: foo
647+ bax: |
648+ some
649+ multi
650+ line
651+ #[\n|]#
652+ string
653+ fook:
654+ "}),
655+ ),
656+ (
657+ helpers::platform_line(indoc! {"\
658+ top:
659+ baz: foo
660+ bax: >
661+ some#[\n|]#
662+ multi
663+ line
664+ string
665+ fook:
666+ "}),
667+ "O",
668+ helpers::platform_line(indoc! {"\
669+ top:
670+ baz: foo
671+ bax: >
672+ #[\n|]#
673+ some
674+ multi
675+ line
676+ string
677+ fook:
678+ "}),
679+ ),
680+ (
681+ helpers::platform_line(indoc! {"\
682+ top:
683+ baz: foo
684+ bax: >
685+ fook:#[\n|]#
686+ "}),
687+ "O",
688+ helpers::platform_line(indoc! {"\
689+ top:
690+ baz: foo
691+ bax: >
692+ #[\n|]#
693+ fook:
694+ "}),
695+ ),
696+ (
697+ helpers::platform_line(indoc! {"\
698+ - top:
699+ baz: foo#[\n|]#
700+ bax: foox
701+ fook:
702+ "}),
703+ "O",
704+ helpers::platform_line(indoc! {"\
705+ - top:
706+ #[\n|]#
707+ baz: foo
708+ bax: foox
709+ fook:
710+ "}),
711+ ),
712+ (
713+ helpers::platform_line(indoc! {"\
714+ - top:
715+ baz: foo
716+ bax: foox
717+ fook:#[\n|]#
718+ "}),
719+ "O",
720+ helpers::platform_line(indoc! {"\
721+ - top:
722+ baz: foo
723+ bax: foox
724+ #[\n|]#
725+ fook:
726+ "}),
727+ ),
728+ (
729+ helpers::platform_line(indoc! {"\
730+ top:
731+ baz:
732+ - one: two#[\n|]#
733+ three: four
734+ - top:
735+ baz: foo
736+ bax: foox
737+ "}),
738+ "O",
739+ helpers::platform_line(indoc! {"\
740+ top:
741+ baz:
742+ #[\n|]#
743+ - one: two
744+ three: four
745+ - top:
746+ baz: foo
747+ bax: foox
748+ "}),
749+ ),
750+ // yaml map without a key
751+ (
752+ helpers::platform_line(indoc! {"\
753+ top:#[\n|]#
754+ "}),
755+ "O",
756+ helpers::platform_line(indoc! {"\
757+ #[\n|]#
758+ top:
759+ "}),
760+ ),
761+ (
762+ helpers::platform_line(indoc! {"\
763+ bottom: withvalue
764+ top#[:|]#
765+ "}),
766+ "O",
767+ helpers::platform_line(indoc! {"\
768+ bottom: withvalue
769+ #[\n|]#
770+ top:
771+ "}),
772+ ),
773+ (
774+ helpers::platform_line(indoc! {"\
775+ top:
776+ bottom:#[ |]#withvalue
777+ "}),
778+ "O",
779+ helpers::platform_line(indoc! {"\
780+ top:
781+ #[\n|]#
782+ bottom: withvalue
783+ "}),
784+ ),
785+ ];
786+
787+ for test in above_tests {
788+ test_with_config(app(), test).await?;
789+ }
790+
791+ let enter_tests = [
792+ (
793+ helpers::platform_line(indoc! {r##"
794+ foo: #[b|]#ar
795+ "##}),
796+ "i<ret>",
797+ helpers::platform_line(indoc! {"\
798+ foo:
799+ #[|b]#ar
800+ "}),
801+ ),
802+ (
803+ helpers::platform_line(indoc! {"\
804+ foo:#[\n|]#
805+ "}),
806+ "i<ret>",
807+ helpers::platform_line(indoc! {"\
808+ foo:
809+ #[|\n]#
810+ "}),
811+ ),
812+ ];
813+
814+ for test in enter_tests {
815+ test_with_config(app(), test).await?;
816+ }
817+
818+ Ok(())
819+}
helix-term/tests/test/movement.rs+39
@@ -513,3 +513,42 @@ async fn select_mode_tree_sitter_prev_function_goes_backwards_to_object() -> any
513513
514514 Ok(())
515515}
516+
517+#[tokio::test(flavor = "multi_thread")]
518+async fn find_char_line_ending() -> anyhow::Result<()> {
519+ test((
520+ helpers::platform_line(indoc! {
521+ "\
522+ one
523+ #[|t]#wo
524+ three"
525+ }),
526+ "T<ret>gll2f<ret>",
527+ helpers::platform_line(indoc! {
528+ "\
529+ one
530+ two#[
531+ |]#three"
532+ }),
533+ ))
534+ .await?;
535+
536+ test((
537+ helpers::platform_line(indoc! {
538+ "\
539+ #[|o]#ne
540+ two
541+ three"
542+ }),
543+ "f<ret>2t<ret>ghT<ret>F<ret>",
544+ helpers::platform_line(indoc! {
545+ "\
546+ one#[|
547+ t]#wo
548+ three"
549+ }),
550+ ))
551+ .await?;
552+
553+ Ok(())
554+}
helix-term/tests/test/splits.rs+3−3
@@ -62,9 +62,9 @@ async fn test_split_write_quit_all() -> anyhow::Result<()> {
6262 )
6363 .await?;
6464
65− helpers::assert_file_has_content(file1.as_file_mut(), "hello1")?;
66− helpers::assert_file_has_content(file2.as_file_mut(), "hello2")?;
67− helpers::assert_file_has_content(file3.as_file_mut(), "hello3")?;
65+ helpers::assert_file_has_content(file1.as_file_mut(), &platform_line("hello1"))?;
66+ helpers::assert_file_has_content(file2.as_file_mut(), &platform_line("hello2"))?;
67+ helpers::assert_file_has_content(file3.as_file_mut(), &platform_line("hello3"))?;
6868
6969 Ok(())
7070}
helix-tui/Cargo.toml+15−15
@@ -1,28 +1,28 @@
11[package]
22name = "helix-tui"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−description = """
6−A library to build rich terminal user interfaces or dashboards
7−"""
8−edition = "2021"
9−license = "MPL-2.0"
10−categories = ["editor"]
11−repository = "https://github.com/helix-editor/helix"
12−homepage = "https://helix-editor.com"
3+description = """A library to build rich terminal user interfaces or dashboards"""
134include = ["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
1413
1514[features]
1615default = ["crossterm"]
1716
1817[dependencies]
19−bitflags = "2.3"
18+helix-view = { path = "../helix-view", features = ["term"] }
19+helix-core = { path = "../helix-core" }
20+
21+bitflags = "2.4"
2022cassowary = "0.3"
2123unicode-segmentation = "1.10"
22−crossterm = { version = "0.26", optional = true }
24+crossterm = { version = "0.27", optional = true }
2325termini = "1.0"
2426serde = { version = "1", "optional" = true, features = ["derive"]}
25−once_cell = "1.18"
27+once_cell = "1.19"
2628log = "~0.4"
27−helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
28−helix-core = { version = "0.6", path = "../helix-core" }
helix-tui/src/backend/crossterm.rs+51−33
@@ -32,10 +32,21 @@ fn vte_version() -> Option<usize> {
3232}
3333
3434/// Describes terminal capabilities like extended underline, truecolor, etc.
35−#[derive(Copy, Clone, Debug, Default)]
35+#[derive(Clone, Debug)]
3636struct Capabilities {
3737 /// Support for undercurled, underdashed, etc.
3838 has_extended_underlines: bool,
39+ /// Support for resetting the cursor style back to normal.
40+ reset_cursor_command: String,
41+}
42+
43+impl Default for Capabilities {
44+ fn default() -> Self {
45+ Self {
46+ has_extended_underlines: false,
47+ reset_cursor_command: "\x1B[0 q".to_string(),
48+ }
49+ }
3950}
4051
4152impl Capabilities {
@@ -54,6 +65,10 @@ impl Capabilities {
5465 || t.extended_cap("Su").is_some()
5566 || vte_version() >= Some(5102)
5667 || matches!(term_program().as_deref(), Some("WezTerm")),
68+ reset_cursor_command: t
69+ .utf8_string_cap(termini::StringCapability::CursorNormal)
70+ .unwrap_or("\x1B[0 q")
71+ .to_string(),
5772 },
5873 }
5974 }
@@ -154,7 +169,8 @@ where
154169
155170 fn restore(&mut self, config: Config) -> io::Result<()> {
156171 // reset cursor shape
157− write!(self.buffer, "\x1B[0 q")?;
172+ self.buffer
173+ .write_all(self.capabilities.reset_cursor_command.as_bytes())?;
158174 if config.enable_mouse_capture {
159175 execute!(self.buffer, DisableMouseCapture)?;
160176 }
@@ -201,7 +217,7 @@ where
201217 for (x, y, cell) in content {
202218 // Move the cursor if the previous location was not (x - 1, y)
203219 if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
204− map_error(queue!(self.buffer, MoveTo(x, y)))?;
220+ queue!(self.buffer, MoveTo(x, y))?;
205221 }
206222 last_pos = Some((x, y));
207223 if cell.modifier != modifier {
@@ -214,12 +230,12 @@ where
214230 }
215231 if cell.fg != fg {
216232 let color = CColor::from(cell.fg);
217− map_error(queue!(self.buffer, SetForegroundColor(color)))?;
233+ queue!(self.buffer, SetForegroundColor(color))?;
218234 fg = cell.fg;
219235 }
220236 if cell.bg != bg {
221237 let color = CColor::from(cell.bg);
222− map_error(queue!(self.buffer, SetBackgroundColor(color)))?;
238+ queue!(self.buffer, SetBackgroundColor(color))?;
223239 bg = cell.bg;
224240 }
225241
@@ -227,7 +243,7 @@ where
227243 if self.capabilities.has_extended_underlines {
228244 if cell.underline_color != underline_color {
229245 let color = CColor::from(cell.underline_color);
230− map_error(queue!(self.buffer, SetUnderlineColor(color)))?;
246+ queue!(self.buffer, SetUnderlineColor(color))?;
231247 underline_color = cell.underline_color;
232248 }
233249 } else {
@@ -239,24 +255,24 @@ where
239255
240256 if new_underline_style != underline_style {
241257 let attr = CAttribute::from(new_underline_style);
242− map_error(queue!(self.buffer, SetAttribute(attr)))?;
258+ queue!(self.buffer, SetAttribute(attr))?;
243259 underline_style = new_underline_style;
244260 }
245261
246− map_error(queue!(self.buffer, Print(&cell.symbol)))?;
262+ queue!(self.buffer, Print(&cell.symbol))?;
247263 }
248264
249− map_error(queue!(
265+ queue!(
250266 self.buffer,
251267 SetUnderlineColor(CColor::Reset),
252268 SetForegroundColor(CColor::Reset),
253269 SetBackgroundColor(CColor::Reset),
254270 SetAttribute(CAttribute::Reset)
255− ))
271+ )
256272 }
257273
258274 fn hide_cursor(&mut self) -> io::Result<()> {
259− map_error(execute!(self.buffer, Hide))
275+ execute!(self.buffer, Hide)
260276 }
261277
262278 fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> {
@@ -266,7 +282,7 @@ where
266282 CursorKind::Underline => SetCursorStyle::SteadyUnderScore,
267283 CursorKind::Hidden => unreachable!(),
268284 };
269− map_error(execute!(self.buffer, Show, shape))
285+ execute!(self.buffer, Show, shape)
270286 }
271287
272288 fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
@@ -275,11 +291,11 @@ where
275291 }
276292
277293 fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
278− map_error(execute!(self.buffer, MoveTo(x, y)))
294+ execute!(self.buffer, MoveTo(x, y))
279295 }
280296
281297 fn clear(&mut self) -> io::Result<()> {
282− map_error(execute!(self.buffer, Clear(ClearType::All)))
298+ execute!(self.buffer, Clear(ClearType::All))
283299 }
284300
285301 fn size(&self) -> io::Result<Rect> {
@@ -294,10 +310,6 @@ where
294310 }
295311}
296312
297−fn map_error(error: crossterm::Result<()>) -> io::Result<()> {
298− error.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
299−}
300−
301313#[derive(Debug)]
302314struct ModifierDiff {
303315 pub from: Modifier,
@@ -312,48 +324,54 @@ impl ModifierDiff {
312324 //use crossterm::Attribute;
313325 let removed = self.from - self.to;
314326 if removed.contains(Modifier::REVERSED) {
315− map_error(queue!(w, SetAttribute(CAttribute::NoReverse)))?;
327+ queue!(w, SetAttribute(CAttribute::NoReverse))?;
316328 }
317329 if removed.contains(Modifier::BOLD) {
318− map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
330+ queue!(w, SetAttribute(CAttribute::NormalIntensity))?;
319331 if self.to.contains(Modifier::DIM) {
320− map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
332+ queue!(w, SetAttribute(CAttribute::Dim))?;
321333 }
322334 }
323335 if removed.contains(Modifier::ITALIC) {
324− map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?;
336+ queue!(w, SetAttribute(CAttribute::NoItalic))?;
325337 }
326338 if removed.contains(Modifier::DIM) {
327− map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
339+ queue!(w, SetAttribute(CAttribute::NormalIntensity))?;
328340 }
329341 if removed.contains(Modifier::CROSSED_OUT) {
330− map_error(queue!(w, SetAttribute(CAttribute::NotCrossedOut)))?;
342+ queue!(w, SetAttribute(CAttribute::NotCrossedOut))?;
331343 }
332344 if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
333− map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
345+ queue!(w, SetAttribute(CAttribute::NoBlink))?;
346+ }
347+ if removed.contains(Modifier::HIDDEN) {
348+ queue!(w, SetAttribute(CAttribute::NoHidden))?;
334349 }
335350
336351 let added = self.to - self.from;
337352 if added.contains(Modifier::REVERSED) {
338− map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
353+ queue!(w, SetAttribute(CAttribute::Reverse))?;
339354 }
340355 if added.contains(Modifier::BOLD) {
341− map_error(queue!(w, SetAttribute(CAttribute::Bold)))?;
356+ queue!(w, SetAttribute(CAttribute::Bold))?;
342357 }
343358 if added.contains(Modifier::ITALIC) {
344− map_error(queue!(w, SetAttribute(CAttribute::Italic)))?;
359+ queue!(w, SetAttribute(CAttribute::Italic))?;
345360 }
346361 if added.contains(Modifier::DIM) {
347− map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
362+ queue!(w, SetAttribute(CAttribute::Dim))?;
348363 }
349364 if added.contains(Modifier::CROSSED_OUT) {
350− map_error(queue!(w, SetAttribute(CAttribute::CrossedOut)))?;
365+ queue!(w, SetAttribute(CAttribute::CrossedOut))?;
351366 }
352367 if added.contains(Modifier::SLOW_BLINK) {
353− map_error(queue!(w, SetAttribute(CAttribute::SlowBlink)))?;
368+ queue!(w, SetAttribute(CAttribute::SlowBlink))?;
354369 }
355370 if added.contains(Modifier::RAPID_BLINK) {
356− map_error(queue!(w, SetAttribute(CAttribute::RapidBlink)))?;
371+ queue!(w, SetAttribute(CAttribute::RapidBlink))?;
372+ }
373+ if added.contains(Modifier::HIDDEN) {
374+ queue!(w, SetAttribute(CAttribute::Hidden))?;
357375 }
358376
359377 Ok(())
@@ -407,7 +425,7 @@ impl Command for SetUnderlineColor {
407425 }
408426
409427 #[cfg(windows)]
410− fn execute_winapi(&self) -> crossterm::Result<()> {
428+ fn execute_winapi(&self) -> io::Result<()> {
411429 Err(std::io::Error::new(
412430 std::io::ErrorKind::Other,
413431 "SetUnderlineColor not supported by winapi.",
helix-tui/src/buffer.rs+2−2
@@ -213,7 +213,7 @@ impl Buffer {
213213 && y < self.area.bottom()
214214 }
215215
216− /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates.
216+ /// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates.
217217 ///
218218 /// Global coordinates are offset by the Buffer's area offset (`x`/`y`).
219219 ///
@@ -242,7 +242,7 @@ impl Buffer {
242242 ((y - self.area.y) as usize) * (self.area.width as usize) + ((x - self.area.x) as usize)
243243 }
244244
245− /// Returns the index in the Vec<Cell> for the given global (x, y) coordinates,
245+ /// Returns the index in the `Vec<Cell>` for the given global (x, y) coordinates,
246246 /// or `None` if the coordinates are outside the buffer's area.
247247 fn index_of_opt(&self, x: u16, y: u16) -> Option<usize> {
248248 if self.in_bounds(x, y) {
helix-tui/src/terminal.rs−14
@@ -65,20 +65,6 @@ where
6565 viewport: Viewport,
6666}
6767
68−impl<B> Drop for Terminal<B>
69−where
70− B: Backend,
71−{
72− fn drop(&mut self) {
73− // Attempt to restore the cursor state
74− if self.cursor_kind == CursorKind::Hidden {
75− if let Err(err) = self.show_cursor(CursorKind::Block) {
76− eprintln!("Failed to show the cursor: {}", err);
77− }
78− }
79− }
80−}
81−
8268impl<B> Terminal<B>
8369where
8470 B: Backend,
helix-vcs/Cargo.toml+12−10
@@ -1,23 +1,25 @@
11[package]
22name = "helix-vcs"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
7−categories = ["editor"]
8−repository = "https://github.com/helix-editor/helix"
9−homepage = "https://helix-editor.com"
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
1011
1112# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1213
1314[dependencies]
14−helix-core = { version = "0.6", path = "../helix-core" }
15+helix-core = { path = "../helix-core" }
16+helix-event = { path = "../helix-event" }
1517
1618tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot", "macros"] }
1719parking_lot = "0.12"
1820arc-swap = { version = "1.6.0" }
1921
20−gix = { version = "0.48.0", default-features = false , optional = true }
22+gix = { version = "0.57.0", default-features = false , optional = true }
2123imara-diff = "0.1.5"
2224anyhow = "1"
2325
@@ -27,4 +29,4 @@ log = "0.4"
2729git = ["gix"]
2830
2931[dev-dependencies]
30−tempfile = "3.6"
32+tempfile = "3.8"
helix-vcs/src/diff.rs+6−19
@@ -2,10 +2,10 @@ use std::ops::Range;
22use std::sync::Arc;
33
44use helix_core::Rope;
5+use helix_event::RenderLockGuard;
56use imara_diff::Algorithm;
67use parking_lot::{Mutex, MutexGuard};
78use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
8−use tokio::sync::{Notify, OwnedRwLockReadGuard, RwLock};
99use tokio::task::JoinHandle;
1010use tokio::time::Instant;
1111
@@ -14,11 +14,9 @@ use crate::diff::worker::DiffWorker;
1414mod line_cache;
1515mod worker;
1616
17−type RedrawHandle = (Arc<Notify>, Arc<RwLock<()>>);
18−
1917/// A rendering lock passed to the differ the prevents redraws from occurring
2018struct RenderLock {
21− pub lock: OwnedRwLockReadGuard<()>,
19+ pub lock: RenderLockGuard,
2220 pub timeout: Option<Instant>,
2321}
2422
@@ -38,28 +36,22 @@ struct DiffInner {
3836#[derive(Clone, Debug)]
3937pub struct DiffHandle {
4038 channel: UnboundedSender<Event>,
41− render_lock: Arc<RwLock<()>>,
4239 diff: Arc<Mutex<DiffInner>>,
4340 inverted: bool,
4441}
4542
4643impl DiffHandle {
47− pub fn new(diff_base: Rope, doc: Rope, redraw_handle: RedrawHandle) -> DiffHandle {
48− DiffHandle::new_with_handle(diff_base, doc, redraw_handle).0
44+ pub fn new(diff_base: Rope, doc: Rope) -> DiffHandle {
45+ DiffHandle::new_with_handle(diff_base, doc).0
4946 }
5047
51− fn new_with_handle(
52− diff_base: Rope,
53− doc: Rope,
54− redraw_handle: RedrawHandle,
55− ) -> (DiffHandle, JoinHandle<()>) {
48+ fn new_with_handle(diff_base: Rope, doc: Rope) -> (DiffHandle, JoinHandle<()>) {
5649 let (sender, receiver) = unbounded_channel();
5750 let diff: Arc<Mutex<DiffInner>> = Arc::default();
5851 let worker = DiffWorker {
5952 channel: receiver,
6053 diff: diff.clone(),
6154 new_hunks: Vec::default(),
62− redraw_notify: redraw_handle.0,
6355 diff_finished_notify: Arc::default(),
6456 };
6557 let handle = tokio::spawn(worker.run(diff_base, doc));
@@ -67,7 +59,6 @@ impl DiffHandle {
6759 channel: sender,
6860 diff,
6961 inverted: false,
70− render_lock: redraw_handle.1,
7162 };
7263 (differ, handle)
7364 }
@@ -87,11 +78,7 @@ impl DiffHandle {
8778 /// This function is only intended to be called from within the rendering loop
8879 /// if called from elsewhere it may fail to acquire the render lock and panic
8980 pub fn update_document(&self, doc: Rope, block: bool) -> bool {
90− // unwrap is ok here because the rendering lock is
91− // only exclusively locked during redraw.
92− // This function is only intended to be called
93− // from the core rendering loop where no redraw can happen in parallel
94− let lock = self.render_lock.clone().try_read_owned().unwrap();
81+ let lock = helix_event::lock_frame();
9582 let timeout = if block {
9683 None
9784 } else {
helix-vcs/src/diff/worker.rs+3−9
@@ -23,7 +23,6 @@ pub(super) struct DiffWorker {
2323 pub channel: UnboundedReceiver<Event>,
2424 pub diff: Arc<Mutex<DiffInner>>,
2525 pub new_hunks: Vec<Hunk>,
26− pub redraw_notify: Arc<Notify>,
2726 pub diff_finished_notify: Arc<Notify>,
2827}
2928
@@ -32,11 +31,7 @@ impl DiffWorker {
3231 let mut accumulator = EventAccumulator::new();
3332 accumulator.handle_event(event).await;
3433 accumulator
35− .accumulate_debounced_events(
36− &mut self.channel,
37− self.redraw_notify.clone(),
38− self.diff_finished_notify.clone(),
39− )
34+ .accumulate_debounced_events(&mut self.channel, self.diff_finished_notify.clone())
4035 .await;
4136 (accumulator.doc, accumulator.diff_base)
4237 }
@@ -137,7 +132,6 @@ impl<'a> EventAccumulator {
137132 async fn accumulate_debounced_events(
138133 &mut self,
139134 channel: &mut UnboundedReceiver<Event>,
140− redraw_notify: Arc<Notify>,
141135 diff_finished_notify: Arc<Notify>,
142136 ) {
143137 let async_debounce = Duration::from_millis(DIFF_DEBOUNCE_TIME_ASYNC);
@@ -164,7 +158,7 @@ impl<'a> EventAccumulator {
164158 None => {
165159 tokio::spawn(async move {
166160 diff_finished_notify.notified().await;
167− redraw_notify.notify_one();
161+ helix_event::request_redraw();
168162 });
169163 }
170164 // diff is performed inside the rendering loop
@@ -190,7 +184,7 @@ impl<'a> EventAccumulator {
190184 // and wait until the diff occurs to trigger an async redraw
191185 log::info!("Diff computation timed out, update of diffs might appear delayed");
192186 diff_finished_notify.notified().await;
193− redraw_notify.notify_one();
187+ helix_event::request_redraw()
194188 });
195189 }
196190 // a blocking diff is performed inside the rendering loop
helix-vcs/src/diff/worker/test.rs+1−5
@@ -5,11 +5,7 @@ use crate::diff::{DiffHandle, Hunk};
55
66impl DiffHandle {
77 fn new_test(diff_base: &str, doc: &str) -> (DiffHandle, JoinHandle<()>) {
8− DiffHandle::new_with_handle(
9− Rope::from_str(diff_base),
10− Rope::from_str(doc),
11− Default::default(),
12− )
8+ DiffHandle::new_with_handle(Rope::from_str(diff_base), Rope::from_str(doc))
139 }
1410 async fn into_diff(self, handle: JoinHandle<()>) -> Vec<Hunk> {
1511 let diff = self.diff;
helix-vcs/src/git.rs+5−5
@@ -3,7 +3,7 @@ use arc_swap::ArcSwap;
33use std::path::Path;
44use std::sync::Arc;
55
6−use gix::objs::tree::EntryMode;
6+use gix::objs::tree::EntryKind;
77use gix::sec::trust::DefaultForLevel;
88use gix::{Commit, ObjectId, Repository, ThreadSafeRepository};
99
@@ -126,14 +126,14 @@ fn find_file_in_commit(repo: &Repository, commit: &Commit, file: &Path) -> Resul
126126 let rel_path = file.strip_prefix(repo_dir)?;
127127 let tree = commit.tree()?;
128128 let tree_entry = tree
129− .lookup_entry_by_path(rel_path)?
129+ .lookup_entry_by_path(rel_path, &mut Vec::new())?
130130 .context("file is untracked")?;
131− match tree_entry.mode() {
131+ match tree_entry.mode().kind() {
132132 // not a file, everything is new, do not show diff
133− mode @ (EntryMode::Tree | EntryMode::Commit | EntryMode::Link) => {
133+ mode @ (EntryKind::Tree | EntryKind::Commit | EntryKind::Link) => {
134134 bail!("entry at {} is not a file but a {mode:?}", file.display())
135135 }
136136 // found a file
137− EntryMode::Blob | EntryMode::BlobExecutable => Ok(tree_entry.object_id()),
137+ EntryKind::Blob | EntryKind::BlobExecutable => Ok(tree_entry.object_id()),
138138 }
139139}
helix-view/Cargo.toml+22−18
@@ -1,31 +1,34 @@
11[package]
22name = "helix-view"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
73description = "UI abstractions for use in backends"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
4+version.workspace = true
5+authors.workspace = true
6+edition.workspace = true
7+license.workspace = true
8+rust-version.workspace = true
9+categories.workspace = true
10+repository.workspace = true
11+homepage.workspace = true
1112
1213[features]
1314default = []
1415term = ["crossterm"]
1516
1617[dependencies]
17−bitflags = "2.3"
18+helix-core = { path = "../helix-core" }
19+helix-event = { path = "../helix-event" }
20+helix-loader = { path = "../helix-loader" }
21+helix-lsp = { path = "../helix-lsp" }
22+helix-dap = { path = "../helix-dap" }
23+helix-vcs = { path = "../helix-vcs" }
24+
25+bitflags = "2.4"
1826anyhow = "1"
19−helix-core = { version = "0.6", path = "../helix-core" }
20−helix-loader = { version = "0.6", path = "../helix-loader" }
21−helix-lsp = { version = "0.6", path = "../helix-lsp" }
22−helix-dap = { version = "0.6", path = "../helix-dap" }
23−crossterm = { version = "0.26", optional = true }
24−helix-vcs = { version = "0.6", path = "../helix-vcs" }
27+crossterm = { version = "0.27", optional = true }
2528
2629# Conversion traits
27−once_cell = "1.18"
28−url = "2"
30+once_cell = "1.19"
31+url = "2.5.0"
2932
3033arc-swap = { version = "1.6.0" }
3134
@@ -42,15 +45,16 @@ serde_json = "1.0"
4245toml = "0.7"
4346log = "~0.4"
4447
45−which = "4.4"
48+which = "5.0.0"
4649parking_lot = "0.12.1"
4750
4851
4952[target.'cfg(windows)'.dependencies]
50−clipboard-win = { version = "4.5", features = ["std"] }
53+clipboard-win = { version = "5.0", features = ["std"] }
5154
5255[target.'cfg(unix)'.dependencies]
5356libc = "0.2"
57+rustix = { version = "0.38", features = ["fs"] }
5458
5559[dev-dependencies]
5660helix-tui = { path = "../helix-tui" }
helix-view/src/clipboard.rs+7−2
@@ -73,9 +73,14 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
7373
7474#[cfg(target_os = "macos")]
7575pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
76− use crate::env::binary_exists;
76+ use crate::env::{binary_exists, env_var_is_set};
7777
78− if binary_exists("pbcopy") && binary_exists("pbpaste") {
78+ if env_var_is_set("TMUX") && binary_exists("tmux") {
79+ command_provider! {
80+ paste => "tmux", "save-buffer", "-";
81+ copy => "tmux", "load-buffer", "-w", "-";
82+ }
83+ } else if binary_exists("pbcopy") && binary_exists("pbpaste") {
7984 command_provider! {
8085 paste => "pbpaste";
8186 copy => "pbcopy";
helix-view/src/document.rs+122−46

This diff is too big to show here.

helix-view/src/editor.rs+170−91

This diff is too big to show here.

helix-view/src/gutter.rs+4−5

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

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

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+581−210

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.scm+5

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/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/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/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/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/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/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+317−231

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.scm+108−26

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/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−81

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/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+62−65

This diff is too big to show here.

runtime/themes/autumn_night.toml+6−6

This diff is too big to show here.

runtime/themes/catppuccin_frappe.toml+2

This diff is too big to show here.

runtime/themes/catppuccin_latte.toml+2

This diff is too big to show here.

runtime/themes/catppuccin_macchiato.toml+2

This diff is too big to show here.

runtime/themes/catppuccin_mocha.toml+11

This diff is too big to show here.

runtime/themes/cyan_light.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/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_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/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/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/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/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.