on master help sign in
409 files changed+22896−10646
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+378−1 Cargo.lock+728−639 Cargo.toml+16 README.md+2−2 VERSION−1 book/book.toml+5−1 book/custom.css+231 book/src/SUMMARY.md+1 book/src/configuration.md+45−14 book/src/generated/lang-support.md+47−21 book/src/generated/typable-cmd.md+15−7 book/src/guides/adding_languages.md+3 book/src/guides/indent.md+319−43 book/src/guides/injection.md+57 book/src/install.md+97−36 book/src/keymap.md+29−19 book/src/languages.md+104−28 book/src/themes.md+10−2 book/src/usage.md+26−10 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+18 contrib/Helix.desktop+3−1 docs/CONTRIBUTING.md+10−3 docs/releases.md+13−6 docs/vision.md+1−1 flake.lock+38−235 flake.nix+171−140 grammars.nix+24−21 helix-core/Cargo.toml+21−17 helix-core/src/diagnostic.rs+5 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+617−145 helix-core/src/lib.rs+5−3 helix-core/src/line_ending.rs+2−2 helix-core/src/match_brackets.rs+232−29 helix-core/src/movement.rs+188−8 helix-core/src/path.rs+5−7 helix-core/src/register.rs−81 helix-core/src/rope_reader.rs+37 helix-core/src/selection.rs+76−43 helix-core/src/surround.rs+1−6 helix-core/src/syntax.rs+407−168 helix-core/src/transaction.rs+242−51 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-dap/src/client.rs+4−6 helix-dap/src/transport.rs+28−18 helix-event/Cargo.toml+16 helix-event/src/lib.rs+8 helix-event/src/redraw.rs+49 helix-loader/Cargo.toml+16−12 helix-loader/build.rs+56−5 helix-loader/src/grammar.rs+121−46 helix-loader/src/lib.rs+95−34 helix-lsp/Cargo.toml+16−14 helix-lsp/src/client.rs+230−29 helix-lsp/src/file_event.rs+193 helix-lsp/src/lib.rs+152−115 helix-lsp/src/transport.rs+52−21 helix-parsec/Cargo.toml+8−7 helix-term/Cargo.toml+33−25 helix-term/src/application.rs+301−190 helix-term/src/args.rs+29 helix-term/src/commands.rs+1098−645 helix-term/src/commands/dap.rs+59−56 helix-term/src/commands/lsp.rs+509−487 helix-term/src/commands/typed.rs+1073−715 helix-term/src/compositor.rs+12 helix-term/src/config.rs+8−8 helix-term/src/health.rs+48−12 helix-term/src/keymap.rs+105−145 helix-term/src/keymap/default.rs+14−7 helix-term/src/keymap/macros.rs+3−4 helix-term/src/main.rs+22−18 helix-term/src/ui/completion.rs+135−75 helix-term/src/ui/document.rs+60−24 helix-term/src/ui/editor.rs+157−84 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+23−14 helix-term/src/ui/menu.rs+73−40 helix-term/src/ui/mod.rs+108−179 helix-term/src/ui/picker.rs+629−594 helix-term/src/ui/popup.rs+27−7 helix-term/src/ui/prompt.rs+55−48 helix-term/src/ui/statusline.rs+30−7 helix-term/tests/integration.rs+1 helix-term/tests/test/auto_pairs.rs+1−1 helix-term/tests/test/commands.rs+189−48 helix-term/tests/test/commands/movement.rs+452 helix-term/tests/test/commands/write.rs+159−2 helix-term/tests/test/helpers.rs+10−2 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+16−16 helix-tui/src/backend/crossterm.rs+67−33 helix-tui/src/backend/mod.rs+1 helix-tui/src/backend/test.rs+4 helix-tui/src/buffer.rs+3−3 helix-tui/src/terminal.rs+4−14 helix-tui/src/widgets/table.rs+3−3 helix-vcs/Cargo.toml+12−10 helix-vcs/src/diff.rs+6−19 helix-vcs/src/diff/line_cache.rs+9−9 helix-vcs/src/diff/worker.rs+3−9 helix-vcs/src/diff/worker/test.rs+1−5 helix-vcs/src/git.rs+21−16 helix-vcs/src/lib.rs+4−4 helix-view/Cargo.toml+22−18 helix-view/src/clipboard.rs+8−3 helix-view/src/document.rs+517−151 helix-view/src/editor.rs+277−93 helix-view/src/gutter.rs+21−24 helix-view/src/handlers/dap.rs+2−3 helix-view/src/info.rs+5−25 helix-view/src/input.rs+67−3 helix-view/src/lib.rs+1 helix-view/src/register.rs+333 helix-view/src/theme.rs+25−4 helix-view/src/tree.rs+1−2 helix-view/src/view.rs+8−2 languages.toml+897−396 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+61 runtime/queries/c/indents.scm+18−1 runtime/queries/cabal/highlights.scm−15 runtime/queries/cairo/highlights.scm+1−94 runtime/queries/cairo/indents.scm+1 runtime/queries/cairo/injections.scm+2−4 runtime/queries/cairo/locals.scm+1 runtime/queries/cairo/textobjects.scm+1 runtime/queries/comment/highlights.scm+8−8 runtime/queries/common-lisp/indents.scm+1 runtime/queries/cpp/highlights.scm+1−1 runtime/queries/dart/highlights.scm+3−1 runtime/queries/dbml/highlights.scm+44 runtime/queries/dockerfile/highlights.scm+4−1 runtime/queries/ecma/README.md+14 runtime/queries/ecma/highlights.scm+9−2 runtime/queries/ecma/locals.scm+17−3 runtime/queries/forth/highlights.scm+7 runtime/queries/fortran/injections.scm+2 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/indents.scm+16−4 runtime/queries/go/locals.scm+11−3 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/highlights.scm+81−2 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/just/folds.scm+4 runtime/queries/just/highlights.scm+33 runtime/queries/just/indents.scm+3 runtime/queries/just/injections.scm+16 runtime/queries/just/locals.scm+10 runtime/queries/just/textobjects.scm+48 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/markdown/injections.scm+4 runtime/queries/matlab/context.scm+41 runtime/queries/matlab/folds.scm+11 runtime/queries/matlab/highlights.scm+108−77 runtime/queries/matlab/indents.scm+23 runtime/queries/matlab/injections.scm+2 runtime/queries/matlab/locals.scm+19 runtime/queries/matlab/textobjects.scm+9 runtime/queries/nickel/highlights.scm+2−2 runtime/queries/nickel/indents.scm+1−1 runtime/queries/nickel/injections.scm+3 runtime/queries/nix/highlights.scm+4−1 runtime/queries/nix/injections.scm+29−3 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/odin/highlights.scm+18−133 runtime/queries/odin/indents.scm+16 runtime/queries/odin/injections.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+10−31 runtime/queries/rust/locals.scm+14−1 runtime/queries/rust/textobjects.scm+4−1 runtime/queries/scala/locals.scm+29 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+326−176 runtime/queries/strace/highlights.scm+8 runtime/queries/svelte/indents.scm+18 runtime/queries/svelte/injections.scm+10 runtime/queries/t32/highlights.scm+227 runtime/queries/t32/injections.scm+2 runtime/queries/templ/highlights.scm+99 runtime/queries/templ/injections.scm+4 runtime/queries/todotxt/highlights.scm+6 runtime/queries/toml/highlights.scm+15−2 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−76 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/webc/highlights.scm+1 runtime/queries/webc/injections.scm+1 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/amberwood.toml+138 runtime/themes/autumn.toml+67−65 runtime/themes/autumn_night.toml+9−8 runtime/themes/ayu_dark.toml+5−1 runtime/themes/ayu_light.toml+5−1 runtime/themes/ayu_mirage.toml+5−1 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+86−93 runtime/themes/everblush.toml+1 runtime/themes/everforest_dark.toml+4 runtime/themes/ferra.toml+2−3 runtime/themes/gruber-darker.toml+114 runtime/themes/gruvbox.toml+1 runtime/themes/gruvbox_dark_hard.toml−2 runtime/themes/gruvbox_dark_soft.toml+7 runtime/themes/gruvbox_light.toml−2 runtime/themes/horizon-dark.toml+80 runtime/themes/jellybeans.toml+1−1 runtime/themes/kanagawa.toml+3 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/merionette.toml+98 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+8−4 runtime/themes/noctis.toml+34−32 runtime/themes/nord-night.toml+23 runtime/themes/nord.toml+162−105 runtime/themes/onedark.toml+1 runtime/themes/onedarker.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/spacebones_light.toml+1 runtime/themes/starlight.toml+113 runtime/themes/vim_dark_high_contrast.toml+72 runtime/themes/yellowed.toml+135 runtime/themes/zed_onedark.toml+110 runtime/themes/zed_onelight.toml+55 runtime/themes/zenburn.toml+1−1 runtime/tutor+165−11 rust-toolchain.toml+2−2 xtask/Cargo.toml+12−6 xtask/src/docgen.rs+6−5
.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@v20
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+378−1
@@ -1,3 +1,380 @@
1+# 23.10 (2023-10-24)
2+
3+A big shout out to all the contributors! We had 118 contributors in this release.
4+
5+Breaking changes:
6+
7+- Support multiple language servers per language ([#2507](https://github.com/helix-editor/helix/pull/2507))
8+ - This is a breaking change to language configuration
9+
10+Features:
11+
12+- Support multiple language servers per language ([#2507](https://github.com/helix-editor/helix/pull/2507), [#7082](https://github.com/helix-editor/helix/pull/7082), [#7286](https://github.com/helix-editor/helix/pull/7286), [#8374](https://github.com/helix-editor/helix/pull/8374))
13+- Add a statusline element for the selected register ([#7222](https://github.com/helix-editor/helix/pull/7222))
14+- Add `%`, `#`, `.`, `*` and `+` special registers ([#6985](https://github.com/helix-editor/helix/pull/6985))
15+- Add initial support for LSP DidChangeWatchedFiles notifications ([#7665](https://github.com/helix-editor/helix/pull/7665))
16+- Search buffer contents in `global_search` ([#5652](https://github.com/helix-editor/helix/pull/5652))
17+- Add a "smart tab" command that intelligently jumps the cursor on tab ([#4443](https://github.com/helix-editor/helix/pull/4443))
18+- Add a statusline element for whether a file is read-only ([#7222](https://github.com/helix-editor/helix/pull/7222), [#7875](https://github.com/helix-editor/helix/pull/7875))
19+- Syntax highlight regex prompts ([#7738](https://github.com/helix-editor/helix/pull/7738))
20+- Allow defining alignment in indent queries ([#5355](https://github.com/helix-editor/helix/pull/5355))
21+- Show visual feedback in `surround_replace` ([#7588](https://github.com/helix-editor/helix/pull/7588))
22+- Switch to Nucleo for fuzzy matching ([#7814](https://github.com/helix-editor/helix/pull/7814), [#8148](https://github.com/helix-editor/helix/pull/8148), [#8192](https://github.com/helix-editor/helix/pull/8192), [#8194](https://github.com/helix-editor/helix/pull/8194))
23+- Insert a trailing newline on write ([#8157](https://github.com/helix-editor/helix/pull/8157))
24+- Add a `-w`/`--working-dir` CLI flag for specifying a working directory on startup ([#8223](https://github.com/helix-editor/helix/pull/8223), [#8498](https://github.com/helix-editor/helix/pull/8498), [#8520](https://github.com/helix-editor/helix/pull/8520))
25+- Accept a `+N` CLI argument to set the first file's line number ([#8521](https://github.com/helix-editor/helix/pull/8521))
26+- Accept Helix-specific ignore files in `.helix/ignore` and `~/.config/helix/ignore` ([#8099](https://github.com/helix-editor/helix/pull/8099))
27+
28+Commands:
29+
30+- `merge_selections` (`A-minus`) - merge all selections into one selection that covers all ranges ([#7053](https://github.com/helix-editor/helix/pull/7053))
31+- `move_prev_long_word_end` and `extend_prev_long_word_end` - move/extend to the end of the previous WORD ([#6905](https://github.com/helix-editor/helix/pull/6905))
32+- `reverse_selection_contents` - swaps the values of each selection so they are reversed ([#7329](https://github.com/helix-editor/helix/pull/7329))
33+- Add `:rl` and `:rla` aliases for `:reload` and `:reload-all` ([#7158](https://github.com/helix-editor/helix/pull/7158))
34+- `yank_joined` - join the selections and yank to the selected register ([#7195](https://github.com/helix-editor/helix/pull/7195))
35+- `:write-all!` (`:wa!`) - forcibly write all buffers to disk and create any necessary subdirectories ([#7577](https://github.com/helix-editor/helix/pull/7577))
36+- `:redraw` - clear re-render the UI ([#6949](https://github.com/helix-editor/helix/pull/6949))
37+- `:tree-sitter-highlight-name` - show the theme scope name of the highlight under the cursor ([#8170](https://github.com/helix-editor/helix/pull/8170))
38+
39+Usability improvements:
40+
41+- Allow cycling option values at runtime ([#4411](https://github.com/helix-editor/helix/pull/4411), [#7240](https://github.com/helix-editor/helix/pull/7240), [#7877](https://github.com/helix-editor/helix/pull/7877))
42+- Exit gracefully on termination signals ([#7236](https://github.com/helix-editor/helix/pull/7236))
43+- Add plaintext matching fallback to tree-sitter pair matching ([#4288](https://github.com/helix-editor/helix/pull/4288))
44+- Persist register selection in pending keymaps ([0e08349](https://github.com/helix-editor/helix/commit/0e08349))
45+- Propagate the count and register to command palette commands ([b394997](https://github.com/helix-editor/helix/commit/b394997))
46+- Auto indent on `insert_at_line_start` ([#5837](https://github.com/helix-editor/helix/pull/5837))
47+- Add a config option to control whether LSP completions are automatically inserted on preview ([#7189](https://github.com/helix-editor/helix/pull/7189))
48+- Add a config option for default line endings ([#5621](https://github.com/helix-editor/helix/pull/5621), [#7357](https://github.com/helix-editor/helix/pull/7357))
49+- Allow ANSI colors in themes ([#5119](https://github.com/helix-editor/helix/pull/5119))
50+- Match pairs that don't form a standalone tree-sitter node ([#7242](https://github.com/helix-editor/helix/pull/7242))
51+- Allow indent sizes of up to 16 columns ([#7429](https://github.com/helix-editor/helix/pull/7429))
52+- Improve performance of mapping positions through changes ([#7408](https://github.com/helix-editor/helix/pull/7408), [8d39a81](https://github.com/helix-editor/helix/commit/8d39a81), [#7471](https://github.com/helix-editor/helix/pull/7471))
53+- Mark buffers created from stdin as modified ([#7431](https://github.com/helix-editor/helix/pull/7431))
54+- Forcibly shut down uninitialized language servers ([#7449](https://github.com/helix-editor/helix/pull/7449))
55+- Add filename completer for shell prompts ([#7569](https://github.com/helix-editor/helix/pull/7569))
56+- Allow binding F13-F24 ([#7672](https://github.com/helix-editor/helix/pull/7672))
57+- Resolve LSP code actions ([#7677](https://github.com/helix-editor/helix/pull/7677), [#8421](https://github.com/helix-editor/helix/pull/8421))
58+- Save an undo checkpoint before accepting completions ([#7747](https://github.com/helix-editor/helix/pull/7747))
59+- Include gitignored files in debugger completions ([#7936](https://github.com/helix-editor/helix/pull/7936))
60+- Make editor remember the last search register ([#5244](https://github.com/helix-editor/helix/pull/5244))
61+- Open directories with `goto_file` ([#7909](https://github.com/helix-editor/helix/pull/7909))
62+- Use relative path to open buffer in `goto_file` (`gf`) ([#7965](https://github.com/helix-editor/helix/pull/7965))
63+- Support `default` color in themes ([#8083](https://github.com/helix-editor/helix/pull/8083), [#8114](https://github.com/helix-editor/helix/pull/8114))
64+- Toggle between relative and absolute line numbers when the terminal loses focus ([#7955](https://github.com/helix-editor/helix/pull/7955))
65+- Lower default idle-timeout to 250ms ([060e73a](https://github.com/helix-editor/helix/commit/060e73a))
66+- Allow theming diff gutters separately from other diff colors ([#8343](https://github.com/helix-editor/helix/pull/8343))
67+- Style bold/italic/strikethrough in markdown doc popups ([#8385](https://github.com/helix-editor/helix/pull/8385))
68+- Maintain the cursor position and view when splitting with `:hsplit`/`:vsplit` ([#8109](https://github.com/helix-editor/helix/pull/8109))
69+- Accept `-` in macros outside of `<`/`>` ([#8475](https://github.com/helix-editor/helix/pull/8475))
70+- Show all language servers for each language in `--health` ([#7315](https://github.com/helix-editor/helix/pull/7315))
71+- Don't break on hyphens in `:reflow` ([#8569](https://github.com/helix-editor/helix/pull/8569))
72+
73+Fixes:
74+
75+- Update diagnostics correctly on language server exit ([#7111](https://github.com/helix-editor/helix/pull/7111))
76+- Fix off-by-one in `select_references_to_symbol_under_cursor` ([#7132](https://github.com/helix-editor/helix/pull/7132))
77+- Extend selection with repeat-last-motion only if the original motion extended the selection ([#7159](https://github.com/helix-editor/helix/pull/7159))
78+- Fix undefined behavior in the diff gutter ([#7227](https://github.com/helix-editor/helix/pull/7227))
79+- Check that tab width is non-zero ([#7178](https://github.com/helix-editor/helix/pull/7178))
80+- Fix styles being overwritten in table rows with multiple cells ([#7281](https://github.com/helix-editor/helix/pull/7281))
81+- Add file for `--log` CLI arg in help text ([#7307](https://github.com/helix-editor/helix/pull/7307))
82+- Fix underflow when repeating a completion that has a negative shift position ([#7322](https://github.com/helix-editor/helix/pull/7322))
83+- Prefer longer matches in `select_next_sibling` and `select_prev_sibling` ([#7332](https://github.com/helix-editor/helix/pull/7332))
84+- Preview scratch buffers in the jumplist picker ([#7331](https://github.com/helix-editor/helix/pull/7331))
85+- Fix chunking by bytes in tree-sitter parsing ([#7417](https://github.com/helix-editor/helix/pull/7417))
86+- Discard LSP publishDiagnostic from uninitialized servers ([#7467](https://github.com/helix-editor/helix/pull/7467))
87+- Use negotiated position encoding for LSP workspace edits ([#7469](https://github.com/helix-editor/helix/pull/7469))
88+- Fix error message for unknown gutter types in config ([#7534](https://github.com/helix-editor/helix/pull/7534))
89+- Fix `:log-open` when `--log` CLI arg is specified ([#7573](https://github.com/helix-editor/helix/pull/7573), [#7585](https://github.com/helix-editor/helix/pull/7585))
90+- Fix debouncing of LSP messages to fix the last message sticking around ([#7538](https://github.com/helix-editor/helix/pull/7538), [#8023](https://github.com/helix-editor/helix/pull/8023))
91+- Fix crash when the current working directory is deleted ([#7185](https://github.com/helix-editor/helix/pull/7185))
92+- Fix piping to Helix on macOS ([#5468](https://github.com/helix-editor/helix/pull/5468))
93+- Fix crash when parsing overlapping injections ([#7621](https://github.com/helix-editor/helix/pull/7621))
94+- Clear the statusline when the prompt is visible ([#7646](https://github.com/helix-editor/helix/pull/7646))
95+- Fix range formatting error message typo ([#7823](https://github.com/helix-editor/helix/pull/7823))
96+- Skip rendering gutters when gutter width exceeds view width ([#7821](https://github.com/helix-editor/helix/pull/7821))
97+- Center the picker preview using visual lines ([#7837](https://github.com/helix-editor/helix/pull/7837))
98+- Align view correctly for background buffers opened with `A-ret` ([#7691](https://github.com/helix-editor/helix/pull/7691))
99+- Fix cursor resetting to block when quitting via a keybind ([#7931](https://github.com/helix-editor/helix/pull/7931))
100+- Remove path completions for the `:new` command ([#8010](https://github.com/helix-editor/helix/pull/8010))
101+- Use binary path resolved by `which` for formatter commands ([#8064](https://github.com/helix-editor/helix/pull/8064))
102+- Handle crossterm's `hidden` modifier ([#8120](https://github.com/helix-editor/helix/pull/8120))
103+- Clear completion when switching between windows with the mouse ([#8118](https://github.com/helix-editor/helix/pull/8118))
104+- Eagerly remove the last picker (`<space>'`) when the picker has many items ([#8127](https://github.com/helix-editor/helix/pull/8127))
105+- Fix find commands for buffers with non-LF line-endings ([#8111](https://github.com/helix-editor/helix/pull/8111))
106+- Detect the tmux clipboard provider on macOS ([#8182](https://github.com/helix-editor/helix/pull/8182))
107+- Fix syntax highlighting in dynamic picker preview pane ([#8206](https://github.com/helix-editor/helix/pull/8206))
108+- Recognize HTML code tags with attributes as code in markdown previews ([#8397](https://github.com/helix-editor/helix/pull/8397))
109+- Fix multicursor snippet placeholder directions ([#8423](https://github.com/helix-editor/helix/pull/8423))
110+- Only show diagnostic highlights when diagnostics are enabled for a language server ([#8551](https://github.com/helix-editor/helix/pull/8551))
111+
112+Themes:
113+
114+- Improve the selection color in `ferra` ([#7138](https://github.com/helix-editor/helix/pull/7138))
115+- Add `variable.other.member` theming to `spacebones_light` ([#7125](https://github.com/helix-editor/helix/pull/7125))
116+- Update `autumn` and theme the soft-wrap indicator ([#7229](https://github.com/helix-editor/helix/pull/7229))
117+- Add `gruvbox_dark_soft` ([#7139](https://github.com/helix-editor/helix/pull/7139))
118+- Add `merionette` ([#7186](https://github.com/helix-editor/helix/pull/7186))
119+- Add `zed_onedark` and `zed_onelight` ([#7250](https://github.com/helix-editor/helix/pull/7250))
120+- Use light-gray for `onedarker` inlay hint theming ([#7433](https://github.com/helix-editor/helix/pull/7433))
121+- Update the Nord theme to follow the style guidelines ([#7490](https://github.com/helix-editor/helix/pull/7490))
122+- Tune `dark_plus` inlay hint colors ([#7611](https://github.com/helix-editor/helix/pull/7611))
123+- Add `naysayer` ([#7570](https://github.com/helix-editor/helix/pull/7570))
124+- Add `kaolin-dark`, `kaolin-light` and `kaolin-valley-dark` ([#7151](https://github.com/helix-editor/helix/pull/7151))
125+- Fix selection highlighting in gruvbox variants ([#7717](https://github.com/helix-editor/helix/pull/7717))
126+- Add soft-wrap indicator to `gruvbox` ([#7736](https://github.com/helix-editor/helix/pull/7736))
127+- Add missing palette definitions in `everforest_dark` ([#7739](https://github.com/helix-editor/helix/pull/7739))
128+- Increase diagnostics clarity in `pop-dark` ([#7702](https://github.com/helix-editor/helix/pull/7702))
129+- Add `vim_dark_high_contrast` ([#7785](https://github.com/helix-editor/helix/pull/7785))
130+- Add `new_moon` ([#7834](https://github.com/helix-editor/helix/pull/7834))
131+- Add `yellowed` ([#7849](https://github.com/helix-editor/helix/pull/7849))
132+- Improve comment readability for `autumn` ([#7939](https://github.com/helix-editor/helix/pull/7939))
133+- Distinguish active bufferline buffer in `monokai` ([#7983](https://github.com/helix-editor/helix/pull/7983))
134+- Update ruler colors in `nord` ([#7995](https://github.com/helix-editor/helix/pull/7995))
135+- Update Catppuccin themes ([#8102](https://github.com/helix-editor/helix/pull/8102))
136+- Add text focus scope and diagnostics undercurls for `nord` ([#8165](https://github.com/helix-editor/helix/pull/8165))
137+- Add material theme collection ([#8211](https://github.com/helix-editor/helix/pull/8211))
138+- Improve indent line color in `dracula` ([#8266](https://github.com/helix-editor/helix/pull/8266))
139+- Clean up and refactor `papercolor` to use inheritance ([#8276](https://github.com/helix-editor/helix/pull/8276))
140+- Fix `zenburn` inlay hint color ([#8278](https://github.com/helix-editor/helix/pull/8278)a)
141+- Fix picker crash when previewing an invalid range ([e9d0bd7](https://github.com/helix-editor/helix/commit/e9d0bd7))
142+- Correctly center items in the picker preview ([13d4463](https://github.com/helix-editor/helix/commit/13d4463))
143+- Add `cyan_light` ([#8293](https://github.com/helix-editor/helix/pull/8293), [#8587](https://github.com/helix-editor/helix/pull/8587))
144+- Theme HTML tags in `onedark` ([#8409](https://github.com/helix-editor/helix/pull/8409))
145+- Refine `darcula` and `darcula-solid` themes ([#8412](https://github.com/helix-editor/helix/pull/8412))
146+- Improve `nord` highlights ([#8414](https://github.com/helix-editor/helix/pull/8414))
147+- Add `nord-night` ([#8549](https://github.com/helix-editor/helix/pull/8549))
148+
149+New languages:
150+
151+- Blueprint ([#7213](https://github.com/helix-editor/helix/pull/7213), [#8161](https://github.com/helix-editor/helix/pull/8161))
152+- Forth ([#7256](https://github.com/helix-editor/helix/pull/7256), [#7334](https://github.com/helix-editor/helix/pull/7334))
153+- t32 ([#7140](https://github.com/helix-editor/helix/pull/7140), [#7811](https://github.com/helix-editor/helix/pull/7811))
154+- WebC ([#7290](https://github.com/helix-editor/helix/pull/7290))
155+- Persistent DSL for Haskell ([#7261](https://github.com/helix-editor/helix/pull/7261))
156+- F# ([#7619](https://github.com/helix-editor/helix/pull/7619), [#8024](https://github.com/helix-editor/helix/pull/8024))
157+- Wren ([#7765](https://github.com/helix-editor/helix/pull/7765), [#7819](https://github.com/helix-editor/helix/pull/7819))
158+- Unison ([#7724](https://github.com/helix-editor/helix/pull/7724))
159+- Todo.txt ([#7835](https://github.com/helix-editor/helix/pull/7835))
160+- Jinja and Handlebars ([#7233](https://github.com/helix-editor/helix/pull/7233))
161+- Pod ([#7907](https://github.com/helix-editor/helix/pull/7907))
162+- Strace ([#7928](https://github.com/helix-editor/helix/pull/7928))
163+- Gemini ([#8070](https://github.com/helix-editor/helix/pull/8070))
164+- GNU Assembler (GAS) ([#8291](https://github.com/helix-editor/helix/pull/8291))
165+- JSON5 ([#8473](https://github.com/helix-editor/helix/pull/8473))
166+- TEMPL ([#8540](https://github.com/helix-editor/helix/pull/8540))
167+
168+Updated languages and queries:
169+
170+- Add one to the ruler numbers for git-commit ([#7072](https://github.com/helix-editor/helix/pull/7072))
171+- Recognize XAML files as XML ([#7083](https://github.com/helix-editor/helix/pull/7083))
172+- Recognize `Cargo.lock` as TOML ([#7095](https://github.com/helix-editor/helix/pull/7095))
173+- Use Rust grammar for Cairo ([c6d1430](https://github.com/helix-editor/helix/commit/c6d1430))
174+- Update tree-sitter-nickel ([#7059](https://github.com/helix-editor/helix/pull/7059), [#7551](https://github.com/helix-editor/helix/pull/7551))
175+- Tune auto-pair characters for Nickel ([#7059](https://github.com/helix-editor/helix/pull/7059))
176+- Recognize `Vagrantfile` as Ruby ([#7112](https://github.com/helix-editor/helix/pull/7112))
177+- Recognize hidden justfiles as Just ([#7088](https://github.com/helix-editor/helix/pull/7088))
178+- Update Java and TypeScript highlight queries ([#7145](https://github.com/helix-editor/helix/pull/7145))
179+- Recognize `.zimrc` as Bash ([#7146](https://github.com/helix-editor/helix/pull/7146))
180+- Recognize `.gir` as XML ([#7152](https://github.com/helix-editor/helix/pull/7152))
181+- Update tree-sitter-scala ([#7147](https://github.com/helix-editor/helix/pull/7147))
182+- Recognize make file-type as Makefile ([#7212](https://github.com/helix-editor/helix/pull/7212))
183+- Update tree-sitter-verilog ([#7262](https://github.com/helix-editor/helix/pull/7262))
184+- Update tree-sitter-cpp ([#7285](https://github.com/helix-editor/helix/pull/7285))
185+- Support core mode for delve debugger ([#7300](https://github.com/helix-editor/helix/pull/7300))
186+- Add Fortran comment injections ([#7305](https://github.com/helix-editor/helix/pull/7305))
187+- Switch Vue language server to `vue-language-server` ([#7312](https://github.com/helix-editor/helix/pull/7312))
188+- Update tree-sitter-sql ([#7387](https://github.com/helix-editor/helix/pull/7387), [#8464](https://github.com/helix-editor/helix/pull/8464))
189+- Replace the MATLAB tre-sitter grammar ([#7388](https://github.com/helix-editor/helix/pull/7388), [#7442](https://github.com/helix-editor/helix/pull/7442), [#7491](https://github.com/helix-editor/helix/pull/7491), [#7493](https://github.com/helix-editor/helix/pull/7493), [#7511](https://github.com/helix-editor/helix/pull/7511), [#7532](https://github.com/helix-editor/helix/pull/7532), [#8040](https://github.com/helix-editor/helix/pull/8040))
190+- Highlight TOML table headers ([#7441](https://github.com/helix-editor/helix/pull/7441))
191+- Recognize `cppm` file-type as C++ ([#7492](https://github.com/helix-editor/helix/pull/7492))
192+- Refactor ecma language queries into private and public queries ([#7207](https://github.com/helix-editor/helix/pull/7207))
193+- Update tree-sitter-dart ([#7576](https://github.com/helix-editor/helix/pull/7576))
194+- Add shebang for nushell files ([#7606](https://github.com/helix-editor/helix/pull/7606))
195+- Recognize systemd files as INI ([#7592](https://github.com/helix-editor/helix/pull/7592))
196+- Update TypeScript, TSX and Svelte grammars ([#6874](https://github.com/helix-editor/helix/pull/6874))
197+- Enable inlay hints in the Svelte language server ([#7622](https://github.com/helix-editor/helix/pull/7622))
198+- Recognize `Brewfile`s as Ruby ([#7629](https://github.com/helix-editor/helix/pull/7629))
199+- Add more file-types for R ([#7633](https://github.com/helix-editor/helix/pull/7633))
200+- Switch tree-sitter-perl to official upstream parser ([#7644](https://github.com/helix-editor/helix/pull/7644), [#7947](https://github.com/helix-editor/helix/pull/7947))
201+- Fix predicate typo in comment highlights ([#7732](https://github.com/helix-editor/helix/pull/7732))
202+- Update tree-sitter-prql ([#7771](https://github.com/helix-editor/helix/pull/7771))
203+- Recognize `.gitf` as JSON ([#7781](https://github.com/helix-editor/helix/pull/7781))
204+- Switch V language server to `v-analyzer` ([#7760](https://github.com/helix-editor/helix/pull/7760))
205+- Add protobuf language servers ([#7796](https://github.com/helix-editor/helix/pull/7796))
206+- Update tree-sitter-zig ([#7803](https://github.com/helix-editor/helix/pull/7803))
207+- Update tree-sitter-hare ([#7784](https://github.com/helix-editor/helix/pull/7784))
208+- Add Java indent queries ([#7844](https://github.com/helix-editor/helix/pull/7844))
209+- Update tree-sitter-scheme ([979933b](https://github.com/helix-editor/helix/commit/979933b))
210+- Recognize `scm` as Scheme instead of TSQ ([5707151](https://github.com/helix-editor/helix/commit/5707151))
211+- Update tree-sitter-git-commit ([#7831](https://github.com/helix-editor/helix/pull/7831))
212+- Update JavaScript, TypeScript and TSX grammars ([#7852](https://github.com/helix-editor/helix/pull/7852))
213+- Update tree-sitter-nu ([#7873](https://github.com/helix-editor/helix/pull/7873))
214+- Fix YAML indentation ([#6768](https://github.com/helix-editor/helix/pull/6768))
215+- Add `csharp-ls`, Pyright, Pylyzer and add roots for Python ([#7897](https://github.com/helix-editor/helix/pull/7897), [#8032](https://github.com/helix-editor/helix/pull/8032))
216+- Update tree-sitter-slint ([#7893](https://github.com/helix-editor/helix/pull/7893))
217+- Recognize more ZSH file-types as Bash ([#7930](https://github.com/helix-editor/helix/pull/7930))
218+- Recognize `star` extension as Starlark ([#7922](https://github.com/helix-editor/helix/pull/7922))
219+- Fix inline HTML tag highlighting in markdown ([#7960](https://github.com/helix-editor/helix/pull/7960))
220+- Update tree-sitter-robot ([#7970](https://github.com/helix-editor/helix/pull/7970))
221+- Highlight Dart 3 `sealed` and `base` keywords ([#7974](https://github.com/helix-editor/helix/pull/7974))
222+- Add configuration for `ltex-ls` to the default `languages.toml` ([#7838](https://github.com/helix-editor/helix/pull/7838))
223+- Update tree-sitter-strace ([#8087](https://github.com/helix-editor/helix/pull/8087))
224+- Update tree-sitter-gleam, enable auto-format ([#8085](https://github.com/helix-editor/helix/pull/8085))
225+- Update tree-sitter-esdl ([#8222](https://github.com/helix-editor/helix/pull/8222))
226+- Expand ignore file-types ([#8220](https://github.com/helix-editor/helix/pull/8220))
227+- Recognize feed related formats as XML ([#8232](https://github.com/helix-editor/helix/pull/8232))
228+- Improve YAML injections ([#8217](https://github.com/helix-editor/helix/pull/8217))
229+- Add shebangs for TypeScript, Julia, Java and OCaml ([95e994a](https://github.com/helix-editor/helix/commit/95e994a))
230+- Highlight abbreviations in Scheme ([ef23847](https://github.com/helix-editor/helix/commit/ef23847))
231+- Remove backtic auto-pair in OCaml ([#8260](https://github.com/helix-editor/helix/pull/8260))
232+- Recognize `flake.lock` as JSON ([#8304](https://github.com/helix-editor/helix/pull/8304))
233+- Add Python test script injection for Nix ([b4494e1](https://github.com/helix-editor/helix/commit/b4494e1))
234+- Fix Nix comment injection precedence ([37e48f4](https://github.com/helix-editor/helix/commit/37e48f4))
235+- Recognize editorconfig files as INI ([#8308](https://github.com/helix-editor/helix/pull/8308))
236+- Recognize `.babelrc` as JSON ([#8309](https://github.com/helix-editor/helix/pull/8309))
237+- Switch Purescript to its own tree-sitter parser ([#8306](https://github.com/helix-editor/helix/pull/8306), [#8338](https://github.com/helix-editor/helix/pull/8338), [#8527](https://github.com/helix-editor/helix/pull/8527))
238+- Update Unison highlights ([#8315](https://github.com/helix-editor/helix/pull/8315))
239+- Recognize `.webmanifest` as JSON ([#8342](https://github.com/helix-editor/helix/pull/8342))
240+- Recognize polkit policy files as XML ([#8369](https://github.com/helix-editor/helix/pull/8369))
241+- Recognize polkit rules files as JavaScript ([#8370](https://github.com/helix-editor/helix/pull/8370))
242+- Update Go highlight queries ([#8399](https://github.com/helix-editor/helix/pull/8399))
243+- Add shebangs for Makefiles ([#8410](https://github.com/helix-editor/helix/pull/8410))
244+- Add file-type associations from VSCode ([#8388](https://github.com/helix-editor/helix/pull/8388))
245+- Add validation to JSON/CSS language server configs ([#8433](https://github.com/helix-editor/helix/pull/8433))
246+- Add a configuration for the tailwind language server ([#8442](https://github.com/helix-editor/helix/pull/8442))
247+- Add a configuration for the ansible language server ([#7973](https://github.com/helix-editor/helix/pull/7973))
248+- Add a configuration for the GraphQL language server ([#8492](https://github.com/helix-editor/helix/pull/8492))
249+- Indent while statements in Bash ([#8528](https://github.com/helix-editor/helix/pull/8528))
250+- Update tree-sitter-haskell and queries ([#8558](https://github.com/helix-editor/helix/pull/8558))
251+
252+Packaging:
253+
254+- Add an overlay to the Nix flake ([#7078](https://github.com/helix-editor/helix/pull/7078))
255+- Check for `git` before fetching or building grammars ([#7320](https://github.com/helix-editor/helix/pull/7320))
256+- Refactor Nix flake to use Crane ([#7763](https://github.com/helix-editor/helix/pull/7763))
257+- Remove the aarch64 appimage from the release CI ([#7832](https://github.com/helix-editor/helix/pull/7832))
258+- Add desktop and icon files to Nix flake output ([#7979](https://github.com/helix-editor/helix/pull/7979))
259+- Build flake packages with the latest stable Rust ([#8133](https://github.com/helix-editor/helix/pull/8133))
260+
261+# 23.05 (2023-05-18)
262+
263+23.05 is a smaller release focusing on fixes. There were 88 contributors in this release. Thank you all!
264+
265+Features:
266+
267+- Add a config option to exclude declaration from LSP references request ([#6886](https://github.com/helix-editor/helix/pull/6886))
268+- Enable injecting languages based on their file extension and shebang ([#3970](https://github.com/helix-editor/helix/pull/3970))
269+- Sort the buffer picker by most recent access ([#2980](https://github.com/helix-editor/helix/pull/2980))
270+- Perform syntax highlighting in the picker asynchronously ([#7028](https://github.com/helix-editor/helix/pull/7028))
271+
272+Commands:
273+
274+- `:update` is now aliased as `:u` ([#6835](https://github.com/helix-editor/helix/pull/6835))
275+- Add `extend_to_first_nonwhitespace` which acts the same as `goto_first_nonwhitespace` but always extends ([#6837](https://github.com/helix-editor/helix/pull/6837))
276+- Add `:clear-register` for clearing the given register or all registers ([#5695](https://github.com/helix-editor/helix/pull/5695))
277+- Add `:write-buffer-close` and `:write-buffer-close!` ([#6947](https://github.com/helix-editor/helix/pull/6947))
278+
279+Fixes:
280+
281+- Normalize LSP workspace paths ([#6517](https://github.com/helix-editor/helix/pull/6517))
282+- Robustly handle invalid LSP ranges ([#6512](https://github.com/helix-editor/helix/pull/6512))
283+- Fix line number display for LSP goto pickers ([#6559](https://github.com/helix-editor/helix/pull/6559))
284+- Fix toggling of `soft-wrap.enable` option ([#6656](https://github.com/helix-editor/helix/pull/6656), [58e457a](https://github.com/helix-editor/helix/commit/58e457a), [#6742](https://github.com/helix-editor/helix/pull/6742))
285+- Handle `workspace/configuration` requests from stopped language servers ([#6693](https://github.com/helix-editor/helix/pull/6693))
286+- Fix possible crash from opening the jumplist picker ([#6672](https://github.com/helix-editor/helix/pull/6672))
287+- Fix theme preview returning to current theme on line and word deletions ([#6694](https://github.com/helix-editor/helix/pull/6694))
288+- Re-run crate build scripts on changes to revision and grammar repositories ([#6743](https://github.com/helix-editor/helix/pull/6743))
289+- Fix crash on opening from suspended state ([#6764](https://github.com/helix-editor/helix/pull/6764))
290+- Fix unwrap bug in DAP ([#6786](https://github.com/helix-editor/helix/pull/6786))
291+- Always build tree-sitter parsers with C++14 and C11 ([#6792](https://github.com/helix-editor/helix/pull/6792), [#6834](https://github.com/helix-editor/helix/pull/6834), [#6845](https://github.com/helix-editor/helix/pull/6845))
292+- Exit with a non-zero statuscode when tree-sitter parser builds fail ([#6795](https://github.com/helix-editor/helix/pull/6795))
293+- Flip symbol range in LSP goto commands ([#6794](https://github.com/helix-editor/helix/pull/6794))
294+- Fix runtime toggling of the `mouse` option ([#6675](https://github.com/helix-editor/helix/pull/6675))
295+- Fix panic in inlay hint computation when view anchor is out of bounds ([#6883](https://github.com/helix-editor/helix/pull/6883))
296+- Significantly improve performance of git discovery on slow file systems ([#6890](https://github.com/helix-editor/helix/pull/6890))
297+- Downgrade gix log level to info ([#6915](https://github.com/helix-editor/helix/pull/6915))
298+- Conserve BOM and properly support saving UTF16 files ([#6497](https://github.com/helix-editor/helix/pull/6497))
299+- Correctly handle completion re-request ([#6594](https://github.com/helix-editor/helix/pull/6594))
300+- Fix offset encoding in LSP `didChange` notifications ([#6921](https://github.com/helix-editor/helix/pull/6921))
301+- Change `gix` logging level to info ([#6915](https://github.com/helix-editor/helix/pull/6915))
302+- Improve error message when writes fail because parent directories do not exist ([#7014](https://github.com/helix-editor/helix/pull/7014))
303+- Replace DAP variables popup instead of pushing more popups ([#7034](https://github.com/helix-editor/helix/pull/7034))
304+- Disable tree-sitter for files after parsing for 500ms ([#7028](https://github.com/helix-editor/helix/pull/7028))
305+- Fix crash when deleting with multiple cursors ([#6024](https://github.com/helix-editor/helix/pull/6024))
306+- Fix selection sliding when deleting forwards in append mode ([#6024](https://github.com/helix-editor/helix/pull/6024))
307+- Fix completion on paths containing spaces ([#6779](https://github.com/helix-editor/helix/pull/6779))
308+
309+Themes:
310+
311+- Style inlay hints in `dracula` theme ([#6515](https://github.com/helix-editor/helix/pull/6515))
312+- Style inlay hints in `onedark` theme ([#6503](https://github.com/helix-editor/helix/pull/6503))
313+- Style inlay hints and the soft-wrap indicator in `varua` ([#6568](https://github.com/helix-editor/helix/pull/6568), [#6589](https://github.com/helix-editor/helix/pull/6589))
314+- Style inlay hints in `emacs` theme ([#6569](https://github.com/helix-editor/helix/pull/6569))
315+- Update `base16_transparent` and `dark_high_contrast` themes ([#6577](https://github.com/helix-editor/helix/pull/6577))
316+- Style inlay hints for `mellow` and `rasmus` themes ([#6583](https://github.com/helix-editor/helix/pull/6583))
317+- Dim pane divider for `base16_transparent` theme ([#6534](https://github.com/helix-editor/helix/pull/6534))
318+- Style inlay hints in `zenburn` theme ([#6593](https://github.com/helix-editor/helix/pull/6593))
319+- Style inlay hints in `boo_berry` theme ([#6625](https://github.com/helix-editor/helix/pull/6625))
320+- Add `ferra` theme ([#6619](https://github.com/helix-editor/helix/pull/6619), [#6776](https://github.com/helix-editor/helix/pull/6776))
321+- Style inlay hints in `nightfox` theme ([#6655](https://github.com/helix-editor/helix/pull/6655))
322+- Fix `ayu` theme family markup code block background ([#6538](https://github.com/helix-editor/helix/pull/6538))
323+- Improve whitespace and search match colors in `rose_pine` theme ([#6679](https://github.com/helix-editor/helix/pull/6679))
324+- Highlight selected items in `base16_transparent` theme ([#6716](https://github.com/helix-editor/helix/pull/6716))
325+- Adjust everforest to resemble original more closely ([#5866](https://github.com/helix-editor/helix/pull/5866))
326+- Refactor `dracula` theme ([#6552](https://github.com/helix-editor/helix/pull/6552), [#6767](https://github.com/helix-editor/helix/pull/6767), [#6855](https://github.com/helix-editor/helix/pull/6855), [#6987](https://github.com/helix-editor/helix/pull/6987))
327+- Style inlay hints in `darcula` theme ([#6732](https://github.com/helix-editor/helix/pull/6732))
328+- Style inlay hints in `kanagawa` theme ([#6773](https://github.com/helix-editor/helix/pull/6773))
329+- Improve `ayu_dark` theme ([#6622](https://github.com/helix-editor/helix/pull/6622))
330+- Refactor `noctis` theme multiple cursor highlighting ([96720e7](https://github.com/helix-editor/helix/commit/96720e7))
331+- Refactor `noctis` theme whitespace rendering and indent guides ([f2ccc03](https://github.com/helix-editor/helix/commit/f2ccc03))
332+- Add `amberwood` theme ([#6924](https://github.com/helix-editor/helix/pull/6924))
333+- Update `nightfox` theme ([#7061](https://github.com/helix-editor/helix/pull/7061))
334+
335+Language support:
336+
337+- R language server: use the `--no-echo` flag to silence output ([#6570](https://github.com/helix-editor/helix/pull/6570))
338+- Recognize CUDA files as C++ ([#6521](https://github.com/helix-editor/helix/pull/6521))
339+- Add support for Hurl ([#6450](https://github.com/helix-editor/helix/pull/6450))
340+- Add textobject queries for Julia ([#6588](https://github.com/helix-editor/helix/pull/6588))
341+- Update Ruby highlight queries ([#6587](https://github.com/helix-editor/helix/pull/6587))
342+- Add xsd to XML file-types ([#6631](https://github.com/helix-editor/helix/pull/6631))
343+- Support Robot Framework ([#6611](https://github.com/helix-editor/helix/pull/6611))
344+- Update Gleam tree-sitter parser ([#6641](https://github.com/helix-editor/helix/pull/6641))
345+- Update git-commit tree-sitter parser ([#6692](https://github.com/helix-editor/helix/pull/6692))
346+- Update Haskell tree-sitter parser ([#6317](https://github.com/helix-editor/helix/pull/6317))
347+- Add injection queries for Haskell quasiquotes ([#6474](https://github.com/helix-editor/helix/pull/6474))
348+- Highlight C/C++ escape sequences ([#6724](https://github.com/helix-editor/helix/pull/6724))
349+- Support Markdoc ([#6432](https://github.com/helix-editor/helix/pull/6432))
350+- Support OpenCL ([#6473](https://github.com/helix-editor/helix/pull/6473))
351+- Support DTD ([#6644](https://github.com/helix-editor/helix/pull/6644))
352+- Fix constant highlighting in Python queries ([#6751](https://github.com/helix-editor/helix/pull/6751))
353+- Support Just ([#6453](https://github.com/helix-editor/helix/pull/6453))
354+- Fix Go locals query for `var_spec` identifiers ([#6763](https://github.com/helix-editor/helix/pull/6763))
355+- Update Markdown tree-sitter parser ([#6785](https://github.com/helix-editor/helix/pull/6785))
356+- Fix Haskell workspace root for cabal projects ([#6828](https://github.com/helix-editor/helix/pull/6828))
357+- Avoid extra indentation in Go switches ([#6817](https://github.com/helix-editor/helix/pull/6817))
358+- Fix Go workspace roots ([#6884](https://github.com/helix-editor/helix/pull/6884))
359+- Set PerlNavigator as the default Perl language server ([#6860](https://github.com/helix-editor/helix/pull/6860))
360+- Highlight more sqlx macros in Rust ([#6793](https://github.com/helix-editor/helix/pull/6793))
361+- Switch Odin tree-sitter grammar ([#6766](https://github.com/helix-editor/helix/pull/6766))
362+- Recognize `poetry.lock` as TOML ([#6928](https://github.com/helix-editor/helix/pull/6928))
363+- Recognize Jupyter notebooks as JSON ([#6927](https://github.com/helix-editor/helix/pull/6927))
364+- Add language server configuration for Crystal ([#6948](https://github.com/helix-editor/helix/pull/6948))
365+- Add `build.gradle.kts` to Java and Scala roots ([#6970](https://github.com/helix-editor/helix/pull/6970))
366+- Recognize `sty` and `cls` files as latex ([#6986](https://github.com/helix-editor/helix/pull/6986))
367+- Update Dockerfile tree-sitter grammar ([#6895](https://github.com/helix-editor/helix/pull/6895))
368+- Add comment injections for Odin ([#7027](https://github.com/helix-editor/helix/pull/7027))
369+- Recognize `gml` as XML ([#7055](https://github.com/helix-editor/helix/pull/7055))
370+- Recognize `geojson` as JSON ([#7054](https://github.com/helix-editor/helix/pull/7054))
371+
372+Packaging:
373+
374+- Update the Nix flake dependencies, remove a deprecated option ([#6546](https://github.com/helix-editor/helix/pull/6546))
375+- Fix and re-enable aarch64-macos release binary builds ([#6504](https://github.com/helix-editor/helix/pull/6504))
376+- The git dependency on `tree-sitter` has been replaced with a regular crates.io dependency ([#6608](https://github.com/helix-editor/helix/pull/6608))
377+
1378# 23.03 (2023-03-31)
2379
338023.03 brings some long-awaited and exciting features. Thank you to everyone involved! This release saw changes from 102 contributors.
@@ -1479,7 +1856,7 @@ to distinguish it in bug reports..
14791856
14801857- The `runtime/` directory is now properly detected on binary releases and
14811858 on cargo run. `~/.config/helix/runtime` can also be used.
1482−- Registers can now be selected via " (for example `"ay`)
1859+- Registers can now be selected via " (for example, `"ay`)
14831860- Support for Nix files was added
14841861- Movement is now fully tested and matches Kakoune implementation
14851862- A per-file LSP symbol picker was added to space+s
Cargo.lock+728−639
@@ -3,43 +3,54 @@
33version = 3
44
55[[package]]
6−name = "adler"
7−version = "1.0.2"
6+name = "addr2line"
7+version = "0.20.0"
88source = "registry+https://github.com/rust-lang/crates.io-index"
9−checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
9+checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
10+dependencies = [
11+ "gimli",
12+]
1013
1114[[package]]
12−name = "ahash"
13−version = "0.7.6"
15+name = "adler"
16+version = "1.0.2"
1417source = "registry+https://github.com/rust-lang/crates.io-index"
15−checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
16−dependencies = [
17− "getrandom",
18− "once_cell",
19− "version_check",
20−]
18+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
2119
2220[[package]]
2321name = "ahash"
24−version = "0.8.3"
22+version = "0.8.6"
2523source = "registry+https://github.com/rust-lang/crates.io-index"
26−checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
24+checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
2725dependencies = [
2826 "cfg-if",
2927 "getrandom",
3028 "once_cell",
3129 "version_check",
30+ "zerocopy",
3231]
3332
3433[[package]]
3534name = "aho-corasick"
36−version = "0.7.20"
35+version = "1.1.2"
3736source = "registry+https://github.com/rust-lang/crates.io-index"
38−checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
37+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
3938dependencies = [
4039 "memchr",
4140]
4241
42+[[package]]
43+name = "allocator-api2"
44+version = "0.2.14"
45+source = "registry+https://github.com/rust-lang/crates.io-index"
46+checksum = "c4f263788a35611fba42eb41ff811c5d0360c58b97402570312a350736e2542e"
47+
48+[[package]]
49+name = "android-tzdata"
50+version = "0.1.1"
51+source = "registry+https://github.com/rust-lang/crates.io-index"
52+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
53+
4354[[package]]
4455name = "android_system_properties"
4556version = "0.1.5"
@@ -51,9 +62,9 @@ dependencies = [
5162
5263[[package]]
5364name = "anyhow"
54−version = "1.0.70"
65+version = "1.0.76"
5566source = "registry+https://github.com/rust-lang/crates.io-index"
56−checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
67+checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
5768
5869[[package]]
5970name = "arc-swap"
@@ -67,6 +78,21 @@ version = "1.1.0"
6778source = "registry+https://github.com/rust-lang/crates.io-index"
6879checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
6980
81+[[package]]
82+name = "backtrace"
83+version = "0.3.68"
84+source = "registry+https://github.com/rust-lang/crates.io-index"
85+checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12"
86+dependencies = [
87+ "addr2line",
88+ "cc",
89+ "cfg-if",
90+ "libc",
91+ "miniz_oxide",
92+ "object",
93+ "rustc-demangle",
94+]
95+
7096[[package]]
7197name = "bitflags"
7298version = "1.3.2"
@@ -75,18 +101,17 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
75101
76102[[package]]
77103name = "bitflags"
78−version = "2.1.0"
104+version = "2.4.1"
79105source = "registry+https://github.com/rust-lang/crates.io-index"
80−checksum = "c70beb79cbb5ce9c4f8e20849978f34225931f665bb49efa6982875a4d5facb3"
106+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
81107
82108[[package]]
83109name = "bstr"
84−version = "1.4.0"
110+version = "1.8.0"
85111source = "registry+https://github.com/rust-lang/crates.io-index"
86−checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
112+checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
87113dependencies = [
88114 "memchr",
89− "once_cell",
90115 "regex-automata",
91116 "serde",
92117]
@@ -106,12 +131,6 @@ version = "3.12.0"
106131source = "registry+https://github.com/rust-lang/crates.io-index"
107132checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
108133
109−[[package]]
110−name = "bytecount"
111−version = "0.6.3"
112−source = "registry+https://github.com/rust-lang/crates.io-index"
113−checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
114−
115134[[package]]
116135name = "bytes"
117136version = "1.4.0"
@@ -126,9 +145,12 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
126145
127146[[package]]
128147name = "cc"
129−version = "1.0.79"
148+version = "1.0.84"
130149source = "registry+https://github.com/rust-lang/crates.io-index"
131−checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
150+checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
151+dependencies = [
152+ "libc",
153+]
132154
133155[[package]]
134156name = "cfg-if"
@@ -149,25 +171,23 @@ dependencies = [
149171
150172[[package]]
151173name = "chrono"
152−version = "0.4.24"
174+version = "0.4.31"
153175source = "registry+https://github.com/rust-lang/crates.io-index"
154−checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
176+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
155177dependencies = [
178+ "android-tzdata",
156179 "iana-time-zone",
157− "num-integer",
158180 "num-traits",
159− "winapi",
181+ "windows-targets 0.48.0",
160182]
161183
162184[[package]]
163185name = "clipboard-win"
164−version = "4.5.0"
186+version = "5.0.0"
165187source = "registry+https://github.com/rust-lang/crates.io-index"
166−checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
188+checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
167189dependencies = [
168190 "error-code",
169− "str-buf",
170− "winapi",
171191]
172192
173193[[package]]
@@ -197,9 +217,15 @@ dependencies = [
197217
198218[[package]]
199219name = "core-foundation-sys"
200−version = "0.8.3"
220+version = "0.8.4"
201221source = "registry+https://github.com/rust-lang/crates.io-index"
202−checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
222+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
223+
224+[[package]]
225+name = "cov-mark"
226+version = "1.1.0"
227+source = "registry+https://github.com/rust-lang/crates.io-index"
228+checksum = "9ffa3d3e0138386cd4361f63537765cac7ee40698028844635a54495a92f67f3"
203229
204230[[package]]
205231name = "crc32fast"
@@ -210,14 +236,58 @@ dependencies = [
210236 "cfg-if",
211237]
212238
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+
213282[[package]]
214283name = "crossterm"
215−version = "0.26.1"
284+version = "0.27.0"
216285source = "registry+https://github.com/rust-lang/crates.io-index"
217−checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
286+checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
218287dependencies = [
219− "bitflags 1.3.2",
288+ "bitflags 2.4.1",
220289 "crossterm_winapi",
290+ "filedescriptor",
221291 "futures-core",
222292 "libc",
223293 "mio",
@@ -229,9 +299,9 @@ dependencies = [
229299
230300[[package]]
231301name = "crossterm_winapi"
232−version = "0.9.0"
302+version = "0.9.1"
233303source = "registry+https://github.com/rust-lang/crates.io-index"
234−checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
304+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
235305dependencies = [
236306 "winapi",
237307]
@@ -260,7 +330,7 @@ dependencies = [
260330 "proc-macro2",
261331 "quote",
262332 "scratch",
263− "syn 2.0.11",
333+ "syn 2.0.38",
264334]
265335
266336[[package]]
@@ -277,55 +347,14 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
277347dependencies = [
278348 "proc-macro2",
279349 "quote",
280− "syn 2.0.11",
281−]
282−
283−[[package]]
284−name = "dirs"
285−version = "4.0.0"
286−source = "registry+https://github.com/rust-lang/crates.io-index"
287−checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
288−dependencies = [
289− "dirs-sys",
290−]
291−
292−[[package]]
293−name = "dirs-next"
294−version = "2.0.0"
295−source = "registry+https://github.com/rust-lang/crates.io-index"
296−checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
297−dependencies = [
298− "cfg-if",
299− "dirs-sys-next",
300−]
301−
302−[[package]]
303−name = "dirs-sys"
304−version = "0.3.7"
305−source = "registry+https://github.com/rust-lang/crates.io-index"
306−checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
307−dependencies = [
308− "libc",
309− "redox_users",
310− "winapi",
311−]
312−
313−[[package]]
314−name = "dirs-sys-next"
315−version = "0.1.2"
316−source = "registry+https://github.com/rust-lang/crates.io-index"
317−checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
318−dependencies = [
319− "libc",
320− "redox_users",
321− "winapi",
350+ "syn 2.0.38",
322351]
323352
324353[[package]]
325354name = "dunce"
326−version = "1.0.3"
355+version = "1.0.4"
327356source = "registry+https://github.com/rust-lang/crates.io-index"
328−checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c"
357+checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
329358
330359[[package]]
331360name = "either"
@@ -335,9 +364,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
335364
336365[[package]]
337366name = "encoding_rs"
338−version = "0.8.32"
367+version = "0.8.33"
339368source = "registry+https://github.com/rust-lang/crates.io-index"
340−checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
369+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
341370dependencies = [
342371 "cfg-if",
343372]
@@ -352,56 +381,53 @@ dependencies = [
352381]
353382
354383[[package]]
355−name = "errno"
356−version = "0.3.0"
384+name = "equivalent"
385+version = "1.0.0"
357386source = "registry+https://github.com/rust-lang/crates.io-index"
358−checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
359−dependencies = [
360− "errno-dragonfly",
361− "libc",
362− "windows-sys",
363−]
387+checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
364388
365389[[package]]
366−name = "errno-dragonfly"
367−version = "0.1.2"
390+name = "errno"
391+version = "0.3.8"
368392source = "registry+https://github.com/rust-lang/crates.io-index"
369−checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
393+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
370394dependencies = [
371− "cc",
372395 "libc",
396+ "windows-sys 0.52.0",
373397]
374398
375399[[package]]
376400name = "error-code"
377−version = "2.3.1"
401+version = "3.0.0"
378402source = "registry+https://github.com/rust-lang/crates.io-index"
379−checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
380−dependencies = [
381− "libc",
382− "str-buf",
383−]
403+checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
384404
385405[[package]]
386406name = "etcetera"
387−version = "0.4.0"
407+version = "0.8.0"
388408source = "registry+https://github.com/rust-lang/crates.io-index"
389−checksum = "d017fce18e4e9bfa75e1db51f49f4487bd3f8a7df509b24a46474a956ee962fd"
409+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
390410dependencies = [
391411 "cfg-if",
392− "dirs-next",
393− "thiserror",
412+ "home",
413+ "windows-sys 0.48.0",
394414]
395415
396416[[package]]
397−name = "fastrand"
398−version = "1.9.0"
417+name = "faster-hex"
418+version = "0.9.0"
399419source = "registry+https://github.com/rust-lang/crates.io-index"
400−checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
420+checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
401421dependencies = [
402− "instant",
422+ "serde",
403423]
404424
425+[[package]]
426+name = "fastrand"
427+version = "2.0.0"
428+source = "registry+https://github.com/rust-lang/crates.io-index"
429+checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
430+
405431[[package]]
406432name = "fern"
407433version = "0.6.2"
@@ -412,53 +438,46 @@ dependencies = [
412438]
413439
414440[[package]]
415−name = "filetime"
416−version = "0.2.20"
441+name = "filedescriptor"
442+version = "0.8.2"
417443source = "registry+https://github.com/rust-lang/crates.io-index"
418−checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412"
444+checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
419445dependencies = [
420− "cfg-if",
421446 "libc",
422− "redox_syscall 0.2.16",
423− "windows-sys",
447+ "thiserror",
448+ "winapi",
424449]
425450
426451[[package]]
427452name = "flate2"
428−version = "1.0.25"
453+version = "1.0.27"
429454source = "registry+https://github.com/rust-lang/crates.io-index"
430−checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
455+checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
431456dependencies = [
432457 "crc32fast",
433458 "miniz_oxide",
434459]
435460
436−[[package]]
437−name = "fnv"
438−version = "1.0.7"
439−source = "registry+https://github.com/rust-lang/crates.io-index"
440−checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
441−
442461[[package]]
443462name = "form_urlencoded"
444−version = "1.1.0"
463+version = "1.2.1"
445464source = "registry+https://github.com/rust-lang/crates.io-index"
446−checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
465+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
447466dependencies = [
448467 "percent-encoding",
449468]
450469
451470[[package]]
452471name = "futures-core"
453−version = "0.3.28"
472+version = "0.3.30"
454473source = "registry+https://github.com/rust-lang/crates.io-index"
455−checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
474+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
456475
457476[[package]]
458477name = "futures-executor"
459−version = "0.3.28"
478+version = "0.3.29"
460479source = "registry+https://github.com/rust-lang/crates.io-index"
461−checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
480+checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
462481dependencies = [
463482 "futures-core",
464483 "futures-task",
@@ -467,15 +486,15 @@ dependencies = [
467486
468487[[package]]
469488name = "futures-task"
470−version = "0.3.28"
489+version = "0.3.30"
471490source = "registry+https://github.com/rust-lang/crates.io-index"
472−checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
491+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
473492
474493[[package]]
475494name = "futures-util"
476−version = "0.3.28"
495+version = "0.3.30"
477496source = "registry+https://github.com/rust-lang/crates.io-index"
478−checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
497+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
479498dependencies = [
480499 "futures-core",
481500 "futures-task",
@@ -484,63 +503,59 @@ dependencies = [
484503 "slab",
485504]
486505
487−[[package]]
488−name = "fuzzy-matcher"
489−version = "0.3.7"
490−source = "registry+https://github.com/rust-lang/crates.io-index"
491−checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
492−dependencies = [
493− "thread_local",
494−]
495−
496506[[package]]
497507name = "getrandom"
498−version = "0.2.8"
508+version = "0.2.9"
499509source = "registry+https://github.com/rust-lang/crates.io-index"
500−checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
510+checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
501511dependencies = [
502512 "cfg-if",
503513 "libc",
504514 "wasi",
505515]
506516
517+[[package]]
518+name = "gimli"
519+version = "0.27.3"
520+source = "registry+https://github.com/rust-lang/crates.io-index"
521+checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
522+
507523[[package]]
508524name = "gix"
509−version = "0.43.1"
525+version = "0.57.0"
510526source = "registry+https://github.com/rust-lang/crates.io-index"
511−checksum = "c256ea71cc1967faaefdaad15f334146b7c806f12460dcafd3afed845c8c78dd"
527+checksum = "721c7497ab24b665ed5a1eb2b3526936aa60068e61ba260651f7e77c52feec69"
512528dependencies = [
513529 "gix-actor",
514− "gix-attributes",
530+ "gix-commitgraph",
515531 "gix-config",
516− "gix-credentials",
517532 "gix-date",
518533 "gix-diff",
519534 "gix-discover",
520535 "gix-features",
536+ "gix-fs",
521537 "gix-glob",
522538 "gix-hash",
523539 "gix-hashtable",
524− "gix-index",
525540 "gix-lock",
526− "gix-mailmap",
541+ "gix-macros",
527542 "gix-object",
528543 "gix-odb",
529544 "gix-pack",
530545 "gix-path",
531− "gix-prompt",
532546 "gix-ref",
533547 "gix-refspec",
534548 "gix-revision",
549+ "gix-revwalk",
535550 "gix-sec",
536551 "gix-tempfile",
552+ "gix-trace",
537553 "gix-traverse",
538554 "gix-url",
555+ "gix-utils",
539556 "gix-validate",
540− "gix-worktree",
541− "log",
542557 "once_cell",
543− "signal-hook",
558+ "parking_lot",
544559 "smallvec",
545560 "thiserror",
546561 "unicode-normalization",
@@ -548,65 +563,46 @@ dependencies = [
548563
549564[[package]]
550565name = "gix-actor"
551−version = "0.19.0"
566+version = "0.29.0"
552567source = "registry+https://github.com/rust-lang/crates.io-index"
553−checksum = "dc22b0cdc52237667c301dd7cdc6ead8f8f73c9f824e9942c8ebd6b764f6c0bf"
568+checksum = "886014c4865b93ce268f1d3eddd4fd3261242c3f3ee61eb36009f913016a9059"
554569dependencies = [
555570 "bstr",
556571 "btoi",
557572 "gix-date",
558573 "itoa",
559− "nom",
560− "thiserror",
561−]
562−
563−[[package]]
564−name = "gix-attributes"
565−version = "0.10.0"
566−source = "registry+https://github.com/rust-lang/crates.io-index"
567−checksum = "2231a25934a240d0a4b6f4478401c73ee81d8be52de0293eedbc172334abf3e1"
568−dependencies = [
569− "bstr",
570− "gix-features",
571− "gix-glob",
572− "gix-path",
573− "gix-quote",
574− "thiserror",
575− "unicode-bom",
576−]
577−
578−[[package]]
579−name = "gix-bitmap"
580−version = "0.2.2"
581−source = "registry+https://github.com/rust-lang/crates.io-index"
582−checksum = "024bca0c7187517bda5ea24ab148c9ca8208dd0c3e2bea88cdb2008f91791a6d"
583−dependencies = [
584574 "thiserror",
575+ "winnow 0.5.28",
585576]
586577
587578[[package]]
588579name = "gix-chunk"
589−version = "0.4.1"
580+version = "0.4.6"
590581source = "registry+https://github.com/rust-lang/crates.io-index"
591−checksum = "b0d39583cab06464b8bf73b3f1707458270f0e7383cb24c3c9c1a16e6f792978"
582+checksum = "2ade37ef69870de0ed966b97c57a3a947a22ff3482a52c3b99b205f77bcb08fb"
592583dependencies = [
593584 "thiserror",
594585]
595586
596587[[package]]
597−name = "gix-command"
598−version = "0.2.4"
588+name = "gix-commitgraph"
589+version = "0.23.0"
599590source = "registry+https://github.com/rust-lang/crates.io-index"
600−checksum = "b2c6f75c1e0f924de39e750880a6e21307194bb1ab773efe3c7d2d787277f8ab"
591+checksum = "e7559ea9cefee188cd88b05afcc8e3ef7a3cb4a5c647bccf06b981e591b02b77"
601592dependencies = [
602593 "bstr",
594+ "gix-chunk",
595+ "gix-features",
596+ "gix-hash",
597+ "memmap2",
598+ "thiserror",
603599]
604600
605601[[package]]
606602name = "gix-config"
607−version = "0.20.1"
603+version = "0.33.0"
608604source = "registry+https://github.com/rust-lang/crates.io-index"
609−checksum = "7fbad5ce54a8fc997acc50febd89ec80fa6e97cb7f8d0654cb229936407489d8"
605+checksum = "9c35dc7f9c00a42bbc9cfa1ca2ec0a78ad1b76ff0736d3d35dfd612962244467"
610606dependencies = [
611607 "bstr",
612608 "gix-config-value",
@@ -615,49 +611,32 @@ dependencies = [
615611 "gix-path",
616612 "gix-ref",
617613 "gix-sec",
618− "log",
619614 "memchr",
620− "nom",
621615 "once_cell",
622616 "smallvec",
623617 "thiserror",
624618 "unicode-bom",
619+ "winnow 0.5.28",
625620]
626621
627622[[package]]
628623name = "gix-config-value"
629−version = "0.10.2"
624+version = "0.14.2"
630625source = "registry+https://github.com/rust-lang/crates.io-index"
631−checksum = "d09154c0c8677e4da0ec35e896f56ee3e338e741b9599fae06075edd83a4081c"
626+checksum = "39f388cb2396aee82d6f460a2e7770659bdf8854e9e5478f7d2b1324a9698284"
632627dependencies = [
633− "bitflags 1.3.2",
628+ "bitflags 2.4.1",
634629 "bstr",
635630 "gix-path",
636631 "libc",
637632 "thiserror",
638633]
639634
640−[[package]]
641−name = "gix-credentials"
642−version = "0.12.0"
643−source = "registry+https://github.com/rust-lang/crates.io-index"
644−checksum = "750b684197374518ea057e0a0594713e07683faa0a3f43c0f93d97f64130ad8d"
645−dependencies = [
646− "bstr",
647− "gix-command",
648− "gix-config-value",
649− "gix-path",
650− "gix-prompt",
651− "gix-sec",
652− "gix-url",
653− "thiserror",
654−]
655−
656635[[package]]
657636name = "gix-date"
658−version = "0.4.3"
637+version = "0.8.2"
659638source = "registry+https://github.com/rust-lang/crates.io-index"
660−checksum = "b96271912ce39822501616f177dea7218784e6c63be90d5f36322ff3a722aae2"
639+checksum = "9d85f4a01e0d05c3de585e28bae514d4baf01655e3fc3f14ce6f30bf62405345"
661640dependencies = [
662641 "bstr",
663642 "itoa",
@@ -667,21 +646,21 @@ dependencies = [
667646
668647[[package]]
669648name = "gix-diff"
670−version = "0.28.1"
649+version = "0.39.0"
671650source = "registry+https://github.com/rust-lang/crates.io-index"
672−checksum = "103a0fa79b0d438f5ecb662502f052e530ace4fe1fe8e1c83c0c6da76d728e67"
651+checksum = "ac0e75f5afd2f6c47c800b6b0a000a08045739d0450d20482e8faa42543f62d1"
673652dependencies = [
653+ "bstr",
674654 "gix-hash",
675655 "gix-object",
676− "imara-diff",
677656 "thiserror",
678657]
679658
680659[[package]]
681660name = "gix-discover"
682−version = "0.16.2"
661+version = "0.28.0"
683662source = "registry+https://github.com/rust-lang/crates.io-index"
684−checksum = "6eba8ba458cb8f4a6c33409b0fe650b1258655175a7ffd1d24fafd3ed31d880b"
663+checksum = "f0b63ce2ad81632ac1a84ac370f85a5e580c3261580bd85ea17ff35d3a0bba18"
685664dependencies = [
686665 "bstr",
687666 "dunce",
@@ -694,13 +673,14 @@ dependencies = [
694673
695674[[package]]
696675name = "gix-features"
697−version = "0.28.1"
676+version = "0.37.0"
698677source = "registry+https://github.com/rust-lang/crates.io-index"
699−checksum = "0b76f9a80f6dd7be66442ae86e1f534effad9546676a392acc95e269d0c21c22"
678+checksum = "befe7edea299a824504b5acc96d7a3a538125b38c42f3a8379f6912a29c90c81"
700679dependencies = [
701680 "crc32fast",
702681 "flate2",
703682 "gix-hash",
683+ "gix-trace",
704684 "libc",
705685 "once_cell",
706686 "prodash",
@@ -709,107 +689,97 @@ dependencies = [
709689 "walkdir",
710690]
711691
692+[[package]]
693+name = "gix-fs"
694+version = "0.9.0"
695+source = "registry+https://github.com/rust-lang/crates.io-index"
696+checksum = "007017ce93b819ea52c0ec68306f7d72212a2d307c3d70f1548c7141c015d0a1"
697+dependencies = [
698+ "gix-features",
699+]
700+
712701[[package]]
713702name = "gix-glob"
714−version = "0.5.5"
703+version = "0.15.0"
715704source = "registry+https://github.com/rust-lang/crates.io-index"
716−checksum = "93e43efd776bc543f46f0fd0ca3d920c37af71a764a16f2aebd89765e9ff2993"
705+checksum = "61fb116c2516d3a1170e010118f639944a6389872c875a008960cdab2a44ac72"
717706dependencies = [
718− "bitflags 1.3.2",
707+ "bitflags 2.4.1",
719708 "bstr",
709+ "gix-features",
710+ "gix-path",
720711]
721712
722713[[package]]
723714name = "gix-hash"
724−version = "0.10.3"
715+version = "0.14.0"
725716source = "registry+https://github.com/rust-lang/crates.io-index"
726−checksum = "0c0c5a9f4d621d4f4ea046bb331df5c746ca735b8cae5b234cc2be70ee4dbef0"
717+checksum = "c52c3170a17b6031833cd2eb4ad7bc23f2755d06e6e70f78dec21d42e8fe1b30"
727718dependencies = [
728− "hex",
719+ "faster-hex",
729720 "thiserror",
730721]
731722
732723[[package]]
733724name = "gix-hashtable"
734−version = "0.1.2"
725+version = "0.5.0"
735726source = "registry+https://github.com/rust-lang/crates.io-index"
736−checksum = "9609c1b8f36f12968e6a6098f7cdb52004f7d42d570f47a2d6d7c16612f19acb"
727+checksum = "40a838e6366a5e5b84668b6997ce0981b833136468e8ba949f424c0ef2927eba"
737728dependencies = [
738729 "gix-hash",
739− "hashbrown 0.13.2",
730+ "hashbrown 0.14.3",
740731 "parking_lot",
741732]
742733
743−[[package]]
744−name = "gix-index"
745−version = "0.15.1"
746−source = "registry+https://github.com/rust-lang/crates.io-index"
747−checksum = "717ab601ece7921f59fe86849dbe27d44a46ebb883b5885732c4f30df4996177"
748−dependencies = [
749− "bitflags 1.3.2",
750− "bstr",
751− "btoi",
752− "filetime",
753− "gix-bitmap",
754− "gix-features",
755− "gix-hash",
756− "gix-lock",
757− "gix-object",
758− "gix-traverse",
759− "itoa",
760− "memmap2",
761− "smallvec",
762− "thiserror",
763−]
764−
765734[[package]]
766735name = "gix-lock"
767−version = "5.0.0"
736+version = "12.0.0"
768737source = "registry+https://github.com/rust-lang/crates.io-index"
769−checksum = "41b80172055c5d8017a48ddac5cc7a95421c00211047db0165c97853c4f05194"
738+checksum = "6cf112ddee94223c119a8534dad027740dc3aba3365ac5edeef8a7f6660c74db"
770739dependencies = [
771− "fastrand",
772740 "gix-tempfile",
741+ "gix-utils",
773742 "thiserror",
774743]
775744
776745[[package]]
777−name = "gix-mailmap"
778−version = "0.11.0"
746+name = "gix-macros"
747+version = "0.1.2"
779748source = "registry+https://github.com/rust-lang/crates.io-index"
780−checksum = "2b66aea5e52875cd4915f4957a6f4b75831a36981e2ec3f5fad9e370e444fe1a"
749+checksum = "fc207b64189cf71bcb17fc841ab99a5d63d0845546b611e2703ce467f659323a"
781750dependencies = [
782− "bstr",
783− "gix-actor",
784− "thiserror",
751+ "proc-macro2",
752+ "quote",
753+ "syn 2.0.38",
785754]
786755
787756[[package]]
788757name = "gix-object"
789−version = "0.28.0"
758+version = "0.40.0"
790759source = "registry+https://github.com/rust-lang/crates.io-index"
791−checksum = "8df068db9180ee935fbb70504848369e270bdcb576b05c0faa8b9fd3b86fc017"
760+checksum = "e4c77e47ffba92127faf632a841fce23d19547e269bd8b88e68961a70eab4e93"
792761dependencies = [
793762 "bstr",
794763 "btoi",
795764 "gix-actor",
765+ "gix-date",
796766 "gix-features",
797767 "gix-hash",
798768 "gix-validate",
799− "hex",
800769 "itoa",
801− "nom",
802770 "smallvec",
803771 "thiserror",
772+ "winnow 0.5.28",
804773]
805774
806775[[package]]
807776name = "gix-odb"
808−version = "0.43.1"
777+version = "0.56.0"
809778source = "registry+https://github.com/rust-lang/crates.io-index"
810−checksum = "e83af2e3e36005bfe010927f0dff41fb5acc3e3d89c6f1174135b3a34086bda2"
779+checksum = "a3254f2005cc7553ea78e85e816a09150c6f7a64e6b7627b8d1fdc56721bea73"
811780dependencies = [
812781 "arc-swap",
782+ "gix-date",
813783 "gix-features",
814784 "gix-hash",
815785 "gix-object",
@@ -823,20 +793,18 @@ dependencies = [
823793
824794[[package]]
825795name = "gix-pack"
826−version = "0.33.2"
796+version = "0.46.0"
827797source = "registry+https://github.com/rust-lang/crates.io-index"
828−checksum = "9401911c7fe032ad7b31c6a6b5be59cb283d1d6c999417a8215056efe6d635f3"
798+checksum = "02ebc8cd657eec207d82d8f876ca402361308ecd3c87a47935b0299506257b4f"
829799dependencies = [
830800 "clru",
831801 "gix-chunk",
832− "gix-diff",
833802 "gix-features",
834803 "gix-hash",
835804 "gix-hashtable",
836805 "gix-object",
837806 "gix-path",
838807 "gix-tempfile",
839− "gix-traverse",
840808 "memmap2",
841809 "parking_lot",
842810 "smallvec",
@@ -845,32 +813,22 @@ dependencies = [
845813
846814[[package]]
847815name = "gix-path"
848−version = "0.7.3"
816+version = "0.10.2"
849817source = "registry+https://github.com/rust-lang/crates.io-index"
850−checksum = "32370dce200bb951df013e03dff35b4233fc7a89458642b047629b91734a7e19"
818+checksum = "10931652a3da126990ac93bce1b1c600cc99d7c268d712b6360ed52174ce1b68"
851819dependencies = [
852820 "bstr",
853− "thiserror",
854−]
855−
856−[[package]]
857−name = "gix-prompt"
858−version = "0.3.3"
859−source = "registry+https://github.com/rust-lang/crates.io-index"
860−checksum = "0f3034d4d935aef2c7bf719aaa54b88c520e82413118d886ae880a31d5bdee57"
861−dependencies = [
862− "gix-command",
863− "gix-config-value",
864− "nix",
865− "parking_lot",
821+ "gix-trace",
822+ "home",
823+ "once_cell",
866824 "thiserror",
867825]
868826
869827[[package]]
870828name = "gix-quote"
871−version = "0.4.3"
829+version = "0.4.9"
872830source = "registry+https://github.com/rust-lang/crates.io-index"
873−checksum = "a282f5a8d9ee0b09ec47390ac727350c48f2f5c76d803cd8da6b3e7ad56e0bcb"
831+checksum = "6c43530cb94a7759807e6f8d180e17ac9c65673b891645c6c433831dc0cf4342"
874832dependencies = [
875833 "bstr",
876834 "btoi",
@@ -879,12 +837,14 @@ dependencies = [
879837
880838[[package]]
881839name = "gix-ref"
882−version = "0.27.2"
840+version = "0.40.0"
883841source = "registry+https://github.com/rust-lang/crates.io-index"
884−checksum = "e4e909396ed3b176823991ccc391c276ae2a015e54edaafa3566d35123cfac9d"
842+checksum = "baa951b3b850d6d1be4f900768e49af20b76bf9505beac22af723d57249a2f1d"
885843dependencies = [
886844 "gix-actor",
845+ "gix-date",
887846 "gix-features",
847+ "gix-fs",
888848 "gix-hash",
889849 "gix-lock",
890850 "gix-object",
@@ -892,15 +852,15 @@ dependencies = [
892852 "gix-tempfile",
893853 "gix-validate",
894854 "memmap2",
895− "nom",
896855 "thiserror",
856+ "winnow 0.5.28",
897857]
898858
899859[[package]]
900860name = "gix-refspec"
901−version = "0.9.0"
861+version = "0.21.0"
902862source = "registry+https://github.com/rust-lang/crates.io-index"
903−checksum = "aba332462bda2e8efeae4302b39a6ed01ad56ef772fd5b7ef197cf2798294d65"
863+checksum = "88c61f849d58c06e3068a0b601cf10127d2a07cdad00a725ed66cf303f76f6b3"
904864dependencies = [
905865 "bstr",
906866 "gix-hash",
@@ -912,62 +872,87 @@ dependencies = [
912872
913873[[package]]
914874name = "gix-revision"
915−version = "0.12.1"
875+version = "0.25.0"
916876source = "registry+https://github.com/rust-lang/crates.io-index"
917−checksum = "b12fc4bbc3161a5b2d68079fce93432cef8771ff88ca017abb01187fddfc41a1"
877+checksum = "bcbbf91f4200c5c76802ef5f057b96f5a336827508881fe55a780be71a794d22"
918878dependencies = [
919879 "bstr",
920880 "gix-date",
921881 "gix-hash",
922882 "gix-hashtable",
923883 "gix-object",
884+ "gix-revwalk",
885+ "gix-trace",
886+ "thiserror",
887+]
888+
889+[[package]]
890+name = "gix-revwalk"
891+version = "0.11.0"
892+source = "registry+https://github.com/rust-lang/crates.io-index"
893+checksum = "33ab66354d83f70f2730391747d0c75f94ef3c3dd40ebde2e206db9faaf7a0a7"
894+dependencies = [
895+ "gix-commitgraph",
896+ "gix-date",
897+ "gix-hash",
898+ "gix-hashtable",
899+ "gix-object",
900+ "smallvec",
924901 "thiserror",
925902]
926903
927904[[package]]
928905name = "gix-sec"
929−version = "0.6.2"
906+version = "0.10.2"
930907source = "registry+https://github.com/rust-lang/crates.io-index"
931−checksum = "e8ffa5bf0772f9b01de501c035b6b084cf9b8bb07dec41e3afc6a17336a65f47"
908+checksum = "a9963f38a42144253ed4d571882d7db5ef644c12e6726e4b75135597cc9d0e1a"
932909dependencies = [
933− "bitflags 1.3.2",
934− "dirs",
910+ "bitflags 2.4.1",
935911 "gix-path",
936912 "libc",
937− "windows 0.43.0",
913+ "windows 0.52.0",
938914]
939915
940916[[package]]
941917name = "gix-tempfile"
942−version = "5.0.2"
918+version = "12.0.0"
943919source = "registry+https://github.com/rust-lang/crates.io-index"
944−checksum = "c2ceb30a610e3f5f2d5f9a5114689fde507ba9417705a8cf3429604275b2153c"
920+checksum = "e76a494bd530e1a1309188ff971825a24f159c76c2db0bf71fa5dfb469a2c915"
945921dependencies = [
922+ "gix-fs",
946923 "libc",
947924 "once_cell",
948925 "parking_lot",
949− "signal-hook",
950− "signal-hook-registry",
951926 "tempfile",
952927]
953928
929+[[package]]
930+name = "gix-trace"
931+version = "0.1.5"
932+source = "registry+https://github.com/rust-lang/crates.io-index"
933+checksum = "bda62acb44dd86a40c7c3762a5403cfc1ac789ea559df54085cedf79864f809e"
934+
954935[[package]]
955936name = "gix-traverse"
956−version = "0.24.0"
937+version = "0.36.0"
957938source = "registry+https://github.com/rust-lang/crates.io-index"
958−checksum = "dd9a4a07bb22168dc79c60e1a6a41919d198187ca83d8a5940ad8d7122a45df3"
939+checksum = "8661fab39985c9214e56d81a63ceb5886ac948cec2fba76c39494d1e0e307ea8"
959940dependencies = [
941+ "gix-commitgraph",
942+ "gix-date",
960943 "gix-hash",
961944 "gix-hashtable",
962945 "gix-object",
946+ "gix-revwalk",
947+ "smallvec",
963948 "thiserror",
964949]
965950
966951[[package]]
967952name = "gix-url"
968−version = "0.16.0"
953+version = "0.26.0"
969954source = "registry+https://github.com/rust-lang/crates.io-index"
970−checksum = "b6a22b4b32ad14d68f7b7fb6458fa58d44b01797d94c1b8f4db2d9c7b3c366b5"
955+checksum = "10a0129c1e8b52736d7c5128300a4485dbc85863001371e2771ac1754bd89fd7"
971956dependencies = [
972957 "bstr",
973958 "gix-features",
@@ -978,82 +963,71 @@ dependencies = [
978963]
979964
980965[[package]]
981−name = "gix-validate"
982−version = "0.7.4"
966+name = "gix-utils"
967+version = "0.1.7"
983968source = "registry+https://github.com/rust-lang/crates.io-index"
984−checksum = "7bd629d3680773e1785e585d76fd4295b740b559cad9141517300d99a0c8c049"
969+checksum = "ab9277a5e32e85d53f738096d872a4a9b76067ad471894ad31bd99c8fa2da1dc"
985970dependencies = [
986− "bstr",
987− "thiserror",
971+ "fastrand",
988972]
989973
990974[[package]]
991−name = "gix-worktree"
992−version = "0.15.2"
975+name = "gix-validate"
976+version = "0.8.2"
993977source = "registry+https://github.com/rust-lang/crates.io-index"
994−checksum = "54ec9a000b4f24af706c3cc680c7cda235656cbe3216336522f5692773b8a301"
978+checksum = "f805ebbdbaa4bfd98e2ee43e6d14099b8a4d9141f5d7b8202fea4d48e44263e7"
995979dependencies = [
996980 "bstr",
997− "gix-attributes",
998− "gix-features",
999− "gix-glob",
1000− "gix-hash",
1001− "gix-index",
1002− "gix-object",
1003− "gix-path",
1004− "io-close",
1005981 "thiserror",
1006982]
1007983
1008984[[package]]
1009985name = "globset"
1010−version = "0.4.10"
986+version = "0.4.14"
1011987source = "registry+https://github.com/rust-lang/crates.io-index"
1012−checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
988+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
1013989dependencies = [
1014990 "aho-corasick",
1015991 "bstr",
1016− "fnv",
1017992 "log",
1018− "regex",
993+ "regex-automata",
994+ "regex-syntax",
1019995]
1020996
1021997[[package]]
1022998name = "grep-matcher"
1023−version = "0.1.6"
999+version = "0.1.7"
10241000source = "registry+https://github.com/rust-lang/crates.io-index"
1025−checksum = "3902ca28f26945fe35cad349d776f163981d777fee382ccd6ef451126f51b319"
1001+checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02"
10261002dependencies = [
10271003 "memchr",
10281004]
10291005
10301006[[package]]
10311007name = "grep-regex"
1032−version = "0.1.11"
1008+version = "0.1.12"
10331009source = "registry+https://github.com/rust-lang/crates.io-index"
1034−checksum = "997598b41d53a37a2e3fc5300d5c11d825368c054420a9c65125b8fe1078463f"
1010+checksum = "f748bb135ca835da5cbc67ca0e6955f968db9c5df74ca4f56b18e1ddbc68230d"
10351011dependencies = [
1036− "aho-corasick",
10371012 "bstr",
10381013 "grep-matcher",
10391014 "log",
1040− "regex",
1015+ "regex-automata",
10411016 "regex-syntax",
1042− "thread_local",
10431017]
10441018
10451019[[package]]
10461020name = "grep-searcher"
1047−version = "0.1.11"
1021+version = "0.1.13"
10481022source = "registry+https://github.com/rust-lang/crates.io-index"
1049−checksum = "5601c4b9f480f0c9ebb40b1f6cbf447b8a50c5369223937a6c5214368c58779f"
1023+checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54"
10501024dependencies = [
10511025 "bstr",
1052− "bytecount",
10531026 "encoding_rs",
10541027 "encoding_rs_io",
10551028 "grep-matcher",
10561029 "log",
1030+ "memchr",
10571031 "memmap2",
10581032]
10591033
@@ -1062,36 +1036,36 @@ name = "hashbrown"
10621036version = "0.12.3"
10631037source = "registry+https://github.com/rust-lang/crates.io-index"
10641038checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1065−dependencies = [
1066− "ahash 0.7.6",
1067−]
10681039
10691040[[package]]
10701041name = "hashbrown"
1071−version = "0.13.2"
1042+version = "0.14.3"
10721043source = "registry+https://github.com/rust-lang/crates.io-index"
1073−checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
1044+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
10741045dependencies = [
1075− "ahash 0.8.3",
1046+ "ahash",
1047+ "allocator-api2",
10761048]
10771049
10781050[[package]]
10791051name = "helix-core"
1080−version = "0.6.0"
1052+version = "23.10.0"
10811053dependencies = [
1082− "ahash 0.8.3",
1054+ "ahash",
10831055 "arc-swap",
1084− "bitflags 2.1.0",
1056+ "bitflags 2.4.1",
10851057 "chrono",
10861058 "dunce",
10871059 "encoding_rs",
10881060 "etcetera",
1089− "hashbrown 0.13.2",
1061+ "hashbrown 0.14.3",
10901062 "helix-loader",
10911063 "imara-diff",
10921064 "indoc",
10931065 "log",
1066+ "nucleo",
10941067 "once_cell",
1068+ "parking_lot",
10951069 "quickcheck",
10961070 "regex",
10971071 "ropey",
@@ -1110,7 +1084,7 @@ dependencies = [
11101084
11111085[[package]]
11121086name = "helix-dap"
1113−version = "0.6.0"
1087+version = "23.10.0"
11141088dependencies = [
11151089 "anyhow",
11161090 "fern",
@@ -1123,29 +1097,41 @@ dependencies = [
11231097 "which",
11241098]
11251099
1100+[[package]]
1101+name = "helix-event"
1102+version = "23.10.0"
1103+dependencies = [
1104+ "parking_lot",
1105+ "tokio",
1106+]
1107+
11261108[[package]]
11271109name = "helix-loader"
1128−version = "0.6.0"
1110+version = "23.10.0"
11291111dependencies = [
11301112 "anyhow",
11311113 "cc",
1114+ "dunce",
11321115 "etcetera",
11331116 "libloading",
11341117 "log",
11351118 "once_cell",
11361119 "serde",
1120+ "tempfile",
11371121 "threadpool",
11381122 "toml",
11391123 "tree-sitter",
1124+ "which",
11401125]
11411126
11421127[[package]]
11431128name = "helix-lsp"
1144−version = "0.6.0"
1129+version = "23.10.0"
11451130dependencies = [
11461131 "anyhow",
11471132 "futures-executor",
11481133 "futures-util",
1134+ "globset",
11491135 "helix-core",
11501136 "helix-loader",
11511137 "helix-parsec",
@@ -1162,11 +1148,11 @@ dependencies = [
11621148
11631149[[package]]
11641150name = "helix-parsec"
1165−version = "0.6.0"
1151+version = "23.10.0"
11661152
11671153[[package]]
11681154name = "helix-term"
1169−version = "0.6.0"
1155+version = "23.10.0"
11701156dependencies = [
11711157 "anyhow",
11721158 "arc-swap",
@@ -1175,11 +1161,11 @@ dependencies = [
11751161 "crossterm",
11761162 "fern",
11771163 "futures-util",
1178− "fuzzy-matcher",
11791164 "grep-regex",
11801165 "grep-searcher",
11811166 "helix-core",
11821167 "helix-dap",
1168+ "helix-event",
11831169 "helix-loader",
11841170 "helix-lsp",
11851171 "helix-tui",
@@ -1189,7 +1175,9 @@ dependencies = [
11891175 "indoc",
11901176 "libc",
11911177 "log",
1178+ "nucleo",
11921179 "once_cell",
1180+ "open",
11931181 "pulldown-cmark",
11941182 "serde",
11951183 "serde_json",
@@ -1200,14 +1188,15 @@ dependencies = [
12001188 "tokio",
12011189 "tokio-stream",
12021190 "toml",
1191+ "url",
12031192 "which",
12041193]
12051194
12061195[[package]]
12071196name = "helix-tui"
1208−version = "0.6.0"
1197+version = "23.10.0"
12091198dependencies = [
1210− "bitflags 2.1.0",
1199+ "bitflags 2.4.1",
12111200 "cassowary",
12121201 "crossterm",
12131202 "helix-core",
@@ -1221,12 +1210,13 @@ dependencies = [
12211210
12221211[[package]]
12231212name = "helix-vcs"
1224−version = "0.6.0"
1213+version = "23.10.0"
12251214dependencies = [
12261215 "anyhow",
12271216 "arc-swap",
12281217 "gix",
12291218 "helix-core",
1219+ "helix-event",
12301220 "imara-diff",
12311221 "log",
12321222 "parking_lot",
@@ -1236,17 +1226,18 @@ dependencies = [
12361226
12371227[[package]]
12381228name = "helix-view"
1239−version = "0.6.0"
1229+version = "23.10.0"
12401230dependencies = [
12411231 "anyhow",
12421232 "arc-swap",
1243− "bitflags 2.1.0",
1233+ "bitflags 2.4.1",
12441234 "chardetng",
12451235 "clipboard-win",
12461236 "crossterm",
12471237 "futures-util",
12481238 "helix-core",
12491239 "helix-dap",
1240+ "helix-event",
12501241 "helix-loader",
12511242 "helix-lsp",
12521243 "helix-tui",
@@ -1255,6 +1246,7 @@ dependencies = [
12551246 "log",
12561247 "once_cell",
12571248 "parking_lot",
1249+ "rustix",
12581250 "serde",
12591251 "serde_json",
12601252 "slotmap",
@@ -1274,39 +1266,27 @@ dependencies = [
12741266 "libc",
12751267]
12761268
1277−[[package]]
1278−name = "hermit-abi"
1279−version = "0.3.1"
1280−source = "registry+https://github.com/rust-lang/crates.io-index"
1281−checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
1282−
1283−[[package]]
1284−name = "hex"
1285−version = "0.4.3"
1286−source = "registry+https://github.com/rust-lang/crates.io-index"
1287−checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1288−
12891269[[package]]
12901270name = "home"
1291−version = "0.5.4"
1271+version = "0.5.5"
12921272source = "registry+https://github.com/rust-lang/crates.io-index"
1293−checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
1273+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
12941274dependencies = [
1295− "winapi",
1275+ "windows-sys 0.48.0",
12961276]
12971277
12981278[[package]]
12991279name = "iana-time-zone"
1300−version = "0.1.55"
1280+version = "0.1.56"
13011281source = "registry+https://github.com/rust-lang/crates.io-index"
1302−checksum = "716f12fbcfac6ffab0a5e9ec51d0a0ff70503742bb2dc7b99396394c9dc323f0"
1282+checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
13031283dependencies = [
13041284 "android_system_properties",
13051285 "core-foundation-sys",
13061286 "iana-time-zone-haiku",
13071287 "js-sys",
13081288 "wasm-bindgen",
1309− "windows 0.47.0",
1289+ "windows 0.48.0",
13101290]
13111291
13121292[[package]]
@@ -1321,9 +1301,9 @@ dependencies = [
13211301
13221302[[package]]
13231303name = "idna"
1324−version = "0.3.0"
1304+version = "0.5.0"
13251305source = "registry+https://github.com/rust-lang/crates.io-index"
1326−checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
1306+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
13271307dependencies = [
13281308 "unicode-bidi",
13291309 "unicode-normalization",
@@ -1331,17 +1311,16 @@ dependencies = [
13311311
13321312[[package]]
13331313name = "ignore"
1334−version = "0.4.20"
1314+version = "0.4.21"
13351315source = "registry+https://github.com/rust-lang/crates.io-index"
1336−checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
1316+checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
13371317dependencies = [
1318+ "crossbeam-deque",
13381319 "globset",
1339− "lazy_static",
13401320 "log",
13411321 "memchr",
1342− "regex",
1322+ "regex-automata",
13431323 "same-file",
1344− "thread_local",
13451324 "walkdir",
13461325 "winapi-util",
13471326]
@@ -1352,54 +1331,43 @@ version = "0.1.5"
13521331source = "registry+https://github.com/rust-lang/crates.io-index"
13531332checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
13541333dependencies = [
1355− "ahash 0.8.3",
1334+ "ahash",
13561335 "hashbrown 0.12.3",
13571336]
13581337
13591338[[package]]
13601339name = "indexmap"
1361−version = "1.9.3"
1340+version = "2.0.0"
13621341source = "registry+https://github.com/rust-lang/crates.io-index"
1363−checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
1342+checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
13641343dependencies = [
1365− "autocfg",
1366− "hashbrown 0.12.3",
1344+ "equivalent",
1345+ "hashbrown 0.14.3",
13671346]
13681347
13691348[[package]]
13701349name = "indoc"
1371−version = "2.0.1"
1372−source = "registry+https://github.com/rust-lang/crates.io-index"
1373−checksum = "9f2cb48b81b1dc9f39676bf99f5499babfec7cd8fe14307f7b3d747208fb5690"
1374−
1375−[[package]]
1376−name = "instant"
1377−version = "0.1.12"
1350+version = "2.0.4"
13781351source = "registry+https://github.com/rust-lang/crates.io-index"
1379−checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
1380−dependencies = [
1381− "cfg-if",
1382−]
1352+checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
13831353
13841354[[package]]
1385−name = "io-close"
1386−version = "0.3.7"
1355+name = "is-docker"
1356+version = "0.2.0"
13871357source = "registry+https://github.com/rust-lang/crates.io-index"
1388−checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc"
1358+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
13891359dependencies = [
1390− "libc",
1391− "winapi",
1360+ "once_cell",
13921361]
13931362
13941363[[package]]
1395−name = "io-lifetimes"
1396−version = "1.0.9"
1364+name = "is-wsl"
1365+version = "0.4.0"
13971366source = "registry+https://github.com/rust-lang/crates.io-index"
1398−checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
1367+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
13991368dependencies = [
1400− "hermit-abi 0.3.1",
1401− "libc",
1402− "windows-sys",
1369+ "is-docker",
1370+ "once_cell",
14031371]
14041372
14051373[[package]]
@@ -1417,26 +1385,20 @@ dependencies = [
14171385 "wasm-bindgen",
14181386]
14191387
1420−[[package]]
1421−name = "lazy_static"
1422−version = "1.4.0"
1423−source = "registry+https://github.com/rust-lang/crates.io-index"
1424−checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
1425−
14261388[[package]]
14271389name = "libc"
1428−version = "0.2.141"
1390+version = "0.2.151"
14291391source = "registry+https://github.com/rust-lang/crates.io-index"
1430−checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
1392+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
14311393
14321394[[package]]
14331395name = "libloading"
1434−version = "0.7.4"
1396+version = "0.8.1"
14351397source = "registry+https://github.com/rust-lang/crates.io-index"
1436−checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
1398+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
14371399dependencies = [
14381400 "cfg-if",
1439− "winapi",
1401+ "windows-sys 0.48.0",
14401402]
14411403
14421404[[package]]
@@ -1450,9 +1412,9 @@ dependencies = [
14501412
14511413[[package]]
14521414name = "linux-raw-sys"
1453−version = "0.3.0"
1415+version = "0.4.11"
14541416source = "registry+https://github.com/rust-lang/crates.io-index"
1455−checksum = "cd550e73688e6d578f0ac2119e32b797a327631a42f9433e59d02e139c8df60d"
1417+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
14561418
14571419[[package]]
14581420name = "lock_api"
@@ -1466,18 +1428,15 @@ dependencies = [
14661428
14671429[[package]]
14681430name = "log"
1469−version = "0.4.17"
1431+version = "0.4.20"
14701432source = "registry+https://github.com/rust-lang/crates.io-index"
1471−checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
1472−dependencies = [
1473− "cfg-if",
1474−]
1433+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
14751434
14761435[[package]]
14771436name = "lsp-types"
1478−version = "0.94.0"
1437+version = "0.95.0"
14791438source = "registry+https://github.com/rust-lang/crates.io-index"
1480−checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
1439+checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
14811440dependencies = [
14821441 "bitflags 1.3.2",
14831442 "serde",
@@ -1488,76 +1447,69 @@ dependencies = [
14881447
14891448[[package]]
14901449name = "memchr"
1491−version = "2.5.0"
1450+version = "2.6.3"
14921451source = "registry+https://github.com/rust-lang/crates.io-index"
1493−checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
1452+checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
14941453
14951454[[package]]
14961455name = "memmap2"
1497−version = "0.5.10"
1456+version = "0.9.0"
14981457source = "registry+https://github.com/rust-lang/crates.io-index"
1499−checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
1458+checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
15001459dependencies = [
15011460 "libc",
15021461]
15031462
15041463[[package]]
1505−name = "minimal-lexical"
1506−version = "0.2.1"
1464+name = "memoffset"
1465+version = "0.9.0"
15071466source = "registry+https://github.com/rust-lang/crates.io-index"
1508−checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
1467+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
1468+dependencies = [
1469+ "autocfg",
1470+]
15091471
15101472[[package]]
15111473name = "miniz_oxide"
1512−version = "0.6.2"
1474+version = "0.7.1"
15131475source = "registry+https://github.com/rust-lang/crates.io-index"
1514−checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
1476+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
15151477dependencies = [
15161478 "adler",
15171479]
15181480
15191481[[package]]
15201482name = "mio"
1521−version = "0.8.6"
1483+version = "0.8.9"
15221484source = "registry+https://github.com/rust-lang/crates.io-index"
1523−checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
1485+checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
15241486dependencies = [
15251487 "libc",
15261488 "log",
15271489 "wasi",
1528− "windows-sys",
1490+ "windows-sys 0.48.0",
15291491]
15301492
15311493[[package]]
1532−name = "nix"
1533−version = "0.26.2"
1494+name = "nucleo"
1495+version = "0.2.1"
15341496source = "registry+https://github.com/rust-lang/crates.io-index"
1535−checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
1497+checksum = "ae5331f4bcce475cf28cb29c95366c3091af4b0aa7703f1a6bc858f29718fdf3"
15361498dependencies = [
1537− "bitflags 1.3.2",
1538− "cfg-if",
1539− "libc",
1540− "static_assertions",
1499+ "nucleo-matcher",
1500+ "parking_lot",
1501+ "rayon",
15411502]
15421503
15431504[[package]]
1544−name = "nom"
1545−version = "7.1.3"
1505+name = "nucleo-matcher"
1506+version = "0.2.0"
15461507source = "registry+https://github.com/rust-lang/crates.io-index"
1547−checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
1508+checksum = "1b702b402fe286162d1f00b552a046ce74365d2ac473a2607ff36ba650f9bd57"
15481509dependencies = [
1510+ "cov-mark",
15491511 "memchr",
1550− "minimal-lexical",
1551−]
1552−
1553−[[package]]
1554−name = "num-integer"
1555−version = "0.1.45"
1556−source = "registry+https://github.com/rust-lang/crates.io-index"
1557−checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
1558−dependencies = [
1559− "autocfg",
1560− "num-traits",
1512+ "unicode-segmentation",
15611513]
15621514
15631515[[package]]
@@ -1575,7 +1527,7 @@ version = "1.15.0"
15751527source = "registry+https://github.com/rust-lang/crates.io-index"
15761528checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
15771529dependencies = [
1578− "hermit-abi 0.2.6",
1530+ "hermit-abi",
15791531 "libc",
15801532]
15811533
@@ -1588,11 +1540,31 @@ dependencies = [
15881540 "libc",
15891541]
15901542
1543+[[package]]
1544+name = "object"
1545+version = "0.31.1"
1546+source = "registry+https://github.com/rust-lang/crates.io-index"
1547+checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
1548+dependencies = [
1549+ "memchr",
1550+]
1551+
15911552[[package]]
15921553name = "once_cell"
1593−version = "1.17.1"
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"
15941561source = "registry+https://github.com/rust-lang/crates.io-index"
1595−checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
1562+checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349"
1563+dependencies = [
1564+ "is-wsl",
1565+ "libc",
1566+ "pathdiff",
1567+]
15961568
15971569[[package]]
15981570name = "parking_lot"
@@ -1614,20 +1586,26 @@ dependencies = [
16141586 "libc",
16151587 "redox_syscall 0.2.16",
16161588 "smallvec",
1617− "windows-sys",
1589+ "windows-sys 0.45.0",
16181590]
16191591
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+
16201598[[package]]
16211599name = "percent-encoding"
1622−version = "2.2.0"
1600+version = "2.3.1"
16231601source = "registry+https://github.com/rust-lang/crates.io-index"
1624−checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
1602+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
16251603
16261604[[package]]
16271605name = "pin-project-lite"
1628−version = "0.2.9"
1606+version = "0.2.12"
16291607source = "registry+https://github.com/rust-lang/crates.io-index"
1630−checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
1608+checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
16311609
16321610[[package]]
16331611name = "pin-utils"
@@ -1637,24 +1615,24 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
16371615
16381616[[package]]
16391617name = "proc-macro2"
1640−version = "1.0.54"
1618+version = "1.0.69"
16411619source = "registry+https://github.com/rust-lang/crates.io-index"
1642−checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534"
1620+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
16431621dependencies = [
16441622 "unicode-ident",
16451623]
16461624
16471625[[package]]
16481626name = "prodash"
1649−version = "23.1.2"
1627+version = "28.0.0"
16501628source = "registry+https://github.com/rust-lang/crates.io-index"
1651−checksum = "9516b775656bc3e8985e19cd4b8c0c0de045095074e453d2c0a513b5f978392d"
1629+checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
16521630
16531631[[package]]
16541632name = "pulldown-cmark"
1655−version = "0.9.2"
1633+version = "0.9.3"
16561634source = "registry+https://github.com/rust-lang/crates.io-index"
1657−checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
1635+checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
16581636dependencies = [
16591637 "bitflags 1.3.2",
16601638 "memchr",
@@ -1672,9 +1650,9 @@ dependencies = [
16721650
16731651[[package]]
16741652name = "quote"
1675−version = "1.0.26"
1653+version = "1.0.29"
16761654source = "registry+https://github.com/rust-lang/crates.io-index"
1677−checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
1655+checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
16781656dependencies = [
16791657 "proc-macro2",
16801658]
@@ -1698,79 +1676,101 @@ dependencies = [
16981676]
16991677
17001678[[package]]
1701−name = "redox_syscall"
1702−version = "0.2.16"
1679+name = "rayon"
1680+version = "1.7.0"
17031681source = "registry+https://github.com/rust-lang/crates.io-index"
1704−checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
1682+checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
17051683dependencies = [
1706− "bitflags 1.3.2",
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",
17071698]
17081699
17091700[[package]]
17101701name = "redox_syscall"
1711−version = "0.3.5"
1702+version = "0.2.16"
17121703source = "registry+https://github.com/rust-lang/crates.io-index"
1713−checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
1704+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
17141705dependencies = [
17151706 "bitflags 1.3.2",
17161707]
17171708
17181709[[package]]
1719−name = "redox_users"
1720−version = "0.4.3"
1710+name = "redox_syscall"
1711+version = "0.4.1"
17211712source = "registry+https://github.com/rust-lang/crates.io-index"
1722−checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
1713+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
17231714dependencies = [
1724− "getrandom",
1725− "redox_syscall 0.2.16",
1726− "thiserror",
1715+ "bitflags 1.3.2",
17271716]
17281717
17291718[[package]]
17301719name = "regex"
1731−version = "1.7.3"
1720+version = "1.10.2"
17321721source = "registry+https://github.com/rust-lang/crates.io-index"
1733−checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
1722+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
17341723dependencies = [
17351724 "aho-corasick",
17361725 "memchr",
1726+ "regex-automata",
17371727 "regex-syntax",
17381728]
17391729
17401730[[package]]
17411731name = "regex-automata"
1742−version = "0.1.10"
1732+version = "0.4.3"
17431733source = "registry+https://github.com/rust-lang/crates.io-index"
1744−checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
1734+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
1735+dependencies = [
1736+ "aho-corasick",
1737+ "memchr",
1738+ "regex-syntax",
1739+]
17451740
17461741[[package]]
17471742name = "regex-syntax"
1748−version = "0.6.29"
1743+version = "0.8.2"
17491744source = "registry+https://github.com/rust-lang/crates.io-index"
1750−checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
1745+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
17511746
17521747[[package]]
17531748name = "ropey"
1754−version = "1.6.0"
1749+version = "1.6.1"
17551750source = "registry+https://github.com/rust-lang/crates.io-index"
1756−checksum = "53ce7a2c43a32e50d666e33c5a80251b31147bb4b49024bcab11fb6f20c671ed"
1751+checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
17571752dependencies = [
17581753 "smallvec",
17591754 "str_indices",
17601755]
17611756
1757+[[package]]
1758+name = "rustc-demangle"
1759+version = "0.1.23"
1760+source = "registry+https://github.com/rust-lang/crates.io-index"
1761+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
1762+
17621763[[package]]
17631764name = "rustix"
1764−version = "0.37.5"
1765+version = "0.38.28"
17651766source = "registry+https://github.com/rust-lang/crates.io-index"
1766−checksum = "0e78cc525325c06b4a7ff02db283472f3c042b7ff0c391f96c6d5ac6f4f91b75"
1767+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
17671768dependencies = [
1768− "bitflags 1.3.2",
1769+ "bitflags 2.4.1",
17691770 "errno",
1770− "io-lifetimes",
17711771 "libc",
17721772 "linux-raw-sys",
1773− "windows-sys",
1773+ "windows-sys 0.52.0",
17741774]
17751775
17761776[[package]]
@@ -1802,29 +1802,29 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
18021802
18031803[[package]]
18041804name = "serde"
1805−version = "1.0.159"
1805+version = "1.0.193"
18061806source = "registry+https://github.com/rust-lang/crates.io-index"
1807−checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
1807+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
18081808dependencies = [
18091809 "serde_derive",
18101810]
18111811
18121812[[package]]
18131813name = "serde_derive"
1814−version = "1.0.159"
1814+version = "1.0.193"
18151815source = "registry+https://github.com/rust-lang/crates.io-index"
1816−checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
1816+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
18171817dependencies = [
18181818 "proc-macro2",
18191819 "quote",
1820− "syn 2.0.11",
1820+ "syn 2.0.38",
18211821]
18221822
18231823[[package]]
18241824name = "serde_json"
1825−version = "1.0.95"
1825+version = "1.0.108"
18261826source = "registry+https://github.com/rust-lang/crates.io-index"
1827−checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744"
1827+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
18281828dependencies = [
18291829 "itoa",
18301830 "ryu",
@@ -1839,14 +1839,14 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
18391839dependencies = [
18401840 "proc-macro2",
18411841 "quote",
1842− "syn 2.0.11",
1842+ "syn 2.0.38",
18431843]
18441844
18451845[[package]]
18461846name = "serde_spanned"
1847−version = "0.6.1"
1847+version = "0.6.3"
18481848source = "registry+https://github.com/rust-lang/crates.io-index"
1849−checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
1849+checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
18501850dependencies = [
18511851 "serde",
18521852]
@@ -1859,9 +1859,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
18591859
18601860[[package]]
18611861name = "signal-hook"
1862−version = "0.3.15"
1862+version = "0.3.17"
18631863source = "registry+https://github.com/rust-lang/crates.io-index"
1864−checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
1864+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
18651865dependencies = [
18661866 "libc",
18671867 "signal-hook-registry",
@@ -1910,18 +1910,18 @@ dependencies = [
19101910
19111911[[package]]
19121912name = "slotmap"
1913−version = "1.0.6"
1913+version = "1.0.7"
19141914source = "registry+https://github.com/rust-lang/crates.io-index"
1915−checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
1915+checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
19161916dependencies = [
19171917 "version_check",
19181918]
19191919
19201920[[package]]
19211921name = "smallvec"
1922−version = "1.10.0"
1922+version = "1.11.2"
19231923source = "registry+https://github.com/rust-lang/crates.io-index"
1924−checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
1924+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
19251925
19261926[[package]]
19271927name = "smartstring"
@@ -1942,12 +1942,12 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
19421942
19431943[[package]]
19441944name = "socket2"
1945−version = "0.4.9"
1945+version = "0.5.5"
19461946source = "registry+https://github.com/rust-lang/crates.io-index"
1947−checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
1947+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
19481948dependencies = [
19491949 "libc",
1950− "winapi",
1950+ "windows-sys 0.48.0",
19511951]
19521952
19531953[[package]]
@@ -1956,12 +1956,6 @@ version = "1.1.0"
19561956source = "registry+https://github.com/rust-lang/crates.io-index"
19571957checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
19581958
1959−[[package]]
1960−name = "str-buf"
1961−version = "1.0.6"
1962−source = "registry+https://github.com/rust-lang/crates.io-index"
1963−checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
1964−
19651959[[package]]
19661960name = "str_indices"
19671961version = "0.4.1"
@@ -1981,9 +1975,9 @@ dependencies = [
19811975
19821976[[package]]
19831977name = "syn"
1984−version = "2.0.11"
1978+version = "2.0.38"
19851979source = "registry+https://github.com/rust-lang/crates.io-index"
1986−checksum = "21e3787bb71465627110e7d87ed4faaa36c1f61042ee67badb9e2ef173accc40"
1980+checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
19871981dependencies = [
19881982 "proc-macro2",
19891983 "quote",
@@ -1992,15 +1986,15 @@ dependencies = [
19921986
19931987[[package]]
19941988name = "tempfile"
1995−version = "3.5.0"
1989+version = "3.8.1"
19961990source = "registry+https://github.com/rust-lang/crates.io-index"
1997−checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
1991+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
19981992dependencies = [
19991993 "cfg-if",
20001994 "fastrand",
2001− "redox_syscall 0.3.5",
1995+ "redox_syscall 0.4.1",
20021996 "rustix",
2003− "windows-sys",
1997+ "windows-sys 0.48.0",
20041998]
20051999
20062000[[package]]
@@ -2014,11 +2008,11 @@ dependencies = [
20142008
20152009[[package]]
20162010name = "termini"
2017−version = "0.1.4"
2011+version = "1.0.0"
20182012source = "registry+https://github.com/rust-lang/crates.io-index"
2019−checksum = "8c0f7ecb9c2a380d2686a747e4fc574043712326e8d39fbd220ab3bd29768a12"
2013+checksum = "2ad441d87dd98bc5eeb31cf2fb7e4839968763006b478efb38668a3bf9da0d59"
20202014dependencies = [
2021− "dirs-next",
2015+ "home",
20222016]
20232017
20242018[[package]]
@@ -2034,32 +2028,22 @@ dependencies = [
20342028
20352029[[package]]
20362030name = "thiserror"
2037−version = "1.0.40"
2031+version = "1.0.52"
20382032source = "registry+https://github.com/rust-lang/crates.io-index"
2039−checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
2033+checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
20402034dependencies = [
20412035 "thiserror-impl",
20422036]
20432037
20442038[[package]]
20452039name = "thiserror-impl"
2046−version = "1.0.40"
2040+version = "1.0.52"
20472041source = "registry+https://github.com/rust-lang/crates.io-index"
2048−checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
2042+checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
20492043dependencies = [
20502044 "proc-macro2",
20512045 "quote",
2052− "syn 2.0.11",
2053−]
2054−
2055−[[package]]
2056−name = "thread_local"
2057−version = "1.1.7"
2058−source = "registry+https://github.com/rust-lang/crates.io-index"
2059−checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
2060−dependencies = [
2061− "cfg-if",
2062− "once_cell",
2046+ "syn 2.0.38",
20632047]
20642048
20652049[[package]]
@@ -2073,9 +2057,9 @@ dependencies = [
20732057
20742058[[package]]
20752059name = "time"
2076−version = "0.3.20"
2060+version = "0.3.23"
20772061source = "registry+https://github.com/rust-lang/crates.io-index"
2078−checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
2062+checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
20792063dependencies = [
20802064 "itoa",
20812065 "libc",
@@ -2087,15 +2071,15 @@ dependencies = [
20872071
20882072[[package]]
20892073name = "time-core"
2090−version = "0.1.0"
2074+version = "0.1.1"
20912075source = "registry+https://github.com/rust-lang/crates.io-index"
2092−checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
2076+checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
20932077
20942078[[package]]
20952079name = "time-macros"
2096−version = "0.2.8"
2080+version = "0.2.10"
20972081source = "registry+https://github.com/rust-lang/crates.io-index"
2098−checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
2082+checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
20992083dependencies = [
21002084 "time-core",
21012085]
@@ -2117,11 +2101,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
21172101
21182102[[package]]
21192103name = "tokio"
2120−version = "1.27.0"
2104+version = "1.35.1"
21212105source = "registry+https://github.com/rust-lang/crates.io-index"
2122−checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001"
2106+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
21232107dependencies = [
2124− "autocfg",
2108+ "backtrace",
21252109 "bytes",
21262110 "libc",
21272111 "mio",
@@ -2131,25 +2115,25 @@ dependencies = [
21312115 "signal-hook-registry",
21322116 "socket2",
21332117 "tokio-macros",
2134− "windows-sys",
2118+ "windows-sys 0.48.0",
21352119]
21362120
21372121[[package]]
21382122name = "tokio-macros"
2139−version = "2.0.0"
2123+version = "2.2.0"
21402124source = "registry+https://github.com/rust-lang/crates.io-index"
2141−checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce"
2125+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
21422126dependencies = [
21432127 "proc-macro2",
21442128 "quote",
2145− "syn 2.0.11",
2129+ "syn 2.0.38",
21462130]
21472131
21482132[[package]]
21492133name = "tokio-stream"
2150−version = "0.1.12"
2134+version = "0.1.14"
21512135source = "registry+https://github.com/rust-lang/crates.io-index"
2152−checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313"
2136+checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
21532137dependencies = [
21542138 "futures-core",
21552139 "pin-project-lite",
@@ -2158,9 +2142,9 @@ dependencies = [
21582142
21592143[[package]]
21602144name = "toml"
2161−version = "0.7.3"
2145+version = "0.7.6"
21622146source = "registry+https://github.com/rust-lang/crates.io-index"
2163−checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
2147+checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
21642148dependencies = [
21652149 "serde",
21662150 "serde_spanned",
@@ -2170,31 +2154,30 @@ dependencies = [
21702154
21712155[[package]]
21722156name = "toml_datetime"
2173−version = "0.6.1"
2157+version = "0.6.3"
21742158source = "registry+https://github.com/rust-lang/crates.io-index"
2175−checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
2159+checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
21762160dependencies = [
21772161 "serde",
21782162]
21792163
21802164[[package]]
21812165name = "toml_edit"
2182−version = "0.19.8"
2166+version = "0.19.12"
21832167source = "registry+https://github.com/rust-lang/crates.io-index"
2184−checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
2168+checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
21852169dependencies = [
21862170 "indexmap",
21872171 "serde",
21882172 "serde_spanned",
21892173 "toml_datetime",
2190− "winnow",
2174+ "winnow 0.4.6",
21912175]
21922176
21932177[[package]]
21942178name = "tree-sitter"
21952179version = "0.20.10"
2196−source = "registry+https://github.com/rust-lang/crates.io-index"
2197−checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d"
2180+source = "git+https://github.com/tree-sitter/tree-sitter?rev=ab09ae20d640711174b8da8a654f6b3dec93da1a#ab09ae20d640711174b8da8a654f6b3dec93da1a"
21982181dependencies = [
21992182 "cc",
22002183 "regex",
@@ -2217,9 +2200,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
22172200
22182201[[package]]
22192202name = "unicode-bom"
2220−version = "1.1.4"
2203+version = "2.0.2"
22212204source = "registry+https://github.com/rust-lang/crates.io-index"
2222−checksum = "63ec69f541d875b783ca40184d655f2927c95f0bffd486faa83cd3ac3529ec32"
2205+checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552"
22232206
22242207[[package]]
22252208name = "unicode-general-category"
@@ -2235,13 +2218,9 @@ checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
22352218
22362219[[package]]
22372220name = "unicode-linebreak"
2238−version = "0.1.4"
2221+version = "0.1.5"
22392222source = "registry+https://github.com/rust-lang/crates.io-index"
2240−checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
2241−dependencies = [
2242− "hashbrown 0.12.3",
2243− "regex",
2244−]
2223+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
22452224
22462225[[package]]
22472226name = "unicode-normalization"
@@ -2260,15 +2239,15 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
22602239
22612240[[package]]
22622241name = "unicode-width"
2263−version = "0.1.10"
2242+version = "0.1.11"
22642243source = "registry+https://github.com/rust-lang/crates.io-index"
2265−checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
2244+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
22662245
22672246[[package]]
22682247name = "url"
2269−version = "2.3.1"
2248+version = "2.5.0"
22702249source = "registry+https://github.com/rust-lang/crates.io-index"
2271−checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
2250+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
22722251dependencies = [
22732252 "form_urlencoded",
22742253 "idna",
@@ -2284,9 +2263,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
22842263
22852264[[package]]
22862265name = "walkdir"
2287−version = "2.3.3"
2266+version = "2.4.0"
22882267source = "registry+https://github.com/rust-lang/crates.io-index"
2289−checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
2268+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
22902269dependencies = [
22912270 "same-file",
22922271 "winapi-util",
@@ -2354,13 +2333,15 @@ checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
23542333
23552334[[package]]
23562335name = "which"
2357−version = "4.4.0"
2336+version = "5.0.0"
23582337source = "registry+https://github.com/rust-lang/crates.io-index"
2359−checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
2338+checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14"
23602339dependencies = [
23612340 "either",
2362− "libc",
2341+ "home",
23632342 "once_cell",
2343+ "rustix",
2344+ "windows-sys 0.48.0",
23642345]
23652346
23662347[[package]]
@@ -2396,26 +2377,30 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
23962377
23972378[[package]]
23982379name = "windows"
2399−version = "0.43.0"
2380+version = "0.48.0"
24002381source = "registry+https://github.com/rust-lang/crates.io-index"
2401−checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244"
2382+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
24022383dependencies = [
2403− "windows_aarch64_gnullvm 0.42.2",
2404− "windows_aarch64_msvc 0.42.2",
2405− "windows_i686_gnu 0.42.2",
2406− "windows_i686_msvc 0.42.2",
2407− "windows_x86_64_gnu 0.42.2",
2408− "windows_x86_64_gnullvm 0.42.2",
2409− "windows_x86_64_msvc 0.42.2",
2384+ "windows-targets 0.48.0",
24102385]
24112386
24122387[[package]]
24132388name = "windows"
2414−version = "0.47.0"
2389+version = "0.52.0"
24152390source = "registry+https://github.com/rust-lang/crates.io-index"
2416−checksum = "2649ff315bee4c98757f15dac226efe3d81927adbb6e882084bb1ee3e0c330a7"
2391+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
24172392dependencies = [
2418− "windows-targets 0.47.0",
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",
24192404]
24202405
24212406[[package]]
@@ -2427,6 +2412,24 @@ dependencies = [
24272412 "windows-targets 0.42.2",
24282413]
24292414
2415+[[package]]
2416+name = "windows-sys"
2417+version = "0.48.0"
2418+source = "registry+https://github.com/rust-lang/crates.io-index"
2419+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
2420+dependencies = [
2421+ "windows-targets 0.48.0",
2422+]
2423+
2424+[[package]]
2425+name = "windows-sys"
2426+version = "0.52.0"
2427+source = "registry+https://github.com/rust-lang/crates.io-index"
2428+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
2429+dependencies = [
2430+ "windows-targets 0.52.0",
2431+]
2432+
24302433[[package]]
24312434name = "windows-targets"
24322435version = "0.42.2"
@@ -2444,17 +2447,32 @@ dependencies = [
24442447
24452448[[package]]
24462449name = "windows-targets"
2447−version = "0.47.0"
2450+version = "0.48.0"
2451+source = "registry+https://github.com/rust-lang/crates.io-index"
2452+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
2453+dependencies = [
2454+ "windows_aarch64_gnullvm 0.48.0",
2455+ "windows_aarch64_msvc 0.48.0",
2456+ "windows_i686_gnu 0.48.0",
2457+ "windows_i686_msvc 0.48.0",
2458+ "windows_x86_64_gnu 0.48.0",
2459+ "windows_x86_64_gnullvm 0.48.0",
2460+ "windows_x86_64_msvc 0.48.0",
2461+]
2462+
2463+[[package]]
2464+name = "windows-targets"
2465+version = "0.52.0"
24482466source = "registry+https://github.com/rust-lang/crates.io-index"
2449−checksum = "2f8996d3f43b4b2d44327cd71b7b0efd1284ab60e6e9d0e8b630e18555d87d3e"
2467+checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
24502468dependencies = [
2451− "windows_aarch64_gnullvm 0.47.0",
2452− "windows_aarch64_msvc 0.47.0",
2453− "windows_i686_gnu 0.47.0",
2454− "windows_i686_msvc 0.47.0",
2455− "windows_x86_64_gnu 0.47.0",
2456− "windows_x86_64_gnullvm 0.47.0",
2457− "windows_x86_64_msvc 0.47.0",
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",
24582476]
24592477
24602478[[package]]
@@ -2465,9 +2483,15 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
24652483
24662484[[package]]
24672485name = "windows_aarch64_gnullvm"
2468−version = "0.47.0"
2486+version = "0.48.0"
2487+source = "registry+https://github.com/rust-lang/crates.io-index"
2488+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
2489+
2490+[[package]]
2491+name = "windows_aarch64_gnullvm"
2492+version = "0.52.0"
24692493source = "registry+https://github.com/rust-lang/crates.io-index"
2470−checksum = "831d567d53d4f3cb1db332b68e6e2b6260228eb4d99a777d8b2e8ed794027c90"
2494+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
24712495
24722496[[package]]
24732497name = "windows_aarch64_msvc"
@@ -2477,9 +2501,15 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
24772501
24782502[[package]]
24792503name = "windows_aarch64_msvc"
2480−version = "0.47.0"
2504+version = "0.48.0"
2505+source = "registry+https://github.com/rust-lang/crates.io-index"
2506+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
2507+
2508+[[package]]
2509+name = "windows_aarch64_msvc"
2510+version = "0.52.0"
24812511source = "registry+https://github.com/rust-lang/crates.io-index"
2482−checksum = "6a42d54a417c60ce4f0e31661eed628f0fa5aca73448c093ec4d45fab4c51cdf"
2512+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
24832513
24842514[[package]]
24852515name = "windows_i686_gnu"
@@ -2489,9 +2519,15 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
24892519
24902520[[package]]
24912521name = "windows_i686_gnu"
2492−version = "0.47.0"
2522+version = "0.48.0"
2523+source = "registry+https://github.com/rust-lang/crates.io-index"
2524+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
2525+
2526+[[package]]
2527+name = "windows_i686_gnu"
2528+version = "0.52.0"
24932529source = "registry+https://github.com/rust-lang/crates.io-index"
2494−checksum = "c1925beafdbb22201a53a483db861a5644123157c1c3cee83323a2ed565d71e3"
2530+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
24952531
24962532[[package]]
24972533name = "windows_i686_msvc"
@@ -2501,9 +2537,15 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
25012537
25022538[[package]]
25032539name = "windows_i686_msvc"
2504−version = "0.47.0"
2540+version = "0.48.0"
2541+source = "registry+https://github.com/rust-lang/crates.io-index"
2542+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
2543+
2544+[[package]]
2545+name = "windows_i686_msvc"
2546+version = "0.52.0"
25052547source = "registry+https://github.com/rust-lang/crates.io-index"
2506−checksum = "3a8ef8f2f1711b223947d9b69b596cf5a4e452c930fb58b6fc3fdae7d0ec6b31"
2548+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
25072549
25082550[[package]]
25092551name = "windows_x86_64_gnu"
@@ -2513,9 +2555,15 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
25132555
25142556[[package]]
25152557name = "windows_x86_64_gnu"
2516−version = "0.47.0"
2558+version = "0.48.0"
2559+source = "registry+https://github.com/rust-lang/crates.io-index"
2560+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
2561+
2562+[[package]]
2563+name = "windows_x86_64_gnu"
2564+version = "0.52.0"
25172565source = "registry+https://github.com/rust-lang/crates.io-index"
2518−checksum = "7acaa0c2cf0d2ef99b61c308a0c3dbae430a51b7345dedec470bd8f53f5a3642"
2566+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
25192567
25202568[[package]]
25212569name = "windows_x86_64_gnullvm"
@@ -2525,9 +2573,15 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
25252573
25262574[[package]]
25272575name = "windows_x86_64_gnullvm"
2528−version = "0.47.0"
2576+version = "0.48.0"
2577+source = "registry+https://github.com/rust-lang/crates.io-index"
2578+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
2579+
2580+[[package]]
2581+name = "windows_x86_64_gnullvm"
2582+version = "0.52.0"
25292583source = "registry+https://github.com/rust-lang/crates.io-index"
2530−checksum = "e5a0628f71be1d11e17ca4a0e9e15b3a5180f6fbf1c2d55e3ba3f850378052c1"
2584+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
25312585
25322586[[package]]
25332587name = "windows_x86_64_msvc"
@@ -2537,22 +2591,37 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
25372591
25382592[[package]]
25392593name = "windows_x86_64_msvc"
2540−version = "0.47.0"
2594+version = "0.48.0"
2595+source = "registry+https://github.com/rust-lang/crates.io-index"
2596+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
2597+
2598+[[package]]
2599+name = "windows_x86_64_msvc"
2600+version = "0.52.0"
25412601source = "registry+https://github.com/rust-lang/crates.io-index"
2542−checksum = "9d6e62c256dc6d40b8c8707df17df8d774e60e39db723675241e7c15e910bce7"
2602+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
25432603
25442604[[package]]
25452605name = "winnow"
2546−version = "0.4.1"
2606+version = "0.4.6"
2607+source = "registry+https://github.com/rust-lang/crates.io-index"
2608+checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
2609+dependencies = [
2610+ "memchr",
2611+]
2612+
2613+[[package]]
2614+name = "winnow"
2615+version = "0.5.28"
25472616source = "registry+https://github.com/rust-lang/crates.io-index"
2548−checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
2617+checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
25492618dependencies = [
25502619 "memchr",
25512620]
25522621
25532622[[package]]
25542623name = "xtask"
2555−version = "0.6.0"
2624+version = "23.10.0"
25562625dependencies = [
25572626 "helix-core",
25582627 "helix-loader",
@@ -2560,3 +2629,23 @@ dependencies = [
25602629 "helix-view",
25612630 "toml",
25622631]
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.03
book/book.toml+5−1
@@ -3,10 +3,14 @@ authors = ["Blaž Hrastnik"]
33language = "en"
44multilingual = false
55src = "src"
6−edit-url-template = "https://github.com/helix-editor/helix/tree/master/book/{path}?mode=edit"
76
87[output.html]
98cname = "docs.helix-editor.com"
109default-theme = "colibri"
1110preferred-dark-theme = "colibri"
1211git-repository-url = "https://github.com/helix-editor/helix"
12+edit-url-template = "https://github.com/helix-editor/helix/edit/master/book/{path}"
13+additional-css = ["custom.css"]
14+
15+[output.html.search]
16+use-boolean-and = true
book/custom.cssnew file+231
@@ -0,0 +1,231 @@
1+html {
2+ font-family: "Inter", sans-serif;
3+}
4+
5+.sidebar .sidebar-scrollbox {
6+ padding: 0;
7+}
8+
9+.chapter {
10+ margin: 0.25rem 0;
11+}
12+
13+.chapter li.chapter-item {
14+ line-height: initial;
15+ margin: 0;
16+ padding: 1rem 1.5rem;
17+}
18+
19+.chapter .section li.chapter-item {
20+ line-height: inherit;
21+ padding: .5rem .5rem 0 .5rem;
22+}
23+
24+.content {
25+ overflow-y: auto;
26+ padding: 0 15px;
27+ padding-bottom: 50px;
28+}
29+
30+/* 2 1.75 1.5 1.25 1 .875 */
31+.content h1 { font-size: 2em }
32+.content h2 { font-size: 1.75em }
33+.content h3 { font-size: 1.5em }
34+.content h4 { font-size: 1.25em }
35+.content h5 { font-size: 1em }
36+.content h6 { font-size: .875em }
37+
38+.content h1,
39+.content h2,
40+.content h3,
41+.content h4 {
42+ font-weight: 500;
43+ margin-top: 1.275em;
44+ margin-bottom: .875em;
45+}
46+
47+.content p,
48+.content ol,
49+.content ul,
50+.content table {
51+ margin-top: 0;
52+ margin-bottom: .875em;
53+}
54+
55+.content ul li {
56+ margin-bottom: .25rem;
57+}
58+
59+.content ul {
60+ list-style-type: square;
61+}
62+
63+.content ul ul,
64+.content ol ul {
65+ margin-bottom: .5rem;
66+}
67+
68+.content li p {
69+ margin-bottom: .5em;
70+}
71+
72+blockquote {
73+ margin: 1.5rem 0;
74+ padding: 1rem 1.5rem;
75+ color: var(--fg);
76+ opacity: .9;
77+ background-color: var(--quote-bg);
78+ border-left: 4px solid var(--quote-border);
79+ border-top: none;
80+ border-bottom: none;
81+}
82+
83+blockquote *:last-child {
84+ margin-bottom: 0;
85+}
86+
87+table {
88+ width: 100%;
89+}
90+
91+table thead th {
92+ padding: .75rem;
93+ text-align: left;
94+ font-weight: 500;
95+ line-height: 1.5;
96+ width: auto;
97+}
98+
99+table td {
100+ padding: .75rem;
101+ border: none;
102+}
103+
104+table thead tr {
105+ border: none;
106+ border-bottom: 2px var(--table-border-color) solid;
107+}
108+
109+table tbody tr {
110+ border-bottom: 1px var(--table-border-line) solid;
111+}
112+
113+table tbody tr:nth-child(2n) {
114+ background: unset;
115+}
116+
117+pre code.hljs {
118+ display: block;
119+ overflow-x: auto;
120+ padding: 1em;
121+}
122+
123+code.hljs {
124+ padding: 3px 5px;
125+}
126+
127+.colibri {
128+ --bg: #3b224c;
129+ --fg: #bcbdd0;
130+ --heading-fg: #fff;
131+
132+ --sidebar-bg: #281733;
133+ --sidebar-fg: #c8c9db;
134+ --sidebar-non-existent: #505274;
135+ --sidebar-active: #a4a0e8;
136+ --sidebar-spacer: #2d334f;
137+
138+ --scrollbar: var(--sidebar-fg);
139+
140+ --icons: #737480;
141+ --icons-hover: #b7b9cc;
142+
143+ /* --links: #a4a0e8; */
144+ --links: #ECCDBA;
145+
146+ --inline-code-color: hsl(48.7, 7.8%, 70%);
147+
148+ --theme-popup-bg: #161923;
149+ --theme-popup-border: #737480;
150+ --theme-hover: rgba(0, 0, 0, .2);
151+
152+ --quote-bg: #281733;
153+ --quote-border: hsl(226, 15%, 22%);
154+
155+ --table-border-color: hsl(226, 23%, 76%);
156+ --table-header-bg: hsla(226, 23%, 31%, 0);
157+ --table-alternate-bg: hsl(226, 23%, 14%);
158+ --table-border-line: hsla(201deg, 20%, 92%, 0.2);
159+
160+ --searchbar-border-color: #aaa;
161+ --searchbar-bg: #aeaec6;
162+ --searchbar-fg: #000;
163+ --searchbar-shadow-color: #aaa;
164+ --searchresults-header-fg: #5f5f71;
165+ --searchresults-border-color: #5c5c68;
166+ --searchresults-li-bg: #242430;
167+ --search-mark-bg: #a2cff5;
168+}
169+
170+.colibri .content .header {
171+ color: #fff;
172+}
173+
174+/* highlight.js theme, :where() is used to avoid increasing specificity */
175+
176+:where(.colibri) .hljs {
177+ background: #2f1e2e;
178+ color: #a39e9b;
179+}
180+
181+:where(.colibri) .hljs-comment,
182+:where(.colibri) .hljs-quote {
183+ color: #8d8687;
184+}
185+
186+:where(.colibri) .hljs-link,
187+:where(.colibri) .hljs-meta,
188+:where(.colibri) .hljs-name,
189+:where(.colibri) .hljs-regexp,
190+:where(.colibri) .hljs-selector-class,
191+:where(.colibri) .hljs-selector-id,
192+:where(.colibri) .hljs-tag,
193+:where(.colibri) .hljs-template-variable,
194+:where(.colibri) .hljs-variable {
195+ color: #ef6155;
196+}
197+
198+:where(.colibri) .hljs-built_in,
199+:where(.colibri) .hljs-deletion,
200+:where(.colibri) .hljs-literal,
201+:where(.colibri) .hljs-number,
202+:where(.colibri) .hljs-params,
203+:where(.colibri) .hljs-type {
204+ color: #f99b15;
205+}
206+
207+:where(.colibri) .hljs-attribute,
208+:where(.colibri) .hljs-section,
209+:where(.colibri) .hljs-title {
210+ color: #fec418;
211+}
212+
213+:where(.colibri) .hljs-addition,
214+:where(.colibri) .hljs-bullet,
215+:where(.colibri) .hljs-string,
216+:where(.colibri) .hljs-symbol {
217+ color: #48b685;
218+}
219+
220+:where(.colibri) .hljs-keyword,
221+:where(.colibri) .hljs-selector-tag {
222+ color: #815ba4;
223+}
224+
225+:where(.colibri) .hljs-emphasis {
226+ font-style: italic;
227+}
228+
229+:where(.colibri) .hljs-strong {
230+ font-weight: 700;
231+}
book/src/SUMMARY.md+1
@@ -16,3 +16,4 @@
1616 - [Adding languages](./guides/adding_languages.md)
1717 - [Adding textobject queries](./guides/textobject.md)
1818 - [Adding indent queries](./guides/indent.md)
19+ - [Adding injection queries](./guides/injection.md)
book/src/configuration.md+45−14
@@ -51,7 +51,8 @@ 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` |
55+| `preview-completion-insert` | Whether to apply completion item instantly when selected | `true` |
5556| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
5657| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
5758| `auto-info` | Whether to display info boxes | `true` |
@@ -60,8 +61,12 @@ Its settings will be merged with the configuration directory `config.toml` and t
6061| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file | `[]` |
6162| `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` |
6263| `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` |
63−| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap_at_text_width` is set | `80` |
64+| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
6465| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
66+| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
67+| `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` |
68+| `popup-border` | Draw border around `popup`, `menu`, `all`, or `none` | `none` |
69+| `indent-heuristic` | How the indentation for a newly inserted line is computed: `simple` just copies the indentation level from the previous line, `tree-sitter` computes the indentation based on the syntax tree and `hybrid` combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being `hybrid` -> `tree-sitter` -> `simple`). | `hybrid`
6570
6671### `[editor.statusline]` Section
6772
@@ -87,9 +92,9 @@ The `[editor.statusline]` key takes the following sub-keys:
8792
8893| Key | Description | Default |
8994| --- | --- | --- |
90−| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name"]` |
95+| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "read-only-indicator", "file-modification-indicator"]` |
9196| `center` | A list of elements aligned to the middle of the statusline | `[]` |
92−| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "position", "file-encoding"]` |
97+| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
9398| `separator` | The character used to separate elements in the statusline | `"│"` |
9499| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
95100| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
@@ -106,6 +111,7 @@ The following statusline elements can be configured:
106111| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
107112| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
108113| `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 |
109115| `total-line-numbers` | The total line numbers of the opened file |
110116| `file-type` | The type of the opened file |
111117| `diagnostics` | The number of warnings and/or errors |
@@ -117,6 +123,7 @@ The following statusline elements can be configured:
117123| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
118124| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
119125| `version-control` | The current branch name or detached commit hash of the opened workspace |
126+| `register` | The current selected register |
120127
121128### `[editor.lsp]` Section
122129
@@ -128,10 +135,11 @@ The following statusline elements can be configured:
128135| `display-inlay-hints` | Display inlay hints[^2] | `false` |
129136| `display-signature-help-docs` | Display docs under signature help popup | `true` |
130137| `snippets` | Enables snippet completions. Requires a server restart (`:lsp-restart`) to take effect after `:config-reload`/`:set`. | `true` |
138+| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
131139
132140[^1]: By default, a progress spinner is shown in the statusline beside the file path.
133−[^2]: You may also have to activate them in the LSP config for them to appear, not just in Helix.
134− Inlay hints in Helix are still being improved on and may be a little bit laggy/janky under some circumstances, please report any bugs you see so we can fix them!
141+
142+[^2]: You may also have to activate them in the LSP config for them to appear, not just in Helix. Inlay hints in Helix are still being improved on and may be a little bit laggy/janky under some circumstances. Please report any bugs you see so we can fix them!
135143
136144### `[editor.cursor-shape]` Section
137145
@@ -160,16 +168,31 @@ All git related options are only enabled in a git repository.
160168
161169| Key | Description | Default |
162170|--|--|---------|
163−|`hidden` | Enables ignoring hidden files | true
164−|`follow-symlinks` | Follow symlinks instead of ignoring them | true
165−|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | true
166−|`parents` | Enables reading ignore files from parent directories | true
167−|`ignore` | Enables reading `.ignore` files | true
168−|`git-ignore` | Enables reading `.gitignore` files | true
169−|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludefile` option | true
170−|`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`
171179|`max-depth` | Set with an integer value for maximum depth to recurse | Defaults to `None`.
172180
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+
173196### `[editor.auto-pairs]` Section
174197
175198Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a
@@ -343,3 +366,11 @@ max-wrap = 25 # increase value to reduce forced mid-word wrapping
343366max-indent-retain = 0
344367wrap-indicator = "" # set wrap-indicator to "" to hide it
345368```
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+47−21
@@ -1,28 +1,31 @@
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` |
910| bicep | ✓ | | | `bicep-langserver` |
11+| blueprint | ✓ | | | `blueprint-compiler` |
1012| c | ✓ | ✓ | ✓ | `clangd` |
1113| c-sharp | ✓ | ✓ | | `OmniSharp` |
12−| cabal | ✓ | | | |
13−| cairo | ✓ | | | |
14+| cabal | | | | `haskell-language-server-wrapper` |
15+| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
1416| capnp | ✓ | | ✓ | |
1517| clojure | ✓ | | | `clojure-lsp` |
1618| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
1719| comment | ✓ | | | |
18−| common-lisp | ✓ | | | `cl-lsp` |
20+| common-lisp | ✓ | | ✓ | `cl-lsp` |
1921| cpon | ✓ | | ✓ | |
2022| cpp | ✓ | ✓ | ✓ | `clangd` |
21−| crystal | ✓ | ✓ | | |
23+| crystal | ✓ | ✓ | | `crystalline` |
2224| css | ✓ | | | `vscode-css-language-server` |
2325| cue | ✓ | | | `cuelsp` |
2426| d | ✓ | ✓ | ✓ | `serve-d` |
2527| dart | ✓ | | ✓ | `dart` |
28+| dbml | ✓ | | | |
2629| devicetree | ✓ | | | |
2730| dhall | ✓ | ✓ | | `dhall-lsp-server` |
2831| diff | ✓ | | | |
@@ -40,8 +43,12 @@
4043| erlang | ✓ | ✓ | | `erlang_ls` |
4144| esdl | ✓ | | | |
4245| fish | ✓ | ✓ | ✓ | |
46+| forth | ✓ | | | `forth-lsp` |
4347| fortran | ✓ | | ✓ | `fortls` |
48+| fsharp | ✓ | | | `fsautocomplete` |
49+| gas | ✓ | ✓ | | |
4450| gdscript | ✓ | ✓ | ✓ | |
51+| gemini | ✓ | | | |
4552| git-attributes | ✓ | | | |
4653| git-commit | ✓ | ✓ | | |
4754| git-config | ✓ | | | |
@@ -49,14 +56,16 @@
4956| git-rebase | ✓ | | | |
5057| gleam | ✓ | ✓ | | `gleam` |
5158| glsl | ✓ | ✓ | ✓ | |
52−| go | ✓ | ✓ | ✓ | `gopls` |
59+| gn | ✓ | | | |
60+| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
5361| godot-resource | ✓ | | | |
5462| gomod | ✓ | | | `gopls` |
5563| gotmpl | ✓ | | | `gopls` |
5664| gowork | ✓ | | | `gopls` |
57−| graphql | ✓ | | | |
65+| graphql | ✓ | | | `graphql-lsp` |
5866| hare | ✓ | | | |
5967| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
68+| haskell-persistent | ✓ | | | |
6069| hcl | ✓ | | ✓ | `terraform-ls` |
6170| heex | ✓ | ✓ | | `elixir-ls` |
6271| hosts | ✓ | | | |
@@ -65,14 +74,18 @@
6574| idris | | | | `idris2-lsp` |
6675| iex | ✓ | | | |
6776| ini | ✓ | | | |
68−| java | ✓ | ✓ | | `jdtls` |
77+| janet | ✓ | | | |
78+| java | ✓ | ✓ | ✓ | `jdtls` |
6979| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
80+| jinja | ✓ | | | |
7081| jsdoc | ✓ | | | |
7182| json | ✓ | | ✓ | `vscode-json-language-server` |
83+| json5 | ✓ | | | |
7284| jsonnet | ✓ | | | `jsonnet-language-server` |
7385| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
7486| julia | ✓ | ✓ | ✓ | `julia` |
75−| kdl | ✓ | | | |
87+| just | ✓ | ✓ | ✓ | |
88+| kdl | ✓ | ✓ | ✓ | |
7689| kotlin | ✓ | | | `kotlin-language-server` |
7790| latex | ✓ | ✓ | | `texlab` |
7891| lean | ✓ | | | `lean` |
@@ -80,12 +93,14 @@
8093| llvm | ✓ | ✓ | ✓ | |
8194| llvm-mir | ✓ | ✓ | ✓ | |
8295| llvm-mir-yaml | ✓ | | ✓ | |
96+| log | ✓ | | | |
97+| lpf | ✓ | | | |
8398| lua | ✓ | ✓ | ✓ | `lua-language-server` |
8499| make | ✓ | | | |
85100| markdoc | ✓ | | | `markdoc-ls` |
86101| markdown | ✓ | | | `marksman` |
87102| markdown.inline | ✓ | | | |
88−| matlab | ✓ | | | |
103+| matlab | ✓ | ✓ | ✓ | |
89104| mermaid | ✓ | | | |
90105| meson | ✓ | | ✓ | |
91106| mint | | | | `mint` |
@@ -94,29 +109,31 @@
94109| nickel | ✓ | | ✓ | `nls` |
95110| nim | ✓ | ✓ | ✓ | `nimlangserver` |
96111| nix | ✓ | | | `nil` |
97−| nu | ✓ | | | |
112+| nu | ✓ | | | `nu` |
113+| nunjucks | ✓ | | | |
98114| ocaml | ✓ | | ✓ | `ocamllsp` |
99115| ocaml-interface | ✓ | | | `ocamllsp` |
100−| odin | ✓ | | | `ols` |
116+| odin | ✓ | | ✓ | `ols` |
101117| opencl | ✓ | ✓ | ✓ | `clangd` |
102118| openscad | ✓ | | | `openscad-lsp` |
103119| org | ✓ | | | |
104120| pascal | ✓ | ✓ | | `pasls` |
105121| passwd | ✓ | | | |
106122| pem | ✓ | | | |
107−| perl | ✓ | ✓ | ✓ | |
123+| perl | ✓ | ✓ | ✓ | `perlnavigator` |
108124| php | ✓ | ✓ | ✓ | `intelephense` |
109125| po | ✓ | ✓ | | |
126+| pod | ✓ | | | |
110127| ponylang | ✓ | ✓ | ✓ | |
111128| prisma | ✓ | | | `prisma-language-server` |
112129| prolog | | | | `swipl` |
113−| protobuf | ✓ | | ✓ | |
130+| protobuf | ✓ | | ✓ | `bufls`, `pb` |
114131| prql | ✓ | | | |
115−| purescript | ✓ | | | `purescript-language-server` |
132+| purescript | ✓ | ✓ | | `purescript-language-server` |
116133| python | ✓ | ✓ | ✓ | `pylsp` |
117134| qml | ✓ | | ✓ | `qmlls` |
118135| r | ✓ | | | `R` |
119−| racket | ✓ | | | `racket` |
136+| racket | ✓ | | ✓ | `racket` |
120137| regex | ✓ | | | |
121138| rego | ✓ | | | `regols` |
122139| rescript | ✓ | ✓ | | `rescript-language-server` |
@@ -128,40 +145,49 @@
128145| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
129146| sage | ✓ | ✓ | | |
130147| scala | ✓ | | ✓ | `metals` |
131−| scheme | ✓ | | | |
148+| scheme | ✓ | | ✓ | |
132149| scss | ✓ | | | `vscode-css-language-server` |
133150| slint | ✓ | | ✓ | `slint-lsp` |
151+| smali | ✓ | | ✓ | |
134152| smithy | ✓ | | | `cs` |
135153| sml | ✓ | | | |
136154| solidity | ✓ | | | `solc` |
137155| sql | ✓ | | | |
138156| sshclientconfig | ✓ | | | |
139157| starlark | ✓ | ✓ | | |
140−| svelte | ✓ | | | `svelteserver` |
158+| strace | ✓ | | | |
159+| svelte | ✓ | | ✓ | `svelteserver` |
141160| sway | ✓ | ✓ | ✓ | `forc` |
142161| swift | ✓ | | | `sourcekit-lsp` |
162+| t32 | ✓ | | | |
143163| tablegen | ✓ | ✓ | ✓ | |
144164| task | ✓ | | | |
165+| templ | ✓ | | | `templ` |
145166| tfvars | ✓ | | ✓ | `terraform-ls` |
167+| todotxt | ✓ | | | |
146168| toml | ✓ | | | `taplo` |
147169| tsq | ✓ | | | |
148170| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
149171| twig | ✓ | | | |
150172| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
173+| typst | ✓ | | | `typst-lsp` |
151174| ungrammar | ✓ | | | |
175+| unison | ✓ | | | |
152176| uxntal | ✓ | | | |
153−| v | ✓ | ✓ | ✓ | `v` |
177+| v | ✓ | ✓ | ✓ | `v-analyzer` |
154178| vala | ✓ | | | `vala-language-server` |
155179| verilog | ✓ | ✓ | | `svlangserver` |
156180| vhdl | ✓ | | | `vhdl_ls` |
157181| vhs | ✓ | | | |
158−| vue | ✓ | | | `vls` |
182+| vue | ✓ | | | `vue-language-server` |
159183| wast | ✓ | | | |
160184| wat | ✓ | | | |
185+| webc | ✓ | | | |
161186| wgsl | ✓ | | | `wgsl_analyzer` |
162187| wit | ✓ | | ✓ | |
188+| wren | ✓ | ✓ | ✓ | |
163189| xit | ✓ | | | |
164190| xml | ✓ | | ✓ | |
165−| yaml | ✓ | | ✓ | `yaml-language-server` |
191+| yaml | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
166192| yuck | ✓ | | | |
167193| zig | ✓ | ✓ | ✓ | `zls` |
book/src/generated/typable-cmd.md+15−7
@@ -12,16 +12,19 @@
1212| `:buffer-next`, `:bn`, `:bnext` | Goto next buffer. |
1313| `:buffer-previous`, `:bp`, `:bprev` | Goto previous buffer. |
1414| `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) |
15−| `:write!`, `:w!` | Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write some/path.txt) |
15+| `:write!`, `:w!` | Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt) |
16+| `:write-buffer-close`, `:wbc` | Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt) |
17+| `:write-buffer-close!`, `:wbc!` | Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt) |
1618| `:new`, `:n` | Create a new scratch buffer. |
1719| `:format`, `:fmt` | Format the file using the LSP formatter. |
18−| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
20+| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.) |
1921| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
2022| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
2123| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
2224| `:write-quit`, `:wq`, `:x` | Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) |
2325| `:write-quit!`, `:wq!`, `:x!` | Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) |
2426| `: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. |
2528| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. |
2629| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes). |
2730| `:quit-all`, `:qa` | Close all views. |
@@ -29,6 +32,7 @@
2932| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
3033| `:cquit!`, `:cq!` | Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2). |
3134| `:theme` | Change the editor theme (show current theme if no name specified). |
35+| `:yank-join` | Yank joined selections. A separator can be provided as first argument. Default value is newline. |
3236| `:clipboard-yank` | Yank main selection into system clipboard. |
3337| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
3438| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
@@ -44,13 +48,14 @@
4448| `:show-directory`, `:pwd` | Show the current working directory. |
4549| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
4650| `:character-info`, `:char` | Get info about the character under the primary cursor. |
47−| `:reload` | Discard changes and reload from the source file. |
48−| `:reload-all` | Discard changes and reload all documents from the source files. |
49−| `:update` | Write changes only if the file has been modified. |
51+| `:reload`, `:rl` | Discard changes and reload from the source file. |
52+| `:reload-all`, `:rla` | Discard changes and reload all documents from the source files. |
53+| `:update`, `:u` | Write changes only if the file has been modified. |
5054| `:lsp-workspace-command` | Open workspace command picker |
51−| `:lsp-restart` | Restarts the Language Server that is in use by the current doc |
52−| `:lsp-stop` | Stops the Language Server that is in use by the current doc |
55+| `:lsp-restart` | Restarts the language servers used by the current doc |
56+| `:lsp-stop` | Stops the language servers that are used by the current doc |
5357| `: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. |
5459| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
5560| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
5661| `:debug-eval` | Evaluate expression in current debug context. |
@@ -78,3 +83,6 @@
7883| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
7984| `:run-shell-command`, `:sh` | Run a shell command |
8085| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
86+| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
87+| `:redraw` | Clear and re-render the whole UI |
88+| `:move` | Move the current buffer and its corresponding file to a different path |
book/src/guides/adding_languages.md+3
@@ -9,6 +9,7 @@ below.
99 necessary configuration for the new language. For more information on
1010 language configuration, refer to the
1111 [language configuration section](../languages.md) of the documentation.
12+ A new language server can be added by extending the `[language-server]` table in the same file.
12132. If you are adding a new language or updating an existing language server
1314 configuration, run the command `cargo xtask docgen` to update the
1415 [Language Support](../lang-support.md) documentation.
@@ -35,6 +36,7 @@ below.
35363. Refer to the
3637 [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
3738 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).
3840
3941> 💡 In Helix, the first matching query takes precedence when evaluating
4042> queries, which is different from other editors such as Neovim where the last
@@ -50,3 +52,4 @@ below.
5052 grammars.
5153- If a parser is causing a segfault, or you want to remove it, make sure to
5254 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.mdnew file+57
@@ -0,0 +1,57 @@
1+# Adding Injection Queries
2+
3+Writing language injection queries allows one to highlight a specific node as a different language.
4+In addition to the [standard][upstream-docs] language injection options used by tree-sitter, there
5+are a few Helix specific extensions that allow for more control.
6+
7+And example of a simple query that would highlight all strings as bash in Nix:
8+```scm
9+((string_expression (string_fragment) @injection.content)
10+ (#set! injection.language "bash"))
11+```
12+
13+## Capture Types
14+
15+- `@injection.language` (standard):
16+The captured node may contain the language name used to highlight the node captured by
17+`@injection.content`.
18+
19+- `@injection.content` (standard):
20+Marks the content to be highlighted as the language captured with `@injection.language` _et al_.
21+
22+- `@injection.filename` (extension):
23+The captured node may contain a filename with a file-extension known to Helix,
24+highlighting `@injection.content` as that language. This uses the language extensions defined in
25+both the default languages.toml distributed with Helix, as well as user defined languages.
26+
27+- `@injection.shebang` (extension):
28+The captured node may contain a shebang used to choose a language to highlight as. This also uses
29+the shebangs defined in the default and user `languages.toml`.
30+
31+## Settings
32+
33+- `injection.combined` (standard):
34+Indicates that all the matching nodes in the tree should have their content parsed as one
35+nested document.
36+
37+- `injection.language` (standard):
38+Forces the captured content to be highlighted as the given language
39+
40+- `injection.include-children` (standard):
41+Indicates that the content node’s entire text should be re-parsed, including the text of its child
42+nodes. By default, child nodes’ text will be excluded from the injected document.
43+
44+- `injection.include-unnamed-children` (extension):
45+Same as `injection.include-children` but only for unnamed child nodes.
46+
47+## Predicates
48+
49+- `#eq?` (standard):
50+The first argument (a capture) must be equal to the second argument
51+(a capture or a string).
52+
53+- `#match?` (standard):
54+The first argument (a capture) must match the regex given in the
55+second argument (a string).
56+
57+[upstream-docs]: http://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
book/src/install.md+97−36
@@ -6,11 +6,14 @@
66- [Linux](#linux)
77 - [Ubuntu](#ubuntu)
88 - [Fedora/RHEL](#fedorarhel)
9− - [Arch Linux community](#arch-linux-community)
9+ - [Arch Linux extra](#arch-linux-extra)
1010 - [NixOS](#nixos)
11+ - [Flatpak](#flatpak)
12+ - [Snap](#snap)
1113 - [AppImage](#appimage)
1214- [macOS](#macos)
1315 - [Homebrew Core](#homebrew-core)
16+ - [MacPorts](#macports)
1417- [Windows](#windows)
1518 - [Winget](#winget)
1619 - [Scoop](#scoop)
@@ -18,6 +21,9 @@
1821 - [MSYS2](#msys2)
1922- [Building from source](#building-from-source)
2023 - [Configuring Helix's runtime files](#configuring-helixs-runtime-files)
24+ - [Linux and macOS](#linux-and-macos)
25+ - [Windows](#windows)
26+ - [Multiple runtime directories](#multiple-runtime-directories)
2127 - [Validating the installation](#validating-the-installation)
2228 - [Configure the desktop shortcut](#configure-the-desktop-shortcut)
2329<!--toc:end-->
@@ -41,8 +47,6 @@ line.
4147
4248## Linux, macOS, Windows and OpenBSD packaging status
4349
44−Helix is available for Linux, macOS and Windows via the official repositories listed below.
45−
4650[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
4751
4852## Linux
@@ -51,7 +55,7 @@ The following third party repositories are available:
5155
5256### Ubuntu
5357
54−Helix is available via [Maveonair's PPA](https://launchpad.net/~maveonair/+archive/ubuntu/helix-editor):
58+Add the `PPA` for Helix:
5559
5660```sh
5761sudo add-apt-repository ppa:maveonair/helix-editor
@@ -61,16 +65,13 @@ sudo apt install helix
6165
6266### Fedora/RHEL
6367
64−Helix is available via `copr`:
65−
6668```sh
67−sudo dnf copr enable varlad/helix
6869sudo dnf install helix
6970```
7071
71−### Arch Linux community
72+### Arch Linux extra
7273
73−Releases are available in the `community` repository:
74+Releases are available in the `extra` repository:
7475
7576```sh
7677sudo pacman -S helix
@@ -80,7 +81,10 @@ in the AUR, which builds the master branch.
8081
8182### NixOS
8283
83−Helix is available as a [flake](https://nixos.wiki/wiki/Flakes) in the project
84+Helix is available in [nixpkgs](https://github.com/nixos/nixpkgs) through the `helix` attribute,
85+the unstable channel usually carries the latest release.
86+
87+Helix is also available as a [flake](https://nixos.wiki/wiki/Flakes) in the project
8488root. Use `nix develop` to spin up a reproducible development shell. Outputs are
8589cached for each push to master using [Cachix](https://www.cachix.org/). The
8690flake is configured to automatically make use of this cache assuming the user
@@ -90,10 +94,29 @@ If you are using a version of Nix without flakes enabled,
9094[install Cachix CLI](https://docs.cachix.org/installation) and use
9195`cachix use helix` to configure Nix to use cached outputs when possible.
9296
97+### Flatpak
98+
99+Helix is available on [Flathub](https://flathub.org/en-GB/apps/com.helix_editor.Helix):
100+
101+```sh
102+flatpak install flathub com.helix_editor.Helix
103+flatpak run com.helix_editor.Helix
104+```
105+
106+### Snap
107+
108+Helix is available on [Snapcraft](https://snapcraft.io/helix) and can be installed with:
109+
110+```sh
111+snap install --classic helix
112+```
113+
114+This will install Helix as both `/snap/bin/helix` and `/snap/bin/hx`, so make sure `/snap/bin` is in your `PATH`.
115+
93116### AppImage
94117
95−Install Helix using [AppImage](https://appimage.org/).
96−Download Helix AppImage from the [latest releases](https://github.com/helix-editor/helix/releases/latest) page.
118+Install Helix using the Linux [AppImage](https://appimage.org/) format.
119+Download the official Helix AppImage from the [latest releases](https://github.com/helix-editor/helix/releases/latest) page.
97120
98121```sh
99122chmod +x helix-*.AppImage # change permission for executable mode
@@ -108,6 +131,12 @@ chmod +x helix-*.AppImage # change permission for executable mode
108131brew install helix
109132```
110133
134+### MacPorts
135+
136+```sh
137+port install helix
138+```
139+
111140## Windows
112141
113142Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
@@ -143,33 +172,41 @@ pacman -S mingw-w64-ucrt-x86_64-helix
143172
144173## Building from source
145174
146−Clone the repository:
175+Requirements:
147176
148−```sh
149−git clone https://github.com/helix-editor/helix
150−cd helix
151−```
177+Clone the Helix GitHub repository into a directory of your choice. The
178+examples in this documentation assume installation into either `~/src/` on
179+Linux and macOS, or `%userprofile%\src\` on Windows.
180+
181+- The [Rust toolchain](https://www.rust-lang.org/tools/install)
182+- The [Git version control system](https://git-scm.com/)
183+- A C++14 compatible compiler to build the tree-sitter grammars, for example GCC or Clang
152184
153−Compile from source:
185+If you are using the `musl-libc` standard library instead of `glibc` the following environment variable must be set during the build to ensure tree-sitter grammars can be loaded correctly:
154186
155187```sh
156−cargo install --path helix-term --locked
188+RUSTFLAGS="-C target-feature=-crt-static"
157189```
158190
159−This command will create the `hx` executable and construct the tree-sitter
160−grammars in the local `runtime` folder. To build the tree-sitter grammars requires
161−a c++ compiler to be installed, for example `gcc-c++`.
191+1. Clone the repository:
192+
193+ ```sh
194+ git clone https://github.com/helix-editor/helix
195+ cd helix
196+ ```
162197
163−> 💡 If you are using the musl-libc instead of glibc the following environment variable must be set during the build
164−> to ensure tree-sitter grammars can be loaded correctly:
165−>
166−> ```sh
167−> RUSTFLAGS="-C target-feature=-crt-static"
168−> ```
198+2. Compile from source:
199+
200+ ```sh
201+ cargo install --path helix-term --locked
202+ ```
203+
204+ This command will create the `hx` executable and construct the tree-sitter
205+ grammars in the local `runtime` folder.
169206
170207> 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch
171−> grammars with `hx --grammar fetch` (requires `git`) and compile them with
172−> `hx --grammar build` (requires a C++ compiler). This will install them in
208+> grammars with `hx --grammar fetch` and compile them with
209+> `hx --grammar build`. This will install them in
173210> the `runtime` directory within the user's helix config directory (more
174211> [details below](#multiple-runtime-directories)).
175212
@@ -177,18 +214,22 @@ a c++ compiler to be installed, for example `gcc-c++`.
177214
178215#### Linux and macOS
179216
180−Either set the `HELIX_RUNTIME` environment variable to point to the runtime files and add it to your `~/.bashrc` or equivalent:
217+The **runtime** directory is one below the Helix source, so either set a
218+`HELIX_RUNTIME` environment variable to point to that directory and add it to
219+your `~/.bashrc` or equivalent:
181220
182221```sh
183−HELIX_RUNTIME=/home/user-name/src/helix/runtime
222+HELIX_RUNTIME=~/src/helix/runtime
184223```
185224
186−Or, create a symlink in `~/.config/helix` that links to the source code directory:
225+Or, create a symbolic link:
187226
188227```sh
189−ln -s $PWD/runtime ~/.config/helix/runtime
228+ln -Ts $PWD/runtime ~/.config/helix/runtime
190229```
191230
231+If the above command fails to create a symbolic link because the file exists either move `~/.config/helix/runtime` to a new location or delete it, then run the symlink command above again.
232+
192233#### Windows
193234
194235Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
@@ -220,12 +261,32 @@ following order:
2202611. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
221262 developing and testing helix only).
2222632. `runtime/` subdirectory of OS-dependent helix user config directory.
223−3. `$HELIX_RUNTIME`.
224−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.
225268
226269This order also sets the priority for selecting which file will be used if multiple runtime
227270directories have files with the same name.
228271
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+
229290### Validating the installation
230291
231292To make sure everything is set up as expected you should run the Helix health
book/src/keymap.md+29−19
@@ -15,7 +15,7 @@
1515 - [Popup](#popup)
1616 - [Unimpaired](#unimpaired)
1717- [Insert mode](#insert-mode)
18−- [Select / extend mode](#select-extend-mode)
18+- [Select / extend mode](#select--extend-mode)
1919- [Picker](#picker)
2020- [Prompt](#prompt)
2121
@@ -25,6 +25,8 @@
2525
2626## Normal mode
2727
28+Normal mode is the default mode when you launch helix. You can return to it from other modes by pressing the `Escape` key.
29+
2830### Movement
2931
3032> NOTE: Unlike Vim, `f`, `F`, `t` and `T` are not confined to the current line.
@@ -32,8 +34,8 @@
3234| Key | Description | Command |
3335| ----- | ----------- | ------- |
3436| `h`, `Left` | Move left | `move_char_left` |
35−| `j`, `Down` | Move down | `move_line_down` |
36−| `k`, `Up` | Move up | `move_line_up` |
37+| `j`, `Down` | Move down | `move_visual_line_down` |
38+| `k`, `Up` | Move up | `move_visual_line_up` |
3739| `l`, `Right` | Move right | `move_char_right` |
3840| `w` | Move next word start | `move_next_word_start` |
3941| `b` | Move previous word start | `move_prev_word_start` |
@@ -111,7 +113,8 @@
111113| `s` | Select all regex matches inside selections | `select_regex` |
112114| `S` | Split selection into sub selections on regex matches | `split_selection` |
113115| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
114−| `Alt-_ ` | Merge consecutive selections | `merge_consecutive_selections` |
116+| `Alt-minus` | Merge selections | `merge_selections` |
117+| `Alt-_` | Merge consecutive selections | `merge_consecutive_selections` |
115118| `&` | Align selection in columns | `align_selections` |
116119| `_` | Trim whitespace from the selection | `trim_selections` |
117120| `;` | Collapse selection onto a single cursor | `collapse_selection` |
@@ -218,6 +221,8 @@ Jumps to various locations.
218221| `n` | Go to next buffer | `goto_next_buffer` |
219222| `p` | Go to previous buffer | `goto_previous_buffer` |
220223| `.` | Go to last modification in current file | `goto_last_modification` |
224+| `j` | Move down textual (instead of visual) line | `move_line_down` |
225+| `k` | Move up textual (instead of visual) line | `move_line_up` |
221226
222227#### Match mode
223228
@@ -286,7 +291,7 @@ This layer is a kludge of mappings, mostly pickers.
286291| `w` | Enter [window mode](#window-mode) | N/A |
287292| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
288293| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
289−| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` |
294+| `y` | Yank selections to clipboard | `yank_to_clipboard` |
290295| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
291296| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
292297| `/` | Global search in workspace folder | `global_search` |
@@ -334,6 +339,8 @@ These mappings are in the style of [vim-unimpaired](https://github.com/tpope/vim
334339
335340## Insert mode
336341
342+Accessed by typing `i` in [normal mode](#normal-mode).
343+
337344Insert mode bindings are minimal by default. Helix is designed to
338345be a modal editor, and this is reflected in the user experience and internal
339346mechanics. Changes to the text are only saved for undos when
@@ -387,9 +394,11 @@ end = "no_op"
387394
388395## Select / extend mode
389396
397+Accessed by typing `v` in [normal mode](#normal-mode).
398+
390399Select mode echoes Normal mode, but changes any movements to extend
391400selections rather than replace them. Goto motions are also changed to
392−extend, so that `vgl` for example extends the selection to the end of
401+extend, so that `vgl`, for example, extends the selection to the end of
393402the line.
394403
395404Search is also affected. By default, `n` and `N` will remove the current
@@ -402,19 +411,20 @@ you to selectively add search terms to your selections.
402411
403412Keys to use within picker. Remapping currently not supported.
404413
405−| Key | Description |
406−| ----- | ------------- |
407−| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
408−| `Tab`, `Down`, `Ctrl-n` | Next entry |
409−| `PageUp`, `Ctrl-u` | Page up |
410−| `PageDown`, `Ctrl-d` | Page down |
411−| `Home` | Go to first entry |
412−| `End` | Go to last entry |
413−| `Enter` | Open selected |
414−| `Ctrl-s` | Open horizontally |
415−| `Ctrl-v` | Open vertically |
416−| `Ctrl-t` | Toggle preview |
417−| `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 |
418428
419429## Prompt
420430
book/src/languages.md+104−28
@@ -7,21 +7,24 @@ in `languages.toml` files.
77
88There are three possible locations for a `languages.toml` file:
99
10−1. In the Helix source code, this lives in the
10+1. In the Helix source code, which lives in the
1111 [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml).
1212 It provides the default configurations for languages and language servers.
1313
14142. In your [configuration directory](./configuration.md). This overrides values
15− from the built-in language configuration. For example to disable
15+ from the built-in language configuration. For example, to disable
1616 auto-LSP-formatting in Rust:
1717
18−```toml
19−# in <config_dir>/helix/languages.toml
18+ ```toml
19+ # in <config_dir>/helix/languages.toml
2020
21−[[language]]
22−name = "rust"
23−auto-format = false
24−```
21+ [language-server.mylang-lsp]
22+ command = "mylang-lsp"
23+
24+ [[language]]
25+ name = "rust"
26+ auto-format = false
27+ ```
2528
26293. In a `.helix` folder in your project. Language configuration may also be
2730 overridden local to a project by creating a `languages.toml` file in a
@@ -41,8 +44,8 @@ injection-regex = "mylang"
4144file-types = ["mylang", "myl"]
4245comment-token = "#"
4346indent = { tab-width = 2, unit = " " }
44−language-server = { command = "mylang-lsp", args = ["--stdio"], environment = { "ENV1" = "value1", "ENV2" = "value2" } }
4547formatter = { command = "mylang-formatter" , args = ["--stdin"] }
48+language-servers = [ "mylang-lsp" ]
4649```
4750
4851These configuration keys are available:
@@ -50,6 +53,7 @@ These configuration keys are available:
5053| Key | Description |
5154| ---- | ----------- |
5255| `name` | The name of the language |
56+| `language-id` | The language-id for language servers, checkout the table at [TextDocumentItem](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem) for the right id |
5357| `scope` | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.<name>` or `text.<name>` in case of markup languages |
5458| `injection-regex` | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. |
5559| `file-types` | The filetypes of the language, for example `["yml", "yaml"]`. See the file-type detection section below. |
@@ -59,12 +63,13 @@ These configuration keys are available:
5963| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
6064| `comment-token` | The token to use as a comment-token |
6165| `indent` | The indent to use. Has sub keys `unit` (the text inserted into the document when indenting; usually set to N spaces or `"\t"` for tabs) and `tab-width` (the number of spaces rendered for a tab) |
62−| `language-server` | The Language Server to run. See the Language Server configuration section below. |
63−| `config` | Language Server configuration |
66+| `language-servers` | The Language Servers used for this language. See below for more information in the section [Configuring Language Servers for a language](#configuring-language-servers-for-a-language) |
6467| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
6568| `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 |
66−| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap_at_text_width` is set, defaults to `editor.text-width` |
69+| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
70+| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
6771| `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.
6873
6974### File-type detection and the `file-types` key
7075
@@ -92,31 +97,102 @@ with the following priorities:
9297 replaced at runtime with the appropriate path separator for the operating
9398 system, so this rule would match against `.git\config` files on Windows.
9499
95−### Language Server configuration
100+## Language Server configuration
96101
97−The `language-server` field takes the following keys:
102+Language servers are configured separately in the table `language-server` in the same file as the languages `languages.toml`
103+
104+For example:
105+
106+```toml
107+[language-server.mylang-lsp]
108+command = "mylang-lsp"
109+args = ["--stdio"]
110+config = { provideFormatter = true }
111+environment = { "ENV1" = "value1", "ENV2" = "value2" }
112+
113+[language-server.efm-lsp-prettier]
114+command = "efm-langserver"
115+
116+[language-server.efm-lsp-prettier.config]
117+documentFormatting = true
118+languages = { typescript = [ { formatCommand ="prettier --stdin-filepath ${INPUT}", formatStdin = true } ] }
119+```
98120
99−| Key | Description |
100−| --- | ----------- |
101−| `command` | The name of the language server binary to execute. Binaries must be in `$PATH` |
102−| `args` | A list of arguments to pass to the language server binary |
103−| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
104−| `language-id` | The language name to pass to the language server. Some language servers support multiple languages and use this field to determine which one is being served in a buffer |
105−| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
121+These are the available options for a language server.
106122
107−The top-level `config` field is used to configure the LSP initialization options. A `format`
108−sub-table within `config` can be used to pass extra formatting options to
109−[Document Formatting Requests](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-16.md#document-formatting-request--leftwards_arrow_with_hook).
110−For example with typescript:
123+| Key | Description |
124+| ---- | ----------- |
125+| `command` | The name or path of the language server binary to execute. Binaries must be in `$PATH` |
126+| `args` | A list of arguments to pass to the language server binary |
127+| `config` | LSP initialization options |
128+| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
129+| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
130+
131+A `format` sub-table within `config` can be used to pass extra formatting options to
132+[Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting).
133+For example, with typescript:
111134
112135```toml
113−[[language]]
114−name = "typescript"
115−auto-format = true
136+[language-server.typescript-language-server]
116137# pass format options according to https://github.com/typescript-language-server/typescript-language-server#workspacedidchangeconfiguration omitting the "[language].format." prefix.
117138config = { format = { "semicolons" = "insert", "insertSpaceBeforeFunctionParenthesis" = true } }
118139```
119140
141+### Configuring Language Servers for a language
142+
143+The `language-servers` attribute in a language tells helix which language servers are used for this language.
144+
145+They have to be defined in the `[language-server]` table as described in the previous section.
146+
147+Different languages can use the same language server instance, e.g. `typescript-language-server` is used for javascript, jsx, tsx and typescript by default.
148+
149+In case multiple language servers are specified in the `language-servers` attribute of a `language`,
150+it's often useful to only enable/disable certain language-server features for these language servers.
151+
152+As an example, `efm-lsp-prettier` of the previous example is used only with a formatting command `prettier`,
153+so everything else should be handled by the `typescript-language-server` (which is configured by default).
154+The language configuration for typescript could look like this:
155+
156+```toml
157+[[language]]
158+name = "typescript"
159+language-servers = [ { name = "efm-lsp-prettier", only-features = [ "format" ] }, "typescript-language-server" ]
160+```
161+
162+or equivalent:
163+
164+```toml
165+[[language]]
166+name = "typescript"
167+language-servers = [ { name = "typescript-language-server", except-features = [ "format" ] }, "efm-lsp-prettier" ]
168+```
169+
170+Each requested LSP feature is prioritized in the order of the `language-servers` array.
171+For example, the first `goto-definition` supported language server (in this case `typescript-language-server`) will be taken for the relevant LSP request (command `goto_definition`).
172+The features `diagnostics`, `code-action`, `completion`, `document-symbols` and `workspace-symbols` are an exception to that rule, as they are working for all language servers at the same time and are merged together, if enabled for the language.
173+If no `except-features` or `only-features` is given, all features for the language server are enabled.
174+If a language server itself doesn't support a feature, the next language server array entry will be tried (and so on).
175+
176+The list of supported features is:
177+
178+- `format`
179+- `goto-definition`
180+- `goto-declaration`
181+- `goto-type-definition`
182+- `goto-reference`
183+- `goto-implementation`
184+- `signature-help`
185+- `hover`
186+- `document-highlight`
187+- `completion`
188+- `code-action`
189+- `workspace-command`
190+- `document-symbols`
191+- `workspace-symbols`
192+- `diagnostics`
193+- `rename-symbol`
194+- `inlay-hints`
195+
120196## Tree-sitter grammar configuration
121197
122198The source for a language's tree-sitter grammar is specified in a `[[grammar]]`
book/src/themes.md+10−2
@@ -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,12 +296,15 @@ 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. |
299−| `ui.text.focus` | |
306+| `ui.text` | Default text style, command prompts, popup text, etc. |
307+| `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 |
302310| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
book/src/usage.md+26−10
@@ -37,19 +37,35 @@ If a register is selected before invoking a change or delete command, the select
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 |
51+
52+### Special registers
53+
54+Some registers have special behavior when read from and written to.
4855
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.
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 |
5164
52−The black hole register is a no-op register, meaning that no data will be read or written to it.
65+When yanking multiple selections to the clipboard registers, the selections
66+are joined with newlines. Pasting from these registers will paste multiple
67+selections if the clipboard was last yanked to by the Helix session. Otherwise
68+the clipboard contents are pasted as one selection.
5369
5470## Surround
5571
@@ -96,13 +112,13 @@ function or block of code.
96112| `(`, `[`, `'`, etc. | Specified surround pairs |
97113| `m` | The closest surround pair |
98114| `f` | Function |
99−| `c` | Class |
115+| `t` | Type (or Class) |
100116| `a` | Argument/parameter |
101−| `o` | Comment |
102−| `t` | Test |
117+| `c` | Comment |
118+| `T` | Test |
103119| `g` | Change |
104120
105−> 💡 `f`, `c`, etc. need a tree-sitter grammar active for the current
121+> 💡 `f`, `t`, etc. need a tree-sitter grammar active for the current
106122document and a special tree-sitter query file to work properly. [Only
107123some grammars][lang-support] currently have the query file implemented.
108124Contributions are welcome!
@@ -112,7 +128,7 @@ Contributions are welcome!
112128Navigating between functions, classes, parameters, and other elements is
113129possible using tree-sitter and textobject queries. For
114130example to move to the next function use `]f`, to move to previous
115−class use `[c`, and so on.
131+type use `[t`, and so on.
116132
117133![Tree-sitter-nav-demo][tree-sitter-nav-demo]
118134
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+18
@@ -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,12 @@
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>
54+ <release version="23.05" date="2023-05-18">
55+ <url>https://github.com/helix-editor/helix/releases/tag/23.05</url>
56+ </release>
3957 <release version="23.03" date="2023-03-31">
4058 <url>https://helix-editor.com/news/release-23-03-highlights/</url>
4159 </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+13−6
@@ -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
@@ -30,7 +37,7 @@ we'll use `<tag>` as a placeholder for the tag being published.
3037
3138The changelog is currently created manually by reading through commits in the
3239log since the last release. GitHub's compare view is a nice way to approach
33−this. For example when creating the 22.07 release notes, this compare link
40+this. For example, when creating the 22.07 release notes, this compare link
3441may be used
3542
3643```
@@ -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.
docs/vision.md+1−1
@@ -20,5 +20,5 @@ Vision statements are all well and good, but are also vague and subjective. Her
2020* **Built-in tools** for working with code bases efficiently. Most projects aren't a single file, and an editor should handle that as a first-class use case. In Helix's case, this means (among other things) a fuzzy-search file navigator and LSP support.
2121* **Edit anything** that comes up when coding, within reason. Whether it's a 200 MB XML file, a megabyte of minified javascript on a single line, or Japanese text encoded in ShiftJIS, you should be able to open it and edit it without problems. (Note: this doesn't mean handle every esoteric use case. Sometimes you do just need a specialized tool, and Helix isn't that.)
2222* **Configurable**, within reason. Although the defaults should be good, not everyone will agree on what "good" is. Within the bounds of Helix's core interaction models, it should be reasonably configurable so that it can be "good" for more people. This means, for example, custom key maps among other things.
23−* **Extensible**, within reason. Although we want Helix to be productive out-of-the-box, it's not practical or desirable to cram every useful feature and use case into the core editor. The basics should be built-in, but you should be able to extend it with additional functionality as needed. Right now we're thinking Wasm-based plugins.
23+* **Extensible**, within reason. Although we want Helix to be productive out-of-the-box, it's not practical or desirable to cram every useful feature and use case into the core editor. The basics should be built-in, but you should be able to extend it with additional functionality as needed.
2424* **Clean code base.** Sometimes other factors (e.g. significant performance gains, important features, correctness, etc.) will trump strict readability, but we nevertheless want to keep the code base straightforward and easy to understand to the extent we can.
flake.lock+38−235
@@ -1,135 +1,35 @@
11{
22 "nodes": {
33 "crane": {
4− "flake": false,
5− "locked": {
6− "lastModified": 1670900067,
7− "narHash": "sha256-VXVa+KBfukhmWizaiGiHRVX/fuk66P8dgSFfkVN4/MY=",
8− "owner": "ipetkov",
9− "repo": "crane",
10− "rev": "59b31b41a589c0a65e4a1f86b0e5eac68081468b",
11− "type": "github"
12− },
13− "original": {
14− "owner": "ipetkov",
15− "repo": "crane",
16− "type": "github"
17− }
18− },
19− "dream2nix": {
204 "inputs": {
21− "all-cabal-json": [
22− "nci"
23− ],
24− "crane": "crane",
25− "devshell": [
26− "nci"
27− ],
28− "drv-parts": "drv-parts",
29− "flake-compat": "flake-compat",
30− "flake-parts": [
31− "nci",
32− "parts"
33− ],
34− "flake-utils-pre-commit": [
35− "nci"
36− ],
37− "ghc-utils": [
38− "nci"
39− ],
40− "gomod2nix": [
41− "nci"
42− ],
43− "mach-nix": [
44− "nci"
45− ],
46− "nix-pypi-fetcher": [
47− "nci"
48− ],
495 "nixpkgs": [
50− "nci",
516 "nixpkgs"
52− ],
53− "nixpkgsV1": "nixpkgsV1",
54− "poetry2nix": [
55− "nci"
56− ],
57− "pre-commit-hooks": [
58− "nci"
59− ],
60− "pruned-racket-catalog": [
61− "nci"
627 ]
638 },
649 "locked": {
65− "lastModified": 1680258209,
66− "narHash": "sha256-lEo50RXI/17/a9aCIun8Hz62ZJ5JM5RGeTgclIP+Lgc=",
67− "owner": "nix-community",
68− "repo": "dream2nix",
69− "rev": "6f512b5a220fdb26bd3c659f7b55e4f052ec8b35",
10+ "lastModified": 1701025348,
11+ "narHash": "sha256-42GHmYH+GF7VjwGSt+fVT1CQuNpGanJbNgVHTAZppUM=",
12+ "owner": "ipetkov",
13+ "repo": "crane",
14+ "rev": "42afaeb1a0325194a7cdb526332d2cb92fddd07b",
7015 "type": "github"
7116 },
7217 "original": {
73− "owner": "nix-community",
74− "repo": "dream2nix",
18+ "owner": "ipetkov",
19+ "repo": "crane",
7520 "type": "github"
7621 }
7722 },
78− "drv-parts": {
23+ "flake-utils": {
7924 "inputs": {
80− "flake-compat": [
81− "nci",
82− "dream2nix",
83− "flake-compat"
84− ],
85− "flake-parts": [
86− "nci",
87− "dream2nix",
88− "flake-parts"
89− ],
90− "nixpkgs": [
91− "nci",
92− "dream2nix",
93− "nixpkgs"
94− ]
25+ "systems": "systems"
9526 },
9627 "locked": {
97− "lastModified": 1680172861,
98− "narHash": "sha256-QMyI338xRxaHFDlCXdLCtgelGQX2PdlagZALky4ZXJ8=",
99− "owner": "davhau",
100− "repo": "drv-parts",
101− "rev": "ced8a52f62b0a94244713df2225c05c85b416110",
102− "type": "github"
103− },
104− "original": {
105− "owner": "davhau",
106− "repo": "drv-parts",
107− "type": "github"
108− }
109− },
110− "flake-compat": {
111− "flake": false,
112− "locked": {
113− "lastModified": 1673956053,
114− "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
115− "owner": "edolstra",
116− "repo": "flake-compat",
117− "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
118− "type": "github"
119− },
120− "original": {
121− "owner": "edolstra",
122− "repo": "flake-compat",
123− "type": "github"
124− }
125− },
126− "flake-utils": {
127− "locked": {
128− "lastModified": 1659877975,
129− "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
28+ "lastModified": 1694529238,
29+ "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
13030 "owner": "numtide",
13131 "repo": "flake-utils",
132− "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
32+ "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
13333 "type": "github"
13434 },
13535 "original": {
@@ -138,55 +38,13 @@
13838 "type": "github"
13939 }
14040 },
141− "mk-naked-shell": {
142− "flake": false,
143− "locked": {
144− "lastModified": 1676572903,
145− "narHash": "sha256-oQoDHHUTxNVSURfkFcYLuAK+btjs30T4rbEUtCUyKy8=",
146− "owner": "yusdacra",
147− "repo": "mk-naked-shell",
148− "rev": "aeca9f8aa592f5e8f71f407d081cb26fd30c5a57",
149− "type": "github"
150− },
151− "original": {
152− "owner": "yusdacra",
153− "repo": "mk-naked-shell",
154− "type": "github"
155− }
156− },
157− "nci": {
158− "inputs": {
159− "dream2nix": "dream2nix",
160− "mk-naked-shell": "mk-naked-shell",
161− "nixpkgs": [
162− "nixpkgs"
163− ],
164− "parts": "parts",
165− "rust-overlay": [
166− "rust-overlay"
167− ]
168− },
169− "locked": {
170− "lastModified": 1680329418,
171− "narHash": "sha256-+KN0eQLSZvL1J0kDO8/fxv0UCHTyZCADLmpIfeeiSGo=",
172− "owner": "yusdacra",
173− "repo": "nix-cargo-integration",
174− "rev": "98c1d2ff5155f0fee5d290f6b982cb990839d540",
175− "type": "github"
176− },
177− "original": {
178− "owner": "yusdacra",
179− "repo": "nix-cargo-integration",
180− "type": "github"
181− }
182− },
18341 "nixpkgs": {
18442 "locked": {
185− "lastModified": 1680213900,
186− "narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=",
43+ "lastModified": 1700794826,
44+ "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=",
18745 "owner": "nixos",
18846 "repo": "nixpkgs",
189− "rev": "e3652e0735fbec227f342712f180f4f21f0594f2",
47+ "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8",
19048 "type": "github"
19149 },
19250 "original": {
@@ -196,99 +54,29 @@
19654 "type": "github"
19755 }
19856 },
199− "nixpkgs-lib": {
200− "locked": {
201− "dir": "lib",
202− "lastModified": 1678375444,
203− "narHash": "sha256-XIgHfGvjFvZQ8hrkfocanCDxMefc/77rXeHvYdzBMc8=",
204− "owner": "NixOS",
205− "repo": "nixpkgs",
206− "rev": "130fa0baaa2b93ec45523fdcde942f6844ee9f6e",
207− "type": "github"
208− },
209− "original": {
210− "dir": "lib",
211− "owner": "NixOS",
212− "ref": "nixos-unstable",
213− "repo": "nixpkgs",
214− "type": "github"
215− }
216− },
217− "nixpkgsV1": {
218− "locked": {
219− "lastModified": 1678500271,
220− "narHash": "sha256-tRBLElf6f02HJGG0ZR7znMNFv/Uf7b2fFInpTHiHaSE=",
221− "owner": "NixOS",
222− "repo": "nixpkgs",
223− "rev": "5eb98948b66de29f899c7fe27ae112a47964baf8",
224− "type": "github"
225− },
226− "original": {
227− "id": "nixpkgs",
228− "ref": "nixos-22.11",
229− "type": "indirect"
230− }
231− },
232− "parts": {
233− "inputs": {
234− "nixpkgs-lib": [
235− "nci",
236− "nixpkgs"
237− ]
238− },
239− "locked": {
240− "lastModified": 1679737941,
241− "narHash": "sha256-srSD9CwsVPnUMsIZ7Kt/UegkKUEBcTyU1Rev7mO45S0=",
242− "owner": "hercules-ci",
243− "repo": "flake-parts",
244− "rev": "3502ee99d6dade045bdeaf7b0cd8ec703484c25c",
245− "type": "github"
246− },
247− "original": {
248− "owner": "hercules-ci",
249− "repo": "flake-parts",
250− "type": "github"
251− }
252− },
253− "parts_2": {
254− "inputs": {
255− "nixpkgs-lib": "nixpkgs-lib"
256− },
257− "locked": {
258− "lastModified": 1679737941,
259− "narHash": "sha256-srSD9CwsVPnUMsIZ7Kt/UegkKUEBcTyU1Rev7mO45S0=",
260− "owner": "hercules-ci",
261− "repo": "flake-parts",
262− "rev": "3502ee99d6dade045bdeaf7b0cd8ec703484c25c",
263− "type": "github"
264− },
265− "original": {
266− "owner": "hercules-ci",
267− "repo": "flake-parts",
268− "type": "github"
269− }
270− },
27157 "root": {
27258 "inputs": {
273− "nci": "nci",
59+ "crane": "crane",
60+ "flake-utils": "flake-utils",
27461 "nixpkgs": "nixpkgs",
275− "parts": "parts_2",
27662 "rust-overlay": "rust-overlay"
27763 }
27864 },
27965 "rust-overlay": {
28066 "inputs": {
281− "flake-utils": "flake-utils",
67+ "flake-utils": [
68+ "flake-utils"
69+ ],
28270 "nixpkgs": [
28371 "nixpkgs"
28472 ]
28573 },
28674 "locked": {
287− "lastModified": 1680315536,
288− "narHash": "sha256-0AsBuKssJMbcRcw4HJQwJsUHhZxR5+gaf6xPQayhR44=",
75+ "lastModified": 1701137803,
76+ "narHash": "sha256-0LcPAdql5IhQSUXJx3Zna0dYTgdIoYO7zUrsKgiBd04=",
28977 "owner": "oxalica",
29078 "repo": "rust-overlay",
291− "rev": "5c8c151bdd639074a0051325c16df1a64ee23497",
79+ "rev": "9dd940c967502f844eacea52a61e9596268d4f70",
29280 "type": "github"
29381 },
29482 "original": {
@@ -296,6 +84,21 @@
29684 "repo": "rust-overlay",
29785 "type": "github"
29886 }
87+ },
88+ "systems": {
89+ "locked": {
90+ "lastModified": 1681028828,
91+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
92+ "owner": "nix-systems",
93+ "repo": "default",
94+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
95+ "type": "github"
96+ },
97+ "original": {
98+ "owner": "nix-systems",
99+ "repo": "default",
100+ "type": "github"
101+ }
299102 }
300103 },
301104 "root": "root",
flake.nix+171−140
@@ -3,162 +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];
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− devShells.default = config.nci.outputs."helix-project".devShell.overrideAttrs (old: {
150− nativeBuildInputs =
151− (old.nativeBuildInputs or [])
152− ++ (with pkgs; [lld_13 cargo-flamegraph rust-analyzer])
153− ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) pkgs.cargo-tarpaulin)
154− ++ (lib.optional stdenv.isLinux pkgs.lldb)
155− ++ (lib.optional stdenv.isDarwin pkgs.darwin.apple_sdk.frameworks.CoreFoundation);
156− shellHook = ''
157− export HELIX_RUNTIME="$PWD/runtime"
158− export RUST_BACKTRACE="1"
159− export RUSTFLAGS="${rustFlagsEnv}"
160− '';
161− });
157+ clippy = craneLibMSRV.cargoClippy (commonArgs
158+ // {
159+ inherit cargoArtifacts;
160+ cargoClippyExtraArgs = "--all-targets -- --deny warnings";
161+ });
162+
163+ fmt = craneLibMSRV.cargoFmt commonArgs;
164+
165+ doc = craneLibMSRV.cargoDoc (commonArgs
166+ // {
167+ inherit cargoArtifacts;
168+ });
169+
170+ test = craneLibMSRV.cargoTest (commonArgs
171+ // {
172+ inherit cargoArtifacts;
173+ });
174+ };
175+
176+ devShells.default = pkgs.mkShell {
177+ inputsFrom = builtins.attrValues self.checks.${system};
178+ nativeBuildInputs = with pkgs;
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;
162193 };
163194 };
164195
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+21−17
@@ -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.17"
29+tree-sitter.workspace = true
30+once_cell = "1.19"
3031arc-swap = "1"
3132regex = "1"
32−bitflags = "2.1"
33−ahash = "0.8.3"
34−hashbrown = { version = "0.13.2", 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"
@@ -45,9 +46,12 @@ encoding_rs = "0.8"
4546
4647chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
4748
48−etcetera = "0.4"
49+etcetera = "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.1"
57+indoc = "2.0.4"
helix-core/src/diagnostic.rs+5
@@ -39,10 +39,15 @@ 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>,
4549 pub code: Option<NumberOrString>,
50+ pub language_server_id: usize,
4651 pub tags: Vec<DiagnosticTag>,
4752 pub source: Option<String>,
4853 pub data: Option<serde_json::Value>,
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+617−145
@@ -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.
@@ -19,6 +20,10 @@ pub enum IndentStyle {
1920 Spaces(u8),
2021}
2122
23+// 16 spaces
24+const INDENTS: &str = " ";
25+pub const MAX_INDENT: u8 = 16;
26+
2227impl IndentStyle {
2328 /// Creates an `IndentStyle` from an indentation string.
2429 ///
@@ -27,10 +32,10 @@ impl IndentStyle {
2732 #[inline]
2833 pub fn from_str(indent: &str) -> Self {
2934 // XXX: do we care about validating the input more than this? Probably not...?
30− debug_assert!(!indent.is_empty() && indent.len() <= 8);
35+ debug_assert!(!indent.is_empty() && indent.len() <= MAX_INDENT as usize);
3136
3237 if indent.starts_with(' ') {
33− IndentStyle::Spaces(indent.len() as u8)
38+ IndentStyle::Spaces(indent.len().clamp(1, MAX_INDENT as usize) as u8)
3439 } else {
3540 IndentStyle::Tabs
3641 }
@@ -40,20 +45,13 @@ impl IndentStyle {
4045 pub fn as_str(&self) -> &'static str {
4146 match *self {
4247 IndentStyle::Tabs => "\t",
43− IndentStyle::Spaces(1) => " ",
44− IndentStyle::Spaces(2) => " ",
45− IndentStyle::Spaces(3) => " ",
46− IndentStyle::Spaces(4) => " ",
47− IndentStyle::Spaces(5) => " ",
48− IndentStyle::Spaces(6) => " ",
49− IndentStyle::Spaces(7) => " ",
50− IndentStyle::Spaces(8) => " ",
51−
52− // Unsupported indentation style. This should never happen,
53− // but just in case fall back to two spaces.
5448 IndentStyle::Spaces(n) => {
55− debug_assert!(n > 0 && n <= 8); // Always triggers. `debug_panic!()` wanted.
56− " "
49+ // Unsupported indentation style. This should never happen,
50+ debug_assert!(n > 0 && n <= MAX_INDENT);
51+
52+ // Either way, clamp to the nearest supported value
53+ let closest_n = n.clamp(1, MAX_INDENT) as usize;
54+ &INDENTS[0..closest_n]
5755 }
5856 }
5957 }
@@ -75,9 +73,9 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
7573 // Build a histogram of the indentation *increases* between
7674 // subsequent lines, ignoring lines that are all whitespace.
7775 //
78− // Index 0 is for tabs, the rest are 1-8 spaces.
79− let histogram: [usize; 9] = {
80− let mut histogram = [0; 9];
76+ // Index 0 is for tabs, the rest are 1-MAX_INDENT spaces.
77+ let histogram: [usize; MAX_INDENT as usize + 1] = {
78+ let mut histogram = [0; MAX_INDENT as usize + 1];
8179 let mut prev_line_is_tabs = false;
8280 let mut prev_line_leading_count = 0usize;
8381
@@ -136,7 +134,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
136134 histogram[0] += 1;
137135 } else {
138136 let amount = leading_count - prev_line_leading_count;
139− if amount <= 8 {
137+ if amount <= MAX_INDENT as usize {
140138 histogram[amount] += 1;
141139 }
142140 }
@@ -199,6 +197,56 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: us
199197 len / indent_width
200198}
201199
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+
202250/// Computes for node and all ancestors whether they are the first node on their line.
203251/// The first entry in the return value represents the root node, the last one the node itself
204252fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bool> {
@@ -240,68 +288,140 @@ fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bo
240288/// This is usually constructed in one of 2 ways:
241289/// - Successively add indent captures to get the (added) indent from a single line
242290/// - Successively add the indent results for each line
243−#[derive(Default)]
244−pub struct Indentation {
245− /// The total indent (the number of indent levels) is defined as max(0, indent-outdent).
246− /// The string that this results in depends on the indent style (spaces or tabs, etc.)
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> {
247296 indent: usize,
297+ indent_always: usize,
248298 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>>,
249303}
250−impl Indentation {
304+
305+impl<'a> Indentation<'a> {
251306 /// Add some other [Indentation] to this.
252− /// The added indent should be the total added indent from one line
253− fn add_line(&mut self, added: &Indentation) {
254− if added.indent > 0 && added.outdent == 0 {
255− self.indent += 1;
256− } else if added.outdent > 0 && added.indent == 0 {
257− 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;
258313 }
314+ if added.align.is_some() {
315+ self.align = added.align;
316+ return;
317+ }
318+ self.indent += added.indent;
319+ self.indent_always += added.indent_always;
320+ self.outdent += added.outdent;
321+ self.outdent_always += added.outdent_always;
259322 }
323+
260324 /// Add an indent capture to this indent.
261− /// All the captures that are added in this way should be on the same line.
262− 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>) {
263329 match added {
264330 IndentCaptureType::Indent => {
265− 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;
266340 }
267341 IndentCaptureType::Outdent => {
268− 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+ }
269354 }
270355 }
271356 }
272− fn as_string(&self, indent_style: &IndentStyle) -> String {
273− let indent_level = if self.indent >= self.outdent {
274− 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+ ))
275380 } else {
276− log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
277− 0
278− };
279− 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+ )
280393 }
281394}
282395
283396/// An indent definition which corresponds to a capture from the indent query
284−struct IndentCapture {
285− capture_type: IndentCaptureType,
397+#[derive(Debug)]
398+struct IndentCapture<'a> {
399+ capture_type: IndentCaptureType<'a>,
286400 scope: IndentScope,
287401}
288−#[derive(Clone, Copy)]
289−enum IndentCaptureType {
402+#[derive(Debug, Clone, PartialEq)]
403+enum IndentCaptureType<'a> {
290404 Indent,
405+ IndentAlways,
291406 Outdent,
407+ OutdentAlways,
408+ /// Alignment given as a string of whitespace
409+ Align(RopeSlice<'a>),
292410}
293−impl IndentCaptureType {
411+
412+impl<'a> IndentCaptureType<'a> {
294413 fn default_scope(&self) -> IndentScope {
295414 match self {
296− IndentCaptureType::Indent => IndentScope::Tail,
297− IndentCaptureType::Outdent => IndentScope::All,
415+ IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
416+ IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All,
417+ IndentCaptureType::Align(_) => IndentScope::All,
298418 }
299419 }
300420}
301421/// This defines which part of a node an [IndentCapture] applies to.
302422/// Each [IndentCaptureType] has a default scope, but the scope can be changed
303423/// with `#set!` property declarations.
304−#[derive(Clone, Copy)]
424+#[derive(Debug, Clone, Copy)]
305425enum IndentScope {
306426 /// The indent applies to the whole node
307427 All,
@@ -311,6 +431,7 @@ enum IndentScope {
311431
312432/// A capture from the indent query which does not define an indent but extends
313433/// the range of a node. This is used before the indent is calculated.
434+#[derive(Debug)]
314435enum ExtendCapture {
315436 Extend,
316437 PreventOnce,
@@ -319,24 +440,41 @@ enum ExtendCapture {
319440/// The result of running a tree-sitter indent query. This stores for
320441/// each node (identified by its ID) the relevant captures (already filtered
321442/// by predicates).
322−struct IndentQueryResult {
323− indent_captures: HashMap<usize, Vec<IndentCapture>>,
443+#[derive(Debug)]
444+struct IndentQueryResult<'a> {
445+ indent_captures: HashMap<usize, Vec<IndentCapture<'a>>>,
324446 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
325447}
326448
327−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>(
328467 query: &Query,
329468 syntax: &Syntax,
330469 cursor: &mut QueryCursor,
331− text: RopeSlice,
470+ text: RopeSlice<'a>,
332471 range: std::ops::Range<usize>,
333− // Position of the (optional) newly inserted line break.
334− // Given as (line, byte_pos)
335− new_line_break: Option<(usize, usize)>,
336−) -> IndentQueryResult {
472+ new_line_byte_pos: Option<usize>,
473+) -> IndentQueryResult<'a> {
337474 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
338475 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
339476 cursor.set_byte_range(range);
477+
340478 // Iterate over all captures from the query
341479 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
342480 // Skip matches where not all custom predicates are fulfilled
@@ -363,21 +501,13 @@ fn query_indents(
363501 Some(QueryPredicateArg::Capture(capt1)),
364502 Some(QueryPredicateArg::Capture(capt2))
365503 ) => {
366− let get_line_num = |node: Node| {
367− let mut node_line = node.start_position().row;
368− // Adjust for the new line that will be inserted
369− if let Some((line, byte)) = new_line_break {
370− if node_line==line && node.start_byte()>=byte {
371− node_line += 1;
372− }
373− }
374− node_line
375− };
376504 let n1 = m.nodes_for_capture_index(*capt1).next();
377505 let n2 = m.nodes_for_capture_index(*capt2).next();
378506 match (n1, n2) {
379507 (Some(n1), Some(n2)) => {
380− let same_line = get_line_num(n1)==get_line_num(n2);
508+ let n1_line = get_node_start_line(n1, new_line_byte_pos);
509+ let n2_line = get_node_start_line(n2, new_line_byte_pos);
510+ let same_line = n1_line == n2_line;
381511 same_line==(pred.operator.as_ref()=="same-line?")
382512 }
383513 _ => true,
@@ -388,6 +518,23 @@ fn query_indents(
388518 }
389519 }
390520 }
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+ },
391538 _ => {
392539 panic!(
393540 "Invalid indent query: Unknown predicate (\"{}\")",
@@ -398,11 +545,28 @@ fn query_indents(
398545 }) {
399546 continue;
400547 }
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;
401553 for capture in m.captures {
402554 let capture_name = query.capture_names()[capture.index as usize].as_str();
403555 let capture_type = match capture_name {
404556 "indent" => IndentCaptureType::Indent,
557+ "indent.always" => IndentCaptureType::IndentAlways,
405558 "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+ }
406570 "extend" => {
407571 extend_captures
408572 .entry(capture.node.id())
@@ -452,17 +616,40 @@ fn query_indents(
452616 }
453617 }
454618 }
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+ }
455638 indent_captures
456− .entry(capture.node.id())
457− // Most entries only need to contain a single IndentCapture
639+ .entry(node_id)
458640 .or_insert_with(|| Vec::with_capacity(1))
459− .push(indent_capture);
641+ .push(capture);
460642 }
461643 }
462− IndentQueryResult {
644+
645+ let result = IndentQueryResult {
463646 indent_captures,
464647 extend_captures,
465− }
648+ };
649+
650+ log::trace!("indent result = {:?}", result);
651+
652+ result
466653}
467654
468655/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
@@ -532,61 +719,27 @@ fn extend_nodes<'a>(
532719 }
533720}
534721
535−/// Use the syntax tree to determine the indentation for a given position.
536−/// This can be used in 2 ways:
537−///
538−/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
539−/// - In this case, pos should be inside the first tree-sitter node on that line.
540−/// In most cases, this can just be the first non-whitespace on that line.
541−/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
542−/// after pos were moved to a new line.
543−///
544−/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
545−/// Each of these nodes produces some [Indentation] for:
546−///
547−/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
548−/// - The line after the node. This is defined by:
549−/// - The scope `tail`.
550−/// - The scope `all` if this node is not the first node on its line.
551−/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
552−/// The indents from different nodes for the same line are then combined.
553−/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
554−///
555−/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
556−/// ```ignore
557−/// some_function(|| {
558−/// // Both the function parameters as well as the contained block should be indented.
559−/// // Because they are on the same line, this only yields one indent level
560−/// });
561−/// ```
562−///
563−/// ```ignore
564−/// some_function(
565−/// param1,
566−/// || {
567−/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
568−/// },
569−/// );
570−/// ```
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.
571725#[allow(clippy::too_many_arguments)]
572−pub fn treesitter_indent_for_pos(
726+fn init_indent_query<'a, 'b>(
573727 query: &Query,
574− syntax: &Syntax,
575− indent_style: &IndentStyle,
728+ syntax: &'a Syntax,
729+ text: RopeSlice<'b>,
576730 tab_width: usize,
577731 indent_width: usize,
578− text: RopeSlice,
579732 line: usize,
580− pos: usize,
581− new_line: bool,
582−) -> Option<String> {
583− 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>>>)> {
584736 // The innermost tree-sitter node which is considered for the indent
585737 // computation. It may change if some predeceding node is extended
586738 let mut node = syntax
587739 .tree()
588740 .root_node()
589741 .descendant_for_byte_range(byte_pos, byte_pos)?;
742+
590743 let (query_result, deepest_preceding) = {
591744 // The query range should intersect with all nodes directly preceding
592745 // the position of the indent query in case one of them is extended.
@@ -617,13 +770,12 @@ pub fn treesitter_indent_for_pos(
617770 &mut cursor,
618771 text,
619772 query_range,
620− new_line.then_some((line, byte_pos)),
773+ new_line_byte_pos,
621774 );
622775 ts_parser.cursors.push(cursor);
623776 (query_result, deepest_preceding)
624777 })
625778 };
626− let indent_captures = query_result.indent_captures;
627779 let extend_captures = query_result.extend_captures;
628780
629781 // Check for extend captures, potentially changing the node that the indent calculation starts with
@@ -638,6 +790,68 @@ pub fn treesitter_indent_for_pos(
638790 indent_width,
639791 );
640792 }
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+ )?;
641855 let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos));
642856
643857 let mut result = Indentation::default();
@@ -645,12 +859,16 @@ pub fn treesitter_indent_for_pos(
645859 // even if there are multiple "indent" nodes on the same line
646860 let mut indent_for_line = Indentation::default();
647861 let mut indent_for_line_below = Indentation::default();
862+
648863 loop {
649864 // This can safely be unwrapped because `first_in_line` contains
650865 // one entry for each ancestor of the node (which is what we iterate over)
651866 let is_first = *first_in_line.last().unwrap();
652− // Apply all indent definitions for this node
653− 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()) {
654872 for definition in definitions {
655873 match definition.scope {
656874 IndentScope::All => {
@@ -668,28 +886,22 @@ pub fn treesitter_indent_for_pos(
668886 }
669887
670888 if let Some(parent) = node.parent() {
671− let mut node_line = node.start_position().row;
672− let mut parent_line = parent.start_position().row;
673− if node_line == line && new_line {
674− // Also consider the line that will be inserted
675− if node.start_byte() >= byte_pos {
676− node_line += 1;
677− }
678− if parent.start_byte() >= byte_pos {
679− parent_line += 1;
680− }
681− };
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+
682892 if node_line != parent_line {
893+ // Don't add indent for the line below the line of the query
683894 if node_line < line + (new_line as usize) {
684− // Don't add indent for the line below the line of the query
685− result.add_line(&indent_for_line_below);
895+ result.add_line(indent_for_line_below);
686896 }
897+
687898 if node_line == parent_line + 1 {
688899 indent_for_line_below = indent_for_line;
689900 } else {
690− result.add_line(&indent_for_line);
901+ result.add_line(indent_for_line);
691902 indent_for_line_below = Indentation::default();
692903 }
904+
693905 indent_for_line = Indentation::default();
694906 }
695907
@@ -701,13 +913,13 @@ pub fn treesitter_indent_for_pos(
701913 if (node.start_position().row < line)
702914 || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
703915 {
704− result.add_line(&indent_for_line_below);
916+ result.add_line(indent_for_line_below);
705917 }
706− result.add_line(&indent_for_line);
918+ result.add_line(indent_for_line);
707919 break;
708920 }
709921 }
710− Some(result.as_string(indent_style))
922+ Some(result)
711923}
712924
713925/// Returns the indentation for a new line.
@@ -716,6 +928,7 @@ pub fn treesitter_indent_for_pos(
716928pub fn indent_for_newline(
717929 language_config: Option<&LanguageConfiguration>,
718930 syntax: Option<&Syntax>,
931+ indent_heuristic: &IndentationHeuristic,
719932 indent_style: &IndentStyle,
720933 tab_width: usize,
721934 text: RopeSlice,
@@ -724,14 +937,18 @@ pub fn indent_for_newline(
724937 current_line: usize,
725938) -> String {
726939 let indent_width = indent_style.indent_width(tab_width);
727− if let (Some(query), Some(syntax)) = (
940+ if let (
941+ IndentationHeuristic::TreeSitter | IndentationHeuristic::Hybrid,
942+ Some(query),
943+ Some(syntax),
944+ ) = (
945+ indent_heuristic,
728946 language_config.and_then(|config| config.indent_query()),
729947 syntax,
730948 ) {
731949 if let Some(indent) = treesitter_indent_for_pos(
732950 query,
733951 syntax,
734− indent_style,
735952 tab_width,
736953 indent_width,
737954 text,
@@ -739,9 +956,57 @@ pub fn indent_for_newline(
739956 line_before_end_pos,
740957 true,
741958 ) {
742− 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);
7431007 };
7441008 }
1009+ // Fallback in case we either don't have indent queries or they failed for some reason
7451010 let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
7461011 indent_style.as_str().repeat(indent_level)
7471012}
@@ -797,4 +1062,211 @@ mod test {
7971062 3
7981063 );
7991064 }
1065+
1066+ #[test]
1067+ fn test_large_indent_level() {
1068+ let tab_width = 16;
1069+ let indent_width = 16;
1070+ let line = Rope::from(" fn new"); // 16 spaces
1071+ assert_eq!(
1072+ indent_level_for_line(line.slice(..), tab_width, indent_width),
1073+ 1
1074+ );
1075+ let line = Rope::from(" fn new"); // 32 spaces
1076+ assert_eq!(
1077+ indent_level_for_line(line.slice(..), tab_width, indent_width),
1078+ 2
1079+ );
1080+ }
1081+
1082+ #[test]
1083+ fn add_capture() {
1084+ let indent = || Indentation {
1085+ indent: 1,
1086+ ..Default::default()
1087+ };
1088+ let indent_always = || Indentation {
1089+ indent_always: 1,
1090+ ..Default::default()
1091+ };
1092+ let outdent = || Indentation {
1093+ outdent: 1,
1094+ ..Default::default()
1095+ };
1096+ let outdent_always = || Indentation {
1097+ outdent_always: 1,
1098+ ..Default::default()
1099+ };
1100+
1101+ fn add_capture<'a>(
1102+ mut indent: Indentation<'a>,
1103+ capture: IndentCaptureType<'a>,
1104+ ) -> Indentation<'a> {
1105+ indent.add_capture(capture);
1106+ indent
1107+ }
1108+
1109+ // adding an indent to no indent makes an indent
1110+ assert_eq!(
1111+ indent(),
1112+ add_capture(Indentation::default(), IndentCaptureType::Indent)
1113+ );
1114+ assert_eq!(
1115+ indent_always(),
1116+ add_capture(Indentation::default(), IndentCaptureType::IndentAlways)
1117+ );
1118+ assert_eq!(
1119+ outdent(),
1120+ add_capture(Indentation::default(), IndentCaptureType::Outdent)
1121+ );
1122+ assert_eq!(
1123+ outdent_always(),
1124+ add_capture(Indentation::default(), IndentCaptureType::OutdentAlways)
1125+ );
1126+
1127+ // adding an indent to an already indented has no effect
1128+ assert_eq!(indent(), add_capture(indent(), IndentCaptureType::Indent));
1129+ assert_eq!(
1130+ outdent(),
1131+ add_capture(outdent(), IndentCaptureType::Outdent)
1132+ );
1133+
1134+ // adding an always to a regular makes it always
1135+ assert_eq!(
1136+ indent_always(),
1137+ add_capture(indent(), IndentCaptureType::IndentAlways)
1138+ );
1139+ assert_eq!(
1140+ outdent_always(),
1141+ add_capture(outdent(), IndentCaptureType::OutdentAlways)
1142+ );
1143+
1144+ // adding an always to an always is additive
1145+ assert_eq!(
1146+ Indentation {
1147+ indent_always: 2,
1148+ ..Default::default()
1149+ },
1150+ add_capture(indent_always(), IndentCaptureType::IndentAlways)
1151+ );
1152+ assert_eq!(
1153+ Indentation {
1154+ outdent_always: 2,
1155+ ..Default::default()
1156+ },
1157+ add_capture(outdent_always(), IndentCaptureType::OutdentAlways)
1158+ );
1159+
1160+ // adding regular to always should be associative
1161+ assert_eq!(
1162+ Indentation {
1163+ indent_always: 1,
1164+ ..Default::default()
1165+ },
1166+ add_capture(
1167+ add_capture(indent(), IndentCaptureType::Indent),
1168+ IndentCaptureType::IndentAlways
1169+ )
1170+ );
1171+ assert_eq!(
1172+ Indentation {
1173+ indent_always: 1,
1174+ ..Default::default()
1175+ },
1176+ add_capture(
1177+ add_capture(indent(), IndentCaptureType::IndentAlways),
1178+ IndentCaptureType::Indent
1179+ )
1180+ );
1181+ assert_eq!(
1182+ Indentation {
1183+ outdent_always: 1,
1184+ ..Default::default()
1185+ },
1186+ add_capture(
1187+ add_capture(outdent(), IndentCaptureType::Outdent),
1188+ IndentCaptureType::OutdentAlways
1189+ )
1190+ );
1191+ assert_eq!(
1192+ Indentation {
1193+ outdent_always: 1,
1194+ ..Default::default()
1195+ },
1196+ add_capture(
1197+ add_capture(outdent(), IndentCaptureType::OutdentAlways),
1198+ IndentCaptureType::Outdent
1199+ )
1200+ );
1201+ }
1202+
1203+ #[test]
1204+ fn test_relative_indent() {
1205+ let indent_style = IndentStyle::Spaces(4);
1206+ let tab_width: usize = 4;
1207+ let no_align = [
1208+ Indentation::default(),
1209+ Indentation {
1210+ indent: 1,
1211+ ..Default::default()
1212+ },
1213+ Indentation {
1214+ indent: 5,
1215+ outdent: 1,
1216+ ..Default::default()
1217+ },
1218+ ];
1219+ let align = no_align.clone().map(|indent| Indentation {
1220+ align: Some(RopeSlice::from("12345")),
1221+ ..indent
1222+ });
1223+ let different_align = Indentation {
1224+ align: Some(RopeSlice::from("123456")),
1225+ ..Default::default()
1226+ };
1227+
1228+ // Check that relative and absolute indentation computation are the same when the line we compare to is
1229+ // indented as we expect.
1230+ let check_consistency = |indent: &Indentation, other: &Indentation| {
1231+ assert_eq!(
1232+ indent.relative_indent(
1233+ other,
1234+ RopeSlice::from(other.to_string(&indent_style, tab_width).as_str()),
1235+ &indent_style,
1236+ tab_width
1237+ ),
1238+ Some(indent.to_string(&indent_style, tab_width))
1239+ );
1240+ };
1241+ for a in &no_align {
1242+ for b in &no_align {
1243+ check_consistency(a, b);
1244+ }
1245+ }
1246+ for a in &align {
1247+ for b in &align {
1248+ check_consistency(a, b);
1249+ }
1250+ }
1251+
1252+ // Relative indent computation makes no sense if the alignment differs
1253+ assert_eq!(
1254+ align[0].relative_indent(
1255+ &no_align[0],
1256+ RopeSlice::from(" "),
1257+ &indent_style,
1258+ tab_width
1259+ ),
1260+ None
1261+ );
1262+ assert_eq!(
1263+ align[0].relative_indent(
1264+ &different_align,
1265+ RopeSlice::from(" "),
1266+ &indent_style,
1267+ tab_width
1268+ ),
1269+ None
1270+ );
1271+ }
8001272}
helix-core/src/lib.rs+5−3
@@ -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;
@@ -66,5 +68,5 @@ pub use syntax::Syntax;
6668
6769pub use diagnostic::Diagnostic;
6870
69−pub use line_ending::{LineEnding, DEFAULT_LINE_ENDING};
70−pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction};
71+pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
72+pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction};
helix-core/src/line_ending.rs+2−2
@@ -1,9 +1,9 @@
11use crate::{Rope, RopeSlice};
22
33#[cfg(target_os = "windows")]
4−pub const DEFAULT_LINE_ENDING: LineEnding = LineEnding::Crlf;
4+pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::Crlf;
55#[cfg(not(target_os = "windows"))]
6−pub const DEFAULT_LINE_ENDING: LineEnding = LineEnding::LF;
6+pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::LF;
77
88/// Represents one of the valid Unicode line endings.
99#[derive(PartialEq, Eq, Copy, Clone, Debug)]
helix-core/src/match_brackets.rs+232−29
@@ -1,7 +1,15 @@
1+use std::iter;
2+
3+use ropey::RopeSlice;
14use tree_sitter::Node;
25
3−use crate::{Rope, Syntax};
6+use crate::movement::Direction::{self, Backward, Forward};
7+use crate::Syntax;
8+
9+const MAX_PLAINTEXT_SCAN: usize = 10000;
10+const MATCH_LIMIT: usize = 16;
411
12+// Limit matching pairs to only ( ) { } [ ] < > ' ' " "
513const PAIRS: &[(char, char)] = &[
614 ('(', ')'),
715 ('{', '}'),
@@ -11,17 +19,17 @@ const PAIRS: &[(char, char)] = &[
1119 ('\"', '\"'),
1220];
1321
14−// limit matching pairs to only ( ) { } [ ] < > ' ' " "
15−
16−// Returns the position of the matching bracket under cursor.
17−//
18−// If the cursor is one the opening bracket, the position of
19−// the closing bracket is returned. If the cursor in the closing
20−// bracket, the position of the opening bracket is returned.
21−//
22−// If the cursor is not on a bracket, `None` is returned.
22+/// Returns the position of the matching bracket under cursor.
23+///
24+/// If the cursor is on the opening bracket, the position of
25+/// the closing bracket is returned. If the cursor on the closing
26+/// bracket, the position of the opening bracket is returned.
27+///
28+/// If the cursor is not on a bracket, `None` is returned.
29+///
30+/// If no matching bracket is found, `None` is returned.
2331#[must_use]
24−pub fn find_matching_bracket(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
32+pub fn find_matching_bracket(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
2533 if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) {
2634 return None;
2735 }
@@ -39,46 +47,159 @@ pub fn find_matching_bracket(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<
3947//
4048// If no surrounding scope is found, the function returns `None`.
4149#[must_use]
42−pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
50+pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
4351 find_pair(syntax, doc, pos, true)
4452}
4553
46−fn find_pair(syntax: &Syntax, doc: &Rope, pos: usize, traverse_parents: bool) -> Option<usize> {
54+fn find_pair(
55+ syntax: &Syntax,
56+ doc: RopeSlice,
57+ pos_: usize,
58+ traverse_parents: bool,
59+) -> Option<usize> {
4760 let tree = syntax.tree();
48− let pos = doc.char_to_byte(pos);
61+ let pos = doc.char_to_byte(pos_);
4962
50− let mut node = tree.root_node().named_descendant_for_byte_range(pos, pos)?;
63+ let mut node = tree.root_node().descendant_for_byte_range(pos, pos)?;
5164
5265 loop {
53− let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
54− let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
66+ if node.is_named() {
67+ let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
68+ let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
69+
70+ if is_valid_pair(doc, start_char, end_char) {
71+ if end_byte == pos {
72+ return Some(start_char);
73+ }
5574
56− if is_valid_pair(doc, start_char, end_char) {
57− if end_byte == pos {
58− return Some(start_char);
75+ // We return the end char if the cursor is either on the start char
76+ // or at some arbitrary position between start and end char.
77+ if traverse_parents || start_byte == pos {
78+ return Some(end_char);
79+ }
5980 }
60− // We return the end char if the cursor is either on the start char
61− // or at some arbitrary position between start and end char.
62− return Some(end_char);
6381 }
82+ // this node itselt wasn't a pair but maybe its siblings are
6483
65− if traverse_parents {
66− node = node.parent()?;
67− } else {
68− return None;
84+ // check if we are *on* the pair (special cased so we don't look
85+ // at the current node twice and to jump to the start on that case)
86+ if let Some(open) = as_close_pair(doc, &node) {
87+ if let Some(pair_start) = find_pair_end(doc, node.prev_sibling(), open, Backward) {
88+ return Some(pair_start);
89+ }
6990 }
91+
92+ if !traverse_parents {
93+ // check if we are *on* the opening pair (special cased here as
94+ // an opptimization since we only care about bracket on the cursor
95+ // here)
96+ if let Some(close) = as_open_pair(doc, &node) {
97+ if let Some(pair_end) = find_pair_end(doc, node.next_sibling(), close, Forward) {
98+ return Some(pair_end);
99+ }
100+ }
101+ if node.is_named() {
102+ break;
103+ }
104+ }
105+
106+ for close in
107+ iter::successors(node.next_sibling(), |node| node.next_sibling()).take(MATCH_LIMIT)
108+ {
109+ let Some(open) = as_close_pair(doc, &close) else {
110+ continue;
111+ };
112+ if find_pair_end(doc, Some(node), open, Backward).is_some() {
113+ return doc.try_byte_to_char(close.start_byte()).ok();
114+ }
115+ }
116+ let Some(parent) = node.parent() else {
117+ break;
118+ };
119+ node = parent;
120+ }
121+ let node = tree.root_node().named_descendant_for_byte_range(pos, pos)?;
122+ if node.child_count() != 0 {
123+ return None;
70124 }
125+ let node_start = doc.byte_to_char(node.start_byte());
126+ find_matching_bracket_plaintext(doc.byte_slice(node.byte_range()), pos_ - node_start)
127+ .map(|pos| pos + node_start)
128+}
129+
130+/// Returns the position of the matching bracket under cursor.
131+/// This function works on plain text and ignores tree-sitter grammar.
132+/// The search is limited to `MAX_PLAINTEXT_SCAN` characters
133+///
134+/// If the cursor is on the opening bracket, the position of
135+/// the closing bracket is returned. If the cursor on the closing
136+/// bracket, the position of the opening bracket is returned.
137+///
138+/// If the cursor is not on a bracket, `None` is returned.
139+///
140+/// If no matching bracket is found, `None` is returned.
141+#[must_use]
142+pub fn find_matching_bracket_plaintext(doc: RopeSlice, cursor_pos: usize) -> Option<usize> {
143+ // Don't do anything when the cursor is not on top of a bracket.
144+ let bracket = doc.char(cursor_pos);
145+ if !is_valid_bracket(bracket) {
146+ return None;
147+ }
148+
149+ // Determine the direction of the matching.
150+ let is_fwd = is_forward_bracket(bracket);
151+ let chars_iter = if is_fwd {
152+ doc.chars_at(cursor_pos + 1)
153+ } else {
154+ doc.chars_at(cursor_pos).reversed()
155+ };
156+
157+ let mut open_cnt = 1;
158+
159+ for (i, candidate) in chars_iter.take(MAX_PLAINTEXT_SCAN).enumerate() {
160+ if candidate == bracket {
161+ open_cnt += 1;
162+ } else if is_valid_pair(
163+ doc,
164+ if is_fwd {
165+ cursor_pos
166+ } else {
167+ cursor_pos - i - 1
168+ },
169+ if is_fwd {
170+ cursor_pos + i + 1
171+ } else {
172+ cursor_pos
173+ },
174+ ) {
175+ // Return when all pending brackets have been closed.
176+ if open_cnt == 1 {
177+ return Some(if is_fwd {
178+ cursor_pos + i + 1
179+ } else {
180+ cursor_pos - i - 1
181+ });
182+ }
183+ open_cnt -= 1;
184+ }
185+ }
186+
187+ None
71188}
72189
73190fn is_valid_bracket(c: char) -> bool {
74191 PAIRS.iter().any(|(l, r)| *l == c || *r == c)
75192}
76193
77−fn is_valid_pair(doc: &Rope, start_char: usize, end_char: usize) -> bool {
194+fn is_forward_bracket(c: char) -> bool {
195+ PAIRS.iter().any(|(l, _)| *l == c)
196+}
197+
198+fn is_valid_pair(doc: RopeSlice, start_char: usize, end_char: usize) -> bool {
78199 PAIRS.contains(&(doc.char(start_char), doc.char(end_char)))
79200}
80201
81−fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> {
202+fn surrounding_bytes(doc: RopeSlice, node: &Node) -> Option<(usize, usize)> {
82203 let len = doc.len_bytes();
83204
84205 let start_byte = node.start_byte();
@@ -90,3 +211,85 @@ fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> {
90211
91212 Some((start_byte, end_byte))
92213}
214+
215+/// Tests if this node is a pair close char and returns the expected open char
216+fn as_close_pair(doc: RopeSlice, node: &Node) -> Option<char> {
217+ let close = as_char(doc, node)?.1;
218+ PAIRS
219+ .iter()
220+ .find_map(|&(open, close_)| (close_ == close).then_some(open))
221+}
222+
223+/// Checks if `node` or its siblings (at most MATCH_LIMIT nodes) is the specified closing char
224+///
225+/// # Returns
226+///
227+/// The position of the found node or `None` otherwise
228+fn find_pair_end(
229+ doc: RopeSlice,
230+ node: Option<Node>,
231+ end_char: char,
232+ direction: Direction,
233+) -> Option<usize> {
234+ let advance = match direction {
235+ Forward => Node::next_sibling,
236+ Backward => Node::prev_sibling,
237+ };
238+ iter::successors(node, advance)
239+ .take(MATCH_LIMIT)
240+ .find_map(|node| {
241+ let (pos, c) = as_char(doc, &node)?;
242+ (end_char == c).then_some(pos)
243+ })
244+}
245+
246+/// Tests if this node is a pair close char and returns the expected open char
247+fn as_open_pair(doc: RopeSlice, node: &Node) -> Option<char> {
248+ let open = as_char(doc, node)?.1;
249+ PAIRS
250+ .iter()
251+ .find_map(|&(open_, close)| (open_ == open).then_some(close))
252+}
253+
254+/// If node is a single char return it (and its char position)
255+fn as_char(doc: RopeSlice, node: &Node) -> Option<(usize, char)> {
256+ // TODO: multi char/non ASCII pairs
257+ if node.byte_range().len() != 1 {
258+ return None;
259+ }
260+ let pos = doc.try_byte_to_char(node.start_byte()).ok()?;
261+ Some((pos, doc.char(pos)))
262+}
263+
264+#[cfg(test)]
265+mod tests {
266+ use super::*;
267+
268+ #[test]
269+ fn test_find_matching_bracket_current_line_plaintext() {
270+ let assert = |input: &str, pos, expected| {
271+ let input = RopeSlice::from(input);
272+ let actual = find_matching_bracket_plaintext(input, pos);
273+ assert_eq!(expected, actual.unwrap());
274+
275+ let actual = find_matching_bracket_plaintext(input, expected);
276+ assert_eq!(pos, actual.unwrap(), "expected symmetrical behaviour");
277+ };
278+
279+ assert("(hello)", 0, 6);
280+ assert("((hello))", 0, 8);
281+ assert("((hello))", 1, 7);
282+ assert("(((hello)))", 2, 8);
283+
284+ assert("key: ${value}", 6, 12);
285+ assert("key: ${value} # (some comment)", 16, 29);
286+
287+ assert("(paren (paren {bracket}))", 0, 24);
288+ assert("(paren (paren {bracket}))", 7, 23);
289+ assert("(paren (paren {bracket}))", 14, 22);
290+
291+ assert("(prev line\n ) (middle) ( \n next line)", 0, 12);
292+ assert("(prev line\n ) (middle) ( \n next line)", 14, 21);
293+ assert("(prev line\n ) (middle) ( \n next line)", 23, 36);
294+ }
295+}
helix-core/src/movement.rs+188−8
@@ -1,4 +1,4 @@
1−use std::iter;
1+use std::{cmp::Reverse, iter};
22
33use ropey::iter::Chars;
44use tree_sitter::{Node, QueryCursor};
@@ -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)]
@@ -62,7 +62,7 @@ pub fn move_vertically_visual(
6262 annotations: &mut TextAnnotations,
6363) -> Range {
6464 if !text_fmt.soft_wrap {
65− move_vertically(slice, range, dir, count, behaviour, text_fmt, annotations);
65+ return move_vertically(slice, range, dir, count, behaviour, text_fmt, annotations);
6666 }
6767 annotations.clear_line_annotations();
6868 let pos = range.cursor(slice);
@@ -177,6 +177,10 @@ pub fn move_prev_word_start(slice: RopeSlice, range: Range, count: usize) -> Ran
177177 word_move(slice, range, count, WordMotionTarget::PrevWordStart)
178178}
179179
180+pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
181+ word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
182+}
183+
180184pub fn move_next_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
181185 word_move(slice, range, count, WordMotionTarget::NextLongWordStart)
182186}
@@ -189,8 +193,8 @@ pub fn move_prev_long_word_start(slice: RopeSlice, range: Range, count: usize) -
189193 word_move(slice, range, count, WordMotionTarget::PrevLongWordStart)
190194}
191195
192−pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
193− word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
196+pub fn move_prev_long_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
197+ word_move(slice, range, count, WordMotionTarget::PrevLongWordEnd)
194198}
195199
196200fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
@@ -199,6 +203,7 @@ fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTar
199203 WordMotionTarget::PrevWordStart
200204 | WordMotionTarget::PrevLongWordStart
201205 | WordMotionTarget::PrevWordEnd
206+ | WordMotionTarget::PrevLongWordEnd
202207 );
203208
204209 // Special-case early-out.
@@ -377,6 +382,7 @@ pub enum WordMotionTarget {
377382 NextLongWordStart,
378383 NextLongWordEnd,
379384 PrevLongWordStart,
385+ PrevLongWordEnd,
380386}
381387
382388pub trait CharHelpers {
@@ -393,6 +399,7 @@ impl CharHelpers for Chars<'_> {
393399 WordMotionTarget::PrevWordStart
394400 | WordMotionTarget::PrevLongWordStart
395401 | WordMotionTarget::PrevWordEnd
402+ | WordMotionTarget::PrevLongWordEnd
396403 );
397404
398405 // Reverse the iterator if needed for the motion direction.
@@ -479,7 +486,7 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
479486 is_word_boundary(prev_ch, next_ch)
480487 && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
481488 }
482− WordMotionTarget::NextLongWordStart => {
489+ WordMotionTarget::NextLongWordStart | WordMotionTarget::PrevLongWordEnd => {
483490 is_long_word_boundary(prev_ch, next_ch)
484491 && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
485492 }
@@ -520,10 +527,10 @@ pub fn goto_treesitter_object(
520527 let node = match dir {
521528 Direction::Forward => nodes
522529 .filter(|n| n.start_byte() > byte_pos)
523− .min_by_key(|n| n.start_byte())?,
530+ .min_by_key(|n| (n.start_byte(), Reverse(n.end_byte())))?,
524531 Direction::Backward => nodes
525532 .filter(|n| n.end_byte() < byte_pos)
526− .max_by_key(|n| n.end_byte())?,
533+ .max_by_key(|n| (n.end_byte(), Reverse(n.start_byte())))?,
527534 };
528535
529536 let len = slice.len_bytes();
@@ -549,6 +556,85 @@ pub fn goto_treesitter_object(
549556 last_range
550557}
551558
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+
552638#[cfg(test)]
553639mod test {
554640 use ropey::Rope;
@@ -1445,6 +1531,100 @@ mod test {
14451531 }
14461532 }
14471533
1534+ #[test]
1535+ fn test_behaviour_when_moving_to_end_of_prev_long_words() {
1536+ let tests = [
1537+ (
1538+ "Basic backward motion from the middle of a word",
1539+ vec![(1, Range::new(3, 3), Range::new(4, 0))],
1540+ ),
1541+ ("Starting from after boundary retreats the anchor",
1542+ vec![(1, Range::new(0, 9), Range::new(8, 0))],
1543+ ),
1544+ (
1545+ "Jump to end of a word succeeded by whitespace",
1546+ vec![(1, Range::new(10, 10), Range::new(10, 4))],
1547+ ),
1548+ (
1549+ " Jump to start of line from end of word preceded by whitespace",
1550+ vec![(1, Range::new(3, 4), Range::new(4, 0))],
1551+ ),
1552+ ("Previous anchor is irrelevant for backward motions",
1553+ vec![(1, Range::new(12, 5), Range::new(6, 0))]),
1554+ (
1555+ " Starting from whitespace moves to first space in sequence",
1556+ vec![(1, Range::new(0, 4), Range::new(4, 0))],
1557+ ),
1558+ ("Identifiers_with_underscores are considered a single word",
1559+ vec![(1, Range::new(0, 20), Range::new(20, 0))]),
1560+ (
1561+ "Jumping\n \nback through a newline selects whitespace",
1562+ vec![(1, Range::new(0, 13), Range::new(12, 8))],
1563+ ),
1564+ (
1565+ "Jumping to start of word from the end selects the word",
1566+ vec![(1, Range::new(6, 7), Range::new(7, 0))],
1567+ ),
1568+ (
1569+ "alphanumeric.!,and.?=punctuation are treated exactly the same",
1570+ vec![(1, Range::new(29, 30), Range::new(30, 0))],
1571+ ),
1572+ (
1573+ "... ... punctuation and spaces behave as expected",
1574+ vec![
1575+ (1, Range::new(0, 10), Range::new(9, 3)),
1576+ (1, Range::new(10, 6), Range::new(7, 3)),
1577+ ],
1578+ ),
1579+ (".._.._ punctuation is joined by underscores into a single block",
1580+ vec![(1, Range::new(0, 6), Range::new(6, 0))]),
1581+ (
1582+ "Newlines\n\nare bridged seamlessly.",
1583+ vec![(1, Range::new(0, 10), Range::new(8, 0))],
1584+ ),
1585+ (
1586+ "Jumping \n\n\n\n\nback from within a newline group selects previous block",
1587+ vec![(1, Range::new(0, 13), Range::new(11, 7))],
1588+ ),
1589+ (
1590+ "Failed motions do not modify the range",
1591+ vec![(0, Range::new(3, 0), Range::new(3, 0))],
1592+ ),
1593+ (
1594+ "Multiple motions at once resolve correctly",
1595+ vec![(3, Range::new(19, 19), Range::new(8, 0))],
1596+ ),
1597+ (
1598+ "Excessive motions are performed partially",
1599+ vec![(999, Range::new(40, 40), Range::new(9, 0))],
1600+ ),
1601+ (
1602+ "", // Edge case of moving backwards in empty string
1603+ vec![(1, Range::new(0, 0), Range::new(0, 0))],
1604+ ),
1605+ (
1606+ "\n\n\n\n\n", // Edge case of moving backwards in all newlines
1607+ vec![(1, Range::new(5, 5), Range::new(0, 0))],
1608+ ),
1609+ (" \n \nJumping back through alternated space blocks and newlines selects the space blocks",
1610+ vec![
1611+ (1, Range::new(0, 8), Range::new(7, 4)),
1612+ (1, Range::new(7, 4), Range::new(3, 0)),
1613+ ]),
1614+ ("ヒーリ..クス multibyte characters behave as normal characters, including when interacting with punctuation",
1615+ vec![
1616+ (1, Range::new(0, 8), Range::new(7, 0)),
1617+ ]),
1618+ ];
1619+
1620+ for (sample, scenario) in tests {
1621+ for (count, begin, expected_end) in scenario.into_iter() {
1622+ let range = move_prev_long_word_end(Rope::from(sample).slice(..), begin, count);
1623+ assert_eq!(range, expected_end, "Case failed: [{}]", sample);
1624+ }
1625+ }
1626+ }
1627+
14481628 #[test]
14491629 fn test_behaviour_when_moving_to_prev_paragraph_single() {
14501630 let tests = [
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−81
@@ -1,81 +0,0 @@
1−use std::collections::HashMap;
2−
3−#[derive(Debug)]
4−pub struct Register {
5− name: char,
6− values: Vec<String>,
7−}
8−
9−impl Register {
10− pub const fn new(name: char) -> Self {
11− Self {
12− name,
13− values: Vec::new(),
14− }
15− }
16−
17− pub fn new_with_values(name: char, values: Vec<String>) -> Self {
18− Self { name, values }
19− }
20−
21− pub const fn name(&self) -> char {
22− self.name
23− }
24−
25− pub fn read(&self) -> &[String] {
26− &self.values
27− }
28−
29− pub fn write(&mut self, values: Vec<String>) {
30− self.values = values;
31− }
32−
33− pub fn push(&mut self, value: String) {
34− self.values.push(value);
35− }
36−}
37−
38−/// Currently just wraps a `HashMap` of `Register`s
39−#[derive(Debug, Default)]
40−pub struct Registers {
41− inner: HashMap<char, Register>,
42−}
43−
44−impl Registers {
45− pub fn get(&self, name: char) -> Option<&Register> {
46− self.inner.get(&name)
47− }
48−
49− pub fn read(&self, name: char) -> Option<&[String]> {
50− self.get(name).map(|reg| reg.read())
51− }
52−
53− pub fn write(&mut self, name: char, values: Vec<String>) {
54− if name != '_' {
55− self.inner
56− .insert(name, Register::new_with_values(name, values));
57− }
58− }
59−
60− pub fn push(&mut self, name: char, value: String) {
61− if name != '_' {
62− if let Some(r) = self.inner.get_mut(&name) {
63− r.push(value);
64− } else {
65− self.write(name, vec![value]);
66− }
67− }
68− }
69−
70− pub fn first(&self, name: char) -> Option<&String> {
71− self.read(name).and_then(|entries| entries.first())
72− }
73−
74− pub fn last(&self, name: char) -> Option<&String> {
75− self.read(name).and_then(|entries| entries.last())
76− }
77−
78− pub fn inner(&self) -> &HashMap<char, Register> {
79− &self.inner
80− }
81−}
helix-core/src/rope_reader.rsnew file+37
@@ -0,0 +1,37 @@
1+use std::io;
2+
3+use ropey::iter::Chunks;
4+use ropey::RopeSlice;
5+
6+pub struct RopeReader<'a> {
7+ current_chunk: &'a [u8],
8+ chunks: Chunks<'a>,
9+}
10+
11+impl<'a> RopeReader<'a> {
12+ pub fn new(rope: RopeSlice<'a>) -> RopeReader<'a> {
13+ RopeReader {
14+ current_chunk: &[],
15+ chunks: rope.chunks(),
16+ }
17+ }
18+}
19+
20+impl io::Read for RopeReader<'_> {
21+ fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
22+ let buf_len = buf.len();
23+ loop {
24+ let read_bytes = self.current_chunk.read(buf)?;
25+ buf = &mut buf[read_bytes..];
26+ if buf.is_empty() {
27+ return Ok(buf_len);
28+ }
29+
30+ if let Some(next_chunk) = self.chunks.next() {
31+ self.current_chunk = next_chunk.as_bytes();
32+ } else {
33+ return Ok(buf_len - buf.len());
34+ }
35+ }
36+ }
37+}
helix-core/src/selection.rs+76−43
@@ -161,34 +161,35 @@ impl Range {
161161 self.from() <= pos && pos < self.to()
162162 }
163163
164− /// Map a range through a set of changes. Returns a new range representing the same position
165− /// after the changes are applied.
166− pub fn map(self, changes: &ChangeSet) -> Self {
164+ /// Map a range through a set of changes. Returns a new range representing
165+ /// the same position after the changes are applied. Note that this
166+ /// function runs in O(N) (N is number of changes) and can therefore
167+ /// cause performance problems if run for a large number of ranges as the
168+ /// complexity is then O(MN) (for multicuror M=N usually). Instead use
169+ /// [Selection::map] or [ChangeSet::update_positions] instead
170+ pub fn map(mut self, changes: &ChangeSet) -> Self {
167171 use std::cmp::Ordering;
168− let (anchor, head) = match self.anchor.cmp(&self.head) {
169− Ordering::Equal => (
170− changes.map_pos(self.anchor, Assoc::After),
171− changes.map_pos(self.head, Assoc::After),
172− ),
173− Ordering::Less => (
174− changes.map_pos(self.anchor, Assoc::After),
175− changes.map_pos(self.head, Assoc::Before),
176− ),
177− Ordering::Greater => (
178− changes.map_pos(self.anchor, Assoc::Before),
179− changes.map_pos(self.head, Assoc::After),
180− ),
181− };
182−
183− // We want to return a new `Range` with `horiz == None` every time,
184− // even if the anchor and head haven't changed, because we don't
185− // know if the *visual* position hasn't changed due to
186− // character-width or grapheme changes earlier in the text.
187− Self {
188− anchor,
189− head,
190− old_visual_position: None,
172+ if changes.is_empty() {
173+ return self;
191174 }
175+
176+ let positions_to_map = match self.anchor.cmp(&self.head) {
177+ Ordering::Equal => [
178+ (&mut self.anchor, Assoc::After),
179+ (&mut self.head, Assoc::After),
180+ ],
181+ Ordering::Less => [
182+ (&mut self.anchor, Assoc::After),
183+ (&mut self.head, Assoc::Before),
184+ ],
185+ Ordering::Greater => [
186+ (&mut self.head, Assoc::After),
187+ (&mut self.anchor, Assoc::Before),
188+ ],
189+ };
190+ changes.update_positions(positions_to_map.into_iter());
191+ self.old_visual_position = None;
192+ self
192193 }
193194
194195 /// Extend the range to cover at least `from` `to`.
@@ -451,17 +452,36 @@ impl Selection {
451452 /// Map selections over a set of changes. Useful for adjusting the selection position after
452453 /// applying changes to a document.
453454 pub fn map(self, changes: &ChangeSet) -> Self {
455+ self.map_no_normalize(changes).normalize()
456+ }
457+
458+ /// Map selections over a set of changes. Useful for adjusting the selection position after
459+ /// applying changes to a document. Doesn't normalize the selection
460+ pub fn map_no_normalize(mut self, changes: &ChangeSet) -> Self {
454461 if changes.is_empty() {
455462 return self;
456463 }
457464
458− Self::new(
459− self.ranges
460− .into_iter()
461− .map(|range| range.map(changes))
462− .collect(),
463− self.primary_index,
464− )
465+ let positions_to_map = self.ranges.iter_mut().flat_map(|range| {
466+ use std::cmp::Ordering;
467+ range.old_visual_position = None;
468+ match range.anchor.cmp(&range.head) {
469+ Ordering::Equal => [
470+ (&mut range.anchor, Assoc::After),
471+ (&mut range.head, Assoc::After),
472+ ],
473+ Ordering::Less => [
474+ (&mut range.anchor, Assoc::After),
475+ (&mut range.head, Assoc::Before),
476+ ],
477+ Ordering::Greater => [
478+ (&mut range.head, Assoc::After),
479+ (&mut range.anchor, Assoc::Before),
480+ ],
481+ }
482+ });
483+ changes.update_positions(positions_to_map);
484+ self
465485 }
466486
467487 pub fn ranges(&self) -> &[Range] {
@@ -497,6 +517,9 @@ impl Selection {
497517
498518 /// Normalizes a `Selection`.
499519 fn normalize(mut self) -> Self {
520+ if self.len() < 2 {
521+ return self;
522+ }
500523 let mut primary = self.ranges[self.primary_index];
501524 self.ranges.sort_unstable_by_key(Range::from);
502525
@@ -522,7 +545,14 @@ impl Selection {
522545 self
523546 }
524547
525− // Merges all ranges that are consecutive
548+ /// Replaces ranges with one spanning from first to last range.
549+ pub fn merge_ranges(self) -> Self {
550+ let first = self.ranges.first().unwrap();
551+ let last = self.ranges.last().unwrap();
552+ Selection::new(smallvec![first.merge(*last)], 0)
553+ }
554+
555+ /// Merges all ranges that are consecutive.
526556 pub fn merge_consecutive_ranges(mut self) -> Self {
527557 let mut primary = self.ranges[self.primary_index];
528558
@@ -554,17 +584,12 @@ impl Selection {
554584 assert!(!ranges.is_empty());
555585 debug_assert!(primary_index < ranges.len());
556586
557− let mut selection = Self {
587+ let selection = Self {
558588 ranges,
559589 primary_index,
560590 };
561591
562− if selection.ranges.len() > 1 {
563− // TODO: only normalize if needed (any ranges out of order)
564− selection = selection.normalize();
565− }
566−
567− selection
592+ selection.normalize()
568593 }
569594
570595 /// Takes a closure and maps each `Range` over the closure.
@@ -605,11 +630,19 @@ impl Selection {
605630 self.transform(|range| Range::point(range.cursor(text)))
606631 }
607632
608− pub fn fragments<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = Cow<str>> + 'a {
633+ pub fn fragments<'a>(
634+ &'a self,
635+ text: RopeSlice<'a>,
636+ ) -> impl DoubleEndedIterator<Item = Cow<'a, str>> + ExactSizeIterator<Item = Cow<str>> + 'a
637+ {
609638 self.ranges.iter().map(move |range| range.fragment(text))
610639 }
611640
612− pub fn slices<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = RopeSlice> + 'a {
641+ pub fn slices<'a>(
642+ &'a self,
643+ text: RopeSlice<'a>,
644+ ) -> impl DoubleEndedIterator<Item = RopeSlice<'a>> + ExactSizeIterator<Item = RopeSlice<'a>> + 'a
645+ {
613646 self.ranges.iter().map(move |range| range.slice(text))
614647 }
615648
helix-core/src/surround.rs+1−6
@@ -397,15 +397,10 @@ mod test {
397397
398398 let selections: SmallVec<[Range; 1]> = spec
399399 .match_indices('^')
400− .into_iter()
401400 .map(|(i, _)| Range::point(i))
402401 .collect();
403402
404− let expectations: Vec<usize> = spec
405− .match_indices('_')
406− .into_iter()
407− .map(|(i, _)| i)
408− .collect();
403+ let expectations: Vec<usize> = spec.match_indices('_').map(|(i, _)| i).collect();
409404
410405 (rope, Selection::new(selections, 0), expectations)
411406 }
helix-core/src/syntax.rs+407−168
@@ -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;
@@ -16,8 +16,8 @@ use slotmap::{DefaultKey as LayerId, HopSlotMap};
1616use std::{
1717 borrow::Cow,
1818 cell::RefCell,
19− collections::{HashMap, VecDeque},
20− fmt,
19+ collections::{HashMap, HashSet, VecDeque},
20+ fmt::{self, Display},
2121 hash::{Hash, Hasher},
2222 mem::{replace, transmute},
2323 path::{Path, PathBuf},
@@ -26,7 +26,7 @@ use std::{
2626};
2727
2828use once_cell::sync::{Lazy, OnceCell};
29−use serde::{Deserialize, Serialize};
29+use serde::{ser::SerializeSeq, Deserialize, Serialize};
3030
3131use helix_loader::grammar::{get_language, load_runtime_file};
3232
@@ -48,6 +48,21 @@ where
4848 .transpose()
4949}
5050
51+fn deserialize_tab_width<'de, D>(deserializer: D) -> Result<usize, D::Error>
52+where
53+ D: serde::Deserializer<'de>,
54+{
55+ usize::deserialize(deserializer).and_then(|n| {
56+ if n > 0 && n <= 16 {
57+ Ok(n)
58+ } else {
59+ Err(serde::de::Error::custom(
60+ "tab width must be a value from 1 to 16 inclusive",
61+ ))
62+ }
63+ })
64+}
65+
5166pub fn deserialize_auto_pairs<'de, D>(deserializer: D) -> Result<Option<AutoPairs>, D::Error>
5267where
5368 D: serde::Deserializer<'de>,
@@ -60,8 +75,11 @@ fn default_timeout() -> u64 {
6075}
6176
6277#[derive(Debug, Serialize, Deserialize)]
78+#[serde(rename_all = "kebab-case")]
6379pub struct Configuration {
6480 pub language: Vec<LanguageConfiguration>,
81+ #[serde(default)]
82+ pub language_server: HashMap<String, LanguageServerConfiguration>,
6583}
6684
6785impl Default for Configuration {
@@ -75,19 +93,20 @@ impl Default for Configuration {
7593#[serde(rename_all = "kebab-case", deny_unknown_fields)]
7694pub struct LanguageConfiguration {
7795 #[serde(rename = "name")]
78− pub language_id: String, // c-sharp, rust
96+ pub language_id: String, // c-sharp, rust, tsx
97+ #[serde(rename = "language-id")]
98+ // see the table under https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem
99+ pub language_server_language_id: Option<String>, // csharp, rust, typescriptreact, for the language-server
79100 pub scope: String, // source.rust
80101 pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
81102 #[serde(default)]
82103 pub shebangs: Vec<String>, // interpreter(s) associated with language
83− 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>
84106 pub comment_token: Option<String>,
85107 pub text_width: Option<usize>,
86108 pub soft_wrap: Option<SoftWrap>,
87109
88− #[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
89− pub config: Option<serde_json::Value>,
90−
91110 #[serde(default)]
92111 pub auto_format: bool,
93112
@@ -107,8 +126,13 @@ pub struct LanguageConfiguration {
107126 #[serde(skip)]
108127 pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
109128 // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
110− #[serde(skip_serializing_if = "Option::is_none")]
111− pub language_server: Option<LanguageServerConfiguration>,
129+ #[serde(
130+ default,
131+ skip_serializing_if = "Vec::is_empty",
132+ serialize_with = "serialize_lang_features",
133+ deserialize_with = "deserialize_lang_features"
134+ )]
135+ pub language_servers: Vec<LanguageServerFeatures>,
112136 #[serde(skip_serializing_if = "Option::is_none")]
113137 pub indent: Option<IndentationConfiguration>,
114138
@@ -131,6 +155,8 @@ pub struct LanguageConfiguration {
131155 /// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
132156 /// Falling back to the current working directory if none are configured.
133157 pub workspace_lsp_roots: Option<Vec<PathBuf>>,
158+ #[serde(default)]
159+ pub persistent_diagnostic_sources: Vec<String>,
134160}
135161
136162#[derive(Debug, PartialEq, Eq, Hash)]
@@ -187,9 +213,9 @@ impl<'de> Deserialize<'de> for FileType {
187213 M: serde::de::MapAccess<'de>,
188214 {
189215 match map.next_entry::<String, String>()? {
190− Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix(
191− suffix.replace('/', &std::path::MAIN_SEPARATOR.to_string()),
192− )),
216+ Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
217+ suffix.replace('/', std::path::MAIN_SEPARATOR_STR)
218+ })),
193219 Some((key, _value)) => Err(serde::de::Error::custom(format!(
194220 "unknown key in `file-types` list: {}",
195221 key
@@ -205,6 +231,133 @@ impl<'de> Deserialize<'de> for FileType {
205231 }
206232}
207233
234+#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
235+#[serde(rename_all = "kebab-case")]
236+pub enum LanguageServerFeature {
237+ Format,
238+ GotoDeclaration,
239+ GotoDefinition,
240+ GotoTypeDefinition,
241+ GotoReference,
242+ GotoImplementation,
243+ // Goto, use bitflags, combining previous Goto members?
244+ SignatureHelp,
245+ Hover,
246+ DocumentHighlight,
247+ Completion,
248+ CodeAction,
249+ WorkspaceCommand,
250+ DocumentSymbols,
251+ WorkspaceSymbols,
252+ // Symbols, use bitflags, see above?
253+ Diagnostics,
254+ RenameSymbol,
255+ InlayHints,
256+}
257+
258+impl Display for LanguageServerFeature {
259+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260+ use LanguageServerFeature::*;
261+ let feature = match self {
262+ Format => "format",
263+ GotoDeclaration => "goto-declaration",
264+ GotoDefinition => "goto-definition",
265+ GotoTypeDefinition => "goto-type-definition",
266+ GotoReference => "goto-type-definition",
267+ GotoImplementation => "goto-implementation",
268+ SignatureHelp => "signature-help",
269+ Hover => "hover",
270+ DocumentHighlight => "document-highlight",
271+ Completion => "completion",
272+ CodeAction => "code-action",
273+ WorkspaceCommand => "workspace-command",
274+ DocumentSymbols => "document-symbols",
275+ WorkspaceSymbols => "workspace-symbols",
276+ Diagnostics => "diagnostics",
277+ RenameSymbol => "rename-symbol",
278+ InlayHints => "inlay-hints",
279+ };
280+ write!(f, "{feature}",)
281+ }
282+}
283+
284+#[derive(Debug, Serialize, Deserialize)]
285+#[serde(untagged, rename_all = "kebab-case", deny_unknown_fields)]
286+enum LanguageServerFeatureConfiguration {
287+ #[serde(rename_all = "kebab-case")]
288+ Features {
289+ #[serde(default, skip_serializing_if = "HashSet::is_empty")]
290+ only_features: HashSet<LanguageServerFeature>,
291+ #[serde(default, skip_serializing_if = "HashSet::is_empty")]
292+ except_features: HashSet<LanguageServerFeature>,
293+ name: String,
294+ },
295+ Simple(String),
296+}
297+
298+#[derive(Debug, Default)]
299+pub struct LanguageServerFeatures {
300+ pub name: String,
301+ pub only: HashSet<LanguageServerFeature>,
302+ pub excluded: HashSet<LanguageServerFeature>,
303+}
304+
305+impl LanguageServerFeatures {
306+ pub fn has_feature(&self, feature: LanguageServerFeature) -> bool {
307+ (self.only.is_empty() || self.only.contains(&feature)) && !self.excluded.contains(&feature)
308+ }
309+}
310+
311+fn deserialize_lang_features<'de, D>(
312+ deserializer: D,
313+) -> Result<Vec<LanguageServerFeatures>, D::Error>
314+where
315+ D: serde::Deserializer<'de>,
316+{
317+ let raw: Vec<LanguageServerFeatureConfiguration> = Deserialize::deserialize(deserializer)?;
318+ let res = raw
319+ .into_iter()
320+ .map(|config| match config {
321+ LanguageServerFeatureConfiguration::Simple(name) => LanguageServerFeatures {
322+ name,
323+ ..Default::default()
324+ },
325+ LanguageServerFeatureConfiguration::Features {
326+ only_features,
327+ except_features,
328+ name,
329+ } => LanguageServerFeatures {
330+ name,
331+ only: only_features,
332+ excluded: except_features,
333+ },
334+ })
335+ .collect();
336+ Ok(res)
337+}
338+fn serialize_lang_features<S>(
339+ map: &Vec<LanguageServerFeatures>,
340+ serializer: S,
341+) -> Result<S::Ok, S::Error>
342+where
343+ S: serde::Serializer,
344+{
345+ let mut serializer = serializer.serialize_seq(Some(map.len()))?;
346+ for features in map {
347+ let features = if features.only.is_empty() && features.excluded.is_empty() {
348+ LanguageServerFeatureConfiguration::Simple(features.name.to_owned())
349+ } else {
350+ LanguageServerFeatureConfiguration::Features {
351+ only_features: features.only.clone(),
352+ except_features: features.excluded.clone(),
353+ name: features.name.to_owned(),
354+ }
355+ };
356+ serializer.serialize_element(&features)?;
357+ }
358+ serializer.end()
359+}
360+
208361#[derive(Debug, Serialize, Deserialize)]
209362#[serde(rename_all = "kebab-case")]
210363pub struct LanguageServerConfiguration {
@@ -214,9 +367,10 @@ pub struct LanguageServerConfiguration {
214367 pub args: Vec<String>,
215368 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
216369 pub environment: HashMap<String, String>,
370+ #[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
371+ pub config: Option<serde_json::Value>,
217372 #[serde(default = "default_timeout")]
218373 pub timeout: u64,
219− pub language_id: Option<String>,
220374}
221375
222376#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -285,10 +439,27 @@ pub struct DebuggerQuirks {
285439#[derive(Debug, Serialize, Deserialize)]
286440#[serde(rename_all = "kebab-case")]
287441pub struct IndentationConfiguration {
442+ #[serde(deserialize_with = "deserialize_tab_width")]
288443 pub tab_width: usize,
289444 pub unit: String,
290445}
291446
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+
292463/// Configuration for auto pairs
293464#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
294465#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
@@ -591,6 +762,8 @@ pub struct Loader {
591762 language_config_ids_by_suffix: HashMap<String, usize>,
592763 language_config_ids_by_shebang: HashMap<String, usize>,
593764
765+ language_server_configs: HashMap<String, LanguageServerConfiguration>,
766+
594767 scopes: ArcSwap<Vec<String>>,
595768}
596769
@@ -598,6 +771,7 @@ impl Loader {
598771 pub fn new(config: Configuration) -> Self {
599772 let mut loader = Self {
600773 language_configs: Vec::new(),
774+ language_server_configs: config.language_server,
601775 language_config_ids_by_extension: HashMap::new(),
602776 language_config_ids_by_suffix: HashMap::new(),
603777 language_config_ids_by_shebang: HashMap::new(),
@@ -660,11 +834,13 @@ impl Loader {
660834 // TODO: content_regex handling conflict resolution
661835 }
662836
663− 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>> {
664841 let line = Cow::from(source.line(0));
665− static SHEBANG_REGEX: Lazy<Regex> = Lazy::new(|| {
666− Regex::new(r"^#!\s*(?:\S*[/\\](?:env\s+(?:\-\S+\s+)*)?)?([^\s\.\d]+)").unwrap()
667− });
842+ static SHEBANG_REGEX: Lazy<Regex> =
843+ Lazy::new(|| Regex::new(&["^", SHEBANG].concat()).unwrap());
668844 let configuration_id = SHEBANG_REGEX
669845 .captures(&line)
670846 .and_then(|cap| self.language_config_ids_by_shebang.get(&cap[1]));
@@ -686,15 +862,14 @@ impl Loader {
686862 .cloned()
687863 }
688864
689− pub fn language_configuration_for_injection_string(
690− &self,
691− string: &str,
692− ) -> Option<Arc<LanguageConfiguration>> {
865+ /// Unlike language_config_for_language_id, which only returns Some for an exact id, this
866+ /// function will perform a regex match on the given string to find the closest language match.
867+ pub fn language_config_for_name(&self, name: &str) -> Option<Arc<LanguageConfiguration>> {
693868 let mut best_match_length = 0;
694869 let mut best_match_position = None;
695870 for (i, configuration) in self.language_configs.iter().enumerate() {
696871 if let Some(injection_regex) = &configuration.injection_regex {
697− if let Some(mat) = injection_regex.find(string) {
872+ if let Some(mat) = injection_regex.find(name) {
698873 let length = mat.end() - mat.start();
699874 if length > best_match_length {
700875 best_match_position = Some(i);
@@ -704,17 +879,30 @@ impl Loader {
704879 }
705880 }
706881
707− if let Some(i) = best_match_position {
708− let configuration = &self.language_configs[i];
709− return Some(configuration.clone());
882+ best_match_position.map(|i| self.language_configs[i].clone())
883+ }
884+
885+ pub fn language_configuration_for_injection_string(
886+ &self,
887+ capture: &InjectionLanguageMarker,
888+ ) -> Option<Arc<LanguageConfiguration>> {
889+ match capture {
890+ InjectionLanguageMarker::Name(string) => self.language_config_for_name(string),
891+ InjectionLanguageMarker::Filename(file) => self.language_config_for_file_name(file),
892+ InjectionLanguageMarker::Shebang(shebang) => {
893+ self.language_config_for_language_id(shebang)
894+ }
710895 }
711− None
712896 }
713897
714898 pub fn language_configs(&self) -> impl Iterator<Item = &Arc<LanguageConfiguration>> {
715899 self.language_configs.iter()
716900 }
717901
902+ pub fn language_server_configs(&self) -> &HashMap<String, LanguageServerConfiguration> {
903+ &self.language_server_configs
904+ }
905+
718906 pub fn set_scopes(&self, scopes: Vec<String>) {
719907 self.scopes.store(Arc::new(scopes));
720908
@@ -758,7 +946,11 @@ fn byte_range_to_str(range: std::ops::Range<usize>, source: RopeSlice) -> Cow<st
758946}
759947
760948impl Syntax {
761− pub fn new(source: &Rope, config: Arc<HighlightConfiguration>, loader: Arc<Loader>) -> Self {
949+ pub fn new(
950+ source: RopeSlice,
951+ config: Arc<HighlightConfiguration>,
952+ loader: Arc<Loader>,
953+ ) -> Option<Self> {
762954 let root_layer = LanguageLayer {
763955 tree: None,
764956 config,
@@ -783,24 +975,26 @@ impl Syntax {
783975 loader,
784976 };
785977
786− syntax
787− .update(source, source, &ChangeSet::new(source))
788− .unwrap();
978+ let res = syntax.update(source, source, &ChangeSet::new(source));
789979
790− syntax
980+ if res.is_err() {
981+ log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
982+ return None;
983+ }
984+ Some(syntax)
791985 }
792986
793987 pub fn update(
794988 &mut self,
795− old_source: &Rope,
796− source: &Rope,
989+ old_source: RopeSlice,
990+ source: RopeSlice,
797991 changeset: &ChangeSet,
798992 ) -> Result<(), Error> {
799993 let mut queue = VecDeque::new();
800994 queue.push_back(self.root);
801995
802996 let scopes = self.loader.scopes.load();
803− let injection_callback = |language: &str| {
997+ let injection_callback = |language: &InjectionLanguageMarker| {
804998 self.loader
805999 .language_configuration_for_injection_string(language)
8061000 .and_then(|language_config| language_config.highlight_config(&scopes))
@@ -915,6 +1109,7 @@ impl Syntax {
9151109
9161110 PARSER.with(|ts_parser| {
9171111 let ts_parser = &mut ts_parser.borrow_mut();
1112+ ts_parser.parser.set_timeout_micros(1000 * 500); // half a second is pretty generours
9181113 let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new);
9191114 // TODO: might need to set cursor range
9201115 cursor.set_byte_range(0..usize::MAX);
@@ -959,14 +1154,37 @@ impl Syntax {
9591154 layer.tree().root_node(),
9601155 RopeProvider(source_slice),
9611156 );
1157+ let mut combined_injections = vec![
1158+ (None, Vec::new(), IncludedChildren::default());
1159+ layer.config.combined_injections_patterns.len()
1160+ ];
9621161 let mut injections = Vec::new();
1162+ let mut last_injection_end = 0;
9631163 for mat in matches {
964− let (language_name, content_node, included_children) = injection_for_match(
965− &layer.config,
966− &layer.config.injections_query,
967− &mat,
968− source_slice,
969− );
1164+ let (injection_capture, content_node, included_children) = layer
1165+ .config
1166+ .injection_for_match(&layer.config.injections_query, &mat, source_slice);
1167+
1168+ // in case this is a combined injection save it for more processing later
1169+ if let Some(combined_injection_idx) = layer
1170+ .config
1171+ .combined_injections_patterns
1172+ .iter()
1173+ .position(|&pattern| pattern == mat.pattern_index)
1174+ {
1175+ let entry = &mut combined_injections[combined_injection_idx];
1176+ if injection_capture.is_some() {
1177+ entry.0 = injection_capture;
1178+ }
1179+ if let Some(content_node) = content_node {
1180+ if content_node.start_byte() >= last_injection_end {
1181+ entry.1.push(content_node);
1182+ last_injection_end = content_node.end_byte();
1183+ }
1184+ }
1185+ entry.2 = included_children;
1186+ continue;
1187+ }
9701188
9711189 // Explicitly remove this match so that none of its other captures will remain
9721190 // in the stream of captures.
@@ -974,59 +1192,31 @@ impl Syntax {
9741192
9751193 // If a language is found with the given name, then add a new language layer
9761194 // to the highlighted document.
977− if let (Some(language_name), Some(content_node)) = (language_name, content_node)
1195+ if let (Some(injection_capture), Some(content_node)) =
1196+ (injection_capture, content_node)
9781197 {
979− if let Some(config) = (injection_callback)(&language_name) {
1198+ if let Some(config) = (injection_callback)(&injection_capture) {
9801199 let ranges =
9811200 intersect_ranges(&layer.ranges, &[content_node], included_children);
9821201
9831202 if !ranges.is_empty() {
1203+ if content_node.start_byte() < last_injection_end {
1204+ continue;
1205+ }
1206+ last_injection_end = content_node.end_byte();
9841207 injections.push((config, ranges));
9851208 }
9861209 }
9871210 }
9881211 }
9891212
990− // Process combined injections.
991− if let Some(combined_injections_query) = &layer.config.combined_injections_query {
992− let mut injections_by_pattern_index =
993− vec![
994− (None, Vec::new(), IncludedChildren::default());
995− combined_injections_query.pattern_count()
996− ];
997− let matches = cursor.matches(
998− combined_injections_query,
999− layer.tree().root_node(),
1000− RopeProvider(source_slice),
1001− );
1002− for mat in matches {
1003− let entry = &mut injections_by_pattern_index[mat.pattern_index];
1004− let (language_name, content_node, included_children) = injection_for_match(
1005− &layer.config,
1006− combined_injections_query,
1007− &mat,
1008− source_slice,
1009− );
1010− if language_name.is_some() {
1011− entry.0 = language_name;
1012− }
1013− if let Some(content_node) = content_node {
1014− entry.1.push(content_node);
1015− }
1016− entry.2 = included_children;
1017− }
1018− for (lang_name, content_nodes, included_children) in injections_by_pattern_index
1019− {
1020− if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) {
1021− if let Some(config) = (injection_callback)(&lang_name) {
1022− let ranges = intersect_ranges(
1023− &layer.ranges,
1024− &content_nodes,
1025− included_children,
1026− );
1027− if !ranges.is_empty() {
1028− injections.push((config, ranges));
1029− }
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));
10301220 }
10311221 }
10321222 }
@@ -1216,7 +1406,7 @@ impl LanguageLayer {
12161406 self.tree.as_ref().unwrap()
12171407 }
12181408
1219− fn parse(&mut self, parser: &mut Parser, source: &Rope) -> Result<(), Error> {
1409+ fn parse(&mut self, parser: &mut Parser, source: RopeSlice) -> Result<(), Error> {
12201410 parser
12211411 .set_included_ranges(&self.ranges)
12221412 .map_err(|_| Error::InvalidRanges)?;
@@ -1231,7 +1421,7 @@ impl LanguageLayer {
12311421 &mut |byte, _| {
12321422 if byte <= source.len_bytes() {
12331423 let (chunk, start_byte, _, _) = source.chunk_at_byte(byte);
1234− chunk[byte - start_byte..].as_bytes()
1424+ &chunk.as_bytes()[byte - start_byte..]
12351425 } else {
12361426 // out of range
12371427 &[]
@@ -1247,7 +1437,7 @@ impl LanguageLayer {
12471437}
12481438
12491439pub(crate) fn generate_edits(
1250− old_text: &Rope,
1440+ old_text: RopeSlice,
12511441 changeset: &ChangeSet,
12521442) -> Vec<tree_sitter::InputEdit> {
12531443 use Operation::*;
@@ -1263,7 +1453,7 @@ pub(crate) fn generate_edits(
12631453
12641454 // TODO; this is a lot easier with Change instead of Operation.
12651455
1266− fn point_at_pos(text: &Rope, pos: usize) -> (usize, Point) {
1456+ fn point_at_pos(text: RopeSlice, pos: usize) -> (usize, Point) {
12671457 let byte = text.char_to_byte(pos); // <- attempted to index past end
12681458 let line = text.char_to_line(pos);
12691459 let line_start_byte = text.line_to_byte(line);
@@ -1389,12 +1579,14 @@ pub struct HighlightConfiguration {
13891579 pub language: Grammar,
13901580 pub query: Query,
13911581 injections_query: Query,
1392− combined_injections_query: Option<Query>,
1582+ combined_injections_patterns: Vec<usize>,
13931583 highlights_pattern_index: usize,
13941584 highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
13951585 non_local_variable_patterns: Vec<bool>,
13961586 injection_content_capture_index: Option<u32>,
13971587 injection_language_capture_index: Option<u32>,
1588+ injection_filename_capture_index: Option<u32>,
1589+ injection_shebang_capture_index: Option<u32>,
13981590 local_scope_capture_index: Option<u32>,
13991591 local_def_capture_index: Option<u32>,
14001592 local_def_value_capture_index: Option<u32>,
@@ -1438,7 +1630,7 @@ impl<'a> Iterator for ChunksBytes<'a> {
14381630}
14391631
14401632pub struct RopeProvider<'a>(pub RopeSlice<'a>);
1441−impl<'a> TextProvider<'a> for RopeProvider<'a> {
1633+impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> {
14421634 type I = ChunksBytes<'a>;
14431635
14441636 fn text(&mut self, node: Node) -> Self::I {
@@ -1452,7 +1644,7 @@ impl<'a> TextProvider<'a> for RopeProvider<'a> {
14521644struct HighlightIterLayer<'a> {
14531645 _tree: Option<Tree>,
14541646 cursor: QueryCursor,
1455− captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>>>>,
1647+ captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>, &'a [u8]>>>,
14561648 config: &'a HighlightConfiguration,
14571649 highlight_end_stack: Vec<usize>,
14581650 scope_stack: Vec<LocalScope<'a>>,
@@ -1503,26 +1695,15 @@ impl HighlightConfiguration {
15031695 }
15041696 }
15051697
1506− let mut injections_query = Query::new(language, injection_query)?;
1507−
1508− // Construct a separate query just for dealing with the 'combined injections'.
1509− // Disable the combined injection patterns in the main query.
1510− let mut combined_injections_query = Query::new(language, injection_query)?;
1511− let mut has_combined_queries = false;
1512− for pattern_index in 0..injections_query.pattern_count() {
1513− let settings = injections_query.property_settings(pattern_index);
1514− if settings.iter().any(|s| &*s.key == "injection.combined") {
1515− has_combined_queries = true;
1516− injections_query.disable_pattern(pattern_index);
1517− } else {
1518− combined_injections_query.disable_pattern(pattern_index);
1519− }
1520− }
1521− let combined_injections_query = if has_combined_queries {
1522− Some(combined_injections_query)
1523− } else {
1524− None
1525− };
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();
15261707
15271708 // Find all of the highlighting patterns that are disabled for nodes that
15281709 // have been identified as local variables.
@@ -1538,6 +1719,8 @@ impl HighlightConfiguration {
15381719 // Store the numeric ids for all of the special captures.
15391720 let mut injection_content_capture_index = None;
15401721 let mut injection_language_capture_index = None;
1722+ let mut injection_filename_capture_index = None;
1723+ let mut injection_shebang_capture_index = None;
15411724 let mut local_def_capture_index = None;
15421725 let mut local_def_value_capture_index = None;
15431726 let mut local_ref_capture_index = None;
@@ -1558,6 +1741,8 @@ impl HighlightConfiguration {
15581741 match name.as_str() {
15591742 "injection.content" => injection_content_capture_index = i,
15601743 "injection.language" => injection_language_capture_index = i,
1744+ "injection.filename" => injection_filename_capture_index = i,
1745+ "injection.shebang" => injection_shebang_capture_index = i,
15611746 _ => {}
15621747 }
15631748 }
@@ -1567,12 +1752,14 @@ impl HighlightConfiguration {
15671752 language,
15681753 query,
15691754 injections_query,
1570− combined_injections_query,
1755+ combined_injections_patterns,
15711756 highlights_pattern_index,
15721757 highlight_indices,
15731758 non_local_variable_patterns,
15741759 injection_content_capture_index,
15751760 injection_language_capture_index,
1761+ injection_filename_capture_index,
1762+ injection_shebang_capture_index,
15761763 local_scope_capture_index,
15771764 local_def_capture_index,
15781765 local_def_value_capture_index,
@@ -1631,6 +1818,90 @@ impl HighlightConfiguration {
16311818
16321819 self.highlight_indices.store(Arc::new(indices));
16331820 }
1821+
1822+ fn injection_pair<'a>(
1823+ &self,
1824+ query_match: &QueryMatch<'a, 'a>,
1825+ source: RopeSlice<'a>,
1826+ ) -> (Option<InjectionLanguageMarker<'a>>, Option<Node<'a>>) {
1827+ let mut injection_capture = None;
1828+ let mut content_node = None;
1829+
1830+ for capture in query_match.captures {
1831+ let index = Some(capture.index);
1832+ if index == self.injection_language_capture_index {
1833+ let name = byte_range_to_str(capture.node.byte_range(), source);
1834+ injection_capture = Some(InjectionLanguageMarker::Name(name));
1835+ } else if index == self.injection_filename_capture_index {
1836+ let name = byte_range_to_str(capture.node.byte_range(), source);
1837+ let path = Path::new(name.as_ref()).to_path_buf();
1838+ injection_capture = Some(InjectionLanguageMarker::Filename(path.into()));
1839+ } else if index == self.injection_shebang_capture_index {
1840+ let node_slice = source.byte_slice(capture.node.byte_range());
1841+
1842+ // some languages allow space and newlines before the actual string content
1843+ // so a shebang could be on either the first or second line
1844+ let lines = if let Ok(end) = node_slice.try_line_to_byte(2) {
1845+ node_slice.byte_slice(..end)
1846+ } else {
1847+ node_slice
1848+ };
1849+
1850+ static SHEBANG_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(SHEBANG).unwrap());
1851+
1852+ injection_capture = SHEBANG_REGEX
1853+ .captures(&Cow::from(lines))
1854+ .map(|cap| InjectionLanguageMarker::Shebang(cap[1].to_owned()))
1855+ } else if index == self.injection_content_capture_index {
1856+ content_node = Some(capture.node);
1857+ }
1858+ }
1859+ (injection_capture, content_node)
1860+ }
1861+
1862+ fn injection_for_match<'a>(
1863+ &self,
1864+ query: &'a Query,
1865+ query_match: &QueryMatch<'a, 'a>,
1866+ source: RopeSlice<'a>,
1867+ ) -> (
1868+ Option<InjectionLanguageMarker<'a>>,
1869+ Option<Node<'a>>,
1870+ IncludedChildren,
1871+ ) {
1872+ let (mut injection_capture, content_node) = self.injection_pair(query_match, source);
1873+
1874+ let mut included_children = IncludedChildren::default();
1875+ for prop in query.property_settings(query_match.pattern_index) {
1876+ match prop.key.as_ref() {
1877+ // In addition to specifying the language name via the text of a
1878+ // captured node, it can also be hard-coded via a `#set!` predicate
1879+ // that sets the injection.language key.
1880+ "injection.language" if injection_capture.is_none() => {
1881+ injection_capture = prop
1882+ .value
1883+ .as_ref()
1884+ .map(|s| InjectionLanguageMarker::Name(s.as_ref().into()));
1885+ }
1886+
1887+ // By default, injections do not include the *children* of an
1888+ // `injection.content` node - only the ranges that belong to the
1889+ // node itself. This can be changed using a `#set!` predicate that
1890+ // sets the `injection.include-children` key.
1891+ "injection.include-children" => included_children = IncludedChildren::All,
1892+
1893+ // Some queries might only exclude named children but include unnamed
1894+ // children in their `injection.content` node. This can be enabled using
1895+ // a `#set!` predicate that sets the `injection.include-unnamed-children` key.
1896+ "injection.include-unnamed-children" => {
1897+ included_children = IncludedChildren::Unnamed
1898+ }
1899+ _ => {}
1900+ }
1901+ }
1902+
1903+ (injection_capture, content_node, included_children)
1904+ }
16341905}
16351906
16361907impl<'a> HighlightIterLayer<'a> {
@@ -2042,56 +2313,15 @@ impl<'a> Iterator for HighlightIter<'a> {
20422313 }
20432314}
20442315
2045−fn injection_for_match<'a>(
2046− config: &HighlightConfiguration,
2047− query: &'a Query,
2048− query_match: &QueryMatch<'a, 'a>,
2049− source: RopeSlice<'a>,
2050−) -> (Option<Cow<'a, str>>, Option<Node<'a>>, IncludedChildren) {
2051− let content_capture_index = config.injection_content_capture_index;
2052− let language_capture_index = config.injection_language_capture_index;
2053−
2054− let mut language_name = None;
2055− let mut content_node = None;
2056− for capture in query_match.captures {
2057− let index = Some(capture.index);
2058− if index == language_capture_index {
2059− let name = byte_range_to_str(capture.node.byte_range(), source);
2060− language_name = Some(name);
2061− } else if index == content_capture_index {
2062− content_node = Some(capture.node);
2063− }
2064− }
2065−
2066− let mut included_children = IncludedChildren::default();
2067− for prop in query.property_settings(query_match.pattern_index) {
2068− match prop.key.as_ref() {
2069− // In addition to specifying the language name via the text of a
2070− // captured node, it can also be hard-coded via a `#set!` predicate
2071− // that sets the injection.language key.
2072− "injection.language" => {
2073− if language_name.is_none() {
2074− language_name = prop.value.as_ref().map(|s| s.as_ref().into())
2075− }
2076− }
2077−
2078− // By default, injections do not include the *children* of an
2079− // `injection.content` node - only the ranges that belong to the
2080− // node itself. This can be changed using a `#set!` predicate that
2081− // sets the `injection.include-children` key.
2082− "injection.include-children" => included_children = IncludedChildren::All,
2083−
2084− // Some queries might only exclude named children but include unnamed
2085− // children in their `injection.content` node. This can be enabled using
2086− // a `#set!` predicate that sets the `injection.include-unnamed-children` key.
2087− "injection.include-unnamed-children" => included_children = IncludedChildren::Unnamed,
2088− _ => {}
2089− }
2090− }
2091−
2092− (language_name, content_node, included_children)
2316+#[derive(Debug, Clone)]
2317+pub enum InjectionLanguageMarker<'a> {
2318+ Name(Cow<'a, str>),
2319+ Filename(Cow<'a, Path>),
2320+ Shebang(String),
20932321}
20942322
2323+const SHEBANG: &str = r"#!\s*(?:\S*[/\\](?:env\s+(?:\-\S+\s+)*)?)?([^\s\.\d]+)";
2324+
20952325pub struct Merge<I> {
20962326 iter: I,
20972327 spans: Box<dyn Iterator<Item = (usize, std::ops::Range<usize>)>>,
@@ -2307,7 +2537,10 @@ mod test {
23072537 "#,
23082538 );
23092539
2310− let loader = Loader::new(Configuration { language: vec![] });
2540+ let loader = Loader::new(Configuration {
2541+ language: vec![],
2542+ language_server: HashMap::new(),
2543+ });
23112544 let language = get_language("rust").unwrap();
23122545
23132546 let query = Query::new(language, query_str).unwrap();
@@ -2315,7 +2548,7 @@ mod test {
23152548 let mut cursor = QueryCursor::new();
23162549
23172550 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2318− let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2551+ let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
23192552
23202553 let root = syntax.tree().root_node();
23212554 let mut test = |capture, range| {
@@ -2366,7 +2599,10 @@ mod test {
23662599 .map(String::from)
23672600 .collect();
23682601
2369− let loader = Loader::new(Configuration { language: vec![] });
2602+ let loader = Loader::new(Configuration {
2603+ language: vec![],
2604+ language_server: HashMap::new(),
2605+ });
23702606
23712607 let language = get_language("rust").unwrap();
23722608 let config = HighlightConfiguration::new(
@@ -2386,7 +2622,7 @@ mod test {
23862622 fn main() {}
23872623 ",
23882624 );
2389− let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2625+ let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
23902626 let tree = syntax.tree();
23912627 let root = tree.root_node();
23922628 assert_eq!(root.kind(), "source_file");
@@ -2413,7 +2649,7 @@ mod test {
24132649 &doc,
24142650 vec![(6, 11, Some("test".into())), (12, 17, None)].into_iter(),
24152651 );
2416− let edits = generate_edits(&doc, transaction.changes());
2652+ let edits = generate_edits(doc.slice(..), transaction.changes());
24172653 // transaction.apply(&mut state);
24182654
24192655 assert_eq!(
@@ -2442,7 +2678,7 @@ mod test {
24422678 let mut doc = Rope::from("fn test() {}");
24432679 let transaction =
24442680 Transaction::change(&doc, vec![(8, 8, Some("a: u32".into()))].into_iter());
2445− let edits = generate_edits(&doc, transaction.changes());
2681+ let edits = generate_edits(doc.slice(..), transaction.changes());
24462682 transaction.apply(&mut doc);
24472683
24482684 assert_eq!(doc, "fn test(a: u32) {}");
@@ -2469,11 +2705,14 @@ mod test {
24692705 ) {
24702706 let source = Rope::from_str(source);
24712707
2472− let loader = Loader::new(Configuration { language: vec![] });
2708+ let loader = Loader::new(Configuration {
2709+ language: vec![],
2710+ language_server: HashMap::new(),
2711+ });
24732712 let language = get_language(language_name).unwrap();
24742713
24752714 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2476− let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2715+ let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
24772716
24782717 let root = syntax
24792718 .tree()
helix-core/src/transaction.rs+242−51
@@ -1,10 +1,12 @@
1+use ropey::RopeSlice;
12use smallvec::SmallVec;
23
3−use crate::{Range, Rope, Selection, Tendril};
4−use std::borrow::Cow;
4+use crate::{chars::char_is_word, Range, Rope, Selection, Tendril};
5+use std::{borrow::Cow, iter::once};
56
67/// (from, to, replacement)
78pub type Change = (usize, usize, Option<Tendril>);
9+pub type Deletion = (usize, usize);
810
911// TODO: pub(crate)
1012#[derive(Debug, Clone, PartialEq, Eq)]
@@ -21,6 +23,30 @@ pub enum Operation {
2123pub enum Assoc {
2224 Before,
2325 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+ }
2450}
2551
2652#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -41,7 +67,7 @@ impl ChangeSet {
4167 }
4268
4369 #[must_use]
44− pub fn new(doc: &Rope) -> Self {
70+ pub fn new(doc: RopeSlice) -> Self {
4571 let len = doc.len_chars();
4672 Self {
4773 changes: Vec::new(),
@@ -325,20 +351,75 @@ impl ChangeSet {
325351 self.changes.is_empty() || self.changes == [Operation::Retain(self.len)]
326352 }
327353
328− /// Map a position through the changes.
354+ /// Map a (mostly) *sorted* list of positions through the changes.
329355 ///
330− /// `assoc` indicates which size to associate the position with. `Before` will keep the
331− /// position close to the character before, and will place it before insertions over that
332− /// range, or at that point. `After` will move it forward, placing it at the end of such
333− /// insertions.
334− pub fn map_pos(&self, pos: usize, assoc: Assoc) -> usize {
356+ /// This is equivalent to updating each position with `map_pos`:
357+ ///
358+ /// ``` no-compile
359+ /// for (pos, assoc) in positions {
360+ /// *pos = changes.map_pos(*pos, assoc);
361+ /// }
362+ /// ```
363+ /// However this function is significantly faster for sorted lists running
364+ /// in `O(N+M)` instead of `O(NM)`. This function also handles unsorted/
365+ /// partially sorted lists. However, in that case worst case complexity is
366+ /// again `O(MN)`. For lists that are often/mostly sorted (like the end of diagnostic ranges)
367+ /// performance is usally close to `O(N + M)`
368+ pub fn update_positions<'a>(&self, positions: impl Iterator<Item = (&'a mut usize, Assoc)>) {
335369 use Operation::*;
370+
371+ let mut positions = positions.peekable();
372+
336373 let mut old_pos = 0;
337374 let mut new_pos = 0;
375+ let mut iter = self.changes.iter().enumerate().peekable();
376+
377+ 'outer: loop {
378+ macro_rules! map {
379+ ($map: expr, $i: expr) => {
380+ loop {
381+ let Some((pos, assoc)) = positions.peek_mut() else { return; };
382+ if **pos < old_pos {
383+ // Positions are not sorted, revert to the last Operation that
384+ // contains this position and continue iterating from there.
385+ // We can unwrap here since `pos` can not be negative
386+ // (unsigned integer) and iterating backwards to the start
387+ // should always move us back to the start
388+ for (i, change) in self.changes[..$i].iter().enumerate().rev() {
389+ match change {
390+ Retain(i) => {
391+ old_pos -= i;
392+ new_pos -= i;
393+ }
394+ Delete(i) => {
395+ old_pos -= i;
396+ }
397+ Insert(ins) => {
398+ new_pos -= ins.chars().count();
399+ }
400+ }
401+ if old_pos <= **pos {
402+ iter = self.changes[i..].iter().enumerate().peekable();
403+ }
404+ }
405+ debug_assert!(old_pos <= **pos, "Reverse Iter across changeset works");
406+ continue 'outer;
407+ }
408+ let Some(new_pos) = $map(**pos, *assoc) else { break; };
409+ **pos = new_pos;
410+ positions.next();
411+ }
412+ };
413+ }
338414
339− let mut iter = self.changes.iter().peekable();
415+ let Some((i, change)) = iter.next() else {
416+ map!(
417+ |pos, _| (old_pos == pos).then_some(new_pos),
418+ self.changes.len()
419+ );
420+ break;
421+ };
340422
341− while let Some(change) = iter.next() {
342423 let len = match change {
343424 Delete(i) | Retain(i) => *i,
344425 Insert(_) => 0,
@@ -347,61 +428,65 @@ impl ChangeSet {
347428
348429 match change {
349430 Retain(_) => {
350− if old_end > pos {
351− return new_pos + (pos - old_pos);
352− }
431+ map!(
432+ |pos, _| (old_end > pos).then_some(new_pos + (pos - old_pos)),
433+ i
434+ );
353435 new_pos += len;
354436 }
355437 Delete(_) => {
356438 // in range
357− if old_end > pos {
358− return new_pos;
359− }
439+ map!(|pos, _| (old_end > pos).then_some(new_pos), i);
360440 }
361441 Insert(s) => {
362− let ins = s.chars().count();
363−
364442 // a subsequent delete means a replace, consume it
365− if let Some(Delete(len)) = iter.peek() {
443+ if let Some((_, Delete(len))) = iter.peek() {
366444 iter.next();
367445
368446 old_end = old_pos + len;
369447 // in range of replaced text
370− if old_end > pos {
371− // at point or tracking before
372− if pos == old_pos || assoc == Assoc::Before {
373− return new_pos;
374− } else {
375− // place to end of insert
376− return new_pos + ins;
377− }
378− }
448+ map!(
449+ |pos, assoc: Assoc| (old_end > pos).then(|| {
450+ // at point or tracking before
451+ if pos == old_pos && assoc.stay_at_gaps() {
452+ new_pos
453+ } else {
454+ // place to end of insert
455+ new_pos + assoc.insert_offset(s)
456+ }
457+ }),
458+ i
459+ );
379460 } else {
380461 // at insert point
381− if old_pos == pos {
382− // return position before inserted text
383− if assoc == Assoc::Before {
384− return new_pos;
385− } else {
386− // after text
387− return new_pos + ins;
388− }
389− }
462+ map!(
463+ |pos, assoc: Assoc| (old_pos == pos).then(|| {
464+ // return position before inserted text
465+ new_pos + assoc.insert_offset(s)
466+ }),
467+ i
468+ );
390469 }
391470
392− new_pos += ins;
471+ new_pos += s.chars().count();
393472 }
394473 }
395474 old_pos = old_end;
396475 }
476+ let out_of_bounds: Vec<_> = positions.collect();
397477
398− if pos > old_pos {
399− panic!(
400− "Position {} is out of range for changeset len {}!",
401− pos, old_pos
402− )
403− }
404− new_pos
478+ panic!("Positions {out_of_bounds:?} are out of range for changeset len {old_pos}!",)
479+ }
480+
481+ /// Map a position through the changes.
482+ ///
483+ /// `assoc` indicates which side to associate the position with. `Before` will keep the
484+ /// position close to the character before, and will place it before insertions over that
485+ /// range, or at that point. `After` will move it forward, placing it at the end of such
486+ /// insertions.
487+ pub fn map_pos(&self, mut pos: usize, assoc: Assoc) -> usize {
488+ self.update_positions(once((&mut pos, assoc)));
489+ pos
405490 }
406491
407492 pub fn changes_iter(&self) -> ChangeIterator {
@@ -421,7 +506,7 @@ impl Transaction {
421506 /// Create a new, empty transaction.
422507 pub fn new(doc: &Rope) -> Self {
423508 Self {
424− changes: ChangeSet::new(doc),
509+ changes: ChangeSet::new(doc.slice(..)),
425510 selection: None,
426511 }
427512 }
@@ -534,6 +619,46 @@ impl Transaction {
534619 Self::from(changeset)
535620 }
536621
622+ /// Generate a transaction from a set of potentially overlapping deletions
623+ /// by merging overlapping deletions together.
624+ pub fn delete<I>(doc: &Rope, deletions: I) -> Self
625+ where
626+ I: Iterator<Item = Deletion>,
627+ {
628+ let len = doc.len_chars();
629+
630+ let (lower, upper) = deletions.size_hint();
631+ let size = upper.unwrap_or(lower);
632+ let mut changeset = ChangeSet::with_capacity(2 * size + 1); // rough estimate
633+
634+ let mut last = 0;
635+ for (mut from, to) in deletions {
636+ if last > to {
637+ continue;
638+ }
639+ if last > from {
640+ from = last
641+ }
642+ debug_assert!(
643+ from <= to,
644+ "Edit end must end before it starts (should {from} <= {to})"
645+ );
646+ // Retain from last "to" to current "from"
647+ changeset.retain(from - last);
648+ changeset.delete(to - from);
649+ last = to;
650+ }
651+
652+ changeset.retain(len - last);
653+
654+ Self::from(changeset)
655+ }
656+
657+ pub fn insert_at_eof(mut self, text: Tendril) -> Transaction {
658+ self.changes.insert(text);
659+ self
660+ }
661+
537662 /// Generate a transaction with a change per selection range.
538663 pub fn change_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
539664 where
@@ -580,6 +705,16 @@ impl Transaction {
580705 )
581706 }
582707
708+ /// Generate a transaction with a deletion per selection range.
709+ /// Compared to using `change_by_selection` directly these ranges may overlap.
710+ /// In that case they are merged
711+ pub fn delete_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
712+ where
713+ F: FnMut(&Range) -> Deletion,
714+ {
715+ Self::delete(doc, selection.iter().map(f))
716+ }
717+
583718 /// Insert text at each selection head.
584719 pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self {
585720 Self::change_by_selection(doc, selection, |range| {
@@ -752,6 +887,62 @@ mod test {
752887 };
753888 assert_eq!(cs.map_pos(2, Assoc::Before), 2);
754889 assert_eq!(cs.map_pos(2, Assoc::After), 2);
890+ // unsorted selection
891+ let cs = ChangeSet {
892+ changes: vec![
893+ Insert("ab".into()),
894+ Delete(2),
895+ Insert("cd".into()),
896+ Delete(2),
897+ ],
898+ len: 4,
899+ len_after: 4,
900+ };
901+ let mut positions = [4, 2];
902+ cs.update_positions(positions.iter_mut().map(|pos| (pos, Assoc::After)));
903+ assert_eq!(positions, [4, 2]);
904+ // stays at word boundary
905+ let cs = ChangeSet {
906+ changes: vec![
907+ Retain(2), // <space><space>
908+ Insert(" ab".into()),
909+ Retain(2), // cd
910+ Insert("de ".into()),
911+ ],
912+ len: 4,
913+ len_after: 10,
914+ };
915+ assert_eq!(cs.map_pos(2, Assoc::BeforeWord), 3);
916+ assert_eq!(cs.map_pos(4, Assoc::AfterWord), 9);
917+ let cs = ChangeSet {
918+ changes: vec![
919+ Retain(1), // <space>
920+ Insert(" b".into()),
921+ Delete(1), // c
922+ Retain(1), // d
923+ Insert("e ".into()),
924+ Delete(1), // <space>
925+ ],
926+ len: 5,
927+ len_after: 7,
928+ };
929+ assert_eq!(cs.map_pos(1, Assoc::BeforeWord), 2);
930+ assert_eq!(cs.map_pos(3, Assoc::AfterWord), 5);
931+ let cs = ChangeSet {
932+ changes: vec![
933+ Retain(1), // <space>
934+ Insert("a".into()),
935+ Delete(2), // <space>b
936+ Retain(1), // d
937+ Insert("e".into()),
938+ Delete(1), // f
939+ Retain(1), // <space>
940+ ],
941+ len: 5,
942+ len_after: 7,
943+ };
944+ assert_eq!(cs.map_pos(2, Assoc::BeforeWord), 1);
945+ assert_eq!(cs.map_pos(4, Assoc::AfterWord), 4);
755946 }
756947
757948 #[test]
@@ -818,9 +1009,9 @@ mod test {
8181009 #[test]
8191010 fn combine_with_empty() {
8201011 let empty = Rope::from("");
821− let a = ChangeSet::new(&empty);
1012+ let a = ChangeSet::new(empty.slice(..));
8221013
823− let mut b = ChangeSet::new(&empty);
1014+ let mut b = ChangeSet::new(empty.slice(..));
8241015 b.insert("a".into());
8251016
8261017 let changes = a.compose(b);
@@ -834,9 +1025,9 @@ mod test {
8341025 const TEST_CASE: &str = "Hello, これはヘリックスエディターです!";
8351026
8361027 let empty = Rope::from("");
837− let a = ChangeSet::new(&empty);
1028+ let a = ChangeSet::new(empty.slice(..));
8381029
839− let mut b = ChangeSet::new(&empty);
1030+ let mut b = ChangeSet::new(empty.slice(..));
8401031 b.insert(TEST_CASE.into());
8411032
8421033 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));
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-dap/src/client.rs+4−6
@@ -62,12 +62,10 @@ impl Client {
6262 if command.is_empty() {
6363 return Result::Err(Error::Other(anyhow!("Command not provided")));
6464 }
65− if transport == "tcp" && port_arg.is_some() {
66− Self::tcp_process(command, args, port_arg.unwrap(), id).await
67− } else if transport == "stdio" {
68− Self::stdio(command, args, id)
69− } else {
70− Result::Err(Error::Other(anyhow!("Incorrect transport {}", transport)))
65+ match (transport, port_arg) {
66+ ("tcp", Some(port_arg)) => Self::tcp_process(command, args, port_arg, id).await,
67+ ("stdio", _) => Self::stdio(command, args, id),
68+ _ => Result::Err(Error::Other(anyhow!("Incorrect transport {}", transport))),
7169 }
7270 }
7371
helix-dap/src/transport.rs+28−18
@@ -230,38 +230,48 @@ impl Transport {
230230 }
231231 }
232232
233− async fn recv(
233+ async fn recv_inner(
234234 transport: Arc<Self>,
235235 mut server_stdout: Box<dyn AsyncBufRead + Unpin + Send>,
236236 client_tx: UnboundedSender<Payload>,
237− ) {
237+ ) -> Result<()> {
238238 let mut recv_buffer = String::new();
239239 loop {
240− match Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await {
241− Ok(msg) => {
242− transport
243− .process_server_message(&client_tx, msg)
244− .await
245− .unwrap();
246− }
247− Err(err) => {
248− error!("err: <- {:?}", err);
249− break;
250− }
251− }
240+ let msg = Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await?;
241+ transport.process_server_message(&client_tx, msg).await?;
252242 }
253243 }
254244
255− async fn send(
245+ async fn recv(
246+ transport: Arc<Self>,
247+ server_stdout: Box<dyn AsyncBufRead + Unpin + Send>,
248+ client_tx: UnboundedSender<Payload>,
249+ ) {
250+ if let Err(err) = Self::recv_inner(transport, server_stdout, client_tx).await {
251+ error!("err: <- {:?}", err);
252+ }
253+ }
254+
255+ async fn send_inner(
256256 transport: Arc<Self>,
257257 mut server_stdin: Box<dyn AsyncWrite + Unpin + Send>,
258258 mut client_rx: UnboundedReceiver<Payload>,
259− ) {
259+ ) -> Result<()> {
260260 while let Some(payload) = client_rx.recv().await {
261261 transport
262262 .send_payload_to_server(&mut server_stdin, payload)
263− .await
264− .unwrap()
263+ .await?;
264+ }
265+ Ok(())
266+ }
267+
268+ async fn send(
269+ transport: Arc<Self>,
270+ server_stdin: Box<dyn AsyncWrite + Unpin + Send>,
271+ client_rx: UnboundedReceiver<Payload>,
272+ ) {
273+ if let Err(err) = Self::send_inner(transport, server_stdin, client_rx).await {
274+ error!("err: <- {:?}", err);
265275 }
266276 }
267277
helix-event/Cargo.tomlnew file+16
@@ -0,0 +1,16 @@
1+[package]
2+name = "helix-event"
3+version.workspace = true
4+authors.workspace = true
5+edition.workspace = true
6+license.workspace = true
7+rust-version.workspace = true
8+categories.workspace = true
9+repository.workspace = true
10+homepage.workspace = true
11+
12+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13+
14+[dependencies]
15+tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot"] }
16+parking_lot = { version = "0.12", features = ["send_guard"] }
helix-event/src/lib.rsnew file+8
@@ -0,0 +1,8 @@
1+//! `helix-event` contains systems that allow (often async) communication between
2+//! different editor components without strongly coupling them. Currently this
3+//! crate only contains some smaller facilities but the intend is to add more
4+//! functionality in the future ( like a generic hook system)
5+
6+pub use redraw::{lock_frame, redraw_requested, request_redraw, start_frame, RenderLockGuard};
7+
8+mod redraw;
helix-event/src/redraw.rsnew file+49
@@ -0,0 +1,49 @@
1+//! Signals that control when/if the editor redraws
2+
3+use std::future::Future;
4+
5+use parking_lot::{RwLock, RwLockReadGuard};
6+use tokio::sync::Notify;
7+
8+/// A `Notify` instance that can be used to (asynchronously) request
9+/// the editor the render a new frame.
10+static REDRAW_NOTIFY: Notify = Notify::const_new();
11+
12+/// A `RwLock` that prevents the next frame from being
13+/// drawn until an exclusive (write) lock can be acquired.
14+/// This allows asynchsonous tasks to acquire `non-exclusive`
15+/// locks (read) to prevent the next frame from being drawn
16+/// until a certain computation has finished.
17+static RENDER_LOCK: RwLock<()> = RwLock::new(());
18+
19+pub type RenderLockGuard = RwLockReadGuard<'static, ()>;
20+
21+/// Requests that the editor is redrawn. The redraws are debounced (currently to
22+/// 30FPS) so this can be called many times without causing a ton of frames to
23+/// be rendered.
24+pub fn request_redraw() {
25+ REDRAW_NOTIFY.notify_one();
26+}
27+
28+/// Returns a future that will yield once a redraw has been asynchronously
29+/// requested using [`request_redraw`].
30+pub fn redraw_requested() -> impl Future<Output = ()> {
31+ REDRAW_NOTIFY.notified()
32+}
33+
34+/// Wait until all locks acquired with [`lock_frame`] have been released.
35+/// This function is called before rendering and is intended to allow the frame
36+/// to wait for async computations that should be included in the current frame.
37+pub fn start_frame() {
38+ drop(RENDER_LOCK.write());
39+ // exhaust any leftover redraw notifications
40+ let notify = REDRAW_NOTIFY.notified();
41+ tokio::pin!(notify);
42+ notify.enable();
43+}
44+
45+/// Acquires the render lock which will prevent the next frame from being drawn
46+/// until the returned guard is dropped.
47+pub fn lock_frame() -> RenderLockGuard {
48+ RENDER_LOCK.read()
49+}
helix-loader/Cargo.toml+16−12
@@ -1,13 +1,14 @@
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"
@@ -17,16 +18,19 @@ path = "src/main.rs"
1718anyhow = "1"
1819serde = { version = "1.0", features = ["derive"] }
1920toml = "0.7"
20−etcetera = "0.4"
21−tree-sitter = "0.20"
22−once_cell = "1.17"
21+etcetera = "0.8"
22+tree-sitter.workspace = true
23+once_cell = "1.19"
2324log = "0.4"
25+which = "5.0.0"
2426
2527# TODO: these two should be on !wasm32 only
2628
2729# cloning/compiling tree-sitter grammars
2830cc = { version = "1" }
2931threadpool = { version = "1.0" }
32+tempfile = "3.8.1"
33+dunce = "1.0.4"
3034
3135[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
32−libloading = "0.7"
36+libloading = "0.8"
helix-loader/build.rs+56−5
@@ -1,7 +1,18 @@
11use std::borrow::Cow;
2+use std::path::Path;
23use std::process::Command;
34
4−const VERSION: &str = include_str!("../VERSION");
5+const MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR");
6+const MINOR: &str = env!("CARGO_PKG_VERSION_MINOR");
7+const PATCH: &str = env!("CARGO_PKG_VERSION_PATCH");
8+
9+fn get_calver() -> String {
10+ if PATCH == "0" {
11+ format!("{MAJOR}.{MINOR}")
12+ } else {
13+ format!("{MAJOR}.{MINOR}.{PATCH}")
14+ }
15+}
516
617fn main() {
718 let git_hash = Command::new("git")
@@ -11,9 +22,10 @@ fn main() {
1122 .filter(|output| output.status.success())
1223 .and_then(|x| String::from_utf8(x.stdout).ok());
1324
14− let version: Cow<_> = match git_hash {
15− Some(git_hash) => format!("{} ({})", VERSION, &git_hash[..8]).into(),
16− None => VERSION.into(),
25+ let calver = get_calver();
26+ let version: Cow<_> = match &git_hash {
27+ Some(git_hash) => format!("{} ({})", calver, &git_hash[..8]).into(),
28+ None => calver.into(),
1729 };
1830
1931 println!(
@@ -21,6 +33,45 @@ fn main() {
2133 std::env::var("TARGET").unwrap()
2234 );
2335
24− println!("cargo:rerun-if-changed=../VERSION");
2536 println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
37+
38+ if git_hash.is_none() {
39+ return;
40+ }
41+
42+ // we need to revparse because the git dir could be anywhere if you are
43+ // using detached worktrees but there is no good way to obtain an OsString
44+ // from command output so for now we can't accept non-utf8 paths here
45+ // probably rare enouch where it doesn't matter tough we could use gitoxide
46+ // here but that would be make it a hard dependency and slow compile times
47+ let Some(git_dir): Option<String> = Command::new("git")
48+ .args(["rev-parse", "--git-dir"])
49+ .output()
50+ .ok()
51+ .filter(|output| output.status.success())
52+ .and_then(|x| String::from_utf8(x.stdout).ok())
53+ else {
54+ return;
55+ };
56+ // If heads starts pointing at something else (different branch)
57+ // we need to return
58+ let head = Path::new(&git_dir).join("HEAD");
59+ if head.exists() {
60+ println!("cargo:rerun-if-changed={}", head.display());
61+ }
62+ // if the thing head points to (branch) itself changes
63+ // we need to return
64+ let Some(head_ref): Option<String> = Command::new("git")
65+ .args(["symbolic-ref", "HEAD"])
66+ .output()
67+ .ok()
68+ .filter(|output| output.status.success())
69+ .and_then(|x| String::from_utf8(x.stdout).ok())
70+ else {
71+ return;
72+ };
73+ let head_ref = Path::new(&git_dir).join(head_ref);
74+ if head_ref.exists() {
75+ println!("cargo:rerun-if-changed={}", head_ref.display());
76+ }
2677}
helix-loader/src/grammar.rs+121−46
@@ -1,4 +1,4 @@
1−use anyhow::{anyhow, Context, Result};
1+use anyhow::{anyhow, bail, Context, Result};
22use serde::{Deserialize, Serialize};
33use std::fs;
44use std::time::SystemTime;
@@ -8,6 +8,7 @@ use std::{
88 process::Command,
99 sync::mpsc::channel,
1010};
11+use tempfile::TempPath;
1112use tree_sitter::Language;
1213
1314#[cfg(unix)]
@@ -84,7 +85,16 @@ pub fn get_language(name: &str) -> Result<Language> {
8485 Ok(language)
8586}
8687
88+fn ensure_git_is_available() -> Result<()> {
89+ match which::which("git") {
90+ Ok(_cmd) => Ok(()),
91+ Err(err) => Err(anyhow::anyhow!("'git' could not be found ({err})")),
92+ }
93+}
94+
8795pub fn fetch_grammars() -> Result<()> {
96+ ensure_git_is_available()?;
97+
8898 // We do not need to fetch local grammars.
8999 let mut grammars = get_grammar_configs()?;
90100 grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. }));
@@ -97,15 +107,12 @@ pub fn fetch_grammars() -> Result<()> {
97107 let mut git_up_to_date = 0;
98108 let mut non_git = Vec::new();
99109
100− for res in results {
110+ for (grammar_id, res) in results {
101111 match res {
102112 Ok(FetchStatus::GitUpToDate) => git_up_to_date += 1,
103− Ok(FetchStatus::GitUpdated {
104− grammar_id,
105− revision,
106− }) => git_updated.push((grammar_id, revision)),
107− Ok(FetchStatus::NonGit { grammar_id }) => non_git.push(grammar_id),
108− Err(e) => errors.push(e),
113+ Ok(FetchStatus::GitUpdated { revision }) => git_updated.push((grammar_id, revision)),
114+ Ok(FetchStatus::NonGit) => non_git.push(grammar_id),
115+ Err(e) => errors.push((grammar_id, e)),
109116 }
110117 }
111118
@@ -137,16 +144,18 @@ pub fn fetch_grammars() -> Result<()> {
137144
138145 if !errors.is_empty() {
139146 let len = errors.len();
140− println!("{} grammars failed to fetch", len);
141− for (i, error) in errors.into_iter().enumerate() {
142− println!("\tFailure {}/{}: {}", i + 1, len, error);
147+ for (i, (grammar, error)) in errors.into_iter().enumerate() {
148+ println!("Failure {}/{len}: {grammar} {error}", i + 1);
143149 }
150+ bail!("{len} grammars failed to fetch");
144151 }
145152
146153 Ok(())
147154}
148155
149156pub fn build_grammars(target: Option<String>) -> Result<()> {
157+ ensure_git_is_available()?;
158+
150159 let grammars = get_grammar_configs()?;
151160 println!("Building {} grammars", grammars.len());
152161 let results = run_parallel(grammars, move |grammar| {
@@ -157,11 +166,11 @@ pub fn build_grammars(target: Option<String>) -> Result<()> {
157166 let mut already_built = 0;
158167 let mut built = Vec::new();
159168
160− for res in results {
169+ for (grammar_id, res) in results {
161170 match res {
162171 Ok(BuildStatus::AlreadyBuilt) => already_built += 1,
163− Ok(BuildStatus::Built { grammar_id }) => built.push(grammar_id),
164− Err(e) => errors.push(e),
172+ Ok(BuildStatus::Built) => built.push(grammar_id),
173+ Err(e) => errors.push((grammar_id, e)),
165174 }
166175 }
167176
@@ -178,10 +187,10 @@ pub fn build_grammars(target: Option<String>) -> Result<()> {
178187
179188 if !errors.is_empty() {
180189 let len = errors.len();
181− println!("{} grammars failed to build", len);
182− for (i, error) in errors.into_iter().enumerate() {
183− println!("\tFailure {}/{}: {}", i, len, error);
190+ for (i, (grammar_id, error)) in errors.into_iter().enumerate() {
191+ println!("Failure {}/{len}: {grammar_id} {error}", i + 1);
184192 }
193+ bail!("{len} grammars failed to build");
185194 }
186195
187196 Ok(())
@@ -213,7 +222,7 @@ fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
213222 Ok(grammars)
214223}
215224
216−fn run_parallel<F, Res>(grammars: Vec<GrammarConfiguration>, job: F) -> Vec<Result<Res>>
225+fn run_parallel<F, Res>(grammars: Vec<GrammarConfiguration>, job: F) -> Vec<(String, Result<Res>)>
217226where
218227 F: Fn(GrammarConfiguration) -> Result<Res> + Send + 'static + Clone,
219228 Res: Send + 'static,
@@ -228,7 +237,7 @@ where
228237 pool.execute(move || {
229238 // Ignore any SendErrors, if any job in another thread has encountered an
230239 // error the Receiver will be closed causing this send to fail.
231− let _ = tx.send(job(grammar));
240+ let _ = tx.send((grammar.grammar_id.clone(), job(grammar)));
232241 });
233242 }
234243
@@ -239,13 +248,8 @@ where
239248
240249enum FetchStatus {
241250 GitUpToDate,
242− GitUpdated {
243− grammar_id: String,
244− revision: String,
245− },
246− NonGit {
247− grammar_id: String,
248− },
251+ GitUpdated { revision: String },
252+ NonGit,
249253}
250254
251255fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
@@ -286,17 +290,12 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
286290 )?;
287291 git(&grammar_dir, ["checkout", &revision])?;
288292
289− Ok(FetchStatus::GitUpdated {
290− grammar_id: grammar.grammar_id,
291− revision,
292− })
293+ Ok(FetchStatus::GitUpdated { revision })
293294 } else {
294295 Ok(FetchStatus::GitUpToDate)
295296 }
296297 } else {
297− Ok(FetchStatus::NonGit {
298− grammar_id: grammar.grammar_id,
299− })
298+ Ok(FetchStatus::NonGit)
300299 }
301300}
302301
@@ -346,7 +345,7 @@ where
346345
347346enum BuildStatus {
348347 AlreadyBuilt,
349− Built { grammar_id: String },
348+ Built,
350349}
351350
352351fn build_grammar(grammar: GrammarConfiguration, target: Option<&str>) -> Result<BuildStatus> {
@@ -413,6 +412,18 @@ fn build_tree_sitter_library(
413412 let mut library_path = parser_lib_path.join(&grammar.grammar_id);
414413 library_path.set_extension(DYLIB_EXTENSION);
415414
415+ // if we are running inside a buildscript emit cargo metadata
416+ // to detect if we are running from a buildscript check some env variables
417+ // that cargo only sets for build scripts
418+ if std::env::var("OUT_DIR").is_ok() && std::env::var("CARGO").is_ok() {
419+ if let Some(scanner_path) = scanner_path.as_ref().and_then(|path| path.to_str()) {
420+ println!("cargo:rerun-if-changed={scanner_path}");
421+ }
422+ if let Some(parser_path) = parser_path.to_str() {
423+ println!("cargo:rerun-if-changed={parser_path}");
424+ }
425+ }
426+
416427 let recompile = needs_recompile(&library_path, &parser_path, &scanner_path)
417428 .context("Failed to compare source and binary timestamps")?;
418429
@@ -433,16 +444,53 @@ fn build_tree_sitter_library(
433444 for (key, value) in compiler.env() {
434445 command.env(key, value);
435446 }
447+
436448 command.args(compiler.args());
449+ // used to delay dropping the temporary object file until after the compilation is complete
450+ let _path_guard;
437451
438− if cfg!(all(windows, target_env = "msvc")) {
452+ if compiler.is_like_msvc() {
439453 command
440454 .args(["/nologo", "/LD", "/I"])
441455 .arg(header_path)
442456 .arg("/Od")
443− .arg("/utf-8");
457+ .arg("/utf-8")
458+ .arg("/std:c11");
444459 if let Some(scanner_path) = scanner_path.as_ref() {
445− command.arg(scanner_path);
460+ if scanner_path.extension() == Some("c".as_ref()) {
461+ command.arg(scanner_path);
462+ } else {
463+ let mut cpp_command = Command::new(compiler.path());
464+ cpp_command.current_dir(src_path);
465+ for (key, value) in compiler.env() {
466+ cpp_command.env(key, value);
467+ }
468+ cpp_command.args(compiler.args());
469+ let object_file =
470+ library_path.with_file_name(format!("{}_scanner.obj", &grammar.grammar_id));
471+ cpp_command
472+ .args(["/nologo", "/LD", "/I"])
473+ .arg(header_path)
474+ .arg("/Od")
475+ .arg("/utf-8")
476+ .arg("/std:c++14")
477+ .arg(format!("/Fo{}", object_file.display()))
478+ .arg("/c")
479+ .arg(scanner_path);
480+ let output = cpp_command
481+ .output()
482+ .context("Failed to execute C++ compiler")?;
483+
484+ if !output.status.success() {
485+ return Err(anyhow!(
486+ "Parser compilation failed.\nStdout: {}\nStderr: {}",
487+ String::from_utf8_lossy(&output.stdout),
488+ String::from_utf8_lossy(&output.stderr)
489+ ));
490+ }
491+ command.arg(&object_file);
492+ _path_guard = TempPath::from_path(object_file);
493+ }
446494 }
447495
448496 command
@@ -454,20 +502,49 @@ fn build_tree_sitter_library(
454502 .arg("-shared")
455503 .arg("-fPIC")
456504 .arg("-fno-exceptions")
457− .arg("-g")
458505 .arg("-I")
459506 .arg(header_path)
460507 .arg("-o")
461− .arg(&library_path)
462− .arg("-O3");
508+ .arg(&library_path);
509+
463510 if let Some(scanner_path) = scanner_path.as_ref() {
464511 if scanner_path.extension() == Some("c".as_ref()) {
465− command.arg("-xc").arg("-std=c99").arg(scanner_path);
512+ command.arg("-xc").arg("-std=c11").arg(scanner_path);
466513 } else {
467− command.arg(scanner_path);
514+ let mut cpp_command = Command::new(compiler.path());
515+ cpp_command.current_dir(src_path);
516+ for (key, value) in compiler.env() {
517+ cpp_command.env(key, value);
518+ }
519+ cpp_command.args(compiler.args());
520+ let object_file =
521+ library_path.with_file_name(format!("{}_scanner.o", &grammar.grammar_id));
522+ cpp_command
523+ .arg("-fPIC")
524+ .arg("-fno-exceptions")
525+ .arg("-I")
526+ .arg(header_path)
527+ .arg("-o")
528+ .arg(&object_file)
529+ .arg("-std=c++14")
530+ .arg("-c")
531+ .arg(scanner_path);
532+ let output = cpp_command
533+ .output()
534+ .context("Failed to execute C++ compiler")?;
535+ if !output.status.success() {
536+ return Err(anyhow!(
537+ "Parser compilation failed.\nStdout: {}\nStderr: {}",
538+ String::from_utf8_lossy(&output.stdout),
539+ String::from_utf8_lossy(&output.stderr)
540+ ));
541+ }
542+
543+ command.arg(&object_file);
544+ _path_guard = TempPath::from_path(object_file);
468545 }
469546 }
470− command.arg("-xc").arg(parser_path);
547+ command.arg("-xc").arg("-std=c11").arg(parser_path);
471548 if cfg!(all(
472549 unix,
473550 not(any(target_os = "macos", target_os = "illumos"))
@@ -487,9 +564,7 @@ fn build_tree_sitter_library(
487564 ));
488565 }
489566
490− Ok(BuildStatus::Built {
491− grammar_id: grammar.grammar_id,
492− })
567+ Ok(BuildStatus::Built)
493568}
494569
495570fn needs_recompile(
helix-loader/src/lib.rs+95−34
@@ -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+ }
26+
27+ let path = std::env::current_dir()
28+ .and_then(dunce::canonicalize)
29+ .expect("Couldn't determine current working directory");
30+ let mut cwd = CWD.write().unwrap();
31+ *cwd = Some(path.clone());
32+
33+ path
34+}
2135
22− config_dir.join("config.toml")
23− });
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+}
2443
25− // We should only initialize this value once.
44+pub fn initialize_config_file(specified_file: Option<PathBuf>) {
45+ let config_file = specified_file.unwrap_or_else(default_config_file);
46+ ensure_parent_dir(&config_file);
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
@@ -209,13 +246,55 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi
209246 }
210247}
211248
249+/// Finds the current workspace folder.
250+/// Used as a ceiling dir for LSP root resolution, the filepicker and potentially as a future filewatching root
251+///
252+/// This function starts searching the FS upward from the CWD
253+/// and returns the first directory that contains either `.git` or `.helix`.
254+/// If no workspace was found returns (CWD, true).
255+/// Otherwise (workspace, false) is returned
256+pub fn find_workspace() -> (PathBuf, bool) {
257+ let current_dir = current_working_dir();
258+ for ancestor in current_dir.ancestors() {
259+ if ancestor.join(".git").exists() || ancestor.join(".helix").exists() {
260+ return (ancestor.to_owned(), false);
261+ }
262+ }
263+
264+ (current_dir, true)
265+}
266+
267+fn default_config_file() -> PathBuf {
268+ config_dir().join("config.toml")
269+}
270+
271+fn ensure_parent_dir(path: &Path) {
272+ if let Some(parent) = path.parent() {
273+ if !parent.exists() {
274+ std::fs::create_dir_all(parent).ok();
275+ }
276+ }
277+}
278+
212279#[cfg(test)]
213280mod merge_toml_tests {
214281 use std::str;
215282
216− use super::merge_toml_values;
283+ use super::{current_working_dir, merge_toml_values, set_current_working_dir};
217284 use toml::Value;
218285
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+
219298 #[test]
220299 fn language_toml_map_merges() {
221300 const USER: &str = r#"
@@ -281,21 +360,3 @@ mod merge_toml_tests {
281360 )
282361 }
283362}
284−
285−/// Finds the current workspace folder.
286−/// Used as a ceiling dir for LSP root resolution, the filepicker and potentially as a future filewatching root
287−///
288−/// This function starts searching the FS upward from the CWD
289−/// and returns the first directory that contains either `.git` or `.helix`.
290−/// If no workspace was found returns (CWD, true).
291−/// Otherwise (workspace, false) is returned
292−pub fn find_workspace() -> (PathBuf, bool) {
293− let current_dir = std::env::current_dir().expect("unable to determine current directory");
294− for ancestor in current_dir.ancestors() {
295− if ancestor.join(".git").exists() || ancestor.join(".helix").exists() {
296− return (ancestor.to_owned(), false);
297− }
298− }
299−
300− (current_dir, true)
301−}
helix-lsp/Cargo.toml+16−14
@@ -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.27", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
28−tokio-stream = "0.1.12"
29−which = "4.4"
29+tokio = { version = "1.35", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
30+tokio-stream = "0.1.14"
31+which = "5.0.0"
3032parking_lot = "0.12.1"
helix-lsp/src/client.rs+230−29
@@ -4,11 +4,12 @@ use crate::{
44 Call, Error, OffsetEncoding, Result,
55};
66
7−use helix_core::{find_workspace, path, ChangeSet, Rope};
7+use 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;
@@ -44,6 +45,7 @@ fn workspace_for_uri(uri: lsp::Url) -> WorkspaceFolder {
4445#[derive(Debug)]
4546pub struct Client {
4647 id: usize,
48+ name: String,
4749 _process: Child,
4850 server_tx: UnboundedSender<Payload>,
4951 request_counter: AtomicU64,
@@ -166,8 +168,7 @@ impl Client {
166168 tokio::spawn(self.did_change_workspace(vec![workspace_for_uri(root_uri)], Vec::new()));
167169 }
168170
169− #[allow(clippy::type_complexity)]
170− #[allow(clippy::too_many_arguments)]
171+ #[allow(clippy::type_complexity, clippy::too_many_arguments)]
171172 pub fn start(
172173 cmd: &str,
173174 args: &[String],
@@ -176,6 +177,7 @@ impl Client {
176177 root_markers: &[String],
177178 manual_roots: &[PathBuf],
178179 id: usize,
180+ name: String,
179181 req_timeout: u64,
180182 doc_path: Option<&std::path::PathBuf>,
181183 ) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
@@ -200,7 +202,7 @@ impl Client {
200202 let stderr = BufReader::new(process.stderr.take().expect("Failed to open stderr"));
201203
202204 let (server_rx, server_tx, initialize_notify) =
203− Transport::start(reader, writer, stderr, id);
205+ Transport::start(reader, writer, stderr, id, name.clone());
204206 let (workspace, workspace_is_cwd) = find_workspace();
205207 let workspace = path::get_normalized_path(&workspace);
206208 let root = find_lsp_workspace(
@@ -225,6 +227,7 @@ impl Client {
225227
226228 let client = Self {
227229 id,
230+ name,
228231 _process: process,
229232 server_tx,
230233 request_counter: AtomicU64::new(0),
@@ -240,6 +243,10 @@ impl Client {
240243 Ok((client, server_rx, initialize_notify))
241244 }
242245
246+ pub fn name(&self) -> &str {
247+ &self.name
248+ }
249+
243250 pub fn id(&self) -> usize {
244251 self.id
245252 }
@@ -270,6 +277,87 @@ impl Client {
270277 .expect("language server not yet initialized!")
271278 }
272279
280+ /// Client has to be initialized otherwise this function panics
281+ #[inline]
282+ pub fn supports_feature(&self, feature: LanguageServerFeature) -> bool {
283+ let capabilities = self.capabilities();
284+
285+ use lsp::*;
286+ match feature {
287+ LanguageServerFeature::Format => matches!(
288+ capabilities.document_formatting_provider,
289+ Some(OneOf::Left(true) | OneOf::Right(_))
290+ ),
291+ LanguageServerFeature::GotoDeclaration => matches!(
292+ capabilities.declaration_provider,
293+ Some(
294+ DeclarationCapability::Simple(true)
295+ | DeclarationCapability::RegistrationOptions(_)
296+ | DeclarationCapability::Options(_),
297+ )
298+ ),
299+ LanguageServerFeature::GotoDefinition => matches!(
300+ capabilities.definition_provider,
301+ Some(OneOf::Left(true) | OneOf::Right(_))
302+ ),
303+ LanguageServerFeature::GotoTypeDefinition => matches!(
304+ capabilities.type_definition_provider,
305+ Some(
306+ TypeDefinitionProviderCapability::Simple(true)
307+ | TypeDefinitionProviderCapability::Options(_),
308+ )
309+ ),
310+ LanguageServerFeature::GotoReference => matches!(
311+ capabilities.references_provider,
312+ Some(OneOf::Left(true) | OneOf::Right(_))
313+ ),
314+ LanguageServerFeature::GotoImplementation => matches!(
315+ capabilities.implementation_provider,
316+ Some(
317+ ImplementationProviderCapability::Simple(true)
318+ | ImplementationProviderCapability::Options(_),
319+ )
320+ ),
321+ LanguageServerFeature::SignatureHelp => capabilities.signature_help_provider.is_some(),
322+ LanguageServerFeature::Hover => matches!(
323+ capabilities.hover_provider,
324+ Some(HoverProviderCapability::Simple(true) | HoverProviderCapability::Options(_),)
325+ ),
326+ LanguageServerFeature::DocumentHighlight => matches!(
327+ capabilities.document_highlight_provider,
328+ Some(OneOf::Left(true) | OneOf::Right(_))
329+ ),
330+ LanguageServerFeature::Completion => capabilities.completion_provider.is_some(),
331+ LanguageServerFeature::CodeAction => matches!(
332+ capabilities.code_action_provider,
333+ Some(
334+ CodeActionProviderCapability::Simple(true)
335+ | CodeActionProviderCapability::Options(_),
336+ )
337+ ),
338+ LanguageServerFeature::WorkspaceCommand => {
339+ capabilities.execute_command_provider.is_some()
340+ }
341+ LanguageServerFeature::DocumentSymbols => matches!(
342+ capabilities.document_symbol_provider,
343+ Some(OneOf::Left(true) | OneOf::Right(_))
344+ ),
345+ LanguageServerFeature::WorkspaceSymbols => matches!(
346+ capabilities.workspace_symbol_provider,
347+ Some(OneOf::Left(true) | OneOf::Right(_))
348+ ),
349+ LanguageServerFeature::Diagnostics => true, // there's no extra server capability
350+ LanguageServerFeature::RenameSymbol => matches!(
351+ capabilities.rename_provider,
352+ Some(OneOf::Left(true)) | Some(OneOf::Right(_))
353+ ),
354+ LanguageServerFeature::InlayHints => matches!(
355+ capabilities.inlay_hint_provider,
356+ Some(OneOf::Left(true) | OneOf::Right(InlayHintServerCapabilities::Options(_)))
357+ ),
358+ }
359+ }
360+
273361 pub fn offset_encoding(&self) -> OffsetEncoding {
274362 self.capabilities()
275363 .position_encoding
@@ -313,12 +401,22 @@ impl Client {
313401 &self,
314402 params: R::Params,
315403 ) -> 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>>
316415 where
317416 R::Params: serde::Serialize,
318417 {
319418 let server_tx = self.server_tx.clone();
320419 let id = self.next_request_id();
321− let timeout_secs = self.req_timeout;
322420
323421 async move {
324422 use std::time::Duration;
@@ -456,6 +554,15 @@ impl Client {
456554 normalizes_line_endings: Some(false),
457555 change_annotation_support: None,
458556 }),
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+ }),
459566 ..Default::default()
460567 }),
461568 text_document: Some(lsp::TextDocumentClientCapabilities {
@@ -522,6 +629,12 @@ impl Client {
522629 .collect(),
523630 },
524631 }),
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+ }),
525638 ..Default::default()
526639 }),
527640 publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
@@ -554,6 +667,7 @@ impl Client {
554667 version: Some(String::from(VERSION_AND_GIT_HASH)),
555668 }),
556669 locale: None, // TODO
670+ work_done_progress_params: lsp::WorkDoneProgressParams::default(),
557671 };
558672
559673 self.request::<lsp::request::Initialize>(params).await
@@ -602,6 +716,65 @@ impl Client {
602716 })
603717 }
604718
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+
605778 // -------------------------------------------------------------------------------------------
606779 // Text document
607780 // -------------------------------------------------------------------------------------------
@@ -645,7 +818,11 @@ impl Client {
645818 // Calculation is therefore a bunch trickier.
646819
647820 use helix_core::RopeSlice;
648− fn traverse(pos: lsp::Position, text: RopeSlice) -> lsp::Position {
821+ fn traverse(
822+ pos: lsp::Position,
823+ text: RopeSlice,
824+ offset_encoding: OffsetEncoding,
825+ ) -> lsp::Position {
649826 let lsp::Position {
650827 mut line,
651828 mut character,
@@ -662,7 +839,11 @@ impl Client {
662839 line += 1;
663840 character = 0;
664841 } else {
665− character += ch.len_utf16() as u32;
842+ character += match offset_encoding {
843+ OffsetEncoding::Utf8 => ch.len_utf8() as u32,
844+ OffsetEncoding::Utf16 => ch.len_utf16() as u32,
845+ OffsetEncoding::Utf32 => 1,
846+ };
666847 }
667848 }
668849 lsp::Position { line, character }
@@ -683,7 +864,7 @@ impl Client {
683864 }
684865 Delete(_) => {
685866 let start = pos_to_lsp_pos(new_text, new_pos, offset_encoding);
686− let end = traverse(start, old_text.slice(old_pos..old_end));
867+ let end = traverse(start, old_text.slice(old_pos..old_end), offset_encoding);
687868
688869 // deletion
689870 changes.push(lsp::TextDocumentContentChangeEvent {
@@ -700,7 +881,8 @@ impl Client {
700881 // a subsequent delete means a replace, consume it
701882 let end = if let Some(Delete(len)) = iter.peek() {
702883 old_end = old_pos + len;
703− let end = traverse(start, old_text.slice(old_pos..old_end));
884+ let end =
885+ traverse(start, old_text.slice(old_pos..old_end), offset_encoding);
704886
705887 iter.next();
706888
@@ -788,20 +970,19 @@ impl Client {
788970 ) -> Option<impl Future<Output = Result<()>>> {
789971 let capabilities = self.capabilities.get().unwrap();
790972
791− let include_text = match &capabilities.text_document_sync {
792− Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
793− save: Some(options),
973+ let include_text = match &capabilities.text_document_sync.as_ref()? {
974+ lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
975+ save: options,
794976 ..
795− })) => match options {
977+ }) => match options.as_ref()? {
796978 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
797979 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
798980 include_text,
799981 }) => include_text.unwrap_or(false),
800− // Supported(false)
801− _ => return None,
982+ lsp::TextDocumentSyncSaveOptions::Supported(false) => return None,
802983 },
803− // unsupported
804− _ => return None,
984+ // see: https://github.com/microsoft/language-server-protocol/issues/288
985+ lsp::TextDocumentSyncCapability::Kind(..) => false,
805986 };
806987
807988 Some(self.notify::<lsp::notification::DidSaveTextDocument>(
@@ -858,6 +1039,24 @@ impl Client {
8581039 Some(self.call::<lsp::request::ResolveCompletionItem>(completion_item))
8591040 }
8601041
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+
8611060 pub fn text_document_signature_help(
8621061 &self,
8631062 text_document: lsp::TextDocumentIdentifier,
@@ -1167,6 +1366,7 @@ impl Client {
11671366 &self,
11681367 text_document: lsp::TextDocumentIdentifier,
11691368 position: lsp::Position,
1369+ include_declaration: bool,
11701370 work_done_token: Option<lsp::ProgressToken>,
11711371 ) -> Option<impl Future<Output = Result<Value>>> {
11721372 let capabilities = self.capabilities.get().unwrap();
@@ -1183,7 +1383,7 @@ impl Client {
11831383 position,
11841384 },
11851385 context: lsp::ReferenceContext {
1186− include_declaration: true,
1386+ include_declaration,
11871387 },
11881388 work_done_progress_params: lsp::WorkDoneProgressParams { work_done_token },
11891389 partial_result_params: lsp::PartialResultParams {
@@ -1285,21 +1485,13 @@ impl Client {
12851485 Some(self.call::<lsp::request::CodeActionRequest>(params))
12861486 }
12871487
1288− pub fn supports_rename(&self) -> bool {
1289− let capabilities = self.capabilities.get().unwrap();
1290− matches!(
1291− capabilities.rename_provider,
1292− Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
1293− )
1294− }
1295−
12961488 pub fn rename_symbol(
12971489 &self,
12981490 text_document: lsp::TextDocumentIdentifier,
12991491 position: lsp::Position,
13001492 new_name: String,
13011493 ) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
1302− if !self.supports_rename() {
1494+ if !self.supports_feature(LanguageServerFeature::RenameSymbol) {
13031495 return None;
13041496 }
13051497
@@ -1339,4 +1531,13 @@ impl Client {
13391531
13401532 Some(self.call::<lsp::request::ExecuteCommand>(params))
13411533 }
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+ }
13421543}
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+152−115
@@ -1,4 +1,5 @@
11mod client;
2+pub mod file_event;
23pub mod jsonrpc;
34pub mod snippet;
45mod transport;
@@ -12,24 +13,21 @@ pub use lsp_types as lsp;
1213use futures_util::stream::select_all::SelectAll;
1314use helix_core::{
1415 path,
15− syntax::{LanguageConfiguration, LanguageServerConfiguration},
16+ syntax::{LanguageConfiguration, LanguageServerConfiguration, LanguageServerFeatures},
1617};
1718use tokio::sync::mpsc::UnboundedReceiver;
1819
1920use std::{
20− collections::{hash_map::Entry, HashMap},
21+ collections::HashMap,
2122 path::{Path, PathBuf},
22− sync::{
23− atomic::{AtomicUsize, Ordering},
24− Arc,
25− },
23+ sync::Arc,
2624};
2725
2826use thiserror::Error;
2927use tokio_stream::wrappers::UnboundedReceiverStream;
3028
3129pub type Result<T> = core::result::Result<T, Error>;
32−type LanguageId = String;
30+pub type LanguageServerName = String;
3331
3432#[derive(Error, Debug)]
3533pub enum Error {
@@ -49,7 +47,7 @@ pub enum Error {
4947 Other(#[from] anyhow::Error),
5048}
5149
52−#[derive(Clone, Copy, Debug, Default)]
50+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
5351pub enum OffsetEncoding {
5452 /// UTF-8 code units aka bytes
5553 Utf8,
@@ -380,7 +378,7 @@ pub mod util {
380378 .expect("transaction must be valid for primary selection");
381379 let removed_text = text.slice(removed_start..removed_end);
382380
383− let (transaction, selection) = Transaction::change_by_selection_ignore_overlapping(
381+ let (transaction, mut selection) = Transaction::change_by_selection_ignore_overlapping(
384382 doc,
385383 selection,
386384 |range| {
@@ -423,6 +421,11 @@ pub mod util {
423421 return transaction;
424422 }
425423
424+ // Don't normalize to avoid merging/reording selections which would
425+ // break the association between tabstops and selections. Most ranges
426+ // will be replaced by tabstops anyways and the final selection will be
427+ // normalized anyways
428+ selection = selection.map_no_normalize(changes);
426429 let mut mapped_selection = SmallVec::with_capacity(selection.len());
427430 let mut mapped_primary_idx = 0;
428431 let primary_range = selection.primary();
@@ -431,9 +434,8 @@ pub mod util {
431434 mapped_primary_idx = mapped_selection.len()
432435 }
433436
434− let range = range.map(changes);
435437 let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty());
436− let Some(tabstops) = tabstops else{
438+ let Some(tabstops) = tabstops else {
437439 // no tabstop normal mapping
438440 mapped_selection.push(range);
439441 continue;
@@ -443,36 +445,36 @@ pub mod util {
443445 // the tabstop closest to the range simply replaces `head` while anchor remains in place
444446 // the remaining tabstops receive their own single-width cursor
445447 if range.head < range.anchor {
446− 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;
447450
448451 // if selection is forward but was moved to the right it is
449452 // contained entirely in the replacement text, just do a point
450453 // selection (fallback below)
451− if range.anchor >= first_tabstop {
452− let range = Range::new(range.anchor, first_tabstop);
454+ if range.anchor > last_tabstop {
455+ let range = Range::new(range.anchor, last_tabstop);
453456 mapped_selection.push(range);
454− let rem_tabstops = tabstops[1..]
457+ let rem_tabstops = tabstops[..last_idx]
455458 .iter()
456− .map(|tabstop| Range::point(tabstop_anchor + tabstop.1));
459+ .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
457460 mapped_selection.extend(rem_tabstops);
458461 continue;
459462 }
460463 } else {
461− let last_idx = tabstops.len() - 1;
462− let last_tabstop = tabstop_anchor + tabstops[last_idx].1;
464+ let first_tabstop = tabstop_anchor + tabstops[0].0;
463465
464466 // if selection is forward but was moved to the right it is
465467 // contained entirely in the replacement text, just do a point
466468 // selection (fallback below)
467− if range.anchor <= last_tabstop {
469+ if range.anchor < first_tabstop {
468470 // we can't properly compute the the next grapheme
469471 // here because the transaction hasn't been applied yet
470472 // that is not a problem because the range gets grapheme aligned anyway
471473 // tough so just adding one will always cause head to be grapheme
472474 // aligned correctly when applied to the document
473− let range = Range::new(range.anchor, last_tabstop + 1);
475+ let range = Range::new(range.anchor, first_tabstop + 1);
474476 mapped_selection.push(range);
475− let rem_tabstops = tabstops[..last_idx]
477+ let rem_tabstops = tabstops[1..]
476478 .iter()
477479 .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
478480 mapped_selection.extend(rem_tabstops);
@@ -546,6 +548,7 @@ pub enum MethodCall {
546548 WorkspaceFolders,
547549 WorkspaceConfiguration(lsp::ConfigurationParams),
548550 RegisterCapability(lsp::RegistrationParams),
551+ UnregisterCapability(lsp::UnregistrationParams),
549552}
550553
551554impl MethodCall {
@@ -569,6 +572,10 @@ impl MethodCall {
569572 let params: lsp::RegistrationParams = params.parse()?;
570573 Self::RegisterCapability(params)
571574 }
575+ lsp::request::UnregisterCapability::METHOD => {
576+ let params: lsp::UnregistrationParams = params.parse()?;
577+ Self::UnregisterCapability(params)
578+ }
572579 _ => {
573580 return Err(Error::Unhandled);
574581 }
@@ -624,24 +631,21 @@ impl Notification {
624631
625632#[derive(Debug)]
626633pub struct Registry {
627− inner: HashMap<LanguageId, Vec<(usize, Arc<Client>)>>,
628−
629− counter: AtomicUsize,
634+ inner: HashMap<LanguageServerName, Vec<Arc<Client>>>,
635+ syn_loader: Arc<helix_core::syntax::Loader>,
636+ counter: usize,
630637 pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
631−}
632−
633−impl Default for Registry {
634− fn default() -> Self {
635− Self::new()
636− }
638+ pub file_event_handler: file_event::Handler,
637639}
638640
639641impl Registry {
640− pub fn new() -> Self {
642+ pub fn new(syn_loader: Arc<helix_core::syntax::Loader>) -> Self {
641643 Self {
642644 inner: HashMap::new(),
643− counter: AtomicUsize::new(0),
645+ syn_loader,
646+ counter: 0,
644647 incoming: SelectAll::new(),
648+ file_event_handler: file_event::Handler::new(),
645649 }
646650 }
647651
@@ -649,65 +653,95 @@ impl Registry {
649653 self.inner
650654 .values()
651655 .flatten()
652− .find(|(client_id, _)| client_id == &id)
653− .map(|(_, client)| client.as_ref())
656+ .find(|client| client.id() == id)
657+ .map(|client| &**client)
654658 }
655659
656660 pub fn remove_by_id(&mut self, id: usize) {
657− self.inner.retain(|_, clients| {
658− clients.retain(|&(client_id, _)| client_id != id);
659− !clients.is_empty()
660− })
661+ self.file_event_handler.remove_client(id);
662+ self.inner.retain(|_, language_servers| {
663+ language_servers.retain(|ls| id != ls.id());
664+ !language_servers.is_empty()
665+ });
661666 }
662667
668+ fn start_client(
669+ &mut self,
670+ name: String,
671+ ls_config: &LanguageConfiguration,
672+ doc_path: Option<&std::path::PathBuf>,
673+ root_dirs: &[PathBuf],
674+ enable_snippets: bool,
675+ ) -> Result<Arc<Client>> {
676+ let config = self
677+ .syn_loader
678+ .language_server_configs()
679+ .get(&name)
680+ .ok_or_else(|| anyhow::anyhow!("Language server '{name}' not defined"))?;
681+ let id = self.counter;
682+ self.counter += 1;
683+ let NewClient(client, incoming) = start_client(
684+ id,
685+ name,
686+ ls_config,
687+ config,
688+ doc_path,
689+ root_dirs,
690+ enable_snippets,
691+ )?;
692+ self.incoming.push(UnboundedReceiverStream::new(incoming));
693+ Ok(client)
694+ }
695+
696+ /// If this method is called, all documents that have a reference to language servers used by the language config have to refresh their language servers,
697+ /// as it could be that language servers of these documents were stopped by this method.
698+ /// See helix_view::editor::Editor::refresh_language_servers
663699 pub fn restart(
664700 &mut self,
665701 language_config: &LanguageConfiguration,
666702 doc_path: Option<&std::path::PathBuf>,
667703 root_dirs: &[PathBuf],
668704 enable_snippets: bool,
669− ) -> Result<Option<Arc<Client>>> {
670− let config = match &language_config.language_server {
671− Some(config) => config,
672− None => return Ok(None),
673− };
674−
675− let scope = language_config.scope.clone();
676−
677− match self.inner.entry(scope) {
678− Entry::Vacant(_) => Ok(None),
679− Entry::Occupied(mut entry) => {
680− // initialize a new client
681− let id = self.counter.fetch_add(1, Ordering::Relaxed);
682−
683− let NewClientResult(client, incoming) = start_client(
684− id,
685− language_config,
686− config,
687− doc_path,
688− root_dirs,
689− enable_snippets,
690− )?;
691− self.incoming.push(UnboundedReceiverStream::new(incoming));
692−
693− let old_clients = entry.insert(vec![(id, client.clone())]);
694−
695− for (_, old_client) in old_clients {
696− tokio::spawn(async move {
697− let _ = old_client.force_shutdown().await;
698− });
705+ ) -> Result<Vec<Arc<Client>>> {
706+ language_config
707+ .language_servers
708+ .iter()
709+ .filter_map(|LanguageServerFeatures { name, .. }| {
710+ if self.inner.contains_key(name) {
711+ let client = match self.start_client(
712+ name.clone(),
713+ language_config,
714+ doc_path,
715+ root_dirs,
716+ enable_snippets,
717+ ) {
718+ Ok(client) => client,
719+ error => return Some(error),
720+ };
721+ let old_clients = self
722+ .inner
723+ .insert(name.clone(), vec![client.clone()])
724+ .unwrap();
725+
726+ for old_client in old_clients {
727+ self.file_event_handler.remove_client(old_client.id());
728+ tokio::spawn(async move {
729+ let _ = old_client.force_shutdown().await;
730+ });
731+ }
732+
733+ Some(Ok(client))
734+ } else {
735+ None
699736 }
700−
701− Ok(Some(client))
702− }
703− }
737+ })
738+ .collect()
704739 }
705740
706− pub fn stop(&mut self, language_config: &LanguageConfiguration) {
707− let scope = language_config.scope.clone();
708−
709− if let Some(clients) = self.inner.remove(&scope) {
710− for (_, client) in clients {
741+ pub fn stop(&mut self, name: &str) {
742+ if let Some(clients) = self.inner.remove(name) {
743+ for client in clients {
744+ self.file_event_handler.remove_client(client.id());
711745 tokio::spawn(async move {
712746 let _ = client.force_shutdown().await;
713747 });
@@ -715,43 +749,44 @@ impl Registry {
715749 }
716750 }
717751
718− pub fn get(
719− &mut self,
720− language_config: &LanguageConfiguration,
721− doc_path: Option<&std::path::PathBuf>,
722− 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],
723757 enable_snippets: bool,
724− ) -> Result<Option<Arc<Client>>> {
725− let config = match &language_config.language_server {
726− Some(config) => config,
727− None => return Ok(None),
728− };
729−
730− let clients = self.inner.entry(language_config.scope.clone()).or_default();
731− // check if we already have a client for this documents root that we can reuse
732− if let Some((_, client)) = clients.iter_mut().enumerate().find(|(i, (_, client))| {
733− client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
734− }) {
735− return Ok(Some(client.1.clone()));
736− }
737− // initialize a new client
738− let id = self.counter.fetch_add(1, Ordering::Relaxed);
739−
740− let NewClientResult(client, incoming) = start_client(
741− id,
742− language_config,
743− config,
744− doc_path,
745− root_dirs,
746− enable_snippets,
747− )?;
748− clients.push((id, client.clone()));
749− self.incoming.push(UnboundedReceiverStream::new(incoming));
750− Ok(Some(client))
758+ ) -> impl Iterator<Item = (LanguageServerName, Result<Arc<Client>>)> + 'a {
759+ language_config.language_servers.iter().map(
760+ move |LanguageServerFeatures { name, .. }| {
761+ if let Some(clients) = self.inner.get(name) {
762+ if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| {
763+ client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
764+ }) {
765+ return (name.to_owned(), Ok(client.clone()));
766+ }
767+ }
768+ match self.start_client(
769+ name.clone(),
770+ language_config,
771+ doc_path,
772+ root_dirs,
773+ enable_snippets,
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+ )
751786 }
752787
753788 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
754− self.inner.values().flatten().map(|(_, client)| client)
789+ self.inner.values().flatten()
755790 }
756791}
757792
@@ -833,26 +868,28 @@ impl LspProgressMap {
833868 }
834869}
835870
836−struct NewClientResult(Arc<Client>, UnboundedReceiver<(usize, Call)>);
871+struct NewClient(Arc<Client>, UnboundedReceiver<(usize, Call)>);
837872
838873/// start_client takes both a LanguageConfiguration and a LanguageServerConfiguration to ensure that
839874/// it is only called when it makes sense.
840875fn start_client(
841876 id: usize,
877+ name: String,
842878 config: &LanguageConfiguration,
843879 ls_config: &LanguageServerConfiguration,
844880 doc_path: Option<&std::path::PathBuf>,
845881 root_dirs: &[PathBuf],
846882 enable_snippets: bool,
847−) -> Result<NewClientResult> {
883+) -> Result<NewClient> {
848884 let (client, incoming, initialize_notify) = Client::start(
849885 &ls_config.command,
850886 &ls_config.args,
851− config.config.clone(),
887+ ls_config.config.clone(),
852888 ls_config.environment.clone(),
853889 &config.roots,
854890 config.workspace_lsp_roots.as_deref().unwrap_or(root_dirs),
855891 id,
892+ name,
856893 ls_config.timeout,
857894 doc_path,
858895 )?;
@@ -886,7 +923,7 @@ fn start_client(
886923 initialize_notify.notify_one();
887924 });
888925
889− Ok(NewClientResult(client, incoming))
926+ Ok(NewClient(client, incoming))
890927}
891928
892929/// Find an LSP workspace of a file using the following mechanism:
@@ -909,7 +946,7 @@ pub fn find_lsp_workspace(
909946 let mut file = if file.is_absolute() {
910947 file.to_path_buf()
911948 } else {
912− let current_dir = std::env::current_dir().expect("unable to determine current directory");
949+ let current_dir = helix_loader::current_working_dir();
913950 current_dir.join(file)
914951 };
915952 file = path::get_normalized_path(&file);
helix-lsp/src/transport.rs+52−21
@@ -38,6 +38,7 @@ enum ServerMessage {
3838#[derive(Debug)]
3939pub struct Transport {
4040 id: usize,
41+ name: String,
4142 pending_requests: Mutex<HashMap<jsonrpc::Id, Sender<Result<Value>>>>,
4243}
4344
@@ -47,6 +48,7 @@ impl Transport {
4748 server_stdin: BufWriter<ChildStdin>,
4849 server_stderr: BufReader<ChildStderr>,
4950 id: usize,
51+ name: String,
5052 ) -> (
5153 UnboundedReceiver<(usize, jsonrpc::Call)>,
5254 UnboundedSender<Payload>,
@@ -58,6 +60,7 @@ impl Transport {
5860
5961 let transport = Self {
6062 id,
63+ name,
6164 pending_requests: Mutex::new(HashMap::default()),
6265 };
6366
@@ -83,6 +86,7 @@ impl Transport {
8386 async fn recv_server_message(
8487 reader: &mut (impl AsyncBufRead + Unpin + Send),
8588 buffer: &mut String,
89+ language_server_name: &str,
8690 ) -> Result<ServerMessage> {
8791 let mut content_length = None;
8892 loop {
@@ -124,7 +128,7 @@ impl Transport {
124128 reader.read_exact(&mut content).await?;
125129 let msg = std::str::from_utf8(&content).context("invalid utf8 from server")?;
126130
127− info!("<- {}", msg);
131+ info!("{language_server_name} <- {msg}");
128132
129133 // try parsing as output (server response) or call (server request)
130134 let output: serde_json::Result<ServerMessage> = serde_json::from_str(msg);
@@ -135,12 +139,13 @@ impl Transport {
135139 async fn recv_server_error(
136140 err: &mut (impl AsyncBufRead + Unpin + Send),
137141 buffer: &mut String,
142+ language_server_name: &str,
138143 ) -> Result<()> {
139144 buffer.truncate(0);
140145 if err.read_line(buffer).await? == 0 {
141146 return Err(Error::StreamClosed);
142147 };
143− error!("err <- {:?}", buffer);
148+ error!("{language_server_name} err <- {buffer:?}");
144149
145150 Ok(())
146151 }
@@ -162,15 +167,17 @@ impl Transport {
162167 Payload::Notification(value) => serde_json::to_string(&value)?,
163168 Payload::Response(error) => serde_json::to_string(&error)?,
164169 };
165− self.send_string_to_server(server_stdin, json).await
170+ self.send_string_to_server(server_stdin, json, &self.name)
171+ .await
166172 }
167173
168174 async fn send_string_to_server(
169175 &self,
170176 server_stdin: &mut BufWriter<ChildStdin>,
171177 request: String,
178+ language_server_name: &str,
172179 ) -> Result<()> {
173− info!("-> {}", request);
180+ info!("{language_server_name} -> {request}");
174181
175182 // send the headers
176183 server_stdin
@@ -189,9 +196,13 @@ impl Transport {
189196 &self,
190197 client_tx: &UnboundedSender<(usize, jsonrpc::Call)>,
191198 msg: ServerMessage,
199+ language_server_name: &str,
192200 ) -> Result<()> {
193201 match msg {
194− ServerMessage::Output(output) => self.process_request_response(output).await?,
202+ ServerMessage::Output(output) => {
203+ self.process_request_response(output, language_server_name)
204+ .await?
205+ }
195206 ServerMessage::Call(call) => {
196207 client_tx
197208 .send((self.id, call))
@@ -202,14 +213,18 @@ impl Transport {
202213 Ok(())
203214 }
204215
205− async fn process_request_response(&self, output: jsonrpc::Output) -> Result<()> {
216+ async fn process_request_response(
217+ &self,
218+ output: jsonrpc::Output,
219+ language_server_name: &str,
220+ ) -> Result<()> {
206221 let (id, result) = match output {
207222 jsonrpc::Output::Success(jsonrpc::Success { id, result, .. }) => {
208− info!("<- {}", result);
223+ info!("{language_server_name} <- {}", result);
209224 (id, Ok(result))
210225 }
211226 jsonrpc::Output::Failure(jsonrpc::Failure { id, error, .. }) => {
212− error!("<- {}", error);
227+ error!("{language_server_name} <- {error}");
213228 (id, Err(error.into()))
214229 }
215230 };
@@ -240,12 +255,17 @@ impl Transport {
240255 ) {
241256 let mut recv_buffer = String::new();
242257 loop {
243− match Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await {
258+ match Self::recv_server_message(&mut server_stdout, &mut recv_buffer, &transport.name)
259+ .await
260+ {
244261 Ok(msg) => {
245− match transport.process_server_message(&client_tx, msg).await {
262+ match transport
263+ .process_server_message(&client_tx, msg, &transport.name)
264+ .await
265+ {
246266 Ok(_) => {}
247267 Err(err) => {
248− error!("err: <- {:?}", err);
268+ error!("{} err: <- {err:?}", transport.name);
249269 break;
250270 }
251271 };
@@ -270,7 +290,7 @@ impl Transport {
270290 params: jsonrpc::Params::None,
271291 }));
272292 match transport
273− .process_server_message(&client_tx, notification)
293+ .process_server_message(&client_tx, notification, &transport.name)
274294 .await
275295 {
276296 Ok(_) => {}
@@ -281,20 +301,22 @@ impl Transport {
281301 break;
282302 }
283303 Err(err) => {
284− error!("err: <- {:?}", err);
304+ error!("{} err: <- {err:?}", transport.name);
285305 break;
286306 }
287307 }
288308 }
289309 }
290310
291− async fn err(_transport: Arc<Self>, mut server_stderr: BufReader<ChildStderr>) {
311+ async fn err(transport: Arc<Self>, mut server_stderr: BufReader<ChildStderr>) {
292312 let mut recv_buffer = String::new();
293313 loop {
294− match Self::recv_server_error(&mut server_stderr, &mut recv_buffer).await {
314+ match Self::recv_server_error(&mut server_stderr, &mut recv_buffer, &transport.name)
315+ .await
316+ {
295317 Ok(_) => {}
296318 Err(err) => {
297− error!("err: <- {:?}", err);
319+ error!("{} err: <- {err:?}", transport.name);
298320 break;
299321 }
300322 }
@@ -331,6 +353,11 @@ impl Transport {
331353 }
332354 }
333355
356+ fn is_shutdown(payload: &Payload) -> bool {
357+ use lsp_types::request::{Request, Shutdown};
358+ matches!(payload, Payload::Request { value: jsonrpc::MethodCall { method, .. }, .. } if method == Shutdown::METHOD)
359+ }
360+
334361 // TODO: events that use capabilities need to do the right thing
335362
336363 loop {
@@ -348,10 +375,11 @@ impl Transport {
348375 method: lsp_types::notification::Initialized::METHOD.to_string(),
349376 params: jsonrpc::Params::None,
350377 }));
351− match transport.process_server_message(&client_tx, notification).await {
378+ let language_server_name = &transport.name;
379+ match transport.process_server_message(&client_tx, notification, language_server_name).await {
352380 Ok(_) => {}
353381 Err(err) => {
354− error!("err: <- {:?}", err);
382+ error!("{language_server_name} err: <- {err:?}");
355383 }
356384 }
357385
@@ -361,14 +389,17 @@ impl Transport {
361389 match transport.send_payload_to_server(&mut server_stdin, msg).await {
362390 Ok(_) => {}
363391 Err(err) => {
364− error!("err: <- {:?}", err);
392+ error!("{language_server_name} err: <- {err:?}");
365393 }
366394 }
367395 }
368396 }
369397 msg = client_rx.recv() => {
370398 if let Some(msg) = msg {
371− if is_pending && !is_initialize(&msg) {
399+ if is_pending && is_shutdown(&msg) {
400+ log::info!("Language server not initialized, shutting down");
401+ break;
402+ } else if is_pending && !is_initialize(&msg) {
372403 // ignore notifications
373404 if let Payload::Notification(_) = msg {
374405 continue;
@@ -380,7 +411,7 @@ impl Transport {
380411 match transport.send_payload_to_server(&mut server_stdin, msg).await {
381412 Ok(_) => {}
382413 Err(err) => {
383− error!("err: <- {:?}", err);
414+ error!("{} err: <- {err:?}", transport.name);
384415 }
385416 }
386417 }
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.17"
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.141"
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.1"
79−tempfile = "3.4.0"
85+smallvec = "1.11"
86+indoc = "2.0.4"
87+tempfile = "3.8.1"
helix-term/src/application.rs+301−190
@@ -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,12 +34,9 @@ use crate::{
2934};
3035
3136use log::{debug, error, warn};
32−use std::{
33− io::{stdin, stdout},
34− path::Path,
35− sync::Arc,
36− time::{Duration, Instant},
37−};
37+#[cfg(not(feature = "integration"))]
38+use std::io::stdout;
39+use std::{collections::btree_map::Entry, io::stdin, path::Path, sync::Arc};
3840
3941use anyhow::{Context, Error};
4042
@@ -44,8 +46,6 @@ use {signal_hook::consts::signal, signal_hook_tokio::Signals};
4446#[cfg(windows)]
4547type Signals = futures_util::stream::Empty<()>;
4648
47−const LSP_DEADLINE: Duration = Duration::from_millis(16);
48−
4949#[cfg(not(feature = "integration"))]
5050use tui::backend::CrosstermBackend;
5151
@@ -75,7 +75,6 @@ pub struct Application {
7575 signals: Signals,
7676 jobs: Jobs,
7777 lsp_progress: LspProgressMap,
78− last_render: Instant,
7978}
8079
8180#[cfg(feature = "integration")]
@@ -162,17 +161,21 @@ impl Application {
162161 let path = helix_loader::runtime_file(Path::new("tutor"));
163162 editor.open(&path, Action::VerticalSplit)?;
164163 // Unset path to prevent accidentally saving to the original tutor file.
165− doc_mut!(editor).set_path(None)?;
164+ doc_mut!(editor).set_path(None);
166165 } else if !args.files.is_empty() {
167− let first = &args.files[0].0; // we know it's not empty
168− if first.is_dir() {
169− std::env::set_current_dir(first).context("set current dir")?;
170− editor.new_file(Action::VerticalSplit);
171− 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);
172171 compositor.push(Box::new(overlaid(picker)));
173− } else {
174− let nr_of_files = args.files.len();
175− 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;
176179 if file.is_dir() {
177180 return Err(anyhow::anyhow!(
178181 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
@@ -184,7 +187,7 @@ impl Application {
184187 // option. If neither of those two arguments are passed
185188 // in, just load the files normally.
186189 let action = match args.split {
187− _ if i == 0 => Action::VerticalSplit,
190+ _ if nr_of_files == 1 => Action::VerticalSplit,
188191 Some(Layout::Vertical) => Action::VerticalSplit,
189192 Some(Layout::Horizontal) => Action::HorizontalSplit,
190193 None => Action::Load,
@@ -211,14 +214,11 @@ impl Application {
211214 // does not affect views without pos since it is at the top
212215 let (view, doc) = current!(editor);
213216 align_view(doc, view, Align::Center);
217+ } else {
218+ editor.new_file(Action::VerticalSplit);
214219 }
215220 } else if stdin().is_tty() || cfg!(feature = "integration") {
216221 editor.new_file(Action::VerticalSplit);
217− } else if cfg!(target_os = "macos") {
218− // On Linux and Windows, we allow the output of a command to be piped into the new buffer.
219− // This doesn't currently work on macOS because of the following issue:
220− // https://github.com/crossterm-rs/crossterm/issues/500
221− anyhow::bail!("Piping into helix-term is currently not supported on macOS");
222222 } else {
223223 editor
224224 .new_file_from_stdin(Action::VerticalSplit)
@@ -230,8 +230,14 @@ impl Application {
230230 #[cfg(windows)]
231231 let signals = futures_util::stream::empty();
232232 #[cfg(not(windows))]
233− let signals = Signals::new([signal::SIGTSTP, signal::SIGCONT, signal::SIGUSR1])
234− .context("build signal handler")?;
233+ let signals = Signals::new([
234+ signal::SIGTSTP,
235+ signal::SIGCONT,
236+ signal::SIGUSR1,
237+ signal::SIGTERM,
238+ signal::SIGINT,
239+ ])
240+ .context("build signal handler")?;
235241
236242 let app = Self {
237243 compositor,
@@ -246,29 +252,25 @@ impl Application {
246252 signals,
247253 jobs: Jobs::new(),
248254 lsp_progress: LspProgressMap::new(),
249− last_render: Instant::now(),
250255 };
251256
252257 Ok(app)
253258 }
254259
255260 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+
256266 let mut cx = crate::compositor::Context {
257267 editor: &mut self.editor,
258268 jobs: &mut self.jobs,
259269 scroll: None,
260270 };
261271
262− // Acquire mutable access to the redraw_handle lock
263− // to ensure that there are no tasks running that want to block rendering
264− drop(cx.editor.redraw_handle.1.write().await);
272+ helix_event::start_frame();
265273 cx.editor.needs_redraw = false;
266− {
267− // exhaust any leftover redraw notifications
268− let notify = cx.editor.redraw_handle.0.notified();
269− tokio::pin!(notify);
270− notify.enable();
271− }
272274
273275 let area = self
274276 .terminal
@@ -290,10 +292,9 @@ impl Application {
290292
291293 pub async fn event_loop<S>(&mut self, input_stream: &mut S)
292294 where
293− S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
295+ S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
294296 {
295297 self.render().await;
296− self.last_render = Instant::now();
297298
298299 loop {
299300 if !self.event_loop_until_idle(input_stream).await {
@@ -304,7 +305,7 @@ impl Application {
304305
305306 pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool
306307 where
307− S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
308+ S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
308309 {
309310 loop {
310311 if self.editor.should_close() {
@@ -317,7 +318,9 @@ impl Application {
317318 biased;
318319
319320 Some(signal) = self.signals.next() => {
320− self.handle_signals(signal).await;
321+ if !self.handle_signals(signal).await {
322+ return false;
323+ };
321324 }
322325 Some(event) = input_stream.next() => {
323326 self.handle_terminal_events(event).await;
@@ -361,6 +364,9 @@ impl Application {
361364 ConfigEvent::Update(editor_config) => {
362365 let mut app_config = (*self.config.load().clone()).clone();
363366 app_config.editor = *editor_config;
367+ if let Err(err) = self.terminal.reconfigure(app_config.editor.clone().into()) {
368+ self.editor.set_error(err.to_string());
369+ };
364370 self.config.store(Arc::new(app_config));
365371 }
366372 }
@@ -419,6 +425,8 @@ impl Application {
419425 .map_err(|err| anyhow::anyhow!("Failed to load config: {}", err))?;
420426 self.refresh_language_config()?;
421427 self.refresh_theme(&default_config)?;
428+ self.terminal
429+ .reconfigure(default_config.editor.clone().into())?;
422430 // Store new config
423431 self.config.store(Arc::new(default_config));
424432 Ok(())
@@ -436,10 +444,12 @@ impl Application {
436444
437445 #[cfg(windows)]
438446 // no signal handling available on windows
439− pub async fn handle_signals(&mut self, _signal: ()) {}
447+ pub async fn handle_signals(&mut self, _signal: ()) -> bool {
448+ true
449+ }
440450
441451 #[cfg(not(windows))]
442− pub async fn handle_signals(&mut self, signal: i32) {
452+ pub async fn handle_signals(&mut self, signal: i32) -> bool {
443453 match signal {
444454 signal::SIGTSTP => {
445455 self.restore_term().unwrap();
@@ -471,7 +481,17 @@ impl Application {
471481 }
472482 }
473483 signal::SIGCONT => {
474− self.claim_term().await.unwrap();
484+ // Copy/Paste from same issue from neovim:
485+ // https://github.com/neovim/neovim/issues/12322
486+ // https://github.com/neovim/neovim/pull/13084
487+ for retries in 1..=10 {
488+ match self.claim_term().await {
489+ Ok(()) => break,
490+ Err(err) if retries == 10 => panic!("Failed to claim terminal: {}", err),
491+ Err(_) => continue,
492+ }
493+ }
494+
475495 // redraw the terminal
476496 let area = self.terminal.size().expect("couldn't get terminal size");
477497 self.compositor.resize(area);
@@ -483,8 +503,14 @@ impl Application {
483503 self.refresh_config();
484504 self.render().await;
485505 }
506+ signal::SIGTERM | signal::SIGINT => {
507+ self.restore_term().unwrap();
508+ return false;
509+ }
486510 _ => unreachable!(),
487511 }
512+
513+ true
488514 }
489515
490516 pub async fn handle_idle_timeout(&mut self) {
@@ -532,16 +558,7 @@ impl Application {
532558 let bytes = doc_save_event.text.len_bytes();
533559
534560 if doc.path() != Some(&doc_save_event.path) {
535− if let Err(err) = doc.set_path(Some(&doc_save_event.path)) {
536− log::error!(
537− "error setting path for doc '{:?}': {}",
538− doc.path(),
539− err.to_string(),
540− );
541−
542− self.editor.set_error(err.to_string());
543− return;
544− }
561+ doc.set_path(Some(&doc_save_event.path));
545562
546563 let loader = self.editor.syn_loader.clone();
547564
@@ -549,7 +566,7 @@ impl Application {
549566 let doc = doc_mut!(self.editor, &doc_save_event.doc_id);
550567 let id = doc.id();
551568 doc.detect_language(loader);
552− let _ = self.editor.refresh_language_server(id);
569+ self.editor.refresh_language_servers(id);
553570 }
554571
555572 // TODO: fix being overwritten by lsp
@@ -577,12 +594,7 @@ impl Application {
577594 EditorEvent::LanguageServerMessage((id, call)) => {
578595 self.handle_language_server_message(call, id).await;
579596 // limit render calls for fast language server messages
580− let last = self.editor.language_servers.incoming.is_empty();
581−
582− if last || self.last_render.elapsed() > LSP_DEADLINE {
583− self.render().await;
584− self.last_render = Instant::now();
585− }
597+ helix_event::request_redraw();
586598 }
587599 EditorEvent::DebuggerEvent(payload) => {
588600 let needs_render = self.editor.handle_debugger_message(payload).await;
@@ -590,6 +602,9 @@ impl Application {
590602 self.render().await;
591603 }
592604 }
605+ EditorEvent::Redraw => {
606+ self.render().await;
607+ }
593608 EditorEvent::IdleTimer => {
594609 self.editor.clear_idle_timer();
595610 self.handle_idle_timeout().await;
@@ -604,10 +619,7 @@ impl Application {
604619 false
605620 }
606621
607− pub async fn handle_terminal_events(
608− &mut self,
609− event: Result<CrosstermEvent, crossterm::ErrorKind>,
610− ) {
622+ pub async fn handle_terminal_events(&mut self, event: std::io::Result<CrosstermEvent>) {
611623 let mut cx = crate::compositor::Context {
612624 editor: &mut self.editor,
613625 jobs: &mut self.jobs,
@@ -647,6 +659,18 @@ impl Application {
647659 ) {
648660 use helix_lsp::{Call, MethodCall, Notification};
649661
662+ macro_rules! language_server {
663+ () => {
664+ match self.editor.language_server_by_id(server_id) {
665+ Some(language_server) => language_server,
666+ None => {
667+ warn!("can't find language server with id `{}`", server_id);
668+ return;
669+ }
670+ }
671+ };
672+ }
673+
650674 match call {
651675 Call::Notification(helix_lsp::jsonrpc::Notification { method, params, .. }) => {
652676 let notification = match Notification::parse(&method, params) {
@@ -662,14 +686,7 @@ impl Application {
662686
663687 match notification {
664688 Notification::Initialized => {
665− let language_server =
666− match self.editor.language_servers.get_by_id(server_id) {
667− Some(language_server) => language_server,
668− None => {
669− warn!("can't find language server with id `{}`", server_id);
670− return;
671− }
672− };
689+ let language_server = language_server!();
673690
674691 // Trigger a workspace/didChangeConfiguration notification after initialization.
675692 // This might not be required by the spec but Neovim does this as well, so it's
@@ -678,9 +695,10 @@ impl Application {
678695 tokio::spawn(language_server.did_change_configuration(config.clone()));
679696 }
680697
681− let docs = self.editor.documents().filter(|doc| {
682− doc.language_server().map(|server| server.id()) == Some(server_id)
683− });
698+ let docs = self
699+ .editor
700+ .documents()
701+ .filter(|doc| doc.supports_language_server(server_id));
684702
685703 // trigger textDocument/didOpen for docs that are already open
686704 for doc in docs {
@@ -708,40 +726,77 @@ impl Application {
708726 return;
709727 }
710728 };
711− let doc = self.editor.document_by_path_mut(&path).filter(|doc| {
712− if let Some(version) = params.version {
713− if version != doc.version() {
714− log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
715− return false;
729+ let language_server = language_server!();
730+ if !language_server.is_initialized() {
731+ log::error!("Discarding publishDiagnostic notification sent by an uninitialized server: {}", language_server.name());
732+ return;
733+ }
734+ let offset_encoding = language_server.offset_encoding();
735+ // have to inline the function because of borrow checking...
736+ let doc = self.editor.documents.values_mut()
737+ .find(|doc| doc.path().map(|p| p == &path).unwrap_or(false))
738+ .filter(|doc| {
739+ if let Some(version) = params.version {
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+ }
716744 }
717− }
718−
719− true
720− });
745+ true
746+ });
721747
722748 if let Some(doc) = doc {
723− let lang_conf = doc.language_config();
724− let text = doc.text();
749+ let lang_conf = doc.language.clone();
750+ let text = doc.text().clone();
725751
726− let diagnostics = params
727− .diagnostics
728− .iter()
729− .filter_map(|diagnostic| {
752+ let mut unchaged_diag_sources_ = Vec::new();
753+ if let Some(lang_conf) = &lang_conf {
754+ if let Some(old_diagnostics) =
755+ self.editor.diagnostics.get(&params.uri)
756+ {
757+ if !lang_conf.persistent_diagnostic_sources.is_empty() {
758+ // Sort diagnostics first by severity and then by line numbers.
759+ // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
760+ params
761+ .diagnostics
762+ .sort_unstable_by_key(|d| (d.severity, d.range.start));
763+ }
764+ for source in &lang_conf.persistent_diagnostic_sources {
765+ let new_diagnostics = params
766+ .diagnostics
767+ .iter()
768+ .filter(|d| d.source.as_ref() == Some(source));
769+ let old_diagnostics = old_diagnostics
770+ .iter()
771+ .filter(|(d, d_server)| {
772+ *d_server == server_id
773+ && d.source.as_ref() == Some(source)
774+ })
775+ .map(|(d, _)| d);
776+ if new_diagnostics.eq(old_diagnostics) {
777+ unchaged_diag_sources_.push(source.clone())
778+ }
779+ }
780+ }
781+ }
782+
783+ let unchaged_diag_sources = &unchaged_diag_sources_;
784+ let diagnostics =
785+ params.diagnostics.iter().filter_map(move |diagnostic| {
730786 use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
731787 use lsp::DiagnosticSeverity;
732788
733− let language_server = if let Some(language_server) = doc.language_server() {
734− language_server
735− } else {
736− log::warn!("Discarding diagnostic because language server is not initialized: {:?}", diagnostic);
789+ if diagnostic.source.as_ref().map_or(false, |source| {
790+ unchaged_diag_sources.contains(source)
791+ }) {
737792 return None;
738− };
793+ }
739794
740795 // TODO: convert inside server
741796 let start = if let Some(start) = lsp_pos_to_pos(
742− text,
797+ &text,
743798 diagnostic.range.start,
744− language_server.offset_encoding(),
799+ offset_encoding,
745800 ) {
746801 start
747802 } else {
@@ -749,17 +804,14 @@ impl Application {
749804 return None;
750805 };
751806
752− let end = if let Some(end) = lsp_pos_to_pos(
753− text,
754− diagnostic.range.end,
755− language_server.offset_encoding(),
756− ) {
807+ let end = if let Some(end) =
808+ lsp_pos_to_pos(&text, diagnostic.range.end, offset_encoding)
809+ {
757810 end
758811 } else {
759812 log::warn!("lsp position out of bounds - {:?}", diagnostic);
760813 return None;
761814 };
762−
763815 let severity =
764816 diagnostic.severity.map(|severity| match severity {
765817 DiagnosticSeverity::ERROR => Error,
@@ -772,7 +824,7 @@ impl Application {
772824 ),
773825 });
774826
775− if let Some(lang_conf) = lang_conf {
827+ if let Some(lang_conf) = &lang_conf {
776828 if let Some(severity) = severity {
777829 if severity < lang_conf.diagnostic_severity {
778830 return None;
@@ -792,22 +844,36 @@ impl Application {
792844 None => None,
793845 };
794846
795− let tags = if let Some(ref tags) = diagnostic.tags {
796− let new_tags = tags.iter().filter_map(|tag| {
797− match *tag {
798− lsp::DiagnosticTag::DEPRECATED => Some(DiagnosticTag::Deprecated),
799− lsp::DiagnosticTag::UNNECESSARY => Some(DiagnosticTag::Unnecessary),
800− _ => None
801− }
802− }).collect();
847+ let tags = if let Some(tags) = &diagnostic.tags {
848+ let new_tags = tags
849+ .iter()
850+ .filter_map(|tag| match *tag {
851+ lsp::DiagnosticTag::DEPRECATED => {
852+ Some(DiagnosticTag::Deprecated)
853+ }
854+ lsp::DiagnosticTag::UNNECESSARY => {
855+ Some(DiagnosticTag::Unnecessary)
856+ }
857+ _ => None,
858+ })
859+ .collect();
803860
804861 new_tags
805862 } else {
806863 Vec::new()
807864 };
808865
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+
809872 Some(Diagnostic {
810873 range: Range { start, end },
874+ ends_at_word,
875+ starts_at_word,
876+ zero_width: start == end,
811877 line: diagnostic.range.start.line as usize,
812878 message: diagnostic.message.clone(),
813879 severity,
@@ -815,25 +881,35 @@ impl Application {
815881 tags,
816882 source: diagnostic.source.clone(),
817883 data: diagnostic.data.clone(),
884+ language_server_id: server_id,
818885 })
819− })
820− .collect();
886+ });
821887
822− doc.set_diagnostics(diagnostics);
888+ doc.replace_diagnostics(diagnostics, unchaged_diag_sources, server_id);
823889 }
824890
825− // Sort diagnostics first by severity and then by line numbers.
826− // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
827− params
828− .diagnostics
829− .sort_unstable_by_key(|d| (d.severity, d.range.start));
891+ let diagnostics = params.diagnostics.into_iter().map(|d| (d, server_id));
830892
831893 // Insert the original lsp::Diagnostics here because we may have no open document
832894 // for diagnosic message and so we can't calculate the exact position.
833895 // When using them later in the diagnostics picker, we calculate them on-demand.
834− self.editor
835− .diagnostics
836− .insert(params.uri, params.diagnostics);
896+ let diagnostics = match self.editor.diagnostics.entry(params.uri) {
897+ Entry::Occupied(o) => {
898+ let current_diagnostics = o.into_mut();
899+ // there may entries of other language servers, which is why we can't overwrite the whole entry
900+ current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
901+ current_diagnostics.extend(diagnostics);
902+ current_diagnostics
903+ // Sort diagnostics first by severity and then by line numbers.
904+ }
905+ Entry::Vacant(v) => v.insert(diagnostics.collect()),
906+ };
907+
908+ // Sort diagnostics first by severity and then by line numbers.
909+ // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
910+ diagnostics.sort_unstable_by_key(|(d, server_id)| {
911+ (d.severity, d.range.start, *server_id)
912+ });
837913 }
838914 Notification::ShowMessage(params) => {
839915 log::warn!("unhandled window/showMessage: {:?}", params);
@@ -930,24 +1006,18 @@ impl Application {
9301006 Notification::Exit => {
9311007 self.editor.set_status("Language server exited");
9321008
933− // Clear any diagnostics for documents with this server open.
934− let urls: Vec<_> = self
935− .editor
936− .documents_mut()
937− .filter_map(|doc| {
938− if doc.language_server().map(|server| server.id())
939− == Some(server_id)
940− {
941− doc.set_diagnostics(Vec::new());
942− doc.url()
943− } else {
944− None
945− }
946− })
947− .collect();
1009+ // LSPs may produce diagnostics for files that haven't been opened in helix,
1010+ // we need to clear those and remove the entries from the list if this leads to
1011+ // an empty diagnostic list for said files
1012+ for diags in self.editor.diagnostics.values_mut() {
1013+ diags.retain(|(_, lsp_id)| *lsp_id != server_id);
1014+ }
1015+
1016+ self.editor.diagnostics.retain(|_, diags| !diags.is_empty());
9481017
949− for url in urls {
950− self.editor.diagnostics.remove(&url);
1018+ // Clear any diagnostics for documents with this server open.
1019+ for doc in self.editor.documents_mut() {
1020+ doc.clear_diagnostics(server_id);
9511021 }
9521022
9531023 // Remove the language server from the registry.
@@ -998,47 +1068,48 @@ impl Application {
9981068 Ok(serde_json::Value::Null)
9991069 }
10001070 Ok(MethodCall::ApplyWorkspaceEdit(params)) => {
1001− let res = apply_workspace_edit(
1002− &mut self.editor,
1003− helix_lsp::OffsetEncoding::Utf8,
1004− &params.edit,
1005− );
1006−
1007− Ok(json!(lsp::ApplyWorkspaceEditResponse {
1008− applied: res.is_ok(),
1009− failure_reason: res.as_ref().err().map(|err| err.kind.to_string()),
1010− failed_change: res
1011− .as_ref()
1012− .err()
1013− .map(|err| err.failed_change_idx as u32),
1014− }))
1071+ let language_server = language_server!();
1072+ if language_server.is_initialized() {
1073+ let offset_encoding = language_server.offset_encoding();
1074+ let res = apply_workspace_edit(
1075+ &mut self.editor,
1076+ offset_encoding,
1077+ &params.edit,
1078+ );
1079+
1080+ Ok(json!(lsp::ApplyWorkspaceEditResponse {
1081+ applied: res.is_ok(),
1082+ failure_reason: res.as_ref().err().map(|err| err.kind.to_string()),
1083+ failed_change: res
1084+ .as_ref()
1085+ .err()
1086+ .map(|err| err.failed_change_idx as u32),
1087+ }))
1088+ } else {
1089+ Err(helix_lsp::jsonrpc::Error {
1090+ code: helix_lsp::jsonrpc::ErrorCode::InvalidRequest,
1091+ message: "Server must be initialized to request workspace edits"
1092+ .to_string(),
1093+ data: None,
1094+ })
1095+ }
10151096 }
10161097 Ok(MethodCall::WorkspaceFolders) => {
1017− let language_server =
1018− self.editor.language_servers.get_by_id(server_id).unwrap();
1019−
1020− Ok(json!(&*language_server.workspace_folders().await))
1098+ Ok(json!(&*language_server!().workspace_folders().await))
10211099 }
10221100 Ok(MethodCall::WorkspaceConfiguration(params)) => {
1101+ let language_server = language_server!();
10231102 let result: Vec<_> = params
10241103 .items
10251104 .iter()
10261105 .map(|item| {
1027− let mut config = match &item.scope_uri {
1028− Some(scope) => {
1029− let path = scope.to_file_path().ok()?;
1030− let doc = self.editor.document_by_path(path)?;
1031− doc.language_config()?.config.as_ref()?
1032− }
1033− None => self
1034− .editor
1035− .language_servers
1036− .get_by_id(server_id)?
1037− .config()?,
1038− };
1106+ let mut config = language_server.config()?;
10391107 if let Some(section) = item.section.as_ref() {
1040− for part in section.split('.') {
1041− config = config.get(part)?;
1108+ // for some reason some lsps send an empty string (observed in 'vscode-eslint-language-server')
1109+ if !section.is_empty() {
1110+ for part in section.split('.') {
1111+ config = config.get(part)?;
1112+ }
10421113 }
10431114 }
10441115 Some(config)
@@ -1046,28 +1117,68 @@ impl Application {
10461117 .collect();
10471118 Ok(json!(result))
10481119 }
1049− Ok(MethodCall::RegisterCapability(_params)) => {
1050− log::warn!("Ignoring a client/registerCapability request because dynamic capability registration is not enabled. Please report this upstream to the language server");
1051− // Language Servers based on the `vscode-languageserver-node` library often send
1052− // client/registerCapability even though we do not enable dynamic registration
1053− // for any capabilities. We should send a MethodNotFound JSONRPC error in this
1054− // case but that rejects the registration promise in the server which causes an
1055− // exit. So we work around this by ignoring the request and sending back an OK
1056− // 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+ }
10571160
10581161 Ok(serde_json::Value::Null)
10591162 }
1060− };
1061−
1062− let language_server = match self.editor.language_servers.get_by_id(server_id) {
1063− Some(language_server) => language_server,
1064− None => {
1065− warn!("can't find language server with id `{}`", server_id);
1066− return;
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)
10671178 }
10681179 };
10691180
1070− tokio::spawn(language_server.reply(id, reply));
1181+ tokio::spawn(language_server!().reply(id, reply));
10711182 }
10721183 Call::Invalid { id } => log::error!("LSP invalid method call id={:?}", id),
10731184 }
@@ -1090,7 +1201,7 @@ impl Application {
10901201
10911202 pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
10921203 where
1093− S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
1204+ S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
10941205 {
10951206 self.claim_term().await?;
10961207
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+1098−645
@@ -23,17 +23,17 @@ use helix_core::{
2323 regex::{self, Regex, RegexBuilder},
2424 search::{self, CharMatcher},
2525 selection, shellwords, surround,
26+ syntax::LanguageServerFeature,
2627 text_annotations::TextAnnotations,
2728 textobject,
2829 tree_sitter::Node,
2930 unicode::width::UnicodeWidthChar,
30− visual_offset_from_block, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice,
31− Selection, SmallVec, Tendril, Transaction,
31+ visual_offset_from_block, Deletion, LineEnding, Position, Range, Rope, RopeGraphemes,
32+ RopeReader, RopeSlice, Selection, SmallVec, Tendril, Transaction,
3233};
3334use helix_view::{
34− clipboard::ClipboardType,
3535 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
36− editor::{Action, Motion},
36+ editor::{Action, CompleteAction},
3737 info::Info,
3838 input::KeyEvent,
3939 keyboard::KeyCode,
@@ -43,7 +43,6 @@ use helix_view::{
4343};
4444
4545use anyhow::{anyhow, bail, ensure, Context as _};
46−use fuzzy_matcher::FuzzyMatcher;
4746use insert::*;
4847use movement::Movement;
4948
@@ -54,15 +53,20 @@ use crate::{
5453 job::Callback,
5554 keymap::ReverseKeymap,
5655 ui::{
57− self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, FilePicker, Picker,
56+ self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, CompletionItem, Picker,
5857 Popup, Prompt, PromptEvent,
5958 },
6059};
6160
6261use crate::job::{self, Jobs};
63−use futures_util::StreamExt;
64−use std::{collections::HashMap, fmt, future::Future};
65−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+};
6670
6771use std::{
6872 borrow::Cow,
@@ -71,11 +75,11 @@ use std::{
7175
7276use once_cell::sync::Lazy;
7377use serde::de::{self, Deserialize, Deserializer};
78+use url::Url;
7479
7580use grep_regex::RegexMatcherBuilder;
7681use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
7782use ignore::{DirEntry, WalkBuilder, WalkState};
78−use tokio_stream::wrappers::UnboundedReceiverStream;
7983
8084pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
8185
@@ -97,6 +101,13 @@ impl<'a> Context<'a> {
97101 }));
98102 }
99103
104+ /// Call `replace_or_push` on the Compositor
105+ pub fn replace_or_push_layer<T: Component>(&mut self, id: &'static str, component: T) {
106+ self.callback = Some(Box::new(move |compositor: &mut Compositor, _| {
107+ compositor.replace_or_push(id, component);
108+ }));
109+ }
110+
100111 #[inline]
101112 pub fn on_next_key(
102113 &mut self,
@@ -239,6 +250,9 @@ impl MappableCommand {
239250 move_next_long_word_start, "Move to start of next long word",
240251 move_prev_long_word_start, "Move to start of previous long word",
241252 move_next_long_word_end, "Move to end of next long word",
253+ move_prev_long_word_end, "Move to end of previous long word",
254+ move_parent_node_end, "Move to end of the parent node",
255+ move_parent_node_start, "Move to beginning of the parent node",
242256 extend_next_word_start, "Extend to start of next word",
243257 extend_prev_word_start, "Extend to start of previous word",
244258 extend_next_word_end, "Extend to end of next word",
@@ -246,6 +260,9 @@ impl MappableCommand {
246260 extend_next_long_word_start, "Extend to start of next long word",
247261 extend_prev_long_word_start, "Extend to start of previous long word",
248262 extend_next_long_word_end, "Extend to end of next long word",
263+ extend_prev_long_word_end, "Extend to end of prev long word",
264+ extend_parent_node_end, "Extend to end of the parent node",
265+ extend_parent_node_start, "Extend to beginning of the parent node",
249266 find_till_char, "Move till next occurrence of char",
250267 find_next_char, "Move to next occurrence of char",
251268 extend_till_char, "Extend till next occurrence of char",
@@ -267,6 +284,7 @@ impl MappableCommand {
267284 select_regex, "Select all regex matches inside selections",
268285 split_selection, "Split selections on regex matches",
269286 split_selection_on_newline, "Split selection on newlines",
287+ merge_selections, "Merge selections",
270288 merge_consecutive_selections, "Merge consecutive selections",
271289 search, "Search for regex pattern",
272290 rsearch, "Reverse search for regex pattern",
@@ -319,7 +337,7 @@ impl MappableCommand {
319337 goto_implementation, "Goto implementation",
320338 goto_file_start, "Goto line number <n> else file start",
321339 goto_file_end, "Goto file end",
322− goto_file, "Goto files in selection",
340+ goto_file, "Goto files/URLs in selection",
323341 goto_file_hsplit, "Goto files in selection (hsplit)",
324342 goto_file_vsplit, "Goto files in selection (vsplit)",
325343 goto_reference, "Goto references",
@@ -347,9 +365,11 @@ impl MappableCommand {
347365 goto_first_nonwhitespace, "Goto first non-blank in line",
348366 trim_selections, "Trim whitespace from selections",
349367 extend_to_line_start, "Extend to line start",
368+ extend_to_first_nonwhitespace, "Extend to first non-blank in line",
350369 extend_to_line_end, "Extend to line end",
351370 extend_to_line_end_newline, "Extend to line end",
352371 signature_help, "Show signature help",
372+ smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.",
353373 insert_tab, "Insert tab char",
354374 insert_newline, "Insert newline char",
355375 delete_char_backward, "Delete previous char",
@@ -364,6 +384,9 @@ impl MappableCommand {
364384 later, "Move forward in history",
365385 commit_undo_checkpoint, "Commit changes to new checkpoint",
366386 yank, "Yank selection",
387+ yank_to_clipboard, "Yank selections to clipboard",
388+ yank_to_primary_clipboard, "Yank selections to primary clipboard",
389+ yank_joined, "Join and yank selections",
367390 yank_joined_to_clipboard, "Join and yank selections to clipboard",
368391 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
369392 yank_joined_to_primary_clipboard, "Join and yank selections to primary clipboard",
@@ -394,6 +417,7 @@ impl MappableCommand {
394417 rotate_selections_backward, "Rotate selections backward",
395418 rotate_selection_contents_forward, "Rotate selection contents forward",
396419 rotate_selection_contents_backward, "Rotate selections contents backward",
420+ reverse_selection_contents, "Reverse selections contents",
397421 expand_selection, "Expand selection to parent syntax node",
398422 shrink_selection, "Shrink selection to previously expanded syntax node",
399423 select_next_sibling, "Select next sibling in syntax tree",
@@ -787,58 +811,72 @@ fn extend_to_line_start(cx: &mut Context) {
787811}
788812
789813fn kill_to_line_start(cx: &mut Context) {
790− let (view, doc) = current!(cx.editor);
791− let text = doc.text().slice(..);
792−
793− let selection = doc.selection(view.id).clone().transform(|range| {
794− let line = range.cursor_line(text);
795− let first_char = text.line_to_char(line);
796− let anchor = range.cursor(text);
797− let head = if anchor == first_char && line != 0 {
798− // select until previous line
799− line_end_char_index(&text, line - 1)
800− } else if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
801− if first_char + pos < anchor {
802− // select until first non-blank in line if cursor is after it
803− first_char + pos
814+ delete_by_selection_insert_mode(
815+ cx,
816+ move |text, range| {
817+ let line = range.cursor_line(text);
818+ let first_char = text.line_to_char(line);
819+ let anchor = range.cursor(text);
820+ let head = if anchor == first_char && line != 0 {
821+ // select until previous line
822+ line_end_char_index(&text, line - 1)
823+ } else if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
824+ if first_char + pos < anchor {
825+ // select until first non-blank in line if cursor is after it
826+ first_char + pos
827+ } else {
828+ // select until start of line
829+ first_char
830+ }
804831 } else {
805832 // select until start of line
806833 first_char
807− }
808− } else {
809− // select until start of line
810− first_char
811− };
812− Range::new(head, anchor)
813− });
814− delete_selection_insert_mode(doc, view, &selection);
815−
816− lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
834+ };
835+ (head, anchor)
836+ },
837+ Direction::Backward,
838+ );
817839}
818840
819841fn kill_to_line_end(cx: &mut Context) {
820− let (view, doc) = current!(cx.editor);
821− let text = doc.text().slice(..);
842+ delete_by_selection_insert_mode(
843+ cx,
844+ |text, range| {
845+ let line = range.cursor_line(text);
846+ let line_end_pos = line_end_char_index(&text, line);
847+ let pos = range.cursor(text);
822848
823− let selection = doc.selection(view.id).clone().transform(|range| {
824− let line = range.cursor_line(text);
825− let line_end_pos = line_end_char_index(&text, line);
826− let pos = range.cursor(text);
849+ // if the cursor is on the newline char delete that
850+ if pos == line_end_pos {
851+ (pos, text.line_to_char(line + 1))
852+ } else {
853+ (pos, line_end_pos)
854+ }
855+ },
856+ Direction::Forward,
857+ );
858+}
827859
828− let mut new_range = range.put_cursor(text, line_end_pos, true);
829− // don't want to remove the line separator itself if the cursor doesn't reach the end of line.
830− if pos != line_end_pos {
831− new_range.head = line_end_pos;
832− }
833− new_range
834− });
835− delete_selection_insert_mode(doc, view, &selection);
860+fn goto_first_nonwhitespace(cx: &mut Context) {
861+ let (view, doc) = current!(cx.editor);
836862
837− lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
863+ goto_first_nonwhitespace_impl(
864+ view,
865+ doc,
866+ if cx.editor.mode == Mode::Select {
867+ Movement::Extend
868+ } else {
869+ Movement::Move
870+ },
871+ )
838872}
839873
840−fn goto_first_nonwhitespace(cx: &mut Context) {
874+fn extend_to_first_nonwhitespace(cx: &mut Context) {
841875 let (view, doc) = current!(cx.editor);
876+ goto_first_nonwhitespace_impl(view, doc, Movement::Extend)
877+}
878+
879+fn goto_first_nonwhitespace_impl(view: &mut View, doc: &mut Document, movement: Movement) {
842880 let text = doc.text().slice(..);
843881
844882 let selection = doc.selection(view.id).clone().transform(|range| {
@@ -846,7 +884,7 @@ fn goto_first_nonwhitespace(cx: &mut Context) {
846884
847885 if let Some(pos) = find_first_non_whitespace_char(text.line(line)) {
848886 let pos = pos + text.line_to_char(line);
849− range.put_cursor(text, pos, cx.editor.mode == Mode::Select)
887+ range.put_cursor(text, pos, movement == Movement::Extend)
850888 } else {
851889 range
852890 }
@@ -952,6 +990,7 @@ fn align_selections(cx: &mut Context) {
952990
953991 let transaction = Transaction::change(doc.text(), changes.into_iter());
954992 doc.apply(&transaction, view.id);
993+ exit_select_mode(cx);
955994}
956995
957996fn goto_window(cx: &mut Context, align: Align) {
@@ -1043,6 +1082,10 @@ fn move_prev_long_word_start(cx: &mut Context) {
10431082 move_word_impl(cx, movement::move_prev_long_word_start)
10441083}
10451084
1085+fn move_prev_long_word_end(cx: &mut Context) {
1086+ move_word_impl(cx, movement::move_prev_long_word_end)
1087+}
1088+
10461089fn move_next_long_word_end(cx: &mut Context) {
10471090 move_word_impl(cx, movement::move_next_long_word_end)
10481091}
@@ -1067,8 +1110,7 @@ where
10671110 .transform(|range| move_fn(text, range, count, behavior));
10681111 doc.set_selection(view.id, selection);
10691112 };
1070− motion(cx.editor);
1071− cx.editor.last_motion = Some(Motion(Box::new(motion)));
1113+ cx.editor.apply_motion(motion)
10721114}
10731115
10741116fn goto_prev_paragraph(cx: &mut Context) {
@@ -1123,6 +1165,10 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11231165 let (view, doc) = current_ref!(cx.editor);
11241166 let text = doc.text();
11251167 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();
11261172 let mut paths: Vec<_> = selections
11271173 .iter()
11281174 .map(|r| text.slice(r.from()..r.to()).to_string())
@@ -1150,16 +1196,80 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11501196 .to_string(),
11511197 );
11521198 }
1199+
11531200 for sel in paths {
11541201 let p = sel.trim();
1155− if !p.is_empty() {
1156− 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) {
11571250 cx.editor.set_error(format!("Open file failed: {:?}", e));
11581251 }
11591252 }
11601253 }
11611254}
11621255
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+
11631273fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
11641274where
11651275 F: Fn(RopeSlice, Range, usize) -> Range,
@@ -1200,14 +1310,74 @@ fn extend_prev_long_word_start(cx: &mut Context) {
12001310 extend_word_impl(cx, movement::move_prev_long_word_start)
12011311}
12021312
1313+fn extend_prev_long_word_end(cx: &mut Context) {
1314+ extend_word_impl(cx, movement::move_prev_long_word_end)
1315+}
1316+
12031317fn extend_next_long_word_end(cx: &mut Context) {
12041318 extend_word_impl(cx, movement::move_next_long_word_end)
12051319}
12061320
1207−fn will_find_char<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool)
1208−where
1209− F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,
1210−{
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) {
12111381 // TODO: count is reset to 1 before next key so we move it into the closure here.
12121382 // Would be nice to carry over.
12131383 let count = cx.count();
@@ -1220,13 +1390,9 @@ where
12201390 KeyEvent {
12211391 code: KeyCode::Enter,
12221392 ..
1223− } =>
1224− // TODO: this isn't quite correct when CRLF is involved.
1225− // This hack will work in most cases, since documents don't
1226− // usually mix line endings. But we should fix it eventually
1227− // anyway.
1228− {
1229− doc!(cx.editor).line_ending.as_str().chars().next().unwrap()
1393+ } => {
1394+ find_char_line_ending(cx, count, direction, inclusive, extend);
1395+ return;
12301396 }
12311397
12321398 KeyEvent {
@@ -1239,11 +1405,18 @@ where
12391405 } => ch,
12401406 _ => return,
12411407 };
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+ };
12421418
1243− find_char_impl(cx.editor, &search_fn, inclusive, extend, ch, count);
1244− cx.editor.last_motion = Some(Motion(Box::new(move |editor: &mut Editor| {
1245− find_char_impl(editor, &search_fn, inclusive, true, ch, 1);
1246− })));
1419+ cx.editor.apply_motion(motion);
12471420 })
12481421}
12491422
@@ -1322,46 +1495,39 @@ fn find_prev_char_impl(
13221495}
13231496
13241497fn find_till_char(cx: &mut Context) {
1325− will_find_char(cx, find_next_char_impl, false, false)
1498+ find_char(cx, Direction::Forward, false, false);
13261499}
13271500
13281501fn find_next_char(cx: &mut Context) {
1329− will_find_char(cx, find_next_char_impl, true, false)
1502+ find_char(cx, Direction::Forward, true, false)
13301503}
13311504
13321505fn extend_till_char(cx: &mut Context) {
1333− will_find_char(cx, find_next_char_impl, false, true)
1506+ find_char(cx, Direction::Forward, false, true)
13341507}
13351508
13361509fn extend_next_char(cx: &mut Context) {
1337− will_find_char(cx, find_next_char_impl, true, true)
1510+ find_char(cx, Direction::Forward, true, true)
13381511}
13391512
13401513fn till_prev_char(cx: &mut Context) {
1341− will_find_char(cx, find_prev_char_impl, false, false)
1514+ find_char(cx, Direction::Backward, false, false)
13421515}
13431516
13441517fn find_prev_char(cx: &mut Context) {
1345− will_find_char(cx, find_prev_char_impl, true, false)
1518+ find_char(cx, Direction::Backward, true, false)
13461519}
13471520
13481521fn extend_till_prev_char(cx: &mut Context) {
1349− will_find_char(cx, find_prev_char_impl, false, true)
1522+ find_char(cx, Direction::Backward, false, true)
13501523}
13511524
13521525fn extend_prev_char(cx: &mut Context) {
1353− will_find_char(cx, find_prev_char_impl, true, true)
1526+ find_char(cx, Direction::Backward, true, true)
13541527}
13551528
13561529fn repeat_last_motion(cx: &mut Context) {
1357− let count = cx.count();
1358− let last_motion = cx.editor.last_motion.take();
1359− if let Some(m) = &last_motion {
1360− for _ in 0..count {
1361− m.run(cx.editor);
1362− }
1363− cx.editor.last_motion = last_motion;
1364− }
1530+ cx.editor.repeat_last_motion(cx.count())
13651531}
13661532
13671533fn replace(cx: &mut Context) {
@@ -1428,6 +1594,7 @@ where
14281594 });
14291595
14301596 doc.apply(&transaction, view.id);
1597+ exit_select_mode(cx);
14311598}
14321599
14331600fn switch_case(cx: &mut Context) {
@@ -1669,8 +1836,8 @@ fn select_regex(cx: &mut Context) {
16691836 "select:".into(),
16701837 Some(reg),
16711838 ui::completers::none,
1672− move |editor, regex, event| {
1673− let (view, doc) = current!(editor);
1839+ move |cx, regex, event| {
1840+ let (view, doc) = current!(cx.editor);
16741841 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
16751842 return;
16761843 }
@@ -1691,8 +1858,8 @@ fn split_selection(cx: &mut Context) {
16911858 "split:".into(),
16921859 Some(reg),
16931860 ui::completers::none,
1694− move |editor, regex, event| {
1695− let (view, doc) = current!(editor);
1861+ move |cx, regex, event| {
1862+ let (view, doc) = current!(cx.editor);
16961863 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
16971864 return;
16981865 }
@@ -1714,6 +1881,12 @@ fn split_selection_on_newline(cx: &mut Context) {
17141881 doc.set_selection(view.id, selection);
17151882}
17161883
1884+fn merge_selections(cx: &mut Context) {
1885+ let (view, doc) = current!(cx.editor);
1886+ let selection = doc.selection(view.id).clone().merge_ranges();
1887+ doc.set_selection(view.id, selection);
1888+}
1889+
17171890fn merge_consecutive_selections(cx: &mut Context) {
17181891 let (view, doc) = current!(cx.editor);
17191892 let selection = doc.selection(view.id).clone().merge_consecutive_ranges();
@@ -1809,11 +1982,11 @@ fn search_impl(
18091982
18101983fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
18111984 let mut items = reg
1812− .and_then(|reg| cx.editor.registers.get(reg))
1813− .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());
18141987 items.sort_unstable();
18151988 items.dedup();
1816− items.into_iter().cloned().collect()
1989+ items.into_iter().map(|value| value.to_string()).collect()
18171990}
18181991
18191992fn search(cx: &mut Context) {
@@ -1850,12 +2023,14 @@ fn searcher(cx: &mut Context, direction: Direction) {
18502023 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
18512024 .collect()
18522025 },
1853− move |editor, regex, event| {
1854− 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 {
18552030 return;
18562031 }
18572032 search_impl(
1858− editor,
2033+ cx.editor,
18592034 &contents,
18602035 &regex,
18612036 Movement::Move,
@@ -1870,11 +2045,13 @@ fn searcher(cx: &mut Context, direction: Direction) {
18702045
18712046fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
18722047 let count = cx.count();
2048+ let register = cx
2049+ .register
2050+ .unwrap_or(cx.editor.registers.last_search_register);
18732051 let config = cx.editor.config();
18742052 let scrolloff = config.scrolloff;
1875− let (_, doc) = current!(cx.editor);
1876− let registers = &cx.editor.registers;
1877− 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);
18782055 let contents = doc.text().slice(..).to_string();
18792056 let search_config = &config.search;
18802057 let case_insensitive = if search_config.smart_case {
@@ -1883,7 +2060,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
18832060 false
18842061 };
18852062 let wrap_around = search_config.wrap_around;
1886− if let Ok(regex) = RegexBuilder::new(query)
2063+ if let Ok(regex) = RegexBuilder::new(&query)
18872064 .case_insensitive(case_insensitive)
18882065 .multi_line(true)
18892066 .build()
@@ -1923,6 +2100,7 @@ fn extend_search_prev(cx: &mut Context) {
19232100}
19242101
19252102fn search_selection(cx: &mut Context) {
2103+ let register = cx.register.unwrap_or('/');
19262104 let (view, doc) = current!(cx.editor);
19272105 let contents = doc.text().slice(..);
19282106
@@ -1935,13 +2113,24 @@ fn search_selection(cx: &mut Context) {
19352113 .collect::<Vec<_>>()
19362114 .join("|");
19372115
1938− let msg = format!("register '{}' set to '{}'", '/', &regex);
1939− cx.editor.registers.push('/', regex);
1940− 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+ }
19412124}
19422125
19432126fn make_search_word_bounded(cx: &mut Context) {
1944− 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) {
19452134 Some(regex) => regex,
19462135 None => return,
19472136 };
@@ -1959,14 +2148,19 @@ fn make_search_word_bounded(cx: &mut Context) {
19592148 if !start_anchored {
19602149 new_regex.push_str("\\b");
19612150 }
1962− new_regex.push_str(regex);
2151+ new_regex.push_str(&regex);
19632152 if !end_anchored {
19642153 new_regex.push_str("\\b");
19652154 }
19662155
1967− let msg = format!("register '{}' set to '{}'", '/', &new_regex);
1968− cx.editor.registers.push('/', new_regex);
1969− 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+ }
19702164}
19712165
19722166fn global_search(cx: &mut Context) {
@@ -2005,13 +2199,11 @@ fn global_search(cx: &mut Context) {
20052199 }
20062200 }
20072201
2008− let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<FileResult>();
20092202 let config = cx.editor.config();
20102203 let smart_case = config.search.smart_case;
20112204 let file_picker_config = config.file_picker.clone();
20122205
20132206 let reg = cx.register.unwrap_or('/');
2014−
20152207 let completions = search_completions(cx, Some(reg));
20162208 ui::regex_prompt(
20172209 cx,
@@ -2024,43 +2216,67 @@ fn global_search(cx: &mut Context) {
20242216 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
20252217 .collect()
20262218 },
2027− move |_editor, regex, event| {
2219+ move |cx, regex, event| {
20282220 if event != PromptEvent::Validate {
20292221 return;
20302222 }
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();
20312231
20322232 if let Ok(matcher) = RegexMatcherBuilder::new()
20332233 .case_smart(smart_case)
20342234 .build(regex.as_str())
20352235 {
2036− let searcher = SearcherBuilder::new()
2037− .binary_detection(BinaryDetection::quit(b'\x00'))
2038− .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);
20392244
2040− let search_root = std::env::current_dir()
2041− .expect("Global search error: Failed to get current dir");
20422245 let dedup_symlinks = file_picker_config.deduplicate_links;
20432246 let absolute_root = search_root
20442247 .canonicalize()
20452248 .unwrap_or_else(|_| search_root.clone());
2046−
2047− WalkBuilder::new(search_root)
2048− .hidden(file_picker_config.hidden)
2049− .parents(file_picker_config.parents)
2050− .ignore(file_picker_config.ignore)
2051− .follow_links(file_picker_config.follow_symlinks)
2052− .git_ignore(file_picker_config.git_ignore)
2053− .git_global(file_picker_config.git_global)
2054− .git_exclude(file_picker_config.git_exclude)
2055− .max_depth(file_picker_config.max_depth)
2056− .filter_entry(move |entry| {
2057− filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2058− })
2059− .build_parallel()
2060− .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(|| {
20612276 let mut searcher = searcher.clone();
20622277 let matcher = matcher.clone();
2063− let all_matches_sx = all_matches_sx.clone();
2278+ let injector = injector_.clone();
2279+ let documents = &documents;
20642280 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
20652281 let entry = match entry {
20662282 Ok(entry) => entry,
@@ -2073,17 +2289,39 @@ fn global_search(cx: &mut Context) {
20732289 _ => return WalkState::Continue,
20742290 };
20752291
2076− let result = searcher.search_path(
2077− &matcher,
2078− entry.path(),
2079− sinks::UTF8(|line_num, _| {
2080− all_matches_sx
2081− .send(FileResult::new(entry.path(), line_num as usize - 1))
2082− .unwrap();
2083−
2084− Ok(true)
2085− }),
2086− );
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+ };
20872325
20882326 if let Err(err) = result {
20892327 log::error!(
@@ -2092,67 +2330,66 @@ fn global_search(cx: &mut Context) {
20922330 err
20932331 );
20942332 }
2095− WalkState::Continue
2333+ if stop {
2334+ WalkState::Quit
2335+ } else {
2336+ WalkState::Continue
2337+ }
20962338 })
20972339 });
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+ })
20982387 } else {
20992388 // Otherwise do nothing
21002389 // log::warn!("Global Search Invalid Pattern")
21012390 }
21022391 },
21032392 );
2104−
2105− let current_path = doc_mut!(cx.editor).path().cloned();
2106−
2107− let show_picker = async move {
2108− let all_matches: Vec<FileResult> =
2109− UnboundedReceiverStream::new(all_matches_rx).collect().await;
2110− let call: job::Callback = Callback::EditorCompositor(Box::new(
2111− move |editor: &mut Editor, compositor: &mut Compositor| {
2112− if all_matches.is_empty() {
2113− editor.set_status("No matches found");
2114− return;
2115− }
2116−
2117− let picker = FilePicker::new(
2118− all_matches,
2119− current_path,
2120− move |cx, FileResult { path, line_num }, action| {
2121− match cx.editor.open(path, action) {
2122− Ok(_) => {}
2123− Err(e) => {
2124− cx.editor.set_error(format!(
2125− "Failed to open file '{}': {}",
2126− path.display(),
2127− e
2128− ));
2129− return;
2130− }
2131− }
2132−
2133− let line_num = *line_num;
2134− let (view, doc) = current!(cx.editor);
2135− let text = doc.text();
2136− if line_num >= text.len_lines() {
2137− cx.editor.set_error("The line you jumped to does not exist anymore because the file has changed.");
2138− return;
2139− }
2140− let start = text.line_to_char(line_num);
2141− let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2142−
2143− doc.set_selection(view.id, Selection::single(start, end));
2144− align_view(doc, view, Align::Center);
2145− },
2146− |_editor, FileResult { path, line_num }| {
2147− Some((path.clone().into(), Some((*line_num, *line_num))))
2148− },
2149− );
2150− compositor.push(Box::new(overlaid(picker)));
2151− },
2152− ));
2153− Ok(call)
2154− };
2155− cx.jobs.callback(show_picker);
21562393}
21572394
21582395enum Extend {
@@ -2275,23 +2512,40 @@ enum Operation {
22752512 Change,
22762513}
22772514
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+
22782529fn delete_selection_impl(cx: &mut Context, op: Operation) {
22792530 let (view, doc) = current!(cx.editor);
22802531
22812532 let selection = doc.selection(view.id);
2533+ let only_whole_lines = selection_is_linewise(selection, doc.text());
22822534
22832535 if cx.register != Some('_') {
22842536 // first yank the selection
22852537 let text = doc.text().slice(..);
22862538 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
22872539 let reg_name = cx.register.unwrap_or('"');
2288− 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+ }
22892544 };
22902545
22912546 // then delete
2292− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
2293− (range.from(), range.to(), None)
2294− });
2547+ let transaction =
2548+ Transaction::delete_by_selection(doc.text(), selection, |range| (range.from(), range.to()));
22952549 doc.apply(&transaction, view.id);
22962550
22972551 match op {
@@ -2300,17 +2554,59 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
23002554 exit_select_mode(cx);
23012555 }
23022556 Operation::Change => {
2303− enter_insert_mode(cx);
2557+ if only_whole_lines {
2558+ open_above(cx);
2559+ } else {
2560+ enter_insert_mode(cx);
2561+ }
23042562 }
23052563 }
23062564}
23072565
23082566#[inline]
2309−fn delete_selection_insert_mode(doc: &mut Document, view: &mut View, selection: &Selection) {
2310− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
2311− (range.from(), range.to(), None)
2312− });
2567+fn delete_by_selection_insert_mode(
2568+ cx: &mut Context,
2569+ mut f: impl FnMut(RopeSlice, &Range) -> Deletion,
2570+ direction: Direction,
2571+) {
2572+ let (view, doc) = current!(cx.editor);
2573+ let text = doc.text().slice(..);
2574+ let mut selection = SmallVec::new();
2575+ let mut insert_newline = false;
2576+ let text_len = text.len_chars();
2577+ let mut transaction =
2578+ Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| {
2579+ let (start, end) = f(text, range);
2580+ if direction == Direction::Forward {
2581+ let mut range = *range;
2582+ if range.head > range.anchor {
2583+ insert_newline |= end == text_len;
2584+ // move the cursor to the right so that the selection
2585+ // doesn't shrink when deleting forward (so the text appears to
2586+ // move to left)
2587+ // += 1 is enough here as the range is normalized to grapheme boundaries
2588+ // later anyway
2589+ range.head += 1;
2590+ }
2591+ selection.push(range);
2592+ }
2593+ (start, end)
2594+ });
2595+
2596+ // in case we delete the last character and the cursor would be moved to the EOF char
2597+ // insert a newline, just like when entering append mode
2598+ if insert_newline {
2599+ transaction = transaction.insert_at_eof(doc.line_ending.as_str().into());
2600+ }
2601+
2602+ if direction == Direction::Forward {
2603+ doc.set_selection(
2604+ view.id,
2605+ Selection::new(selection, doc.selection(view.id).primary_index()),
2606+ );
2607+ }
23132608 doc.apply(&transaction, view.id);
2609+ lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
23142610}
23152611
23162612fn delete_selection(cx: &mut Context) {
@@ -2384,6 +2680,10 @@ fn insert_mode(cx: &mut Context) {
23842680 .transform(|range| Range::new(range.to(), range.from()));
23852681
23862682 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();
23872687}
23882688
23892689// inserts at the end of each selection
@@ -2420,6 +2720,10 @@ fn append_mode(cx: &mut Context) {
24202720
24212721fn file_picker(cx: &mut Context) {
24222722 let root = find_workspace().0;
2723+ if !root.exists() {
2724+ cx.editor.set_error("Workspace directory does not exist");
2725+ return;
2726+ }
24232727 let picker = ui::file_picker(root, &cx.editor.config());
24242728 cx.push_layer(Box::new(overlaid(picker)));
24252729}
@@ -2440,8 +2744,14 @@ fn file_picker_in_current_buffer_directory(cx: &mut Context) {
24402744 let picker = ui::file_picker(path, &cx.editor.config());
24412745 cx.push_layer(Box::new(overlaid(picker)));
24422746}
2747+
24432748fn file_picker_in_current_directory(cx: &mut Context) {
2444− 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+ }
24452755 let picker = ui::file_picker(cwd, &cx.editor.config());
24462756 cx.push_layer(Box::new(overlaid(picker)));
24472757}
@@ -2454,6 +2764,7 @@ fn buffer_picker(cx: &mut Context) {
24542764 path: Option<PathBuf>,
24552765 is_modified: bool,
24562766 is_current: bool,
2767+ focused_at: std::time::Instant,
24572768 }
24582769
24592770 impl ui::menu::Item for BufferMeta {
@@ -2486,28 +2797,31 @@ fn buffer_picker(cx: &mut Context) {
24862797 path: doc.path().cloned(),
24872798 is_modified: doc.is_modified(),
24882799 is_current: doc.id() == current,
2800+ focused_at: doc.focused_at,
24892801 };
24902802
2491− let picker = FilePicker::new(
2492− cx.editor
2493− .documents
2494− .values()
2495− .map(|doc| new_meta(doc))
2496− .collect(),
2497− (),
2498− |cx, meta, action| {
2499− cx.editor.switch(meta.id, action);
2500− },
2501− |editor, meta| {
2502− let doc = &editor.documents.get(&meta.id)?;
2503− let &view_id = doc.selections().keys().next()?;
2504− let line = doc
2505− .selection(view_id)
2506− .primary()
2507− .cursor_line(doc.text().slice(..));
2508− Some((meta.id.into(), Some((line, line))))
2509− },
2510− );
2803+ let mut items = cx
2804+ .editor
2805+ .documents
2806+ .values()
2807+ .map(|doc| new_meta(doc))
2808+ .collect::<Vec<BufferMeta>>();
2809+
2810+ // mru
2811+ items.sort_unstable_by_key(|item| std::cmp::Reverse(item.focused_at));
2812+
2813+ let picker = Picker::new(items, (), |cx, meta, action| {
2814+ cx.editor.switch(meta.id, action);
2815+ })
2816+ .with_preview(|editor, meta| {
2817+ let doc = &editor.documents.get(&meta.id)?;
2818+ let &view_id = doc.selections().keys().next()?;
2819+ let line = doc
2820+ .selection(view_id)
2821+ .primary()
2822+ .cursor_line(doc.text().slice(..));
2823+ Some((meta.id.into(), Some((line, line))))
2824+ });
25112825 cx.push_layer(Box::new(overlaid(picker)));
25122826}
25132827
@@ -2573,7 +2887,7 @@ fn jumplist_picker(cx: &mut Context) {
25732887 }
25742888 };
25752889
2576− let picker = FilePicker::new(
2890+ let picker = Picker::new(
25772891 cx.editor
25782892 .tree
25792893 .views()
@@ -2587,16 +2901,18 @@ fn jumplist_picker(cx: &mut Context) {
25872901 |cx, meta, action| {
25882902 cx.editor.switch(meta.id, action);
25892903 let config = cx.editor.config();
2590− let (view, doc) = current!(cx.editor);
2904+ let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id));
25912905 doc.set_selection(view.id, meta.selection.clone());
2592− view.ensure_cursor_in_view_center(doc, config.scrolloff);
2593− },
2594− |editor, meta| {
2595− let doc = &editor.documents.get(&meta.id)?;
2596− let line = meta.selection.primary().cursor_line(doc.text().slice(..));
2597− Some((meta.path.clone()?.into(), Some((line, line))))
2906+ if action.align_view(view, doc.id()) {
2907+ view.ensure_cursor_in_view_center(doc, config.scrolloff);
2908+ }
25982909 },
2599− );
2910+ )
2911+ .with_preview(|editor, meta| {
2912+ let doc = &editor.documents.get(&meta.id)?;
2913+ let line = meta.selection.primary().cursor_line(doc.text().slice(..));
2914+ Some((meta.id.into(), Some((line, line))))
2915+ });
26002916 cx.push_layer(Box::new(overlaid(picker)));
26012917}
26022918
@@ -2630,6 +2946,9 @@ impl ui::menu::Item for MappableCommand {
26302946}
26312947
26322948pub fn command_palette(cx: &mut Context) {
2949+ let register = cx.register;
2950+ let count = cx.count;
2951+
26332952 cx.callback = Some(Box::new(
26342953 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
26352954 let keymap = compositor.find::<ui::EditorView>().unwrap().keymaps.map()
@@ -2647,8 +2966,8 @@ pub fn command_palette(cx: &mut Context) {
26472966
26482967 let picker = Picker::new(commands, keymap, move |cx, command, _action| {
26492968 let mut ctx = Context {
2650− register: None,
2651− count: std::num::NonZeroUsize::new(1),
2969+ register,
2970+ count,
26522971 editor: cx.editor,
26532972 callback: None,
26542973 on_next_key_callback: None,
@@ -2687,42 +3006,106 @@ fn last_picker(cx: &mut Context) {
26873006 }));
26883007}
26893008
2690−// I inserts at the first nonwhitespace character of each line with a selection
3009+/// Fallback position to use for [`insert_with_indent`].
3010+enum IndentFallbackPos {
3011+ LineStart,
3012+ LineEnd,
3013+}
3014+
3015+// `I` inserts at the first nonwhitespace character of each line with a selection.
3016+// If the line is empty, automatically indent.
26913017fn insert_at_line_start(cx: &mut Context) {
2692− goto_first_nonwhitespace(cx);
2693− enter_insert_mode(cx);
3018+ insert_with_indent(cx, IndentFallbackPos::LineStart);
26943019}
26953020
2696−// A inserts at the end of each line with a selection
3021+// `A` inserts at the end of each line with a selection.
3022+// If the line is empty, automatically indent.
26973023fn insert_at_line_end(cx: &mut Context) {
3024+ insert_with_indent(cx, IndentFallbackPos::LineEnd);
3025+}
3026+
3027+// Enter insert mode and auto-indent the current line if it is empty.
3028+// If the line is not empty, move the cursor to the specified fallback position.
3029+fn insert_with_indent(cx: &mut Context, cursor_fallback: IndentFallbackPos) {
26983030 enter_insert_mode(cx);
3031+
26993032 let (view, doc) = current!(cx.editor);
27003033
2701− let selection = doc.selection(view.id).clone().transform(|range| {
2702− let text = doc.text().slice(..);
2703− let line = range.cursor_line(text);
2704− let pos = line_end_char_index(&text, line);
2705− Range::new(pos, pos)
2706− });
2707− doc.set_selection(view.id, selection);
2708−}
3034+ let text = doc.text().slice(..);
3035+ let contents = doc.text();
3036+ let selection = doc.selection(view.id);
27093037
2710−// Creates an LspCallback that waits for formatting changes to be computed. When they're done,
2711−// it applies them, but only if the doc hasn't changed.
2712−//
2713−// TODO: provide some way to cancel this, probably as part of a more general job cancellation
2714−// scheme
2715−async fn make_format_callback(
2716− doc_id: DocumentId,
2717− doc_version: i32,
2718− view_id: ViewId,
2719− format: impl Future<Output = Result<Transaction, FormatterError>> + Send + 'static,
2720− write: Option<(Option<PathBuf>, bool)>,
2721−) -> anyhow::Result<job::Callback> {
2722− let format = format.await;
3038+ let language_config = doc.language_config();
3039+ let syntax = doc.syntax();
3040+ let tab_width = doc.tab_width();
27233041
2724− let call: job::Callback = Callback::Editor(Box::new(move |editor| {
2725− if !editor.documents.contains_key(&doc_id) || !editor.tree.contains(view_id) {
3042+ let mut ranges = SmallVec::with_capacity(selection.len());
3043+ let mut offs = 0;
3044+
3045+ let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
3046+ let cursor_line = range.cursor_line(text);
3047+ let cursor_line_start = text.line_to_char(cursor_line);
3048+
3049+ if line_end_char_index(&text, cursor_line) == cursor_line_start {
3050+ // line is empty => auto indent
3051+ let line_end_index = cursor_line_start;
3052+
3053+ let indent = indent::indent_for_newline(
3054+ language_config,
3055+ syntax,
3056+ &doc.config.load().indent_heuristic,
3057+ &doc.indent_style,
3058+ tab_width,
3059+ text,
3060+ cursor_line,
3061+ line_end_index,
3062+ cursor_line,
3063+ );
3064+
3065+ // calculate new selection ranges
3066+ let pos = offs + cursor_line_start;
3067+ let indent_width = indent.chars().count();
3068+ ranges.push(Range::point(pos + indent_width));
3069+ offs += indent_width;
3070+
3071+ (line_end_index, line_end_index, Some(indent.into()))
3072+ } else {
3073+ // move cursor to the fallback position
3074+ let pos = match cursor_fallback {
3075+ IndentFallbackPos::LineStart => {
3076+ find_first_non_whitespace_char(text.line(cursor_line))
3077+ .map(|ws_offset| ws_offset + cursor_line_start)
3078+ .unwrap_or(cursor_line_start)
3079+ }
3080+ IndentFallbackPos::LineEnd => line_end_char_index(&text, cursor_line),
3081+ };
3082+
3083+ ranges.push(range.put_cursor(text, pos + offs, cx.editor.mode == Mode::Select));
3084+
3085+ (cursor_line_start, cursor_line_start, None)
3086+ }
3087+ });
3088+
3089+ transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
3090+ doc.apply(&transaction, view.id);
3091+}
3092+
3093+// Creates an LspCallback that waits for formatting changes to be computed. When they're done,
3094+// it applies them, but only if the doc hasn't changed.
3095+//
3096+// TODO: provide some way to cancel this, probably as part of a more general job cancellation
3097+// scheme
3098+async fn make_format_callback(
3099+ doc_id: DocumentId,
3100+ doc_version: i32,
3101+ view_id: ViewId,
3102+ format: impl Future<Output = Result<Transaction, FormatterError>> + Send + 'static,
3103+ write: Option<(Option<PathBuf>, bool)>,
3104+) -> anyhow::Result<job::Callback> {
3105+ let format = format.await;
3106+
3107+ let call: job::Callback = Callback::Editor(Box::new(move |editor| {
3108+ if !editor.documents.contains_key(&doc_id) || !editor.tree.contains(view_id) {
27263109 return;
27273110 }
27283111
@@ -2775,6 +3158,7 @@ fn open(cx: &mut Context, open: Open) {
27753158 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
27763159 Open::Above => range.from(),
27773160 });
3161+
27783162 let new_line = match open {
27793163 // adjust position to the end of the line (next line - 1)
27803164 Open::Below => cursor_line + 1,
@@ -2782,13 +3166,15 @@ fn open(cx: &mut Context, open: Open) {
27823166 Open::Above => cursor_line,
27833167 };
27843168
3169+ let line_num = new_line.saturating_sub(1);
3170+
27853171 // Index to insert newlines after, as well as the char width
27863172 // to use to compensate for those inserted newlines.
27873173 let (line_end_index, line_end_offset_width) = if new_line == 0 {
27883174 (0, 0)
27893175 } else {
27903176 (
2791− line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
3177+ line_end_char_index(&text, line_num),
27923178 doc.line_ending.len_chars(),
27933179 )
27943180 };
@@ -2796,13 +3182,15 @@ fn open(cx: &mut Context, open: Open) {
27963182 let indent = indent::indent_for_newline(
27973183 doc.language_config(),
27983184 doc.syntax(),
3185+ &doc.config.load().indent_heuristic,
27993186 &doc.indent_style,
28003187 doc.tab_width(),
28013188 text,
2802− new_line.saturating_sub(1),
3189+ line_num,
28033190 line_end_index,
28043191 cursor_line,
28053192 );
3193+
28063194 let indent_len = indent.len();
28073195 let mut text = String::with_capacity(1 + indent_len);
28083196 text.push_str(doc.line_ending.as_str());
@@ -2962,7 +3350,7 @@ fn exit_select_mode(cx: &mut Context) {
29623350
29633351fn goto_first_diag(cx: &mut Context) {
29643352 let (view, doc) = current!(cx.editor);
2965− let selection = match doc.diagnostics().first() {
3353+ let selection = match doc.shown_diagnostics().next() {
29663354 Some(diag) => Selection::single(diag.range.start, diag.range.end),
29673355 None => return,
29683356 };
@@ -2971,7 +3359,7 @@ fn goto_first_diag(cx: &mut Context) {
29713359
29723360fn goto_last_diag(cx: &mut Context) {
29733361 let (view, doc) = current!(cx.editor);
2974− let selection = match doc.diagnostics().last() {
3362+ let selection = match doc.shown_diagnostics().last() {
29753363 Some(diag) => Selection::single(diag.range.start, diag.range.end),
29763364 None => return,
29773365 };
@@ -2987,10 +3375,9 @@ fn goto_next_diag(cx: &mut Context) {
29873375 .cursor(doc.text().slice(..));
29883376
29893377 let diag = doc
2990− .diagnostics()
2991− .iter()
3378+ .shown_diagnostics()
29923379 .find(|diag| diag.range.start > cursor_pos)
2993− .or_else(|| doc.diagnostics().first());
3380+ .or_else(|| doc.shown_diagnostics().next());
29943381
29953382 let selection = match diag {
29963383 Some(diag) => Selection::single(diag.range.start, diag.range.end),
@@ -3008,11 +3395,10 @@ fn goto_prev_diag(cx: &mut Context) {
30083395 .cursor(doc.text().slice(..));
30093396
30103397 let diag = doc
3011− .diagnostics()
3012− .iter()
3398+ .shown_diagnostics()
30133399 .rev()
30143400 .find(|diag| diag.range.start < cursor_pos)
3015− .or_else(|| doc.diagnostics().last());
3401+ .or_else(|| doc.shown_diagnostics().last());
30163402
30173403 let selection = match diag {
30183404 // NOTE: the selection is reversed because we're jumping to the
@@ -3103,8 +3489,7 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
31033489
31043490 doc.set_selection(view.id, selection)
31053491 };
3106− motion(cx.editor);
3107− cx.editor.last_motion = Some(Motion(Box::new(motion)));
3492+ cx.editor.apply_motion(motion);
31083493}
31093494
31103495/// Returns the [Range] for a [Hunk] in the given text.
@@ -3167,23 +3552,19 @@ pub mod insert {
31673552 use helix_lsp::lsp;
31683553 // if ch matches completion char, trigger completion
31693554 let doc = doc_mut!(cx.editor);
3170− let language_server = match doc.language_server() {
3171− Some(language_server) => language_server,
3172− None => return,
3173− };
3174−
3175− let capabilities = language_server.capabilities();
3555+ let trigger_completion = doc
3556+ .language_servers_with_feature(LanguageServerFeature::Completion)
3557+ .any(|ls| {
3558+ // TODO: what if trigger is multiple chars long
3559+ matches!(&ls.capabilities().completion_provider, Some(lsp::CompletionOptions {
3560+ trigger_characters: Some(triggers),
3561+ ..
3562+ }) if triggers.iter().any(|trigger| trigger.contains(ch)))
3563+ });
31763564
3177− if let Some(lsp::CompletionOptions {
3178− trigger_characters: Some(triggers),
3179− ..
3180− }) = &capabilities.completion_provider
3181− {
3182− // TODO: what if trigger is multiple chars long
3183− if triggers.iter().any(|trigger| trigger.contains(ch)) {
3184− cx.editor.clear_idle_timer();
3185− super::completion(cx);
3186− }
3565+ if trigger_completion {
3566+ cx.editor.clear_idle_timer();
3567+ super::completion(cx);
31873568 }
31883569 }
31893570
@@ -3191,12 +3572,12 @@ pub mod insert {
31913572 use helix_lsp::lsp;
31923573 // if ch matches signature_help char, trigger
31933574 let doc = doc_mut!(cx.editor);
3194− // The language_server!() macro is not used here since it will
3195− // print an "LSP not active for current buffer" message on
3196− // every keypress.
3197− let language_server = match doc.language_server() {
3198− Some(language_server) => language_server,
3199− None => return,
3575+ // TODO support multiple language servers (not just the first that is found), likely by merging UI somehow
3576+ let Some(language_server) = doc
3577+ .language_servers_with_feature(LanguageServerFeature::SignatureHelp)
3578+ .next()
3579+ else {
3580+ return;
32003581 };
32013582
32023583 let capabilities = language_server.capabilities();
@@ -3235,6 +3616,7 @@ pub mod insert {
32353616 }
32363617
32373618 use helix_core::auto_pairs;
3619+ use helix_view::editor::SmartTabConfig;
32383620
32393621 pub fn insert_char(cx: &mut Context, c: char) {
32403622 let (view, doc) = current_ref!(cx.editor);
@@ -3260,6 +3642,31 @@ pub mod insert {
32603642 }
32613643 }
32623644
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+
32633670 pub fn insert_tab(cx: &mut Context) {
32643671 let (view, doc) = current!(cx.editor);
32653672 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
@@ -3315,6 +3722,7 @@ pub mod insert {
33153722 let indent = indent::indent_for_newline(
33163723 doc.language_config(),
33173724 doc.syntax(),
3725+ &doc.config.load().indent_heuristic,
33183726 &doc.indent_style,
33193727 doc.tab_width(),
33203728 text,
@@ -3388,10 +3796,10 @@ pub mod insert {
33883796 let auto_pairs = doc.auto_pairs(cx.editor);
33893797
33903798 let transaction =
3391− Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3799+ Transaction::delete_by_selection(doc.text(), doc.selection(view.id), |range| {
33923800 let pos = range.cursor(text);
33933801 if pos == 0 {
3394− return (pos, pos, None);
3802+ return (pos, pos);
33953803 }
33963804 let line_start_pos = text.line_to_char(range.cursor_line(text));
33973805 // consider to delete by indent level if all characters before `pos` are indent units.
@@ -3399,11 +3807,7 @@ pub mod insert {
33993807 if !fragment.is_empty() && fragment.chars().all(|ch| ch == ' ' || ch == '\t') {
34003808 if text.get_char(pos.saturating_sub(1)) == Some('\t') {
34013809 // fast path, delete one char
3402− (
3403− graphemes::nth_prev_grapheme_boundary(text, pos, 1),
3404− pos,
3405− None,
3406− )
3810+ (graphemes::nth_prev_grapheme_boundary(text, pos, 1), pos)
34073811 } else {
34083812 let width: usize = fragment
34093813 .chars()
@@ -3430,7 +3834,7 @@ pub mod insert {
34303834 _ => break,
34313835 }
34323836 }
3433− (start, pos, None) // delete!
3837+ (start, pos) // delete!
34343838 }
34353839 } else {
34363840 match (
@@ -3448,17 +3852,12 @@ pub mod insert {
34483852 (
34493853 graphemes::nth_prev_grapheme_boundary(text, pos, count),
34503854 graphemes::nth_next_grapheme_boundary(text, pos, count),
3451− None,
34523855 )
34533856 }
34543857 _ =>
34553858 // delete 1 char
34563859 {
3457− (
3458− graphemes::nth_prev_grapheme_boundary(text, pos, count),
3459− pos,
3460− None,
3461− )
3860+ (graphemes::nth_prev_grapheme_boundary(text, pos, count), pos)
34623861 }
34633862 }
34643863 }
@@ -3471,50 +3870,40 @@ pub mod insert {
34713870
34723871 pub fn delete_char_forward(cx: &mut Context) {
34733872 let count = cx.count();
3474− let (view, doc) = current!(cx.editor);
3475− let text = doc.text().slice(..);
3476− let transaction =
3477− Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| {
3873+ delete_by_selection_insert_mode(
3874+ cx,
3875+ |text, range| {
34783876 let pos = range.cursor(text);
3479− (
3480− pos,
3481− graphemes::nth_next_grapheme_boundary(text, pos, count),
3482− None,
3483− )
3484− });
3485− doc.apply(&transaction, view.id);
3486−
3487− lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
3877+ (pos, graphemes::nth_next_grapheme_boundary(text, pos, count))
3878+ },
3879+ Direction::Forward,
3880+ )
34883881 }
34893882
34903883 pub fn delete_word_backward(cx: &mut Context) {
34913884 let count = cx.count();
3492− let (view, doc) = current!(cx.editor);
3493− let text = doc.text().slice(..);
3494−
3495− let selection = doc.selection(view.id).clone().transform(|range| {
3496− let anchor = movement::move_prev_word_start(text, range, count).from();
3497− let next = Range::new(anchor, range.cursor(text));
3498− exclude_cursor(text, next, range)
3499− });
3500− delete_selection_insert_mode(doc, view, &selection);
3501−
3502− lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
3885+ delete_by_selection_insert_mode(
3886+ cx,
3887+ |text, range| {
3888+ let anchor = movement::move_prev_word_start(text, *range, count).from();
3889+ let next = Range::new(anchor, range.cursor(text));
3890+ let range = exclude_cursor(text, next, *range);
3891+ (range.from(), range.to())
3892+ },
3893+ Direction::Backward,
3894+ );
35033895 }
35043896
35053897 pub fn delete_word_forward(cx: &mut Context) {
35063898 let count = cx.count();
3507− let (view, doc) = current!(cx.editor);
3508− let text = doc.text().slice(..);
3509−
3510− let selection = doc.selection(view.id).clone().transform(|range| {
3511− let head = movement::move_next_word_end(text, range, count).to();
3512− Range::new(range.cursor(text), head)
3513− });
3514−
3515− delete_selection_insert_mode(doc, view, &selection);
3516−
3517− lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic);
3899+ delete_by_selection_insert_mode(
3900+ cx,
3901+ |text, range| {
3902+ let head = movement::move_next_word_end(text, *range, count).to();
3903+ (range.cursor(text), head)
3904+ },
3905+ Direction::Forward,
3906+ );
35183907 }
35193908}
35203909
@@ -3574,34 +3963,21 @@ fn commit_undo_checkpoint(cx: &mut Context) {
35743963// Yank / Paste
35753964
35763965fn yank(cx: &mut Context) {
3577− let (view, doc) = current!(cx.editor);
3578− let text = doc.text().slice(..);
3579−
3580− let values: Vec<String> = doc
3581− .selection(view.id)
3582− .fragments(text)
3583− .map(Cow::into_owned)
3584− .collect();
3585−
3586− let msg = format!(
3587− "yanked {} selection(s) to register {}",
3588− values.len(),
3589− cx.register.unwrap_or('"')
3590− );
3966+ yank_impl(cx.editor, cx.register.unwrap_or('"'));
3967+ exit_select_mode(cx);
3968+}
35913969
3592− cx.editor
3593− .registers
3594− .write(cx.register.unwrap_or('"'), values);
3970+fn yank_to_clipboard(cx: &mut Context) {
3971+ yank_impl(cx.editor, '+');
3972+ exit_select_mode(cx);
3973+}
35953974
3596− cx.editor.set_status(msg);
3975+fn yank_to_primary_clipboard(cx: &mut Context) {
3976+ yank_impl(cx.editor, '*');
35973977 exit_select_mode(cx);
35983978}
35993979
3600−fn yank_joined_to_clipboard_impl(
3601− editor: &mut Editor,
3602− separator: &str,
3603− clipboard_type: ClipboardType,
3604−) -> anyhow::Result<()> {
3980+fn yank_impl(editor: &mut Editor, register: char) {
36053981 let (view, doc) = current!(editor);
36063982 let text = doc.text().slice(..);
36073983
@@ -3610,74 +3986,79 @@ fn yank_joined_to_clipboard_impl(
36103986 .fragments(text)
36113987 .map(Cow::into_owned)
36123988 .collect();
3989+ let selections = values.len();
36133990
3614− let clipboard_text = match clipboard_type {
3615− ClipboardType::Clipboard => "system clipboard",
3616− ClipboardType::Selection => "primary clipboard",
3617− };
3618−
3619− let msg = format!(
3620− "joined and yanked {} selection(s) to {}",
3621− values.len(),
3622− clipboard_text,
3623− );
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+ }
3998+}
36243999
3625− let joined = values.join(separator);
4000+fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
4001+ let (view, doc) = current!(editor);
4002+ let text = doc.text().slice(..);
36264003
3627− editor
3628− .clipboard_provider
3629− .set_contents(joined, clipboard_type)
3630− .context("Couldn't set system clipboard content")?;
4004+ let selection = doc.selection(view.id);
4005+ let selections = selection.len();
4006+ let joined = selection
4007+ .fragments(text)
4008+ .fold(String::new(), |mut acc, fragment| {
4009+ if !acc.is_empty() {
4010+ acc.push_str(separator);
4011+ }
4012+ acc.push_str(&fragment);
4013+ acc
4014+ });
36314015
3632− 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+ }
4023+}
36334024
3634− Ok(())
4025+fn yank_joined(cx: &mut Context) {
4026+ let separator = doc!(cx.editor).line_ending.as_str();
4027+ yank_joined_impl(cx.editor, separator, cx.register.unwrap_or('"'));
4028+ exit_select_mode(cx);
36354029}
36364030
36374031fn yank_joined_to_clipboard(cx: &mut Context) {
36384032 let line_ending = doc!(cx.editor).line_ending;
3639− let _ =
3640− yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Clipboard);
4033+ yank_joined_impl(cx.editor, line_ending.as_str(), '+');
36414034 exit_select_mode(cx);
36424035}
36434036
3644−fn yank_main_selection_to_clipboard_impl(
3645− editor: &mut Editor,
3646− clipboard_type: ClipboardType,
3647−) -> anyhow::Result<()> {
4037+fn yank_joined_to_primary_clipboard(cx: &mut Context) {
4038+ let line_ending = doc!(cx.editor).line_ending;
4039+ yank_joined_impl(cx.editor, line_ending.as_str(), '*');
4040+ exit_select_mode(cx);
4041+}
4042+
4043+fn yank_primary_selection_impl(editor: &mut Editor, register: char) {
36484044 let (view, doc) = current!(editor);
36494045 let text = doc.text().slice(..);
36504046
3651− let message_text = match clipboard_type {
3652− ClipboardType::Clipboard => "yanked main selection to system clipboard",
3653− ClipboardType::Selection => "yanked main selection to primary clipboard",
3654− };
3655−
3656− let value = doc.selection(view.id).primary().fragment(text);
4047+ let selection = doc.selection(view.id).primary().fragment(text).to_string();
36574048
3658− if let Err(e) = editor
3659− .clipboard_provider
3660− .set_contents(value.into_owned(), clipboard_type)
3661− {
3662− 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()),
36634052 }
3664−
3665− editor.set_status(message_text);
3666− Ok(())
36674053}
36684054
36694055fn yank_main_selection_to_clipboard(cx: &mut Context) {
3670− let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard);
3671−}
3672−
3673−fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3674− let line_ending = doc!(cx.editor).line_ending;
3675− let _ =
3676− 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);
36774058}
36784059
36794060fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
3680− let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection);
4061+ yank_primary_selection_impl(cx.editor, '*');
36814062 exit_select_mode(cx);
36824063}
36834064
@@ -3775,146 +4156,99 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
37754156 };
37764157 let (view, doc) = current!(cx.editor);
37774158 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
3778−}
3779−
3780−fn paste_clipboard_impl(
3781− editor: &mut Editor,
3782− action: Paste,
3783− clipboard_type: ClipboardType,
3784− count: usize,
3785−) -> anyhow::Result<()> {
3786− let (view, doc) = current!(editor);
3787− match editor.clipboard_provider.get_contents(clipboard_type) {
3788− Ok(contents) => {
3789− paste_impl(&[contents], doc, view, action, count, editor.mode);
3790− Ok(())
3791− }
3792− Err(e) => Err(e.context("Couldn't get system clipboard contents")),
3793− }
4159+ exit_select_mode(cx);
37944160}
37954161
37964162fn paste_clipboard_after(cx: &mut Context) {
3797− let _ = paste_clipboard_impl(
3798− cx.editor,
3799− Paste::After,
3800− ClipboardType::Clipboard,
3801− cx.count(),
3802− );
4163+ paste(cx.editor, '+', Paste::After, cx.count());
4164+ exit_select_mode(cx);
38034165}
38044166
38054167fn paste_clipboard_before(cx: &mut Context) {
3806− let _ = paste_clipboard_impl(
3807− cx.editor,
3808− Paste::Before,
3809− ClipboardType::Clipboard,
3810− cx.count(),
3811− );
4168+ paste(cx.editor, '+', Paste::Before, cx.count());
4169+ exit_select_mode(cx);
38124170}
38134171
38144172fn paste_primary_clipboard_after(cx: &mut Context) {
3815− let _ = paste_clipboard_impl(
3816− cx.editor,
3817− Paste::After,
3818− ClipboardType::Selection,
3819− cx.count(),
3820− );
4173+ paste(cx.editor, '*', Paste::After, cx.count());
4174+ exit_select_mode(cx);
38214175}
38224176
38234177fn paste_primary_clipboard_before(cx: &mut Context) {
3824− let _ = paste_clipboard_impl(
3825− cx.editor,
3826− Paste::Before,
3827− ClipboardType::Selection,
3828− cx.count(),
3829− );
4178+ paste(cx.editor, '*', Paste::Before, cx.count());
4179+ exit_select_mode(cx);
38304180}
38314181
38324182fn replace_with_yanked(cx: &mut Context) {
3833− let count = cx.count();
3834− let reg_name = cx.register.unwrap_or('"');
3835− let (view, doc) = current!(cx.editor);
3836− let registers = &mut cx.editor.registers;
3837−
3838− if let Some(values) = registers.read(reg_name) {
3839− if !values.is_empty() {
3840− let repeat = std::iter::repeat(
3841− values
3842− .last()
3843− .map(|value| Tendril::from(&value.repeat(count)))
3844− .unwrap(),
3845− );
3846− let mut values = values
3847− .iter()
3848− .map(|value| Tendril::from(&value.repeat(count)))
3849− .chain(repeat);
3850− let selection = doc.selection(view.id);
3851− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3852− if !range.is_empty() {
3853− (range.from(), range.to(), Some(values.next().unwrap()))
3854− } else {
3855− (range.from(), range.to(), None)
3856− }
3857− });
3858−
3859− doc.apply(&transaction, view.id);
3860− exit_select_mode(cx);
3861− }
3862− }
4183+ replace_with_yanked_impl(cx.editor, cx.register.unwrap_or('"'), cx.count());
4184+ exit_select_mode(cx);
38634185}
38644186
3865−fn replace_selections_with_clipboard_impl(
3866− cx: &mut Context,
3867− clipboard_type: ClipboardType,
3868−) -> anyhow::Result<()> {
3869− let count = cx.count();
3870− let (view, doc) = current!(cx.editor);
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();
38714192
3872− match cx.editor.clipboard_provider.get_contents(clipboard_type) {
3873− Ok(contents) => {
3874− let selection = doc.selection(view.id);
3875− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3876− (
3877− range.from(),
3878− range.to(),
3879− Some(contents.repeat(count).as_str().into()),
3880− )
3881− });
3882−
3883− doc.apply(&transaction, view.id);
3884− 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)
38854210 }
3886− Err(e) => return Err(e.context("Couldn't get system clipboard contents")),
3887− }
4211+ });
38884212
3889− exit_select_mode(cx);
3890− Ok(())
4213+ doc.apply(&transaction, view.id);
38914214}
38924215
38934216fn replace_selections_with_clipboard(cx: &mut Context) {
3894− let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard);
4217+ replace_with_yanked_impl(cx.editor, '+', cx.count());
4218+ exit_select_mode(cx);
38954219}
38964220
38974221fn replace_selections_with_primary_clipboard(cx: &mut Context) {
3898− let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Selection);
4222+ replace_with_yanked_impl(cx.editor, '*', cx.count());
4223+ exit_select_mode(cx);
38994224}
39004225
3901−fn paste(cx: &mut Context, pos: Paste) {
3902− let count = cx.count();
3903− let reg_name = cx.register.unwrap_or('"');
3904− let (view, doc) = current!(cx.editor);
3905− 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();
39064229
3907− if let Some(values) = registers.read(reg_name) {
3908− paste_impl(values, doc, view, pos, count, cx.editor.mode);
3909− }
4230+ let (view, doc) = current!(editor);
4231+ paste_impl(&values, doc, view, pos, count, editor.mode);
39104232}
39114233
39124234fn paste_after(cx: &mut Context) {
3913− 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);
39144242}
39154243
39164244fn paste_before(cx: &mut Context) {
3917− 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);
39184252}
39194253
39204254fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
@@ -3953,6 +4287,7 @@ fn indent(cx: &mut Context) {
39534287 }),
39544288 );
39554289 doc.apply(&transaction, view.id);
4290+ exit_select_mode(cx);
39564291}
39574292
39584293fn unindent(cx: &mut Context) {
@@ -3992,71 +4327,76 @@ fn unindent(cx: &mut Context) {
39924327 let transaction = Transaction::change(doc.text(), changes.into_iter());
39934328
39944329 doc.apply(&transaction, view.id);
4330+ exit_select_mode(cx);
39954331}
39964332
39974333fn format_selections(cx: &mut Context) {
39984334 use helix_lsp::{lsp, util::range_to_lsp_range};
39994335
40004336 let (view, doc) = current!(cx.editor);
4337+ let view_id = view.id;
40014338
40024339 // via lsp if available
40034340 // TODO: else via tree-sitter indentation calculations
40044341
4005− let language_server = match doc.language_server() {
4006− Some(language_server) => language_server,
4007− None => return,
4342+ if doc.selection(view_id).len() != 1 {
4343+ cx.editor
4344+ .set_error("format_selections only supports a single selection for now");
4345+ return;
4346+ }
4347+
4348+ // TODO extra LanguageServerFeature::FormatSelections?
4349+ // maybe such that LanguageServerFeature::Format contains it as well
4350+ let Some(language_server) = doc
4351+ .language_servers_with_feature(LanguageServerFeature::Format)
4352+ .find(|ls| {
4353+ matches!(
4354+ ls.capabilities().document_range_formatting_provider,
4355+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
4356+ )
4357+ })
4358+ else {
4359+ cx.editor
4360+ .set_error("No configured language server supports range formatting");
4361+ return;
40084362 };
40094363
4364+ let offset_encoding = language_server.offset_encoding();
40104365 let ranges: Vec<lsp::Range> = doc
4011− .selection(view.id)
4366+ .selection(view_id)
40124367 .iter()
4013− .map(|range| range_to_lsp_range(doc.text(), *range, language_server.offset_encoding()))
4368+ .map(|range| range_to_lsp_range(doc.text(), *range, offset_encoding))
40144369 .collect();
40154370
4016− if ranges.len() != 1 {
4017− cx.editor
4018− .set_error("format_selections only supports a single selection for now");
4019− return;
4020− }
4021−
40224371 // TODO: handle fails
40234372 // TODO: concurrent map over all ranges
40244373
40254374 let range = ranges[0];
40264375
4027− let request = match language_server.text_document_range_formatting(
4028− doc.identifier(),
4029− range,
4030− lsp::FormattingOptions::default(),
4031− None,
4032− ) {
4033− Some(future) => future,
4034− None => {
4035− cx.editor
4036− .set_error("Language server does not support range formatting");
4037− return;
4038− }
4039− };
4376+ let future = language_server
4377+ .text_document_range_formatting(
4378+ doc.identifier(),
4379+ range,
4380+ lsp::FormattingOptions::default(),
4381+ None,
4382+ )
4383+ .unwrap();
40404384
4041− let edits = tokio::task::block_in_place(|| helix_lsp::block_on(request)).unwrap_or_default();
4385+ let edits = tokio::task::block_in_place(|| helix_lsp::block_on(future)).unwrap_or_default();
40424386
4043− let transaction = helix_lsp::util::generate_transaction_from_edits(
4044− doc.text(),
4045− edits,
4046− language_server.offset_encoding(),
4047− );
4387+ let transaction =
4388+ helix_lsp::util::generate_transaction_from_edits(doc.text(), edits, offset_encoding);
40484389
4049− doc.apply(&transaction, view.id);
4390+ doc.apply(&transaction, view_id);
40504391}
40514392
40524393fn join_selections_impl(cx: &mut Context, select_space: bool) {
40534394 use movement::skip_while;
40544395 let (view, doc) = current!(cx.editor);
40554396 let text = doc.text();
4056− let slice = doc.text().slice(..);
4397+ let slice = text.slice(..);
40574398
40584399 let mut changes = Vec::new();
4059− let fragment = Tendril::from(" ");
40604400
40614401 for selection in doc.selection(view.id) {
40624402 let (start, mut end) = selection.line_range(slice);
@@ -4072,9 +4412,13 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
40724412 let mut end = text.line_to_char(line + 1);
40734413 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
40744414
4075− // need to skip from start, not end
4076− let change = (start, end, Some(fragment.clone()));
4077− 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));
40784422 }
40794423 }
40804424
@@ -4086,9 +4430,6 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
40864430 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
40874431 changes.dedup();
40884432
4089− // TODO: joining multiple empty lines should be replaced by a single space.
4090− // need to merge change ranges that touch
4091−
40924433 // select inserted spaces
40934434 let transaction = if select_space {
40944435 let ranges: SmallVec<_> = changes
@@ -4100,9 +4441,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
41004441 })
41014442 .collect();
41024443 let selection = Selection::new(ranges, 0);
4103− Transaction::change(doc.text(), changes.into_iter()).with_selection(selection)
4444+ Transaction::change(text, changes.into_iter()).with_selection(selection)
41044445 } else {
4105− Transaction::change(doc.text(), changes.into_iter())
4446+ Transaction::change(text, changes.into_iter())
41064447 };
41074448
41084449 doc.apply(&transaction, view.id);
@@ -4116,8 +4457,8 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
41164457 if remove { "remove:" } else { "keep:" }.into(),
41174458 Some(reg),
41184459 ui::completers::none,
4119− move |editor, regex, event| {
4120− let (view, doc) = current!(editor);
4460+ move |cx, regex, event| {
4461+ let (view, doc) = current!(cx.editor);
41214462 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
41224463 return;
41234464 }
@@ -4176,21 +4517,53 @@ pub fn completion(cx: &mut Context) {
41764517
41774518 let (view, doc) = current!(cx.editor);
41784519
4179− let language_server = match doc.language_server() {
4180− Some(language_server) => language_server,
4181− None => return,
4520+ let savepoint = if let Some(CompleteAction::Selected { savepoint }) = &cx.editor.last_completion
4521+ {
4522+ savepoint.clone()
4523+ } else {
4524+ doc.savepoint(view)
41824525 };
41834526
4184− let offset_encoding = language_server.offset_encoding();
4185− let text = doc.text().slice(..);
4186− let cursor = doc.selection(view.id).primary().cursor(text);
4187−
4188− let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
4527+ let text = savepoint.text.clone();
4528+ let cursor = savepoint.cursor();
4529+
4530+ let mut seen_language_servers = HashSet::new();
4531+
4532+ let mut futures: FuturesUnordered<_> = doc
4533+ .language_servers_with_feature(LanguageServerFeature::Completion)
4534+ .filter(|ls| seen_language_servers.insert(ls.id()))
4535+ .map(|language_server| {
4536+ let language_server_id = language_server.id();
4537+ let offset_encoding = language_server.offset_encoding();
4538+ let pos = pos_to_lsp_pos(&text, cursor, offset_encoding);
4539+ let doc_id = doc.identifier();
4540+ let completion_request = language_server.completion(doc_id, pos, None).unwrap();
4541+
4542+ async move {
4543+ let json = completion_request.await?;
4544+ let response: Option<lsp::CompletionResponse> = serde_json::from_value(json)?;
4545+
4546+ let items = match response {
4547+ Some(lsp::CompletionResponse::Array(items)) => items,
4548+ // TODO: do something with is_incomplete
4549+ Some(lsp::CompletionResponse::List(lsp::CompletionList {
4550+ is_incomplete: _is_incomplete,
4551+ items,
4552+ })) => items,
4553+ None => Vec::new(),
4554+ }
4555+ .into_iter()
4556+ .map(|item| CompletionItem {
4557+ item,
4558+ language_server_id,
4559+ resolved: false,
4560+ })
4561+ .collect();
41894562
4190− let future = match language_server.completion(doc.identifier(), pos, None) {
4191− Some(future) => future,
4192− None => return,
4193− };
4563+ anyhow::Ok(items)
4564+ }
4565+ })
4566+ .collect();
41944567
41954568 // setup a channel that allows the request to be canceled
41964569 let (tx, rx) = oneshot::channel();
@@ -4199,12 +4572,20 @@ pub fn completion(cx: &mut Context) {
41994572 // and the associated request is automatically dropped
42004573 cx.editor.completion_request_handle = Some(tx);
42014574 let future = async move {
4575+ let items_future = async move {
4576+ let mut items = Vec::new();
4577+ // TODO if one completion request errors, all other completion requests are discarded (even if they're valid)
4578+ while let Some(mut lsp_items) = futures.try_next().await? {
4579+ items.append(&mut lsp_items);
4580+ }
4581+ anyhow::Ok(items)
4582+ };
42024583 tokio::select! {
42034584 biased;
42044585 _ = rx => {
4205− Ok(serde_json::Value::Null)
4586+ Ok(Vec::new())
42064587 }
4207− res = future => {
4588+ res = items_future => {
42084589 res
42094590 }
42104591 }
@@ -4220,7 +4601,6 @@ pub fn completion(cx: &mut Context) {
42204601 iter.reverse();
42214602 let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count();
42224603 let start_offset = cursor.saturating_sub(offset);
4223− let savepoint = doc.savepoint(view);
42244604
42254605 let trigger_doc = doc.id();
42264606 let trigger_view = view.id;
@@ -4239,9 +4619,9 @@ pub fn completion(cx: &mut Context) {
42394619 },
42404620 ));
42414621
4242− cx.callback(
4243− future,
4244− move |editor, compositor, response: Option<lsp::CompletionResponse>| {
4622+ cx.jobs.callback(async move {
4623+ let items = future.await?;
4624+ let call = move |editor: &mut Editor, compositor: &mut Compositor| {
42454625 let (view, doc) = current_ref!(editor);
42464626 // check if the completion request is stale.
42474627 //
@@ -4252,16 +4632,6 @@ pub fn completion(cx: &mut Context) {
42524632 return;
42534633 }
42544634
4255− let items = match response {
4256− Some(lsp::CompletionResponse::Array(items)) => items,
4257− // TODO: do something with is_incomplete
4258− Some(lsp::CompletionResponse::List(lsp::CompletionList {
4259− is_incomplete: _is_incomplete,
4260− items,
4261− })) => items,
4262− None => Vec::new(),
4263− };
4264−
42654635 if items.is_empty() {
42664636 // editor.set_error("No completion available");
42674637 return;
@@ -4272,7 +4642,6 @@ pub fn completion(cx: &mut Context) {
42724642 editor,
42734643 savepoint,
42744644 items,
4275− offset_encoding,
42764645 start_offset,
42774646 trigger_offset,
42784647 size,
@@ -4286,8 +4655,9 @@ pub fn completion(cx: &mut Context) {
42864655 {
42874656 compositor.remove(SignatureHelp::ID);
42884657 }
4289− },
4290− );
4658+ };
4659+ Ok(Callback::EditorCompositor(Box::new(call)))
4660+ });
42914661}
42924662
42934663// comments
@@ -4322,7 +4692,13 @@ fn rotate_selections_backward(cx: &mut Context) {
43224692 rotate_selections(cx, Direction::Backward)
43234693}
43244694
4325−fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
4695+enum ReorderStrategy {
4696+ RotateForward,
4697+ RotateBackward,
4698+ Reverse,
4699+}
4700+
4701+fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
43264702 let count = cx.count;
43274703 let (view, doc) = current!(cx.editor);
43284704 let text = doc.text().slice(..);
@@ -4340,9 +4716,10 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
43404716
43414717 for chunk in fragments.chunks_mut(group) {
43424718 // TODO: also modify main index
4343− match direction {
4344− Direction::Forward => chunk.rotate_right(1),
4345− Direction::Backward => chunk.rotate_left(1),
4719+ match strategy {
4720+ ReorderStrategy::RotateForward => chunk.rotate_right(1),
4721+ ReorderStrategy::RotateBackward => chunk.rotate_left(1),
4722+ ReorderStrategy::Reverse => chunk.reverse(),
43464723 };
43474724 }
43484725
@@ -4359,10 +4736,13 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
43594736}
43604737
43614738fn rotate_selection_contents_forward(cx: &mut Context) {
4362− rotate_selection_contents(cx, Direction::Forward)
4739+ reorder_selection_contents(cx, ReorderStrategy::RotateForward)
43634740}
43644741fn rotate_selection_contents_backward(cx: &mut Context) {
4365− rotate_selection_contents(cx, Direction::Backward)
4742+ reorder_selection_contents(cx, ReorderStrategy::RotateBackward)
4743+}
4744+fn reverse_selection_contents(cx: &mut Context) {
4745+ reorder_selection_contents(cx, ReorderStrategy::Reverse)
43664746}
43674747
43684748// tree sitter node selection
@@ -4386,8 +4766,7 @@ fn expand_selection(cx: &mut Context) {
43864766 }
43874767 }
43884768 };
4389− motion(cx.editor);
4390− cx.editor.last_motion = Some(Motion(Box::new(motion)));
4769+ cx.editor.apply_motion(motion);
43914770}
43924771
43934772fn shrink_selection(cx: &mut Context) {
@@ -4411,8 +4790,7 @@ fn shrink_selection(cx: &mut Context) {
44114790 doc.set_selection(view.id, selection);
44124791 }
44134792 };
4414− motion(cx.editor);
4415− cx.editor.last_motion = Some(Motion(Box::new(motion)));
4793+ cx.editor.apply_motion(motion);
44164794}
44174795
44184796fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
@@ -4430,8 +4808,7 @@ where
44304808 doc.set_selection(view.id, selection);
44314809 }
44324810 };
4433− motion(cx.editor);
4434− cx.editor.last_motion = Some(Motion(Box::new(motion)));
4811+ cx.editor.apply_motion(motion);
44354812}
44364813
44374814fn select_next_sibling(cx: &mut Context) {
@@ -4442,22 +4819,68 @@ fn select_prev_sibling(cx: &mut Context) {
44424819 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
44434820}
44444821
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+
44454865fn match_brackets(cx: &mut Context) {
44464866 let (view, doc) = current!(cx.editor);
4867+ let is_select = cx.editor.mode == Mode::Select;
4868+ let text = doc.text();
4869+ let text_slice = text.slice(..);
44474870
4448− if let Some(syntax) = doc.syntax() {
4449− let text = doc.text().slice(..);
4450− let selection = doc.selection(view.id).clone().transform(|range| {
4451− if let Some(pos) =
4452− match_brackets::find_matching_bracket_fuzzy(syntax, doc.text(), range.cursor(text))
4453− {
4454− range.put_cursor(text, pos, cx.editor.mode == Mode::Select)
4455− } else {
4456− range
4457− }
4458− });
4459− doc.set_selection(view.id, selection);
4460− }
4871+ let selection = doc.selection(view.id).clone().transform(|range| {
4872+ let pos = range.cursor(text_slice);
4873+ if let Some(matched_pos) = doc.syntax().map_or_else(
4874+ || match_brackets::find_matching_bracket_plaintext(text.slice(..), pos),
4875+ |syntax| match_brackets::find_matching_bracket_fuzzy(syntax, text.slice(..), pos),
4876+ ) {
4877+ range.put_cursor(text_slice, matched_pos, is_select)
4878+ } else {
4879+ range
4880+ }
4881+ });
4882+
4883+ doc.set_selection(view.id, selection);
44614884}
44624885
44634886//
@@ -4552,17 +4975,19 @@ fn transpose_view(cx: &mut Context) {
45524975 cx.editor.transpose_view()
45534976}
45544977
4555−// split helper, clear it later
4556−fn split(cx: &mut Context, action: Action) {
4557− 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);
45584983 let id = doc.id();
45594984 let selection = doc.selection(view.id).clone();
45604985 let offset = view.offset;
45614986
4562− cx.editor.switch(id, action);
4987+ editor.switch(id, action);
45634988
45644989 // match the selection in the previous view
4565− let (view, doc) = current!(cx.editor);
4990+ let (view, doc) = current!(editor);
45664991 doc.set_selection(view.id, selection);
45674992 // match the view scroll offset (switch doesn't handle this fully
45684993 // since the selection is only matched after the split)
@@ -4570,7 +4995,7 @@ fn split(cx: &mut Context, action: Action) {
45704995}
45714996
45724997fn hsplit(cx: &mut Context) {
4573− split(cx, Action::HorizontalSplit);
4998+ split(cx.editor, Action::HorizontalSplit);
45744999}
45755000
45765001fn hsplit_new(cx: &mut Context) {
@@ -4578,7 +5003,7 @@ fn hsplit_new(cx: &mut Context) {
45785003}
45795004
45805005fn vsplit(cx: &mut Context) {
4581− split(cx, Action::VerticalSplit);
5006+ split(cx.editor, Action::VerticalSplit);
45825007}
45835008
45845009fn vsplit_new(cx: &mut Context) {
@@ -4627,7 +5052,12 @@ fn insert_register(cx: &mut Context) {
46275052 if let Some(ch) = event.char() {
46285053 cx.editor.autoinfo = None;
46295054 cx.register = Some(ch);
4630− paste(cx, Paste::Cursor);
5055+ paste(
5056+ cx.editor,
5057+ cx.register.unwrap_or('"'),
5058+ Paste::Cursor,
5059+ cx.count(),
5060+ );
46315061 }
46325062 })
46335063}
@@ -4711,8 +5141,7 @@ fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direct
47115141 editor.set_status("Syntax-tree is not available in current buffer");
47125142 }
47135143 };
4714− motion(cx.editor);
4715− cx.editor.last_motion = Some(Motion(Box::new(motion)));
5144+ cx.editor.apply_motion(motion);
47165145}
47175146
47185147fn goto_next_function(cx: &mut Context) {
@@ -4833,8 +5262,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
48335262 });
48345263 doc.set_selection(view.id, selection);
48355264 };
4836− textobject(cx.editor);
4837− cx.editor.last_motion = Some(Motion(Box::new(textobject)));
5265+ cx.editor.apply_motion(textobject);
48385266 }
48395267 });
48405268
@@ -4924,11 +5352,18 @@ fn surround_replace(cx: &mut Context) {
49245352 }
49255353 };
49265354
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+
49275362 cx.on_next_key(move |cx, event| {
49285363 let (view, doc) = current!(cx.editor);
49295364 let to = match event.char() {
49305365 Some(to) => to,
4931− None => return,
5366+ None => return doc.set_selection(view.id, selection),
49325367 };
49335368 let (open, close) = surround::get_pair(to);
49345369 let transaction = Transaction::change(
@@ -4939,6 +5374,7 @@ fn surround_replace(cx: &mut Context) {
49395374 (pos, pos + 1, Some(t))
49405375 }),
49415376 );
5377+ doc.set_selection(view.id, selection);
49425378 doc.apply(&transaction, view.id);
49435379 exit_select_mode(cx);
49445380 });
@@ -5084,9 +5520,10 @@ async fn shell_impl_async(
50845520 let output = if let Some(mut stdin) = process.stdin.take() {
50855521 let input_task = tokio::spawn(async move {
50865522 if let Some(input) = input {
5087− helix_view::document::to_writer(&mut stdin, encoding::UTF_8, &input).await?;
5523+ helix_view::document::to_writer(&mut stdin, (encoding::UTF_8, false), &input)
5524+ .await?;
50885525 }
5089− Ok::<_, anyhow::Error>(())
5526+ anyhow::Ok(())
50905527 });
50915528 let (output, _) = tokio::join! {
50925529 process.wait_with_output(),
@@ -5172,12 +5609,18 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
51725609 };
51735610
51745611 // These `usize`s cannot underflow because selection ranges cannot overlap.
5175− // Once the MSRV is 1.66.0 (mixed_integer_ops is stabilized), we can use checked
5176− // arithmetic to assert this.
5177− 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");
51785617 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
51795618 ranges.push(new_range);
5180− 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");
51815624
51825625 changes.push((from, to, Some(output)));
51835626 }
@@ -5199,7 +5642,7 @@ fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBeha
51995642 cx,
52005643 prompt,
52015644 Some('|'),
5202− ui::completers::none,
5645+ ui::completers::filename,
52035646 move |cx, input: &str, event: PromptEvent| {
52045647 if event != PromptEvent::Validate {
52055648 return;
@@ -5316,6 +5759,7 @@ fn increment_impl(cx: &mut Context, increment_direction: IncrementDirection) {
53165759 let transaction = Transaction::change(doc.text(), changes.into_iter());
53175760 let transaction = transaction.with_selection(new_selection);
53185761 doc.apply(&transaction, view.id);
5762+ exit_select_mode(cx);
53195763 }
53205764}
53215765
@@ -5334,9 +5778,12 @@ fn record_macro(cx: &mut Context) {
53345778 }
53355779 })
53365780 .collect::<String>();
5337− cx.editor.registers.write(reg, vec![s]);
5338− cx.editor
5339− .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+ }
53405787 } else {
53415788 let reg = cx.register.take().unwrap_or('@');
53425789 cx.editor.macro_recording = Some((reg, Vec::new()));
@@ -5356,8 +5803,14 @@ fn replay_macro(cx: &mut Context) {
53565803 return;
53575804 }
53585805
5359− let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
5360− 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) {
53615814 Ok(keys) => keys,
53625815 Err(err) => {
53635816 cx.editor.set_error(format!("Invalid macro: {}", err));
helix-term/src/commands/dap.rs+59−56
@@ -2,13 +2,13 @@ use super::{Context, Editor};
22use crate::{
33 compositor::{self, Compositor},
44 job::{Callback, Jobs},
5− ui::{self, overlay::overlaid, FilePicker, Picker, Popup, Prompt, PromptEvent, Text},
5+ ui::{self, overlay::overlaid, Picker, Popup, Prompt, PromptEvent, Text},
66};
77use 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;
@@ -73,21 +73,19 @@ fn thread_picker(
7373 let debugger = debugger!(editor);
7474
7575 let thread_states = debugger.thread_states.clone();
76− let picker = FilePicker::new(
77− threads,
78− thread_states,
79− move |cx, thread, _action| callback_fn(cx.editor, thread),
80− move |editor, thread| {
81− let frames = editor.debugger.as_ref()?.stack_frames.get(&thread.id)?;
82− let frame = frames.get(0)?;
83− let path = frame.source.as_ref()?.path.clone()?;
84− let pos = Some((
85− frame.line.saturating_sub(1),
86− frame.end_line.unwrap_or(frame.line).saturating_sub(1),
87− ));
88− Some((path.into(), pos))
89− },
90− );
76+ let picker = Picker::new(threads, thread_states, move |cx, thread, _action| {
77+ callback_fn(cx.editor, thread)
78+ })
79+ .with_preview(move |editor, thread| {
80+ let frames = editor.debugger.as_ref()?.stack_frames.get(&thread.id)?;
81+ let frame = frames.get(0)?;
82+ let path = frame.source.as_ref()?.path.clone()?;
83+ let pos = Some((
84+ frame.line.saturating_sub(1),
85+ frame.end_line.unwrap_or(frame.line).saturating_sub(1),
86+ ));
87+ Some((path.into(), pos))
88+ });
9189 compositor.push(Box::new(picker));
9290 },
9391 );
@@ -219,7 +217,7 @@ pub fn dap_start_impl(
219217 }
220218 }
221219
222− args.insert("cwd", to_value(std::env::current_dir().unwrap())?);
220+ args.insert("cwd", to_value(helix_loader::current_working_dir())?);
223221
224222 let args = to_value(args).unwrap();
225223
@@ -341,8 +339,12 @@ fn debug_parameter_prompt(
341339 .to_owned();
342340
343341 let completer = match field_type {
344− "filename" => ui::completers::filename,
345− "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+ },
346348 _ => ui::completers::none,
347349 };
348350
@@ -579,8 +581,13 @@ pub fn dap_variables(cx: &mut Context) {
579581 }
580582
581583 let contents = Text::from(tui::text::Text::from(variables));
582− let popup = Popup::new("dap-variables", contents);
583− cx.push_layer(Box::new(popup));
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);
590+ cx.replace_or_push_layer("dap-variables", popup);
584591}
585592
586593pub fn dap_terminate(cx: &mut Context) {
@@ -728,39 +735,35 @@ pub fn dap_switch_stack_frame(cx: &mut Context) {
728735
729736 let frames = debugger.stack_frames[&thread_id].clone();
730737
731− let picker = FilePicker::new(
732− frames,
733− (),
734− move |cx, frame, _action| {
735− let debugger = debugger!(cx.editor);
736− // TODO: this should be simpler to find
737− let pos = debugger.stack_frames[&thread_id]
738− .iter()
739− .position(|f| f.id == frame.id);
740− debugger.active_frame = pos;
741−
742− let frame = debugger.stack_frames[&thread_id]
743− .get(pos.unwrap_or(0))
744− .cloned();
745− if let Some(frame) = &frame {
746− jump_to_stack_frame(cx.editor, frame);
747− }
748− },
749− move |_editor, frame| {
750− frame
751− .source
752− .as_ref()
753− .and_then(|source| source.path.clone())
754− .map(|path| {
755− (
756− path.into(),
757− Some((
758− frame.line.saturating_sub(1),
759− frame.end_line.unwrap_or(frame.line).saturating_sub(1),
760− )),
761− )
762− })
763− },
764− );
738+ let picker = Picker::new(frames, (), move |cx, frame, _action| {
739+ let debugger = debugger!(cx.editor);
740+ // TODO: this should be simpler to find
741+ let pos = debugger.stack_frames[&thread_id]
742+ .iter()
743+ .position(|f| f.id == frame.id);
744+ debugger.active_frame = pos;
745+
746+ let frame = debugger.stack_frames[&thread_id]
747+ .get(pos.unwrap_or(0))
748+ .cloned();
749+ if let Some(frame) = &frame {
750+ jump_to_stack_frame(cx.editor, frame);
751+ }
752+ })
753+ .with_preview(move |_editor, frame| {
754+ frame
755+ .source
756+ .as_ref()
757+ .and_then(|source| source.path.clone())
758+ .map(|path| {
759+ (
760+ path.into(),
761+ Some((
762+ frame.line.saturating_sub(1),
763+ frame.end_line.unwrap_or(frame.line).saturating_sub(1),
764+ )),
765+ )
766+ })
767+ });
765768 cx.push_layer(Box::new(picker))
766769}
helix-term/src/commands/lsp.rs+509−487
@@ -1,4 +1,4 @@
1−use futures_util::FutureExt;
1+use futures_util::{future::BoxFuture, stream::FuturesUnordered, FutureExt};
22use helix_lsp::{
33 block_on,
44 lsp::{
@@ -6,8 +6,10 @@ use helix_lsp::{
66 NumberOrString,
77 },
88 util::{diagnostic_to_lsp_diagnostic, lsp_range_to_range, range_to_lsp_range},
9− OffsetEncoding,
9+ Client, OffsetEncoding,
1010};
11+use serde_json::Value;
12+use tokio_stream::StreamExt;
1113use tui::{
1214 text::{Span, Spans},
1315 widgets::Row,
@@ -15,42 +17,55 @@ use tui::{
1517
1618use super::{align_view, push_jump, Align, Context, Editor, Open};
1719
18−use helix_core::{path, text_annotations::InlineAnnotation, Selection};
20+use helix_core::{
21+ path, syntax::LanguageServerFeature, text_annotations::InlineAnnotation, Selection,
22+};
1923use helix_view::{
2024 document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
2125 editor::Action,
26+ graphics::Margin,
2227 theme::Style,
2328 Document, View,
2429};
2530
2631use crate::{
2732 compositor::{self, Compositor},
33+ job::Callback,
2834 ui::{
29− self, lsp::SignatureHelp, overlay::overlaid, DynamicPicker, FileLocation, FilePicker,
30− Popup, PromptEvent,
35+ self, lsp::SignatureHelp, overlay::overlaid, DynamicPicker, FileLocation, Picker, Popup,
36+ PromptEvent,
3137 },
3238};
3339
3440use std::{
35− cmp::Ordering, collections::BTreeMap, fmt::Write, future::Future, path::PathBuf, sync::Arc,
41+ cmp::Ordering,
42+ collections::{BTreeMap, HashSet},
43+ fmt::Write,
44+ future::Future,
45+ path::PathBuf,
46+ sync::Arc,
3647};
3748
38−/// Gets the language server that is attached to a document, and
39−/// if it's not active displays a status message. Using this macro
40−/// in a context where the editor automatically queries the LSP
41−/// (instead of when the user explicitly does so via a keybind like
42−/// `gd`) will spam the "LSP inactive" status message confusingly.
49+/// Gets the first language server that is attached to a document which supports a specific feature.
50+/// If there is no configured language server that supports the feature, this displays a status message.
51+/// Using this macro in a context where the editor automatically queries the LSP
52+/// (instead of when the user explicitly does so via a keybind like `gd`)
53+/// will spam the "No configured language server supports \<feature>" status message confusingly.
4354#[macro_export]
44−macro_rules! language_server {
45− ($editor:expr, $doc:expr) => {
46− match $doc.language_server() {
55+macro_rules! language_server_with_feature {
56+ ($editor:expr, $doc:expr, $feature:expr) => {{
57+ let language_server = $doc.language_servers_with_feature($feature).next();
58+ match language_server {
4759 Some(language_server) => language_server,
4860 None => {
49− $editor.set_status("Language server not active for current buffer");
61+ $editor.set_status(format!(
62+ "No configured language server supports {}",
63+ $feature
64+ ));
5065 return;
5166 }
5267 }
53− };
68+ }};
5469}
5570
5671impl ui::menu::Item for lsp::Location {
@@ -87,20 +102,30 @@ impl ui::menu::Item for lsp::Location {
87102 }
88103}
89104
90−impl ui::menu::Item for lsp::SymbolInformation {
105+struct SymbolInformationItem {
106+ symbol: lsp::SymbolInformation,
107+ offset_encoding: OffsetEncoding,
108+}
109+
110+impl ui::menu::Item for SymbolInformationItem {
91111 /// Path to currently focussed document
92112 type Data = Option<lsp::Url>;
93113
94114 fn format(&self, current_doc_path: &Self::Data) -> Row {
95− if current_doc_path.as_ref() == Some(&self.location.uri) {
96− self.name.as_str().into()
115+ if current_doc_path.as_ref() == Some(&self.symbol.location.uri) {
116+ self.symbol.name.as_str().into()
97117 } else {
98− match self.location.uri.to_file_path() {
118+ match self.symbol.location.uri.to_file_path() {
99119 Ok(path) => {
100120 let get_relative_path = path::get_relative_path(path.as_path());
101− format!("{} ({})", &self.name, get_relative_path.to_string_lossy()).into()
121+ format!(
122+ "{} ({})",
123+ &self.symbol.name,
124+ get_relative_path.to_string_lossy()
125+ )
126+ .into()
102127 }
103− Err(_) => format!("{} ({})", &self.name, &self.location.uri).into(),
128+ Err(_) => format!("{} ({})", &self.symbol.name, &self.symbol.location.uri).into(),
104129 }
105130 }
106131 }
@@ -116,6 +141,7 @@ struct DiagnosticStyles {
116141struct PickerDiagnostic {
117142 url: lsp::Url,
118143 diag: lsp::Diagnostic,
144+ offset_encoding: OffsetEncoding,
119145}
120146
121147impl ui::menu::Item for PickerDiagnostic {
@@ -170,7 +196,6 @@ fn location_to_file_location(location: &lsp::Location) -> FileLocation {
170196 (path.into(), line)
171197}
172198
173−// TODO: share with symbol picker(symbol.location)
174199fn jump_to_location(
175200 editor: &mut Editor,
176201 location: &lsp::Location,
@@ -188,15 +213,16 @@ fn jump_to_location(
188213 return;
189214 }
190215 };
191− match editor.open(&path, action) {
192− Ok(_) => (),
216+
217+ let doc = match editor.open(&path, action) {
218+ Ok(id) => doc_mut!(editor, &id),
193219 Err(err) => {
194220 let err = format!("failed to open path: {:?}: {:?}", location.uri, err);
195221 editor.set_error(err);
196222 return;
197223 }
198− }
199− let (view, doc) = current!(editor);
224+ };
225+ let view = view_mut!(editor);
200226 // TODO: convert inside server
201227 let new_range =
202228 if let Some(new_range) = lsp_range_to_range(doc.text(), location.range, offset_encoding) {
@@ -205,54 +231,27 @@ fn jump_to_location(
205231 log::warn!("lsp position out of bounds - {:?}", location.range);
206232 return;
207233 };
208− doc.set_selection(view.id, Selection::single(new_range.anchor, new_range.head));
209− align_view(doc, view, Align::Center);
234+ // we flip the range so that the cursor sits on the start of the symbol
235+ // (for example start of the function).
236+ doc.set_selection(view.id, Selection::single(new_range.head, new_range.anchor));
237+ if action.align_view(view, doc.id()) {
238+ align_view(doc, view, Align::Center);
239+ }
210240}
211241
212−fn sym_picker(
213− symbols: Vec<lsp::SymbolInformation>,
214− current_path: Option<lsp::Url>,
215− offset_encoding: OffsetEncoding,
216−) -> FilePicker<lsp::SymbolInformation> {
217− // TODO: drop current_path comparison and instead use workspace: bool flag?
218− FilePicker::new(
219− symbols,
220− current_path.clone(),
221− move |cx, symbol, action| {
222− let (view, doc) = current!(cx.editor);
223− push_jump(view, doc);
224−
225− if current_path.as_ref() != Some(&symbol.location.uri) {
226− let uri = &symbol.location.uri;
227− let path = match uri.to_file_path() {
228− Ok(path) => path,
229− Err(_) => {
230− let err = format!("unable to convert URI to filepath: {}", uri);
231− cx.editor.set_error(err);
232− return;
233− }
234− };
235− if let Err(err) = cx.editor.open(&path, action) {
236− let err = format!("failed to open document: {}: {}", uri, err);
237− log::error!("{}", err);
238− cx.editor.set_error(err);
239− return;
240− }
241− }
242−
243− let (view, doc) = current!(cx.editor);
242+type SymbolPicker = Picker<SymbolInformationItem>;
244243
245− if let Some(range) =
246− lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding)
247− {
248− // we flip the range so that the cursor sits on the start of the symbol
249− // (for example start of the function).
250− doc.set_selection(view.id, Selection::single(range.head, range.anchor));
251− align_view(doc, view, Align::Center);
252− }
253− },
254− move |_editor, symbol| Some(location_to_file_location(&symbol.location)),
255− )
244+fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url>) -> SymbolPicker {
245+ // TODO: drop current_path comparison and instead use workspace: bool flag?
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+ );
253+ })
254+ .with_preview(move |_editor, item| Some(location_to_file_location(&item.symbol.location)))
256255 .truncate_start(false)
257256}
258257
@@ -264,22 +263,25 @@ enum DiagnosticsFormat {
264263
265264fn diag_picker(
266265 cx: &Context,
267− diagnostics: BTreeMap<lsp::Url, Vec<lsp::Diagnostic>>,
268− current_path: Option<lsp::Url>,
266+ diagnostics: BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize)>>,
267+ _current_path: Option<lsp::Url>,
269268 format: DiagnosticsFormat,
270− offset_encoding: OffsetEncoding,
271−) -> FilePicker<PickerDiagnostic> {
269+) -> Picker<PickerDiagnostic> {
272270 // TODO: drop current_path comparison and instead use workspace: bool flag?
273271
274272 // flatten the map to a vec of (url, diag) pairs
275273 let mut flat_diag = Vec::new();
276274 for (url, diags) in diagnostics {
277275 flat_diag.reserve(diags.len());
278− for diag in diags {
279− flat_diag.push(PickerDiagnostic {
280− url: url.clone(),
281− diag,
282− });
276+
277+ for (diag, ls) in diags {
278+ if let Some(ls) = cx.editor.language_server_by_id(ls) {
279+ flat_diag.push(PickerDiagnostic {
280+ url: url.clone(),
281+ diag,
282+ offset_encoding: ls.offset_encoding(),
283+ });
284+ }
283285 }
284286 }
285287
@@ -290,157 +292,190 @@ fn diag_picker(
290292 error: cx.editor.theme.get("error"),
291293 };
292294
293− FilePicker::new(
295+ Picker::new(
294296 flat_diag,
295297 (styles, format),
296− move |cx, PickerDiagnostic { url, diag }, action| {
297− if current_path.as_ref() == Some(url) {
298− let (view, doc) = current!(cx.editor);
299− push_jump(view, doc);
300− } else {
301− let path = url.to_file_path().unwrap();
302− cx.editor.open(&path, action).expect("editor.open failed");
303− }
304−
305− let (view, doc) = current!(cx.editor);
306−
307− if let Some(range) = lsp_range_to_range(doc.text(), diag.range, offset_encoding) {
308− // we flip the range so that the cursor sits on the start of the symbol
309− // (for example start of the function).
310− doc.set_selection(view.id, Selection::single(range.head, range.anchor));
311− align_view(doc, view, Align::Center);
312− }
313− },
314− move |_editor, PickerDiagnostic { url, diag }| {
315− let location = lsp::Location::new(url.clone(), diag.range);
316− Some(location_to_file_location(&location))
298+ move |cx,
299+ PickerDiagnostic {
300+ url,
301+ diag,
302+ offset_encoding,
303+ },
304+ action| {
305+ jump_to_location(
306+ cx.editor,
307+ &lsp::Location::new(url.clone(), diag.range),
308+ *offset_encoding,
309+ action,
310+ )
317311 },
318312 )
313+ .with_preview(move |_editor, PickerDiagnostic { url, diag, .. }| {
314+ let location = lsp::Location::new(url.clone(), diag.range);
315+ Some(location_to_file_location(&location))
316+ })
319317 .truncate_start(false)
320318}
321319
322320pub fn symbol_picker(cx: &mut Context) {
323321 fn nested_to_flat(
324− list: &mut Vec<lsp::SymbolInformation>,
322+ list: &mut Vec<SymbolInformationItem>,
325323 file: &lsp::TextDocumentIdentifier,
326324 symbol: lsp::DocumentSymbol,
325+ offset_encoding: OffsetEncoding,
327326 ) {
328327 #[allow(deprecated)]
329− list.push(lsp::SymbolInformation {
330− name: symbol.name,
331− kind: symbol.kind,
332− tags: symbol.tags,
333− deprecated: symbol.deprecated,
334− location: lsp::Location::new(file.uri.clone(), symbol.selection_range),
335− container_name: None,
328+ list.push(SymbolInformationItem {
329+ symbol: lsp::SymbolInformation {
330+ name: symbol.name,
331+ kind: symbol.kind,
332+ tags: symbol.tags,
333+ deprecated: symbol.deprecated,
334+ location: lsp::Location::new(file.uri.clone(), symbol.selection_range),
335+ container_name: None,
336+ },
337+ offset_encoding,
336338 });
337339 for child in symbol.children.into_iter().flatten() {
338− nested_to_flat(list, file, child);
340+ nested_to_flat(list, file, child, offset_encoding);
339341 }
340342 }
341343 let doc = doc!(cx.editor);
342344
343− let language_server = language_server!(cx.editor, doc);
344− let current_url = doc.url();
345− let offset_encoding = language_server.offset_encoding();
345+ let mut seen_language_servers = HashSet::new();
346346
347− let future = match language_server.document_symbols(doc.identifier()) {
348− Some(future) => future,
349− None => {
350− cx.editor
351− .set_error("Language server does not support document symbols");
352− return;
353− }
354− };
355−
356− cx.callback(
357− future,
358− move |editor, compositor, response: Option<lsp::DocumentSymbolResponse>| {
359− if let Some(symbols) = response {
347+ let mut futures: FuturesUnordered<_> = doc
348+ .language_servers_with_feature(LanguageServerFeature::DocumentSymbols)
349+ .filter(|ls| seen_language_servers.insert(ls.id()))
350+ .map(|language_server| {
351+ let request = language_server.document_symbols(doc.identifier()).unwrap();
352+ let offset_encoding = language_server.offset_encoding();
353+ let doc_id = doc.identifier();
354+
355+ async move {
356+ let json = request.await?;
357+ let response: Option<lsp::DocumentSymbolResponse> = serde_json::from_value(json)?;
358+ let symbols = match response {
359+ Some(symbols) => symbols,
360+ None => return anyhow::Ok(vec![]),
361+ };
360362 // lsp has two ways to represent symbols (flat/nested)
361363 // convert the nested variant to flat, so that we have a homogeneous list
362364 let symbols = match symbols {
363− lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
365+ lsp::DocumentSymbolResponse::Flat(symbols) => symbols
366+ .into_iter()
367+ .map(|symbol| SymbolInformationItem {
368+ symbol,
369+ offset_encoding,
370+ })
371+ .collect(),
364372 lsp::DocumentSymbolResponse::Nested(symbols) => {
365− let doc = doc!(editor);
366373 let mut flat_symbols = Vec::new();
367374 for symbol in symbols {
368− nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
375+ nested_to_flat(&mut flat_symbols, &doc_id, symbol, offset_encoding)
369376 }
370377 flat_symbols
371378 }
372379 };
373−
374− let picker = sym_picker(symbols, current_url, offset_encoding);
375− compositor.push(Box::new(overlaid(picker)))
380+ Ok(symbols)
376381 }
377− },
378− )
382+ })
383+ .collect();
384+ let current_url = doc.url();
385+
386+ if futures.is_empty() {
387+ cx.editor
388+ .set_error("No configured language server supports document symbols");
389+ return;
390+ }
391+
392+ cx.jobs.callback(async move {
393+ let mut symbols = Vec::new();
394+ // TODO if one symbol request errors, all other requests are discarded (even if they're valid)
395+ while let Some(mut lsp_items) = futures.try_next().await? {
396+ symbols.append(&mut lsp_items);
397+ }
398+ let call = move |_editor: &mut Editor, compositor: &mut Compositor| {
399+ let picker = sym_picker(symbols, current_url);
400+ compositor.push(Box::new(overlaid(picker)))
401+ };
402+
403+ Ok(Callback::EditorCompositor(Box::new(call)))
404+ });
379405}
380406
381407pub fn workspace_symbol_picker(cx: &mut Context) {
382408 let doc = doc!(cx.editor);
383− let current_url = doc.url();
384− let language_server = language_server!(cx.editor, doc);
385− let offset_encoding = language_server.offset_encoding();
386− let future = match language_server.workspace_symbols("".to_string()) {
387− Some(future) => future,
388− None => {
389− cx.editor
390− .set_error("Language server does not support workspace symbols");
391− return;
409+ if doc
410+ .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
411+ .count()
412+ == 0
413+ {
414+ cx.editor
415+ .set_error("No configured language server supports workspace symbols");
416+ return;
417+ }
418+
419+ let get_symbols = move |pattern: String, editor: &mut Editor| {
420+ let doc = doc!(editor);
421+ let mut seen_language_servers = HashSet::new();
422+ let mut futures: FuturesUnordered<_> = doc
423+ .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
424+ .filter(|ls| seen_language_servers.insert(ls.id()))
425+ .map(|language_server| {
426+ let request = language_server.workspace_symbols(pattern.clone()).unwrap();
427+ let offset_encoding = language_server.offset_encoding();
428+ async move {
429+ let json = request.await?;
430+
431+ let response =
432+ serde_json::from_value::<Option<Vec<lsp::SymbolInformation>>>(json)?
433+ .unwrap_or_default()
434+ .into_iter()
435+ .map(|symbol| SymbolInformationItem {
436+ symbol,
437+ offset_encoding,
438+ })
439+ .collect();
440+
441+ anyhow::Ok(response)
442+ }
443+ })
444+ .collect();
445+
446+ if futures.is_empty() {
447+ editor.set_error("No configured language server supports workspace symbols");
392448 }
393− };
394449
395− cx.callback(
396− future,
397− move |_editor, compositor, response: Option<Vec<lsp::SymbolInformation>>| {
398− let symbols = response.unwrap_or_default();
399− let picker = sym_picker(symbols, current_url, offset_encoding);
400− let get_symbols = |query: String, editor: &mut Editor| {
401− let doc = doc!(editor);
402− let language_server = match doc.language_server() {
403− Some(s) => s,
404− None => {
405− // This should not generally happen since the picker will not
406− // even open in the first place if there is no server.
407− return async move { Err(anyhow::anyhow!("LSP not active")) }.boxed();
408− }
409− };
410− let symbol_request = match language_server.workspace_symbols(query) {
411− Some(future) => future,
412− None => {
413− // This should also not happen since the language server must have
414− // supported workspace symbols before to reach this block.
415− return async move {
416− Err(anyhow::anyhow!(
417− "Language server does not support workspace symbols"
418− ))
419− }
420− .boxed();
421− }
422− };
450+ async move {
451+ let mut symbols = Vec::new();
452+ // TODO if one symbol request errors, all other requests are discarded (even if they're valid)
453+ while let Some(mut lsp_items) = futures.try_next().await? {
454+ symbols.append(&mut lsp_items);
455+ }
456+ anyhow::Ok(symbols)
457+ }
458+ .boxed()
459+ };
423460
424− let future = async move {
425− let json = symbol_request.await?;
426− let response: Option<Vec<lsp::SymbolInformation>> =
427− serde_json::from_value(json)?;
461+ let current_url = doc.url();
462+ let initial_symbols = get_symbols("".to_owned(), cx.editor);
428463
429− Ok(response.unwrap_or_default())
430− };
431− future.boxed()
432− };
464+ cx.jobs.callback(async move {
465+ let symbols = initial_symbols.await?;
466+ let call = move |_editor: &mut Editor, compositor: &mut Compositor| {
467+ let picker = sym_picker(symbols, current_url);
433468 let dyn_picker = DynamicPicker::new(picker, Box::new(get_symbols));
434469 compositor.push(Box::new(overlaid(dyn_picker)))
435− },
436− )
470+ };
471+
472+ Ok(Callback::EditorCompositor(Box::new(call)))
473+ });
437474}
438475
439476pub fn diagnostics_picker(cx: &mut Context) {
440477 let doc = doc!(cx.editor);
441− let language_server = language_server!(cx.editor, doc);
442478 if let Some(current_url) = doc.url() {
443− let offset_encoding = language_server.offset_encoding();
444479 let diagnostics = cx
445480 .editor
446481 .diagnostics
@@ -452,7 +487,6 @@ pub fn diagnostics_picker(cx: &mut Context) {
452487 [(current_url.clone(), diagnostics)].into(),
453488 Some(current_url),
454489 DiagnosticsFormat::HideSourcePath,
455− offset_encoding,
456490 );
457491 cx.push_layer(Box::new(overlaid(picker)));
458492 }
@@ -460,24 +494,27 @@ pub fn diagnostics_picker(cx: &mut Context) {
460494
461495pub fn workspace_diagnostics_picker(cx: &mut Context) {
462496 let doc = doc!(cx.editor);
463− let language_server = language_server!(cx.editor, doc);
464497 let current_url = doc.url();
465− let offset_encoding = language_server.offset_encoding();
498+ // TODO not yet filtered by LanguageServerFeature, need to do something similar as Document::shown_diagnostics here for all open documents
466499 let diagnostics = cx.editor.diagnostics.clone();
467500 let picker = diag_picker(
468501 cx,
469502 diagnostics,
470503 current_url,
471504 DiagnosticsFormat::ShowSourcePath,
472− offset_encoding,
473505 );
474506 cx.push_layer(Box::new(overlaid(picker)));
475507}
476508
477−impl ui::menu::Item for lsp::CodeActionOrCommand {
509+struct CodeActionOrCommandItem {
510+ lsp_item: lsp::CodeActionOrCommand,
511+ language_server_id: usize,
512+}
513+
514+impl ui::menu::Item for CodeActionOrCommandItem {
478515 type Data = ();
479516 fn format(&self, _data: &Self::Data) -> Row {
480− match self {
517+ match &self.lsp_item {
481518 lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str().into(),
482519 lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
483520 }
@@ -544,45 +581,42 @@ fn action_fixes_diagnostics(action: &CodeActionOrCommand) -> bool {
544581pub fn code_action(cx: &mut Context) {
545582 let (view, doc) = current!(cx.editor);
546583
547− let language_server = language_server!(cx.editor, doc);
548−
549584 let selection_range = doc.selection(view.id).primary();
550− let offset_encoding = language_server.offset_encoding();
551−
552− let range = range_to_lsp_range(doc.text(), selection_range, offset_encoding);
553585
554− let future = match language_server.code_actions(
555− doc.identifier(),
556− range,
557− // Filter and convert overlapping diagnostics
558− lsp::CodeActionContext {
559− diagnostics: doc
560− .diagnostics()
561− .iter()
562− .filter(|&diag| {
563− selection_range
564− .overlaps(&helix_core::Range::new(diag.range.start, diag.range.end))
565− })
566− .map(|diag| diagnostic_to_lsp_diagnostic(doc.text(), diag, offset_encoding))
567− .collect(),
568− only: None,
569− trigger_kind: Some(CodeActionTriggerKind::INVOKED),
570− },
571− ) {
572− Some(future) => future,
573− None => {
574− cx.editor
575− .set_error("Language server does not support code actions");
576− return;
577− }
578− };
586+ let mut seen_language_servers = HashSet::new();
579587
580− cx.callback(
581− future,
582− move |editor, compositor, response: Option<lsp::CodeActionResponse>| {
588+ let mut futures: FuturesUnordered<_> = doc
589+ .language_servers_with_feature(LanguageServerFeature::CodeAction)
590+ .filter(|ls| seen_language_servers.insert(ls.id()))
591+ // TODO this should probably already been filtered in something like "language_servers_with_feature"
592+ .filter_map(|language_server| {
593+ let offset_encoding = language_server.offset_encoding();
594+ let language_server_id = language_server.id();
595+ let range = range_to_lsp_range(doc.text(), selection_range, offset_encoding);
596+ // Filter and convert overlapping diagnostics
597+ let code_action_context = lsp::CodeActionContext {
598+ diagnostics: doc
599+ .diagnostics()
600+ .iter()
601+ .filter(|&diag| {
602+ selection_range
603+ .overlaps(&helix_core::Range::new(diag.range.start, diag.range.end))
604+ })
605+ .map(|diag| diagnostic_to_lsp_diagnostic(doc.text(), diag, offset_encoding))
606+ .collect(),
607+ only: None,
608+ trigger_kind: Some(CodeActionTriggerKind::INVOKED),
609+ };
610+ let code_action_request =
611+ language_server.code_actions(doc.identifier(), range, code_action_context)?;
612+ Some((code_action_request, language_server_id))
613+ })
614+ .map(|(request, ls_id)| async move {
615+ let json = request.await?;
616+ let response: Option<lsp::CodeActionResponse> = serde_json::from_value(json)?;
583617 let mut actions = match response {
584618 Some(a) => a,
585− None => return,
619+ None => return anyhow::Ok(Vec::new()),
586620 };
587621
588622 // remove disabled code actions
@@ -594,11 +628,6 @@ pub fn code_action(cx: &mut Context) {
594628 )
595629 });
596630
597− if actions.is_empty() {
598− editor.set_status("No code actions available");
599− return;
600− }
601−
602631 // Sort codeactions into a useful order. This behaviour is only partially described in the LSP spec.
603632 // Many details are modeled after vscode because language servers are usually tested against it.
604633 // VScode sorts the codeaction two times:
@@ -634,22 +663,74 @@ pub fn code_action(cx: &mut Context) {
634663 .reverse()
635664 });
636665
637− let mut picker = ui::Menu::new(actions, (), move |editor, code_action, event| {
666+ Ok(actions
667+ .into_iter()
668+ .map(|lsp_item| CodeActionOrCommandItem {
669+ lsp_item,
670+ language_server_id: ls_id,
671+ })
672+ .collect())
673+ })
674+ .collect();
675+
676+ if futures.is_empty() {
677+ cx.editor
678+ .set_error("No configured language server supports code actions");
679+ return;
680+ }
681+
682+ cx.jobs.callback(async move {
683+ let mut actions = Vec::new();
684+ // TODO if one code action request errors, all other requests are ignored (even if they're valid)
685+ while let Some(mut lsp_items) = futures.try_next().await? {
686+ actions.append(&mut lsp_items);
687+ }
688+
689+ let call = move |editor: &mut Editor, compositor: &mut Compositor| {
690+ if actions.is_empty() {
691+ editor.set_error("No code actions available");
692+ return;
693+ }
694+ let mut picker = ui::Menu::new(actions, (), move |editor, action, event| {
638695 if event != PromptEvent::Validate {
639696 return;
640697 }
641698
642699 // always present here
643− let code_action = code_action.unwrap();
700+ let action = action.unwrap();
701+ let Some(language_server) = editor.language_server_by_id(action.language_server_id)
702+ else {
703+ editor.set_error("Language Server disappeared");
704+ return;
705+ };
706+ let offset_encoding = language_server.offset_encoding();
644707
645− match code_action {
708+ match &action.lsp_item {
646709 lsp::CodeActionOrCommand::Command(command) => {
647710 log::debug!("code action command: {:?}", command);
648− execute_lsp_command(editor, command.clone());
711+ execute_lsp_command(editor, action.language_server_id, command.clone());
649712 }
650713 lsp::CodeActionOrCommand::CodeAction(code_action) => {
651714 log::debug!("code action: {:?}", code_action);
652− 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 {
653734 log::debug!("edit: {:?}", workspace_edit);
654735 let _ = apply_workspace_edit(editor, offset_encoding, workspace_edit);
655736 }
@@ -657,17 +738,28 @@ pub fn code_action(cx: &mut Context) {
657738 // if code action provides both edit and command first the edit
658739 // should be applied and then the command
659740 if let Some(command) = &code_action.command {
660− execute_lsp_command(editor, command.clone());
741+ execute_lsp_command(editor, action.language_server_id, command.clone());
661742 }
662743 }
663744 }
664745 });
665746 picker.move_down(); // pre-select the first item
666747
667− 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+
668758 compositor.replace_or_push("code-action", popup);
669− },
670− )
759+ };
760+
761+ Ok(Callback::EditorCompositor(Box::new(call)))
762+ });
671763}
672764
673765impl ui::menu::Item for lsp::Command {
@@ -677,13 +769,13 @@ impl ui::menu::Item for lsp::Command {
677769 }
678770}
679771
680−pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
681− let doc = doc!(editor);
682− let language_server = language_server!(editor, doc);
683−
772+pub fn execute_lsp_command(editor: &mut Editor, language_server_id: usize, cmd: lsp::Command) {
684773 // the command is executed on the server and communicated back
685774 // to the client asynchronously using workspace edits
686− let future = match language_server.command(cmd) {
775+ let future = match editor
776+ .language_server_by_id(language_server_id)
777+ .and_then(|language_server| language_server.command(cmd))
778+ {
687779 Some(future) => future,
688780 None => {
689781 editor.set_error("Language server does not support executing commands");
@@ -937,7 +1029,7 @@ fn goto_impl(
9371029 locations: Vec<lsp::Location>,
9381030 offset_encoding: OffsetEncoding,
9391031) {
940− let cwdir = std::env::current_dir().unwrap_or_default();
1032+ let cwdir = helix_loader::current_working_dir();
9411033
9421034 match locations.as_slice() {
9431035 [location] => {
@@ -947,14 +1039,10 @@ fn goto_impl(
9471039 editor.set_error("No definition found.");
9481040 }
9491041 _locations => {
950− let picker = FilePicker::new(
951− locations,
952− cwdir,
953− move |cx, location, action| {
954− jump_to_location(cx.editor, location, offset_encoding, action)
955− },
956− move |_editor, location| Some(location_to_file_location(location)),
957− );
1042+ let picker = Picker::new(locations, cwdir, move |cx, location, action| {
1043+ jump_to_location(cx.editor, location, offset_encoding, action)
1044+ })
1045+ .with_preview(move |_editor, location| Some(location_to_file_location(location)));
9581046 compositor.push(Box::new(overlaid(picker)));
9591047 }
9601048 }
@@ -975,21 +1063,17 @@ fn to_locations(definitions: Option<lsp::GotoDefinitionResponse>) -> Vec<lsp::Lo
9751063 }
9761064}
9771065
978−pub fn goto_declaration(cx: &mut Context) {
1066+fn goto_single_impl<P, F>(cx: &mut Context, feature: LanguageServerFeature, request_provider: P)
1067+where
1068+ P: Fn(&Client, lsp::Position, lsp::TextDocumentIdentifier) -> Option<F>,
1069+ F: Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send,
1070+{
9791071 let (view, doc) = current!(cx.editor);
980− let language_server = language_server!(cx.editor, doc);
981− let offset_encoding = language_server.offset_encoding();
9821072
1073+ let language_server = language_server_with_feature!(cx.editor, doc, feature);
1074+ let offset_encoding = language_server.offset_encoding();
9831075 let pos = doc.position(view.id, offset_encoding);
984−
985− let future = match language_server.goto_declaration(doc.identifier(), pos, None) {
986− Some(future) => future,
987− None => {
988− cx.editor
989− .set_error("Language server does not support goto-declaration");
990− return;
991− }
992− };
1076+ let future = request_provider(language_server, pos, doc.identifier()).unwrap();
9931077
9941078 cx.callback(
9951079 future,
@@ -1000,96 +1084,56 @@ pub fn goto_declaration(cx: &mut Context) {
10001084 );
10011085}
10021086
1003−pub fn goto_definition(cx: &mut Context) {
1004− let (view, doc) = current!(cx.editor);
1005− let language_server = language_server!(cx.editor, doc);
1006− let offset_encoding = language_server.offset_encoding();
1007−
1008− let pos = doc.position(view.id, offset_encoding);
1009−
1010− let future = match language_server.goto_definition(doc.identifier(), pos, None) {
1011− Some(future) => future,
1012− None => {
1013− cx.editor
1014− .set_error("Language server does not support goto-definition");
1015− return;
1016− }
1017− };
1087+pub fn goto_declaration(cx: &mut Context) {
1088+ goto_single_impl(
1089+ cx,
1090+ LanguageServerFeature::GotoDeclaration,
1091+ |ls, pos, doc_id| ls.goto_declaration(doc_id, pos, None),
1092+ );
1093+}
10181094
1019− cx.callback(
1020− future,
1021− move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
1022− let items = to_locations(response);
1023− goto_impl(editor, compositor, items, offset_encoding);
1024− },
1095+pub fn goto_definition(cx: &mut Context) {
1096+ goto_single_impl(
1097+ cx,
1098+ LanguageServerFeature::GotoDefinition,
1099+ |ls, pos, doc_id| ls.goto_definition(doc_id, pos, None),
10251100 );
10261101}
10271102
10281103pub fn goto_type_definition(cx: &mut Context) {
1029− let (view, doc) = current!(cx.editor);
1030− let language_server = language_server!(cx.editor, doc);
1031− let offset_encoding = language_server.offset_encoding();
1032−
1033− let pos = doc.position(view.id, offset_encoding);
1034−
1035− let future = match language_server.goto_type_definition(doc.identifier(), pos, None) {
1036− Some(future) => future,
1037− None => {
1038− cx.editor
1039− .set_error("Language server does not support goto-type-definition");
1040− return;
1041− }
1042− };
1043−
1044− cx.callback(
1045− future,
1046− move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
1047− let items = to_locations(response);
1048− goto_impl(editor, compositor, items, offset_encoding);
1049− },
1104+ goto_single_impl(
1105+ cx,
1106+ LanguageServerFeature::GotoTypeDefinition,
1107+ |ls, pos, doc_id| ls.goto_type_definition(doc_id, pos, None),
10501108 );
10511109}
10521110
10531111pub fn goto_implementation(cx: &mut Context) {
1054− let (view, doc) = current!(cx.editor);
1055− let language_server = language_server!(cx.editor, doc);
1056− let offset_encoding = language_server.offset_encoding();
1057−
1058− let pos = doc.position(view.id, offset_encoding);
1059−
1060− let future = match language_server.goto_implementation(doc.identifier(), pos, None) {
1061− Some(future) => future,
1062− None => {
1063− cx.editor
1064− .set_error("Language server does not support goto-implementation");
1065− return;
1066− }
1067− };
1068−
1069− cx.callback(
1070− future,
1071− move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
1072− let items = to_locations(response);
1073− goto_impl(editor, compositor, items, offset_encoding);
1074− },
1112+ goto_single_impl(
1113+ cx,
1114+ LanguageServerFeature::GotoImplementation,
1115+ |ls, pos, doc_id| ls.goto_implementation(doc_id, pos, None),
10751116 );
10761117}
10771118
10781119pub fn goto_reference(cx: &mut Context) {
1120+ let config = cx.editor.config();
10791121 let (view, doc) = current!(cx.editor);
1080− let language_server = language_server!(cx.editor, doc);
1081− let offset_encoding = language_server.offset_encoding();
10821122
1123+ // TODO could probably support multiple language servers,
1124+ // not sure if there's a real practical use case for this though
1125+ let language_server =
1126+ language_server_with_feature!(cx.editor, doc, LanguageServerFeature::GotoReference);
1127+ let offset_encoding = language_server.offset_encoding();
10831128 let pos = doc.position(view.id, offset_encoding);
1084−
1085− let future = match language_server.goto_reference(doc.identifier(), pos, None) {
1086− Some(future) => future,
1087− None => {
1088− cx.editor
1089− .set_error("Language server does not support goto-reference");
1090− return;
1091− }
1092− };
1129+ let future = language_server
1130+ .goto_reference(
1131+ doc.identifier(),
1132+ pos,
1133+ config.lsp.goto_reference_include_declaration,
1134+ None,
1135+ )
1136+ .unwrap();
10931137
10941138 cx.callback(
10951139 future,
@@ -1100,7 +1144,7 @@ pub fn goto_reference(cx: &mut Context) {
11001144 );
11011145}
11021146
1103−#[derive(PartialEq, Eq)]
1147+#[derive(PartialEq, Eq, Clone, Copy)]
11041148pub enum SignatureHelpInvoked {
11051149 Manual,
11061150 Automatic,
@@ -1112,35 +1156,32 @@ pub fn signature_help(cx: &mut Context) {
11121156
11131157pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
11141158 let (view, doc) = current!(cx.editor);
1115− let was_manually_invoked = invoked == SignatureHelpInvoked::Manual;
11161159
1117− let language_server = match doc.language_server() {
1118− Some(language_server) => language_server,
1119− None => {
1120− // Do not show the message if signature help was invoked
1121− // automatically on backspace, trigger characters, etc.
1122− if was_manually_invoked {
1123− cx.editor
1124− .set_status("Language server not active for current buffer");
1125− }
1126− return;
1127− }
1128− };
1129− let offset_encoding = language_server.offset_encoding();
1130−
1131− let pos = doc.position(view.id, offset_encoding);
1160+ // TODO merge multiple language server signature help into one instead of just taking the first language server that supports it
1161+ let future = doc
1162+ .language_servers_with_feature(LanguageServerFeature::SignatureHelp)
1163+ .find_map(|language_server| {
1164+ let pos = doc.position(view.id, language_server.offset_encoding());
1165+ language_server.text_document_signature_help(doc.identifier(), pos, None)
1166+ });
11321167
1133− let future = match language_server.text_document_signature_help(doc.identifier(), pos, None) {
1134− Some(f) => f,
1135− None => {
1136− if was_manually_invoked {
1137− cx.editor
1138− .set_error("Language server does not support signature-help");
1139− }
1140− return;
1168+ let Some(future) = future else {
1169+ // Do not show the message if signature help was invoked
1170+ // automatically on backspace, trigger characters, etc.
1171+ if invoked == SignatureHelpInvoked::Manual {
1172+ cx.editor
1173+ .set_error("No configured language server supports signature-help");
11411174 }
1175+ return;
11421176 };
1177+ signature_help_impl_with_future(cx, future.boxed(), invoked);
1178+}
11431179
1180+pub fn signature_help_impl_with_future(
1181+ cx: &mut Context,
1182+ future: BoxFuture<'static, helix_lsp::Result<Value>>,
1183+ invoked: SignatureHelpInvoked,
1184+) {
11441185 cx.callback(
11451186 future,
11461187 move |editor, compositor, response: Option<lsp::SignatureHelp>| {
@@ -1148,7 +1189,7 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
11481189
11491190 if !(config.lsp.auto_signature_help
11501191 || SignatureHelp::visible_popup(compositor).is_some()
1151− || was_manually_invoked)
1192+ || invoked == SignatureHelpInvoked::Manual)
11521193 {
11531194 return;
11541195 }
@@ -1157,7 +1198,7 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
11571198 // it very probably means the server was a little slow to respond and the user has
11581199 // already moved on to something else, making a signature help popup will just be an
11591200 // annoyance, see https://github.com/helix-editor/helix/issues/3112
1160− if !was_manually_invoked && editor.mode != Mode::Insert {
1201+ if invoked == SignatureHelpInvoked::Automatic && editor.mode != Mode::Insert {
11611202 return;
11621203 }
11631204
@@ -1247,21 +1288,15 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
12471288
12481289pub fn hover(cx: &mut Context) {
12491290 let (view, doc) = current!(cx.editor);
1250− let language_server = language_server!(cx.editor, doc);
1251− let offset_encoding = language_server.offset_encoding();
12521291
1292+ // TODO support multiple language servers (merge UI somehow)
1293+ let language_server =
1294+ language_server_with_feature!(cx.editor, doc, LanguageServerFeature::Hover);
12531295 // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
1254−
1255− let pos = doc.position(view.id, offset_encoding);
1256−
1257− let future = match language_server.text_document_hover(doc.identifier(), pos, None) {
1258− Some(future) => future,
1259− None => {
1260− cx.editor
1261− .set_error("Language server does not support hover");
1262− return;
1263− }
1264− };
1296+ let pos = doc.position(view.id, language_server.offset_encoding());
1297+ let future = language_server
1298+ .text_document_hover(doc.identifier(), pos, None)
1299+ .unwrap();
12651300
12661301 cx.callback(
12671302 future,
@@ -1341,7 +1376,11 @@ pub fn rename_symbol(cx: &mut Context) {
13411376 }
13421377 }
13431378
1344− fn create_rename_prompt(editor: &Editor, prefill: String) -> Box<ui::Prompt> {
1379+ fn create_rename_prompt(
1380+ editor: &Editor,
1381+ prefill: String,
1382+ language_server_id: Option<usize>,
1383+ ) -> Box<ui::Prompt> {
13451384 let prompt = ui::Prompt::new(
13461385 "rename-to:".into(),
13471386 None,
@@ -1350,22 +1389,23 @@ pub fn rename_symbol(cx: &mut Context) {
13501389 if event != PromptEvent::Validate {
13511390 return;
13521391 }
1353−
13541392 let (view, doc) = current!(cx.editor);
1355− let language_server = language_server!(cx.editor, doc);
1356− let offset_encoding = language_server.offset_encoding();
13571393
1394+ let Some(language_server) = doc
1395+ .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1396+ .find(|ls| language_server_id.map_or(true, |id| id == ls.id()))
1397+ else {
1398+ cx.editor
1399+ .set_error("No configured language server supports symbol renaming");
1400+ return;
1401+ };
1402+
1403+ let offset_encoding = language_server.offset_encoding();
13581404 let pos = doc.position(view.id, offset_encoding);
1405+ let future = language_server
1406+ .rename_symbol(doc.identifier(), pos, input.to_string())
1407+ .unwrap();
13591408
1360− let future =
1361− match language_server.rename_symbol(doc.identifier(), pos, input.to_string()) {
1362− Some(future) => future,
1363− None => {
1364− cx.editor
1365− .set_error("Language server does not support symbol renaming");
1366− return;
1367− }
1368− };
13691409 match block_on(future) {
13701410 Ok(edits) => {
13711411 let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits);
@@ -1379,21 +1419,28 @@ pub fn rename_symbol(cx: &mut Context) {
13791419 Box::new(prompt)
13801420 }
13811421
1382− let (view, doc) = current!(cx.editor);
1383− let language_server = language_server!(cx.editor, doc);
1384− let offset_encoding = language_server.offset_encoding();
1385−
1386− if !language_server.supports_rename() {
1387− cx.editor
1388− .set_error("Language server does not support symbol renaming");
1389− return;
1390− }
1391−
1392− let pos = doc.position(view.id, offset_encoding);
1422+ let (view, doc) = current_ref!(cx.editor);
1423+
1424+ let language_server_with_prepare_rename_support = doc
1425+ .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1426+ .find(|ls| {
1427+ matches!(
1428+ ls.capabilities().rename_provider,
1429+ Some(lsp::OneOf::Right(lsp::RenameOptions {
1430+ prepare_provider: Some(true),
1431+ ..
1432+ }))
1433+ )
1434+ });
13931435
1394− match language_server.prepare_rename(doc.identifier(), pos) {
1395− // Language server supports textDocument/prepareRename, use it.
1396− Some(future) => cx.callback(
1436+ if let Some(language_server) = language_server_with_prepare_rename_support {
1437+ let ls_id = language_server.id();
1438+ let offset_encoding = language_server.offset_encoding();
1439+ let pos = doc.position(view.id, offset_encoding);
1440+ let future = language_server
1441+ .prepare_rename(doc.identifier(), pos)
1442+ .unwrap();
1443+ cx.callback(
13971444 future,
13981445 move |editor, compositor, response: Option<lsp::PrepareRenameResponse>| {
13991446 let prefill = match get_prefill_from_lsp_response(editor, offset_encoding, response)
@@ -1405,39 +1452,27 @@ pub fn rename_symbol(cx: &mut Context) {
14051452 }
14061453 };
14071454
1408− let prompt = create_rename_prompt(editor, prefill);
1455+ let prompt = create_rename_prompt(editor, prefill, Some(ls_id));
14091456
14101457 compositor.push(prompt);
14111458 },
1412− ),
1413− // Language server does not support textDocument/prepareRename, fall back
1414− // to word boundary selection.
1415− None => {
1416− let prefill = get_prefill_from_word_boundary(cx.editor);
1417−
1418− let prompt = create_rename_prompt(cx.editor, prefill);
1419−
1420− cx.push_layer(prompt);
1421− }
1422− };
1459+ );
1460+ } else {
1461+ let prefill = get_prefill_from_word_boundary(cx.editor);
1462+ let prompt = create_rename_prompt(cx.editor, prefill, None);
1463+ cx.push_layer(prompt);
1464+ }
14231465}
14241466
14251467pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
14261468 let (view, doc) = current!(cx.editor);
1427− let language_server = language_server!(cx.editor, doc);
1469+ let language_server =
1470+ language_server_with_feature!(cx.editor, doc, LanguageServerFeature::DocumentHighlight);
14281471 let offset_encoding = language_server.offset_encoding();
1429−
14301472 let pos = doc.position(view.id, offset_encoding);
1431−
1432− let future = match language_server.text_document_document_highlight(doc.identifier(), pos, None)
1433− {
1434− Some(future) => future,
1435− None => {
1436− cx.editor
1437− .set_error("Language server does not support document highlight");
1438− return;
1439− }
1440− };
1473+ let future = language_server
1474+ .text_document_document_highlight(doc.identifier(), pos, None)
1475+ .unwrap();
14411476
14421477 cx.callback(
14431478 future,
@@ -1447,10 +1482,8 @@ pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
14471482 _ => return,
14481483 };
14491484 let (view, doc) = current!(editor);
1450− let language_server = language_server!(editor, doc);
1451− let offset_encoding = language_server.offset_encoding();
14521485 let text = doc.text();
1453− let pos = doc.selection(view.id).primary().head;
1486+ let pos = doc.selection(view.id).primary().cursor(text.slice(..));
14541487
14551488 // We must find the range that contains our primary cursor to prevent our primary cursor to move
14561489 let mut primary_index = 0;
@@ -1494,62 +1527,51 @@ fn compute_inlay_hints_for_view(
14941527 let view_id = view.id;
14951528 let doc_id = view.doc;
14961529
1497− let language_server = doc.language_server()?;
1498−
1499− let capabilities = language_server.capabilities();
1500−
1501− let (future, new_doc_inlay_hints_id) = match capabilities.inlay_hint_provider {
1502− Some(
1503− lsp::OneOf::Left(true)
1504− | lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_)),
1505− ) => {
1506− let doc_text = doc.text();
1507− let len_lines = doc_text.len_lines();
1508−
1509− // Compute ~3 times the current view height of inlay hints, that way some scrolling
1510− // will not show half the view with hints and half without while still being faster
1511− // than computing all the hints for the full file (which could be dozens of time
1512− // longer than the view is).
1513− let view_height = view.inner_height();
1514− let first_visible_line = doc_text.char_to_line(view.offset.anchor);
1515− let first_line = first_visible_line.saturating_sub(view_height);
1516− let last_line = first_visible_line
1517− .saturating_add(view_height.saturating_mul(2))
1518− .min(len_lines);
1519−
1520− let new_doc_inlay_hint_id = DocumentInlayHintsId {
1521− first_line,
1522− last_line,
1523− };
1524− // Don't recompute the annotations in case nothing has changed about the view
1525− if !doc.inlay_hints_oudated
1526− && doc
1527− .inlay_hints(view_id)
1528− .map_or(false, |dih| dih.id == new_doc_inlay_hint_id)
1529− {
1530− return None;
1531− }
1530+ let language_server = doc
1531+ .language_servers_with_feature(LanguageServerFeature::InlayHints)
1532+ .next()?;
1533+
1534+ let doc_text = doc.text();
1535+ let len_lines = doc_text.len_lines();
1536+
1537+ // Compute ~3 times the current view height of inlay hints, that way some scrolling
1538+ // will not show half the view with hints and half without while still being faster
1539+ // than computing all the hints for the full file (which could be dozens of time
1540+ // longer than the view is).
1541+ let view_height = view.inner_height();
1542+ let first_visible_line = doc_text.char_to_line(view.offset.anchor.min(doc_text.len_chars()));
1543+ let first_line = first_visible_line.saturating_sub(view_height);
1544+ let last_line = first_visible_line
1545+ .saturating_add(view_height.saturating_mul(2))
1546+ .min(len_lines);
1547+
1548+ let new_doc_inlay_hints_id = DocumentInlayHintsId {
1549+ first_line,
1550+ last_line,
1551+ };
1552+ // Don't recompute the annotations in case nothing has changed about the view
1553+ if !doc.inlay_hints_oudated
1554+ && doc
1555+ .inlay_hints(view_id)
1556+ .map_or(false, |dih| dih.id == new_doc_inlay_hints_id)
1557+ {
1558+ return None;
1559+ }
15321560
1533− let doc_slice = doc_text.slice(..);
1534− let first_char_in_range = doc_slice.line_to_char(first_line);
1535− let last_char_in_range = doc_slice.line_to_char(last_line);
1561+ let doc_slice = doc_text.slice(..);
1562+ let first_char_in_range = doc_slice.line_to_char(first_line);
1563+ let last_char_in_range = doc_slice.line_to_char(last_line);
15361564
1537− let range = helix_lsp::util::range_to_lsp_range(
1538− doc_text,
1539− helix_core::Range::new(first_char_in_range, last_char_in_range),
1540− language_server.offset_encoding(),
1541− );
1565+ let range = helix_lsp::util::range_to_lsp_range(
1566+ doc_text,
1567+ helix_core::Range::new(first_char_in_range, last_char_in_range),
1568+ language_server.offset_encoding(),
1569+ );
15421570
1543− (
1544− language_server.text_document_range_inlay_hints(doc.identifier(), range, None),
1545− new_doc_inlay_hint_id,
1546− )
1547− }
1548− _ => return None,
1549− };
1571+ let offset_encoding = language_server.offset_encoding();
15501572
15511573 let callback = super::make_job_callback(
1552− future?,
1574+ language_server.text_document_range_inlay_hints(doc.identifier(), range, None)?,
15531575 move |editor, _compositor, response: Option<Vec<lsp::InlayHint>>| {
15541576 // The config was modified or the window was closed while the request was in flight
15551577 if !editor.config().lsp.display_inlay_hints || editor.tree.try_get(view_id).is_none() {
@@ -1563,8 +1585,8 @@ fn compute_inlay_hints_for_view(
15631585 };
15641586
15651587 // If we have neither hints nor an LSP, empty the inlay hints since they're now oudated
1566− let (mut hints, offset_encoding) = match (response, doc.language_server()) {
1567− (Some(h), Some(ls)) if !h.is_empty() => (h, ls.offset_encoding()),
1588+ let mut hints = match response {
1589+ Some(hints) if !hints.is_empty() => hints,
15681590 _ => {
15691591 doc.set_inlay_hints(
15701592 view_id,
helix-term/src/commands/typed.rs+1073−715

This diff is too big to show here.

helix-term/src/compositor.rs+12

This diff is too big to show here.

helix-term/src/config.rs+8−8

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/src/ui/markdown.rs+23−14

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/tests/integration.rs+1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/tests/test/commands/write.rs+159−2

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-tui/Cargo.toml+16−16

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-vcs/Cargo.toml+12−10

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-vcs/src/git.rs+21−16

This diff is too big to show here.

helix-vcs/src/lib.rs+4−4

This diff is too big to show here.

helix-view/Cargo.toml+22−18

This diff is too big to show here.

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

This diff is too big to show here.

helix-view/src/document.rs+517−151

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-view/src/lib.rs+1

This diff is too big to show here.

helix-view/src/register.rsnew file+333

This diff is too big to show here.

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

This diff is too big to show here.

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

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+897−396

This diff is too big to show here.

logo.svg+1−1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/agda/highlights.scmnew file+124

This diff is too big to show here.

runtime/queries/bash/indents.scm+1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/cabal/highlights.scmdeleted−15

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/common-lisp/indents.scmnew file+1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/haskell/injections.scm+8

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/markdown/injections.scm+4

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/nu/folds.scmdeleted−4

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/nu/locals.scmdeleted−13

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/ocaml/highlights.scm+2

This diff is too big to show here.

runtime/queries/odin/highlights.scm+18−133

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/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+10−31

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/scala/locals.scmnew file+29

This diff is too big to show here.

runtime/queries/scheme/highlights.scm+5

This diff is too big to show here.

runtime/queries/scheme/indents.scmnew file+43

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/smali/folds.scmnew file+12

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/svelte/indents.scmnew file+18

This diff is too big to show here.

runtime/queries/svelte/injections.scm+10

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/v/highlights.scm+85−51

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/yaml/injections.scm+52

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/README.md+8

This diff is too big to show here.

runtime/themes/amberwood.tomlnew file+138

This diff is too big to show here.

runtime/themes/autumn.toml+67−65

This diff is too big to show here.

runtime/themes/autumn_night.toml+9−8

This diff is too big to show here.

runtime/themes/ayu_dark.toml+5−1

This diff is too big to show here.

runtime/themes/ayu_light.toml+5−1

This diff is too big to show here.

runtime/themes/ayu_mirage.toml+5−1

This diff is too big to show here.

runtime/themes/catppuccin_frappe.toml+2

This diff is too big to show here.

runtime/themes/catppuccin_latte.toml+2

This diff is too big to show here.

runtime/themes/catppuccin_macchiato.toml+2

This diff is too big to show here.

runtime/themes/catppuccin_mocha.toml+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+86−93

This diff is too big to show here.

runtime/themes/everblush.toml+1

This diff is too big to show here.

runtime/themes/everforest_dark.toml+4

This diff is too big to show here.

runtime/themes/ferra.toml+2−3

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/gruvbox.toml+1

This diff is too big to show here.

runtime/themes/gruvbox_dark_hard.toml−2

This diff is too big to show here.

runtime/themes/gruvbox_dark_soft.tomlnew file+7

This diff is too big to show here.

runtime/themes/gruvbox_light.toml−2

This diff is too big to show here.

runtime/themes/horizon-dark.tomlnew file+80

This diff is too big to show here.

runtime/themes/jellybeans.toml+1−1

This diff is too big to show here.

runtime/themes/kanagawa.toml+3

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/material_darker.tomlnew file+20

This diff is too big to show here.

runtime/themes/material_deep_ocean.tomlnew file+123

This diff is too big to show here.

runtime/themes/material_oceanic.tomlnew file+21

This diff is too big to show here.

runtime/themes/material_palenight.tomlnew file+19

This diff is too big to show here.

runtime/themes/merionette.tomlnew file+98

This diff is too big to show here.

runtime/themes/modus_operandi.tomlnew file+194

This diff is too big to show here.

runtime/themes/modus_operandi_deuteranopia.tomlnew file+46

This diff is too big to show here.

runtime/themes/modus_operandi_tinted.tomlnew file+45

This diff is too big to show here.

runtime/themes/modus_operandi_tritanopia.tomlnew file+73

This diff is too big to show here.

runtime/themes/modus_vivendi.tomlnew file+242

This diff is too big to show here.

runtime/themes/modus_vivendi_deuteranopia.tomlnew file+158

This diff is too big to show here.

runtime/themes/modus_vivendi_tinted.tomlnew file+144

This diff is too big to show here.

runtime/themes/modus_vivendi_tritanopia.tomlnew file+186

This diff is too big to show here.

runtime/themes/molokai.toml+1−1

This diff is too big to show here.

runtime/themes/monokai.toml+5

This diff is too big to show here.

runtime/themes/naysayer.tomlnew file+91

This diff is too big to show here.

runtime/themes/new_moon.tomlnew file+124

This diff is too big to show here.

runtime/themes/nightfox.toml+8−4

This diff is too big to show here.

runtime/themes/noctis.toml+34−32

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/nord.toml+162−105

This diff is too big to show here.

runtime/themes/onedark.toml+1

This diff is too big to show here.

runtime/themes/onedarker.toml+1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/poimandres.tomlnew file+113

This diff is too big to show here.

runtime/themes/poimandres_storm.tomlnew file+19

This diff is too big to show here.

runtime/themes/pop-dark.toml+4−4

This diff is too big to show here.

runtime/themes/rose_pine.toml+31−29

This diff is too big to show here.

runtime/themes/spacebones_light.toml+1

This diff is too big to show here.

runtime/themes/starlight.tomlnew file+113

This diff is too big to show here.

runtime/themes/vim_dark_high_contrast.tomlnew file+72

This diff is too big to show here.

runtime/themes/yellowed.tomlnew file+135

This diff is too big to show here.

runtime/themes/zed_onedark.tomlnew file+110

This diff is too big to show here.

runtime/themes/zed_onelight.tomlnew file+55

This diff is too big to show here.

runtime/themes/zenburn.toml+1−1

This diff is too big to show here.

runtime/tutor+165−11

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.

xtask/src/docgen.rs+6−5

This diff is too big to show here.