on 073000e help sign in
381 files changed+8973−19529
since splitsnapshots
unifiedsplit
.envrc−1 .github/ISSUE_TEMPLATE/bug_report.yaml−10 .github/workflows/build.yml+7−15 .github/workflows/cachix.yml+3−3 .github/workflows/gh-pages.yml+5−5 .github/workflows/release.yml+15−15 .ignore+3 CHANGELOG.md+1−261 Cargo.lock+570−662 Cargo.toml−16 README.md+2−2 VERSION+1 book/book.toml+1−5 book/custom.css−231 book/src/configuration.md+13−43 book/src/generated/lang-support.md+18−43 book/src/generated/typable-cmd.md+3−8 book/src/guides/adding_languages.md−2 book/src/guides/indent.md+43−319 book/src/guides/injection.md+1−1 book/src/install.md+23−69 book/src/keymap.md+16−24 book/src/languages.md+18−20 book/src/themes.md+2−10 book/src/usage.md+10−26 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+51−87 contrib/Helix.appdata.xml−15 contrib/Helix.desktop+1−3 docs/CONTRIBUTING.md+3−10 docs/releases.md+6−13 docs/vision.md+1−1 flake.lock+237−21 flake.nix+140−171 grammars.nix+21−24 helix-core/Cargo.toml+16−20 helix-core/src/diagnostic.rs−4 helix-core/src/fuzzy.rs−43 helix-core/src/graphemes.rs+1−1 helix-core/src/history.rs+2−2 helix-core/src/indent.rs+145−617 helix-core/src/lib.rs+2−4 helix-core/src/line_ending.rs+2−2 helix-core/src/match_brackets.rs+29−232 helix-core/src/movement.rs+7−187 helix-core/src/path.rs+7−5 helix-core/src/register.rs+89 helix-core/src/rope_reader.rs−37 helix-core/src/selection.rs+43−76 helix-core/src/syntax.rs+81−105 helix-core/src/transaction.rs+51−191 helix-core/src/wrap.rs+1−3 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+25−153 helix-dap/Cargo.toml+9−11 helix-event/Cargo.toml−16 helix-event/src/lib.rs−8 helix-event/src/redraw.rs−49 helix-loader/Cargo.toml+11−14 helix-loader/build.rs+6−20 helix-loader/src/grammar.rs−11 helix-loader/src/lib.rs+17−78 helix-lsp/Cargo.toml+13−15 helix-lsp/src/client.rs+40−146 helix-lsp/src/file_event.rs−193 helix-lsp/src/lib.rs+41−57 helix-lsp/src/transport.rs+1−9 helix-parsec/Cargo.toml+7−8 helix-term/Cargo.toml+25−33 helix-term/src/application.rs+142−239 helix-term/src/args.rs−29 helix-term/src/commands.rs+435−810 helix-term/src/commands/dap.rs+55−58 helix-term/src/commands/lsp.rs+94−94 helix-term/src/commands/typed.rs+704−987 helix-term/src/compositor.rs−12 helix-term/src/config.rs+8−8 helix-term/src/health.rs+16−44 helix-term/src/keymap.rs+145−105 helix-term/src/keymap/default.rs+7−14 helix-term/src/keymap/macros.rs+4−3 helix-term/src/main.rs+18−22 helix-term/src/ui/completion.rs+32−60 helix-term/src/ui/document.rs+24−60 helix-term/src/ui/editor.rs+57−110 helix-term/src/ui/fuzzy_match.rs+239 helix-term/src/ui/fuzzy_match/test.rs+47 helix-term/src/ui/lsp.rs+3−5 helix-term/src/ui/markdown.rs+12−19 helix-term/src/ui/menu.rs+40−73 helix-term/src/ui/mod.rs+164−101 helix-term/src/ui/picker.rs+577−572 helix-term/src/ui/popup.rs+7−27 helix-term/src/ui/prompt.rs+48−55 helix-term/src/ui/statusline.rs+1−25 helix-term/tests/integration.rs−1 helix-term/tests/test/auto_pairs.rs+1−1 helix-term/tests/test/commands.rs−100 helix-term/tests/test/commands/movement.rs−452 helix-term/tests/test/commands/write.rs+3−122 helix-term/tests/test/helpers.rs+2−10 helix-term/tests/test/languages/go.rs−41 helix-term/tests/test/languages/mod.rs−4 helix-term/tests/test/languages/yaml.rs−819 helix-term/tests/test/movement.rs−39 helix-term/tests/test/splits.rs+3−3 helix-tui/Cargo.toml+15−15 helix-tui/src/backend/crossterm.rs+33−51 helix-tui/src/buffer.rs+2−2 helix-tui/src/terminal.rs+14 helix-tui/src/widgets/table.rs+3−3 helix-vcs/Cargo.toml+10−12 helix-vcs/src/diff.rs+19−6 helix-vcs/src/diff/line_cache.rs+9−9 helix-vcs/src/diff/worker.rs+9−3 helix-vcs/src/diff/worker/test.rs+5−1 helix-vcs/src/git.rs+5−5 helix-view/Cargo.toml+18−22 helix-view/src/clipboard.rs+2−7 helix-view/src/document.rs+76−220 helix-view/src/editor.rs+94−253 helix-view/src/gutter.rs+28−15 helix-view/src/handlers/dap.rs+3−1 helix-view/src/info.rs+25−5 helix-view/src/input.rs+3−67 helix-view/src/lib.rs−1 helix-view/src/register.rs−333 helix-view/src/theme.rs+4−25 helix-view/src/view.rs+2−8 languages.toml+295−720 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+1−18 runtime/queries/cairo/highlights.scm+94−1 runtime/queries/cairo/indents.scm−1 runtime/queries/cairo/injections.scm+4−2 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+1−3 runtime/queries/dbml/highlights.scm−44 runtime/queries/ecma/README.md−14 runtime/queries/ecma/highlights.scm+2−9 runtime/queries/ecma/locals.scm+3−17 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+5−16 runtime/queries/gn/highlights.scm−42 runtime/queries/gn/injections.scm−15 runtime/queries/gn/locals.scm−16 runtime/queries/go/highlights.scm+5−13 runtime/queries/go/locals.scm+2−10 runtime/queries/hare/highlights.scm+1−4 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+11−24 runtime/queries/haskell/injections.scm−8 runtime/queries/janet/highlights.scm−1 runtime/queries/java/highlights.scm+2−81 runtime/queries/java/indents.scm−35 runtime/queries/javascript/highlights.scm+37−2 runtime/queries/javascript/indents.scm+1−3 runtime/queries/javascript/injections.scm+1−3 runtime/queries/javascript/locals.scm+1−3 runtime/queries/javascript/tags.scm+87−2 runtime/queries/javascript/textobjects.scm+1−3 runtime/queries/jinja/highlights.scm−7 runtime/queries/jinja/injections.scm−4 runtime/queries/json5/highlights.scm−11 runtime/queries/jsx/highlights.scm+36−2 runtime/queries/jsx/indents.scm+8−2 runtime/queries/jsx/injections.scm+1−3 runtime/queries/jsx/locals.scm+1−3 runtime/queries/jsx/tags.scm−3 runtime/queries/jsx/textobjects.scm+1−3 runtime/queries/kdl/highlights.scm+4−4 runtime/queries/kdl/indents.scm−3 runtime/queries/kdl/textobjects.scm−27 runtime/queries/kotlin/highlights.scm+1−4 runtime/queries/kotlin/locals.scm−15 runtime/queries/log/highlights.scm−10 runtime/queries/lpf/highlights.scm−19 runtime/queries/markdown.inline/injections.scm+1−4 runtime/queries/matlab/context.scm−41 runtime/queries/matlab/folds.scm−11 runtime/queries/matlab/highlights.scm+77−108 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/injections.scm−14 runtime/queries/nu/folds.scm+4 runtime/queries/nu/highlights.scm+44−276 runtime/queries/nu/injections.scm+1−1 runtime/queries/nu/locals.scm+13 runtime/queries/nunjucks/highlights.scm−1 runtime/queries/nunjucks/injections.scm−1 runtime/queries/ocaml/highlights.scm−2 runtime/queries/perl/fold.scm−19 runtime/queries/perl/highlights.scm+155−72 runtime/queries/perl/indents.scm+11−25 runtime/queries/perl/injections.scm+1−3 runtime/queries/perl/textobjects.scm+13−10 runtime/queries/pod/highlights.scm−61 runtime/queries/prql/highlights.scm+12−19 runtime/queries/purescript/highlights.scm+1−164 runtime/queries/purescript/injections.scm+1−2 runtime/queries/purescript/locals.scm+1−4 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+13−51 runtime/queries/ron/highlights.scm+1−43 runtime/queries/ron/indents.scm+1−12 runtime/queries/ron/injections.scm+1−2 runtime/queries/rust/highlights.scm−7 runtime/queries/rust/indents.scm+2−69 runtime/queries/rust/injections.scm−6 runtime/queries/rust/locals.scm+1−14 runtime/queries/rust/textobjects.scm+1−4 runtime/queries/scala/locals.scm−29 runtime/queries/scheme/highlights.scm−5 runtime/queries/scheme/indents.scm−43 runtime/queries/slint/highlights.scm+72−118 runtime/queries/slint/indents.scm+5−6 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+176−326 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+2−15 runtime/queries/tsx/highlights.scm+1−3 runtime/queries/tsx/indents.scm+1−3 runtime/queries/tsx/injections.scm+1−3 runtime/queries/tsx/locals.scm−3 runtime/queries/tsx/tags.scm−3 runtime/queries/tsx/textobjects.scm+1−3 runtime/queries/typescript/highlights.scm+76−2 runtime/queries/typescript/indents.scm+6−2 runtime/queries/typescript/injections.scm+1−3 runtime/queries/typescript/locals.scm+2−3 runtime/queries/typescript/tags.scm+22−2 runtime/queries/typescript/textobjects.scm+7−2 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+51−85 runtime/queries/v/indents.scm+3−2 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+1−35 runtime/queries/yaml/injections.scm−52 runtime/queries/yuck/highlights.scm+1−1 runtime/queries/zig/highlights.scm+2−5 runtime/queries/zig/textobjects.scm+3−3 runtime/themes/README.md−8 runtime/themes/autumn.toml+65−67 runtime/themes/autumn_night.toml+8−9 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+4−1 runtime/themes/darcula.toml+4−10 runtime/themes/dark_plus.toml+1−2 runtime/themes/dracula.toml−2 runtime/themes/everblush.toml−1 runtime/themes/everforest_dark.toml−4 runtime/themes/ferra.toml+2−1 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/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+2−2 runtime/themes/noctis.toml+26−26 runtime/themes/nord-night.toml−23 runtime/themes/nord.toml+105−162 runtime/themes/onedark.toml−1 runtime/themes/onedarker.toml−1 runtime/themes/papercolor-dark.toml+116−67 runtime/themes/papercolor-light.toml+103−194 runtime/themes/poimandres.toml−113 runtime/themes/poimandres_storm.toml−19 runtime/themes/pop-dark.toml+4−4 runtime/themes/rose_pine.toml+29−31 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+3−157 rust-toolchain.toml+2−2 xtask/Cargo.toml+6−12
.envrc−1
@@ -1,6 +1,5 @@
11watch_file shell.nix
22watch_file flake.lock
3−watch_file rust-toolchain.toml
43
54# try to use flakes, if it fails use normal nix (ie. shell.nix)
65use flake || use nix
.github/ISSUE_TEMPLATE/bug_report.yaml−10
@@ -55,16 +55,6 @@ 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
6858 - type: input
6959 id: helix-version
7060 attributes:
.github/workflows/build.yml+7−15
@@ -14,7 +14,7 @@ jobs:
1414 runs-on: ubuntu-latest
1515 steps:
1616 - name: Checkout sources
17− uses: actions/checkout@v4
17+ uses: actions/checkout@v3
1818 - name: Install stable toolchain
1919 uses: helix-editor/rust-toolchain@v1
2020 with:
@@ -22,8 +22,6 @@ jobs:
2222 override: true
2323
2424 - uses: Swatinem/rust-cache@v2
25− with:
26− shared-key: "build"
2725
2826 - name: Run cargo check
2927 run: cargo check
@@ -36,14 +34,12 @@ jobs:
3634 HELIX_LOG_LEVEL: info
3735 steps:
3836 - name: Checkout sources
39− uses: actions/checkout@v4
37+ uses: actions/checkout@v3
4038
4139 - name: Install stable toolchain
42− uses: dtolnay/rust-toolchain@1.70
40+ uses: dtolnay/rust-toolchain@1.65
4341
4442 - uses: Swatinem/rust-cache@v2
45− with:
46− shared-key: "build"
4743
4844 - name: Cache test tree-sitter grammar
4945 uses: actions/cache@v3
@@ -67,16 +63,14 @@ jobs:
6763 runs-on: ubuntu-latest
6864 steps:
6965 - name: Checkout sources
70− uses: actions/checkout@v4
66+ uses: actions/checkout@v3
7167
7268 - name: Install stable toolchain
73− uses: dtolnay/rust-toolchain@1.70
69+ uses: dtolnay/rust-toolchain@1.65
7470 with:
7571 components: rustfmt, clippy
7672
7773 - uses: Swatinem/rust-cache@v2
78− with:
79− shared-key: "build"
8074
8175 - name: Run cargo fmt
8276 run: cargo fmt --all --check
@@ -94,14 +88,12 @@ jobs:
9488 runs-on: ubuntu-latest
9589 steps:
9690 - name: Checkout sources
97− uses: actions/checkout@v4
91+ uses: actions/checkout@v3
9892
9993 - name: Install stable toolchain
100− uses: dtolnay/rust-toolchain@1.70
94+ uses: dtolnay/rust-toolchain@1.65
10195
10296 - uses: Swatinem/rust-cache@v2
103− with:
104− shared-key: "build"
10597
10698 - name: Validate queries
10799 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@v4
14+ uses: actions/checkout@v3
1515
1616 - name: Install nix
17− uses: cachix/install-nix-action@v24
17+ uses: cachix/install-nix-action@v20
1818
1919 - name: Authenticate with Cachix
20− uses: cachix/cachix-action@v13
20+ uses: cachix/cachix-action@v12
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@v4
14+ - uses: actions/checkout@v3
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 stable
29+ - name: Deploy
3030 uses: peaceiris/actions-gh-pages@v3
31− if: startswith(github.ref, 'refs/tags/')
3231 with:
3332 github_token: ${{ secrets.GITHUB_TOKEN }}
3433 publish_dir: ./book/book
34+ destination_dir: ./${{ env.OUTDIR }}
3535
36− - name: Deploy
36+ - name: Deploy stable
3737 uses: peaceiris/actions-gh-pages@v3
38+ if: startswith(github.ref, 'refs/tags/')
3839 with:
3940 github_token: ${{ secrets.GITHUB_TOKEN }}
4041 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@v4
26+ uses: actions/checkout@v3
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@v4
39+ - uses: actions/upload-artifact@v3
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@v4
106+ uses: actions/checkout@v3
107107
108108 - name: Download grammars
109− uses: actions/download-artifact@v4
109+ uses: actions/download-artifact@v3
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 == 'x86_64-linux'
163+ if: matrix.build == 'aarch64-linux' || 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@v4
223+ - uses: actions/upload-artifact@v3
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@v4
234+ uses: actions/checkout@v3
235235
236− - uses: actions/download-artifact@v4
236+ - uses: actions/download-artifact@v3
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" = "x86_64-linux" ]]; then
266+ if [[ "$platform" = "aarch64-linux" || "$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@v4
291+ uses: actions/upload-artifact@v3
292292 if: env.preview == 'true'
293293 with:
294294 name: release
.ignore+3
@@ -1,2 +1,5 @@
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+1−261
@@ -1,263 +1,3 @@
1−# 23.10 (2023-10-24)
2−
3−A big shout out to all the contributors! We had 118 contributors in this release.
4−
5−Breaking changes:
6−
7−- Support multiple language servers per language ([#2507](https://github.com/helix-editor/helix/pull/2507))
8− - This is a breaking change to language configuration
9−
10−Features:
11−
12−- Support multiple language servers per language ([#2507](https://github.com/helix-editor/helix/pull/2507), [#7082](https://github.com/helix-editor/helix/pull/7082), [#7286](https://github.com/helix-editor/helix/pull/7286), [#8374](https://github.com/helix-editor/helix/pull/8374))
13−- Add a statusline element for the selected register ([#7222](https://github.com/helix-editor/helix/pull/7222))
14−- Add `%`, `#`, `.`, `*` and `+` special registers ([#6985](https://github.com/helix-editor/helix/pull/6985))
15−- Add initial support for LSP DidChangeWatchedFiles notifications ([#7665](https://github.com/helix-editor/helix/pull/7665))
16−- Search buffer contents in `global_search` ([#5652](https://github.com/helix-editor/helix/pull/5652))
17−- Add a "smart tab" command that intelligently jumps the cursor on tab ([#4443](https://github.com/helix-editor/helix/pull/4443))
18−- Add a statusline element for whether a file is read-only ([#7222](https://github.com/helix-editor/helix/pull/7222), [#7875](https://github.com/helix-editor/helix/pull/7875))
19−- Syntax highlight regex prompts ([#7738](https://github.com/helix-editor/helix/pull/7738))
20−- Allow defining alignment in indent queries ([#5355](https://github.com/helix-editor/helix/pull/5355))
21−- Show visual feedback in `surround_replace` ([#7588](https://github.com/helix-editor/helix/pull/7588))
22−- Switch to Nucleo for fuzzy matching ([#7814](https://github.com/helix-editor/helix/pull/7814), [#8148](https://github.com/helix-editor/helix/pull/8148), [#8192](https://github.com/helix-editor/helix/pull/8192), [#8194](https://github.com/helix-editor/helix/pull/8194))
23−- Insert a trailing newline on write ([#8157](https://github.com/helix-editor/helix/pull/8157))
24−- Add a `-w`/`--working-dir` CLI flag for specifying a working directory on startup ([#8223](https://github.com/helix-editor/helix/pull/8223), [#8498](https://github.com/helix-editor/helix/pull/8498), [#8520](https://github.com/helix-editor/helix/pull/8520))
25−- Accept a `+N` CLI argument to set the first file's line number ([#8521](https://github.com/helix-editor/helix/pull/8521))
26−- Accept Helix-specific ignore files in `.helix/ignore` and `~/.config/helix/ignore` ([#8099](https://github.com/helix-editor/helix/pull/8099))
27−
28−Commands:
29−
30−- `merge_selections` (`A-minus`) - merge all selections into one selection that covers all ranges ([#7053](https://github.com/helix-editor/helix/pull/7053))
31−- `move_prev_long_word_end` and `extend_prev_long_word_end` - move/extend to the end of the previous WORD ([#6905](https://github.com/helix-editor/helix/pull/6905))
32−- `reverse_selection_contents` - swaps the values of each selection so they are reversed ([#7329](https://github.com/helix-editor/helix/pull/7329))
33−- Add `:rl` and `:rla` aliases for `:reload` and `:reload-all` ([#7158](https://github.com/helix-editor/helix/pull/7158))
34−- `yank_joined` - join the selections and yank to the selected register ([#7195](https://github.com/helix-editor/helix/pull/7195))
35−- `:write-all!` (`:wa!`) - forcibly write all buffers to disk and create any necessary subdirectories ([#7577](https://github.com/helix-editor/helix/pull/7577))
36−- `:redraw` - clear re-render the UI ([#6949](https://github.com/helix-editor/helix/pull/6949))
37−- `:tree-sitter-highlight-name` - show the theme scope name of the highlight under the cursor ([#8170](https://github.com/helix-editor/helix/pull/8170))
38−
39−Usability improvements:
40−
41−- Allow cycling option values at runtime ([#4411](https://github.com/helix-editor/helix/pull/4411), [#7240](https://github.com/helix-editor/helix/pull/7240), [#7877](https://github.com/helix-editor/helix/pull/7877))
42−- Exit gracefully on termination signals ([#7236](https://github.com/helix-editor/helix/pull/7236))
43−- Add plaintext matching fallback to tree-sitter pair matching ([#4288](https://github.com/helix-editor/helix/pull/4288))
44−- Persist register selection in pending keymaps ([0e08349](https://github.com/helix-editor/helix/commit/0e08349))
45−- Propagate the count and register to command palette commands ([b394997](https://github.com/helix-editor/helix/commit/b394997))
46−- Auto indent on `insert_at_line_start` ([#5837](https://github.com/helix-editor/helix/pull/5837))
47−- Add a config option to control whether LSP completions are automatically inserted on preview ([#7189](https://github.com/helix-editor/helix/pull/7189))
48−- Add a config option for default line endings ([#5621](https://github.com/helix-editor/helix/pull/5621), [#7357](https://github.com/helix-editor/helix/pull/7357))
49−- Allow ANSI colors in themes ([#5119](https://github.com/helix-editor/helix/pull/5119))
50−- Match pairs that don't form a standalone tree-sitter node ([#7242](https://github.com/helix-editor/helix/pull/7242))
51−- Allow indent sizes of up to 16 columns ([#7429](https://github.com/helix-editor/helix/pull/7429))
52−- Improve performance of mapping positions through changes ([#7408](https://github.com/helix-editor/helix/pull/7408), [8d39a81](https://github.com/helix-editor/helix/commit/8d39a81), [#7471](https://github.com/helix-editor/helix/pull/7471))
53−- Mark buffers created from stdin as modified ([#7431](https://github.com/helix-editor/helix/pull/7431))
54−- Forcibly shut down uninitialized language servers ([#7449](https://github.com/helix-editor/helix/pull/7449))
55−- Add filename completer for shell prompts ([#7569](https://github.com/helix-editor/helix/pull/7569))
56−- Allow binding F13-F24 ([#7672](https://github.com/helix-editor/helix/pull/7672))
57−- Resolve LSP code actions ([#7677](https://github.com/helix-editor/helix/pull/7677), [#8421](https://github.com/helix-editor/helix/pull/8421))
58−- Save an undo checkpoint before accepting completions ([#7747](https://github.com/helix-editor/helix/pull/7747))
59−- Include gitignored files in debugger completions ([#7936](https://github.com/helix-editor/helix/pull/7936))
60−- Make editor remember the last search register ([#5244](https://github.com/helix-editor/helix/pull/5244))
61−- Open directories with `goto_file` ([#7909](https://github.com/helix-editor/helix/pull/7909))
62−- Use relative path to open buffer in `goto_file` (`gf`) ([#7965](https://github.com/helix-editor/helix/pull/7965))
63−- Support `default` color in themes ([#8083](https://github.com/helix-editor/helix/pull/8083), [#8114](https://github.com/helix-editor/helix/pull/8114))
64−- Toggle between relative and absolute line numbers when the terminal loses focus ([#7955](https://github.com/helix-editor/helix/pull/7955))
65−- Lower default idle-timeout to 250ms ([060e73a](https://github.com/helix-editor/helix/commit/060e73a))
66−- Allow theming diff gutters separately from other diff colors ([#8343](https://github.com/helix-editor/helix/pull/8343))
67−- Style bold/italic/strikethrough in markdown doc popups ([#8385](https://github.com/helix-editor/helix/pull/8385))
68−- Maintain the cursor position and view when splitting with `:hsplit`/`:vsplit` ([#8109](https://github.com/helix-editor/helix/pull/8109))
69−- Accept `-` in macros outside of `<`/`>` ([#8475](https://github.com/helix-editor/helix/pull/8475))
70−- Show all language servers for each language in `--health` ([#7315](https://github.com/helix-editor/helix/pull/7315))
71−- Don't break on hyphens in `:reflow` ([#8569](https://github.com/helix-editor/helix/pull/8569))
72−
73−Fixes:
74−
75−- Update diagnostics correctly on language server exit ([#7111](https://github.com/helix-editor/helix/pull/7111))
76−- Fix off-by-one in `select_references_to_symbol_under_cursor` ([#7132](https://github.com/helix-editor/helix/pull/7132))
77−- Extend selection with repeat-last-motion only if the original motion extended the selection ([#7159](https://github.com/helix-editor/helix/pull/7159))
78−- Fix undefined behavior in the diff gutter ([#7227](https://github.com/helix-editor/helix/pull/7227))
79−- Check that tab width is non-zero ([#7178](https://github.com/helix-editor/helix/pull/7178))
80−- Fix styles being overwritten in table rows with multiple cells ([#7281](https://github.com/helix-editor/helix/pull/7281))
81−- Add file for `--log` CLI arg in help text ([#7307](https://github.com/helix-editor/helix/pull/7307))
82−- Fix underflow when repeating a completion that has a negative shift position ([#7322](https://github.com/helix-editor/helix/pull/7322))
83−- Prefer longer matches in `select_next_sibling` and `select_prev_sibling` ([#7332](https://github.com/helix-editor/helix/pull/7332))
84−- Preview scratch buffers in the jumplist picker ([#7331](https://github.com/helix-editor/helix/pull/7331))
85−- Fix chunking by bytes in tree-sitter parsing ([#7417](https://github.com/helix-editor/helix/pull/7417))
86−- Discard LSP publishDiagnostic from uninitialized servers ([#7467](https://github.com/helix-editor/helix/pull/7467))
87−- Use negotiated position encoding for LSP workspace edits ([#7469](https://github.com/helix-editor/helix/pull/7469))
88−- Fix error message for unknown gutter types in config ([#7534](https://github.com/helix-editor/helix/pull/7534))
89−- Fix `:log-open` when `--log` CLI arg is specified ([#7573](https://github.com/helix-editor/helix/pull/7573), [#7585](https://github.com/helix-editor/helix/pull/7585))
90−- Fix debouncing of LSP messages to fix the last message sticking around ([#7538](https://github.com/helix-editor/helix/pull/7538), [#8023](https://github.com/helix-editor/helix/pull/8023))
91−- Fix crash when the current working directory is deleted ([#7185](https://github.com/helix-editor/helix/pull/7185))
92−- Fix piping to Helix on macOS ([#5468](https://github.com/helix-editor/helix/pull/5468))
93−- Fix crash when parsing overlapping injections ([#7621](https://github.com/helix-editor/helix/pull/7621))
94−- Clear the statusline when the prompt is visible ([#7646](https://github.com/helix-editor/helix/pull/7646))
95−- Fix range formatting error message typo ([#7823](https://github.com/helix-editor/helix/pull/7823))
96−- Skip rendering gutters when gutter width exceeds view width ([#7821](https://github.com/helix-editor/helix/pull/7821))
97−- Center the picker preview using visual lines ([#7837](https://github.com/helix-editor/helix/pull/7837))
98−- Align view correctly for background buffers opened with `A-ret` ([#7691](https://github.com/helix-editor/helix/pull/7691))
99−- Fix cursor resetting to block when quitting via a keybind ([#7931](https://github.com/helix-editor/helix/pull/7931))
100−- Remove path completions for the `:new` command ([#8010](https://github.com/helix-editor/helix/pull/8010))
101−- Use binary path resolved by `which` for formatter commands ([#8064](https://github.com/helix-editor/helix/pull/8064))
102−- Handle crossterm's `hidden` modifier ([#8120](https://github.com/helix-editor/helix/pull/8120))
103−- Clear completion when switching between windows with the mouse ([#8118](https://github.com/helix-editor/helix/pull/8118))
104−- Eagerly remove the last picker (`<space>'`) when the picker has many items ([#8127](https://github.com/helix-editor/helix/pull/8127))
105−- Fix find commands for buffers with non-LF line-endings ([#8111](https://github.com/helix-editor/helix/pull/8111))
106−- Detect the tmux clipboard provider on macOS ([#8182](https://github.com/helix-editor/helix/pull/8182))
107−- Fix syntax highlighting in dynamic picker preview pane ([#8206](https://github.com/helix-editor/helix/pull/8206))
108−- Recognize HTML code tags with attributes as code in markdown previews ([#8397](https://github.com/helix-editor/helix/pull/8397))
109−- Fix multicursor snippet placeholder directions ([#8423](https://github.com/helix-editor/helix/pull/8423))
110−- Only show diagnostic highlights when diagnostics are enabled for a language server ([#8551](https://github.com/helix-editor/helix/pull/8551))
111−
112−Themes:
113−
114−- Improve the selection color in `ferra` ([#7138](https://github.com/helix-editor/helix/pull/7138))
115−- Add `variable.other.member` theming to `spacebones_light` ([#7125](https://github.com/helix-editor/helix/pull/7125))
116−- Update `autumn` and theme the soft-wrap indicator ([#7229](https://github.com/helix-editor/helix/pull/7229))
117−- Add `gruvbox_dark_soft` ([#7139](https://github.com/helix-editor/helix/pull/7139))
118−- Add `merionette` ([#7186](https://github.com/helix-editor/helix/pull/7186))
119−- Add `zed_onedark` and `zed_onelight` ([#7250](https://github.com/helix-editor/helix/pull/7250))
120−- Use light-gray for `onedarker` inlay hint theming ([#7433](https://github.com/helix-editor/helix/pull/7433))
121−- Update the Nord theme to follow the style guidelines ([#7490](https://github.com/helix-editor/helix/pull/7490))
122−- Tune `dark_plus` inlay hint colors ([#7611](https://github.com/helix-editor/helix/pull/7611))
123−- Add `naysayer` ([#7570](https://github.com/helix-editor/helix/pull/7570))
124−- Add `kaolin-dark`, `kaolin-light` and `kaolin-valley-dark` ([#7151](https://github.com/helix-editor/helix/pull/7151))
125−- Fix selection highlighting in gruvbox variants ([#7717](https://github.com/helix-editor/helix/pull/7717))
126−- Add soft-wrap indicator to `gruvbox` ([#7736](https://github.com/helix-editor/helix/pull/7736))
127−- Add missing palette definitions in `everforest_dark` ([#7739](https://github.com/helix-editor/helix/pull/7739))
128−- Increase diagnostics clarity in `pop-dark` ([#7702](https://github.com/helix-editor/helix/pull/7702))
129−- Add `vim_dark_high_contrast` ([#7785](https://github.com/helix-editor/helix/pull/7785))
130−- Add `new_moon` ([#7834](https://github.com/helix-editor/helix/pull/7834))
131−- Add `yellowed` ([#7849](https://github.com/helix-editor/helix/pull/7849))
132−- Improve comment readability for `autumn` ([#7939](https://github.com/helix-editor/helix/pull/7939))
133−- Distinguish active bufferline buffer in `monokai` ([#7983](https://github.com/helix-editor/helix/pull/7983))
134−- Update ruler colors in `nord` ([#7995](https://github.com/helix-editor/helix/pull/7995))
135−- Update Catppuccin themes ([#8102](https://github.com/helix-editor/helix/pull/8102))
136−- Add text focus scope and diagnostics undercurls for `nord` ([#8165](https://github.com/helix-editor/helix/pull/8165))
137−- Add material theme collection ([#8211](https://github.com/helix-editor/helix/pull/8211))
138−- Improve indent line color in `dracula` ([#8266](https://github.com/helix-editor/helix/pull/8266))
139−- Clean up and refactor `papercolor` to use inheritance ([#8276](https://github.com/helix-editor/helix/pull/8276))
140−- Fix `zenburn` inlay hint color ([#8278](https://github.com/helix-editor/helix/pull/8278)a)
141−- Fix picker crash when previewing an invalid range ([e9d0bd7](https://github.com/helix-editor/helix/commit/e9d0bd7))
142−- Correctly center items in the picker preview ([13d4463](https://github.com/helix-editor/helix/commit/13d4463))
143−- Add `cyan_light` ([#8293](https://github.com/helix-editor/helix/pull/8293), [#8587](https://github.com/helix-editor/helix/pull/8587))
144−- Theme HTML tags in `onedark` ([#8409](https://github.com/helix-editor/helix/pull/8409))
145−- Refine `darcula` and `darcula-solid` themes ([#8412](https://github.com/helix-editor/helix/pull/8412))
146−- Improve `nord` highlights ([#8414](https://github.com/helix-editor/helix/pull/8414))
147−- Add `nord-night` ([#8549](https://github.com/helix-editor/helix/pull/8549))
148−
149−New languages:
150−
151−- Blueprint ([#7213](https://github.com/helix-editor/helix/pull/7213), [#8161](https://github.com/helix-editor/helix/pull/8161))
152−- Forth ([#7256](https://github.com/helix-editor/helix/pull/7256), [#7334](https://github.com/helix-editor/helix/pull/7334))
153−- t32 ([#7140](https://github.com/helix-editor/helix/pull/7140), [#7811](https://github.com/helix-editor/helix/pull/7811))
154−- WebC ([#7290](https://github.com/helix-editor/helix/pull/7290))
155−- Persistent DSL for Haskell ([#7261](https://github.com/helix-editor/helix/pull/7261))
156−- F# ([#7619](https://github.com/helix-editor/helix/pull/7619), [#8024](https://github.com/helix-editor/helix/pull/8024))
157−- Wren ([#7765](https://github.com/helix-editor/helix/pull/7765), [#7819](https://github.com/helix-editor/helix/pull/7819))
158−- Unison ([#7724](https://github.com/helix-editor/helix/pull/7724))
159−- Todo.txt ([#7835](https://github.com/helix-editor/helix/pull/7835))
160−- Jinja and Handlebars ([#7233](https://github.com/helix-editor/helix/pull/7233))
161−- Pod ([#7907](https://github.com/helix-editor/helix/pull/7907))
162−- Strace ([#7928](https://github.com/helix-editor/helix/pull/7928))
163−- Gemini ([#8070](https://github.com/helix-editor/helix/pull/8070))
164−- GNU Assembler (GAS) ([#8291](https://github.com/helix-editor/helix/pull/8291))
165−- JSON5 ([#8473](https://github.com/helix-editor/helix/pull/8473))
166−- TEMPL ([#8540](https://github.com/helix-editor/helix/pull/8540))
167−
168−Updated languages and queries:
169−
170−- Add one to the ruler numbers for git-commit ([#7072](https://github.com/helix-editor/helix/pull/7072))
171−- Recognize XAML files as XML ([#7083](https://github.com/helix-editor/helix/pull/7083))
172−- Recognize `Cargo.lock` as TOML ([#7095](https://github.com/helix-editor/helix/pull/7095))
173−- Use Rust grammar for Cairo ([c6d1430](https://github.com/helix-editor/helix/commit/c6d1430))
174−- Update tree-sitter-nickel ([#7059](https://github.com/helix-editor/helix/pull/7059), [#7551](https://github.com/helix-editor/helix/pull/7551))
175−- Tune auto-pair characters for Nickel ([#7059](https://github.com/helix-editor/helix/pull/7059))
176−- Recognize `Vagrantfile` as Ruby ([#7112](https://github.com/helix-editor/helix/pull/7112))
177−- Recognize hidden justfiles as Just ([#7088](https://github.com/helix-editor/helix/pull/7088))
178−- Update Java and TypeScript highlight queries ([#7145](https://github.com/helix-editor/helix/pull/7145))
179−- Recognize `.zimrc` as Bash ([#7146](https://github.com/helix-editor/helix/pull/7146))
180−- Recognize `.gir` as XML ([#7152](https://github.com/helix-editor/helix/pull/7152))
181−- Update tree-sitter-scala ([#7147](https://github.com/helix-editor/helix/pull/7147))
182−- Recognize make file-type as Makefile ([#7212](https://github.com/helix-editor/helix/pull/7212))
183−- Update tree-sitter-verilog ([#7262](https://github.com/helix-editor/helix/pull/7262))
184−- Update tree-sitter-cpp ([#7285](https://github.com/helix-editor/helix/pull/7285))
185−- Support core mode for delve debugger ([#7300](https://github.com/helix-editor/helix/pull/7300))
186−- Add Fortran comment injections ([#7305](https://github.com/helix-editor/helix/pull/7305))
187−- Switch Vue language server to `vue-language-server` ([#7312](https://github.com/helix-editor/helix/pull/7312))
188−- Update tree-sitter-sql ([#7387](https://github.com/helix-editor/helix/pull/7387), [#8464](https://github.com/helix-editor/helix/pull/8464))
189−- Replace the MATLAB tre-sitter grammar ([#7388](https://github.com/helix-editor/helix/pull/7388), [#7442](https://github.com/helix-editor/helix/pull/7442), [#7491](https://github.com/helix-editor/helix/pull/7491), [#7493](https://github.com/helix-editor/helix/pull/7493), [#7511](https://github.com/helix-editor/helix/pull/7511), [#7532](https://github.com/helix-editor/helix/pull/7532), [#8040](https://github.com/helix-editor/helix/pull/8040))
190−- Highlight TOML table headers ([#7441](https://github.com/helix-editor/helix/pull/7441))
191−- Recognize `cppm` file-type as C++ ([#7492](https://github.com/helix-editor/helix/pull/7492))
192−- Refactor ecma language queries into private and public queries ([#7207](https://github.com/helix-editor/helix/pull/7207))
193−- Update tree-sitter-dart ([#7576](https://github.com/helix-editor/helix/pull/7576))
194−- Add shebang for nushell files ([#7606](https://github.com/helix-editor/helix/pull/7606))
195−- Recognize systemd files as INI ([#7592](https://github.com/helix-editor/helix/pull/7592))
196−- Update TypeScript, TSX and Svelte grammars ([#6874](https://github.com/helix-editor/helix/pull/6874))
197−- Enable inlay hints in the Svelte language server ([#7622](https://github.com/helix-editor/helix/pull/7622))
198−- Recognize `Brewfile`s as Ruby ([#7629](https://github.com/helix-editor/helix/pull/7629))
199−- Add more file-types for R ([#7633](https://github.com/helix-editor/helix/pull/7633))
200−- Switch tree-sitter-perl to official upstream parser ([#7644](https://github.com/helix-editor/helix/pull/7644), [#7947](https://github.com/helix-editor/helix/pull/7947))
201−- Fix predicate typo in comment highlights ([#7732](https://github.com/helix-editor/helix/pull/7732))
202−- Update tree-sitter-prql ([#7771](https://github.com/helix-editor/helix/pull/7771))
203−- Recognize `.gitf` as JSON ([#7781](https://github.com/helix-editor/helix/pull/7781))
204−- Switch V language server to `v-analyzer` ([#7760](https://github.com/helix-editor/helix/pull/7760))
205−- Add protobuf language servers ([#7796](https://github.com/helix-editor/helix/pull/7796))
206−- Update tree-sitter-zig ([#7803](https://github.com/helix-editor/helix/pull/7803))
207−- Update tree-sitter-hare ([#7784](https://github.com/helix-editor/helix/pull/7784))
208−- Add Java indent queries ([#7844](https://github.com/helix-editor/helix/pull/7844))
209−- Update tree-sitter-scheme ([979933b](https://github.com/helix-editor/helix/commit/979933b))
210−- Recognize `scm` as Scheme instead of TSQ ([5707151](https://github.com/helix-editor/helix/commit/5707151))
211−- Update tree-sitter-git-commit ([#7831](https://github.com/helix-editor/helix/pull/7831))
212−- Update JavaScript, TypeScript and TSX grammars ([#7852](https://github.com/helix-editor/helix/pull/7852))
213−- Update tree-sitter-nu ([#7873](https://github.com/helix-editor/helix/pull/7873))
214−- Fix YAML indentation ([#6768](https://github.com/helix-editor/helix/pull/6768))
215−- Add `csharp-ls`, Pyright, Pylyzer and add roots for Python ([#7897](https://github.com/helix-editor/helix/pull/7897), [#8032](https://github.com/helix-editor/helix/pull/8032))
216−- Update tree-sitter-slint ([#7893](https://github.com/helix-editor/helix/pull/7893))
217−- Recognize more ZSH file-types as Bash ([#7930](https://github.com/helix-editor/helix/pull/7930))
218−- Recognize `star` extension as Starlark ([#7922](https://github.com/helix-editor/helix/pull/7922))
219−- Fix inline HTML tag highlighting in markdown ([#7960](https://github.com/helix-editor/helix/pull/7960))
220−- Update tree-sitter-robot ([#7970](https://github.com/helix-editor/helix/pull/7970))
221−- Highlight Dart 3 `sealed` and `base` keywords ([#7974](https://github.com/helix-editor/helix/pull/7974))
222−- Add configuration for `ltex-ls` to the default `languages.toml` ([#7838](https://github.com/helix-editor/helix/pull/7838))
223−- Update tree-sitter-strace ([#8087](https://github.com/helix-editor/helix/pull/8087))
224−- Update tree-sitter-gleam, enable auto-format ([#8085](https://github.com/helix-editor/helix/pull/8085))
225−- Update tree-sitter-esdl ([#8222](https://github.com/helix-editor/helix/pull/8222))
226−- Expand ignore file-types ([#8220](https://github.com/helix-editor/helix/pull/8220))
227−- Recognize feed related formats as XML ([#8232](https://github.com/helix-editor/helix/pull/8232))
228−- Improve YAML injections ([#8217](https://github.com/helix-editor/helix/pull/8217))
229−- Add shebangs for TypeScript, Julia, Java and OCaml ([95e994a](https://github.com/helix-editor/helix/commit/95e994a))
230−- Highlight abbreviations in Scheme ([ef23847](https://github.com/helix-editor/helix/commit/ef23847))
231−- Remove backtic auto-pair in OCaml ([#8260](https://github.com/helix-editor/helix/pull/8260))
232−- Recognize `flake.lock` as JSON ([#8304](https://github.com/helix-editor/helix/pull/8304))
233−- Add Python test script injection for Nix ([b4494e1](https://github.com/helix-editor/helix/commit/b4494e1))
234−- Fix Nix comment injection precedence ([37e48f4](https://github.com/helix-editor/helix/commit/37e48f4))
235−- Recognize editorconfig files as INI ([#8308](https://github.com/helix-editor/helix/pull/8308))
236−- Recognize `.babelrc` as JSON ([#8309](https://github.com/helix-editor/helix/pull/8309))
237−- Switch Purescript to its own tree-sitter parser ([#8306](https://github.com/helix-editor/helix/pull/8306), [#8338](https://github.com/helix-editor/helix/pull/8338), [#8527](https://github.com/helix-editor/helix/pull/8527))
238−- Update Unison highlights ([#8315](https://github.com/helix-editor/helix/pull/8315))
239−- Recognize `.webmanifest` as JSON ([#8342](https://github.com/helix-editor/helix/pull/8342))
240−- Recognize polkit policy files as XML ([#8369](https://github.com/helix-editor/helix/pull/8369))
241−- Recognize polkit rules files as JavaScript ([#8370](https://github.com/helix-editor/helix/pull/8370))
242−- Update Go highlight queries ([#8399](https://github.com/helix-editor/helix/pull/8399))
243−- Add shebangs for Makefiles ([#8410](https://github.com/helix-editor/helix/pull/8410))
244−- Add file-type associations from VSCode ([#8388](https://github.com/helix-editor/helix/pull/8388))
245−- Add validation to JSON/CSS language server configs ([#8433](https://github.com/helix-editor/helix/pull/8433))
246−- Add a configuration for the tailwind language server ([#8442](https://github.com/helix-editor/helix/pull/8442))
247−- Add a configuration for the ansible language server ([#7973](https://github.com/helix-editor/helix/pull/7973))
248−- Add a configuration for the GraphQL language server ([#8492](https://github.com/helix-editor/helix/pull/8492))
249−- Indent while statements in Bash ([#8528](https://github.com/helix-editor/helix/pull/8528))
250−- Update tree-sitter-haskell and queries ([#8558](https://github.com/helix-editor/helix/pull/8558))
251−
252−Packaging:
253−
254−- Add an overlay to the Nix flake ([#7078](https://github.com/helix-editor/helix/pull/7078))
255−- Check for `git` before fetching or building grammars ([#7320](https://github.com/helix-editor/helix/pull/7320))
256−- Refactor Nix flake to use Crane ([#7763](https://github.com/helix-editor/helix/pull/7763))
257−- Remove the aarch64 appimage from the release CI ([#7832](https://github.com/helix-editor/helix/pull/7832))
258−- Add desktop and icon files to Nix flake output ([#7979](https://github.com/helix-editor/helix/pull/7979))
259−- Build flake packages with the latest stable Rust ([#8133](https://github.com/helix-editor/helix/pull/8133))
260−
2611# 23.05 (2023-05-18)
2622
263323.05 is a smaller release focusing on fixes. There were 88 contributors in this release. Thank you all!
@@ -1856,7 +1596,7 @@ to distinguish it in bug reports..
18561596
18571597- The `runtime/` directory is now properly detected on binary releases and
18581598 on cargo run. `~/.config/helix/runtime` can also be used.
1859−- Registers can now be selected via " (for example, `"ay`)
1599+- Registers can now be selected via " (for example `"ay`)
18601600- Support for Nix files was added
18611601- Movement is now fully tested and matches Kakoune implementation
18621602- A per-file LSP symbol picker was added to space+s
Cargo.lock+570−662
@@ -2,15 +2,6 @@
22# It is not intended for manual editing.
33version = 3
44
5−[[package]]
6−name = "addr2line"
7−version = "0.20.0"
8−source = "registry+https://github.com/rust-lang/crates.io-index"
9−checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3"
10−dependencies = [
11− "gimli",
12−]
13−
145[[package]]
156name = "adler"
167version = "1.0.2"
@@ -19,37 +10,44 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
1910
2011[[package]]
2112name = "ahash"
22−version = "0.8.6"
13+version = "0.7.6"
2314source = "registry+https://github.com/rust-lang/crates.io-index"
24−checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
15+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
2516dependencies = [
26− "cfg-if",
2717 "getrandom",
2818 "once_cell",
2919 "version_check",
30− "zerocopy",
3120]
3221
3322[[package]]
34−name = "aho-corasick"
35−version = "1.1.2"
23+name = "ahash"
24+version = "0.8.3"
3625source = "registry+https://github.com/rust-lang/crates.io-index"
37−checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
26+checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
3827dependencies = [
39− "memchr",
28+ "cfg-if",
29+ "getrandom",
30+ "once_cell",
31+ "version_check",
4032]
4133
4234[[package]]
43−name = "allocator-api2"
44−version = "0.2.14"
35+name = "aho-corasick"
36+version = "0.7.20"
4537source = "registry+https://github.com/rust-lang/crates.io-index"
46−checksum = "c4f263788a35611fba42eb41ff811c5d0360c58b97402570312a350736e2542e"
38+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
39+dependencies = [
40+ "memchr",
41+]
4742
4843[[package]]
49−name = "android-tzdata"
50−version = "0.1.1"
44+name = "aho-corasick"
45+version = "1.0.1"
5146source = "registry+https://github.com/rust-lang/crates.io-index"
52−checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
47+checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
48+dependencies = [
49+ "memchr",
50+]
5351
5452[[package]]
5553name = "android_system_properties"
@@ -62,9 +60,9 @@ dependencies = [
6260
6361[[package]]
6462name = "anyhow"
65−version = "1.0.76"
63+version = "1.0.71"
6664source = "registry+https://github.com/rust-lang/crates.io-index"
67−checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
65+checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
6866
6967[[package]]
7068name = "arc-swap"
@@ -78,21 +76,6 @@ version = "1.1.0"
7876source = "registry+https://github.com/rust-lang/crates.io-index"
7977checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
8078
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−
9679[[package]]
9780name = "bitflags"
9881version = "1.3.2"
@@ -101,17 +84,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
10184
10285[[package]]
10386name = "bitflags"
104−version = "2.4.1"
87+version = "2.2.1"
10588source = "registry+https://github.com/rust-lang/crates.io-index"
106−checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
89+checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813"
10790
10891[[package]]
10992name = "bstr"
110−version = "1.8.0"
93+version = "1.4.0"
11194source = "registry+https://github.com/rust-lang/crates.io-index"
112−checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
95+checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
11396dependencies = [
11497 "memchr",
98+ "once_cell",
11599 "regex-automata",
116100 "serde",
117101]
@@ -131,6 +115,12 @@ version = "3.12.0"
131115source = "registry+https://github.com/rust-lang/crates.io-index"
132116checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
133117
118+[[package]]
119+name = "bytecount"
120+version = "0.6.3"
121+source = "registry+https://github.com/rust-lang/crates.io-index"
122+checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
123+
134124[[package]]
135125name = "bytes"
136126version = "1.4.0"
@@ -145,12 +135,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
145135
146136[[package]]
147137name = "cc"
148−version = "1.0.84"
138+version = "1.0.79"
149139source = "registry+https://github.com/rust-lang/crates.io-index"
150−checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
151−dependencies = [
152− "libc",
153−]
140+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
154141
155142[[package]]
156143name = "cfg-if"
@@ -171,23 +158,25 @@ dependencies = [
171158
172159[[package]]
173160name = "chrono"
174−version = "0.4.31"
161+version = "0.4.24"
175162source = "registry+https://github.com/rust-lang/crates.io-index"
176−checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
163+checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
177164dependencies = [
178− "android-tzdata",
179165 "iana-time-zone",
166+ "num-integer",
180167 "num-traits",
181− "windows-targets 0.48.0",
168+ "winapi",
182169]
183170
184171[[package]]
185172name = "clipboard-win"
186−version = "5.0.0"
173+version = "4.5.0"
187174source = "registry+https://github.com/rust-lang/crates.io-index"
188−checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
175+checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
189176dependencies = [
190177 "error-code",
178+ "str-buf",
179+ "winapi",
191180]
192181
193182[[package]]
@@ -221,12 +210,6 @@ version = "0.8.4"
221210source = "registry+https://github.com/rust-lang/crates.io-index"
222211checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
223212
224−[[package]]
225−name = "cov-mark"
226−version = "1.1.0"
227−source = "registry+https://github.com/rust-lang/crates.io-index"
228−checksum = "9ffa3d3e0138386cd4361f63537765cac7ee40698028844635a54495a92f67f3"
229−
230213[[package]]
231214name = "crc32fast"
232215version = "1.3.2"
@@ -236,58 +219,14 @@ dependencies = [
236219 "cfg-if",
237220]
238221
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−
282222[[package]]
283223name = "crossterm"
284−version = "0.27.0"
224+version = "0.26.1"
285225source = "registry+https://github.com/rust-lang/crates.io-index"
286−checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
226+checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
287227dependencies = [
288− "bitflags 2.4.1",
228+ "bitflags 1.3.2",
289229 "crossterm_winapi",
290− "filedescriptor",
291230 "futures-core",
292231 "libc",
293232 "mio",
@@ -299,9 +238,9 @@ dependencies = [
299238
300239[[package]]
301240name = "crossterm_winapi"
302−version = "0.9.1"
241+version = "0.9.0"
303242source = "registry+https://github.com/rust-lang/crates.io-index"
304−checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
243+checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
305244dependencies = [
306245 "winapi",
307246]
@@ -330,7 +269,7 @@ dependencies = [
330269 "proc-macro2",
331270 "quote",
332271 "scratch",
333− "syn 2.0.38",
272+ "syn 2.0.15",
334273]
335274
336275[[package]]
@@ -347,7 +286,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
347286dependencies = [
348287 "proc-macro2",
349288 "quote",
350− "syn 2.0.38",
289+ "syn 2.0.15",
351290]
352291
353292[[package]]
@@ -364,9 +303,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
364303
365304[[package]]
366305name = "encoding_rs"
367−version = "0.8.33"
306+version = "0.8.32"
368307source = "registry+https://github.com/rust-lang/crates.io-index"
369−checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
308+checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
370309dependencies = [
371310 "cfg-if",
372311]
@@ -381,26 +320,35 @@ dependencies = [
381320]
382321
383322[[package]]
384−name = "equivalent"
385−version = "1.0.0"
323+name = "errno"
324+version = "0.3.1"
386325source = "registry+https://github.com/rust-lang/crates.io-index"
387−checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
326+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
327+dependencies = [
328+ "errno-dragonfly",
329+ "libc",
330+ "windows-sys 0.48.0",
331+]
388332
389333[[package]]
390−name = "errno"
391−version = "0.3.8"
334+name = "errno-dragonfly"
335+version = "0.1.2"
392336source = "registry+https://github.com/rust-lang/crates.io-index"
393−checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
337+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
394338dependencies = [
339+ "cc",
395340 "libc",
396− "windows-sys 0.52.0",
397341]
398342
399343[[package]]
400344name = "error-code"
401−version = "3.0.0"
345+version = "2.3.1"
402346source = "registry+https://github.com/rust-lang/crates.io-index"
403−checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
347+checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
348+dependencies = [
349+ "libc",
350+ "str-buf",
351+]
404352
405353[[package]]
406354name = "etcetera"
@@ -414,20 +362,14 @@ dependencies = [
414362]
415363
416364[[package]]
417−name = "faster-hex"
418−version = "0.9.0"
365+name = "fastrand"
366+version = "1.9.0"
419367source = "registry+https://github.com/rust-lang/crates.io-index"
420−checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
368+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
421369dependencies = [
422− "serde",
370+ "instant",
423371]
424372
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−
431373[[package]]
432374name = "fern"
433375version = "0.6.2"
@@ -438,46 +380,53 @@ dependencies = [
438380]
439381
440382[[package]]
441−name = "filedescriptor"
442−version = "0.8.2"
383+name = "filetime"
384+version = "0.2.21"
443385source = "registry+https://github.com/rust-lang/crates.io-index"
444−checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
386+checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
445387dependencies = [
388+ "cfg-if",
446389 "libc",
447− "thiserror",
448− "winapi",
390+ "redox_syscall 0.2.16",
391+ "windows-sys 0.48.0",
449392]
450393
451394[[package]]
452395name = "flate2"
453−version = "1.0.27"
396+version = "1.0.25"
454397source = "registry+https://github.com/rust-lang/crates.io-index"
455−checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
398+checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
456399dependencies = [
457400 "crc32fast",
458401 "miniz_oxide",
459402]
460403
404+[[package]]
405+name = "fnv"
406+version = "1.0.7"
407+source = "registry+https://github.com/rust-lang/crates.io-index"
408+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
409+
461410[[package]]
462411name = "form_urlencoded"
463−version = "1.2.1"
412+version = "1.1.0"
464413source = "registry+https://github.com/rust-lang/crates.io-index"
465−checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
414+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
466415dependencies = [
467416 "percent-encoding",
468417]
469418
470419[[package]]
471420name = "futures-core"
472−version = "0.3.30"
421+version = "0.3.28"
473422source = "registry+https://github.com/rust-lang/crates.io-index"
474−checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
423+checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
475424
476425[[package]]
477426name = "futures-executor"
478−version = "0.3.29"
427+version = "0.3.28"
479428source = "registry+https://github.com/rust-lang/crates.io-index"
480−checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
429+checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
481430dependencies = [
482431 "futures-core",
483432 "futures-task",
@@ -486,15 +435,15 @@ dependencies = [
486435
487436[[package]]
488437name = "futures-task"
489−version = "0.3.30"
438+version = "0.3.28"
490439source = "registry+https://github.com/rust-lang/crates.io-index"
491−checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
440+checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
492441
493442[[package]]
494443name = "futures-util"
495−version = "0.3.30"
444+version = "0.3.28"
496445source = "registry+https://github.com/rust-lang/crates.io-index"
497−checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
446+checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
498447dependencies = [
499448 "futures-core",
500449 "futures-task",
@@ -503,6 +452,15 @@ dependencies = [
503452 "slab",
504453]
505454
455+[[package]]
456+name = "fuzzy-matcher"
457+version = "0.3.7"
458+source = "registry+https://github.com/rust-lang/crates.io-index"
459+checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
460+dependencies = [
461+ "thread_local",
462+]
463+
506464[[package]]
507465name = "getrandom"
508466version = "0.2.9"
@@ -514,21 +472,16 @@ dependencies = [
514472 "wasi",
515473]
516474
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−
523475[[package]]
524476name = "gix"
525−version = "0.57.0"
477+version = "0.44.1"
526478source = "registry+https://github.com/rust-lang/crates.io-index"
527−checksum = "721c7497ab24b665ed5a1eb2b3526936aa60068e61ba260651f7e77c52feec69"
479+checksum = "6bf41b61f7df395284f7a579c0fa1a7e012c5aede655174d4e91299ef1cac643"
528480dependencies = [
529481 "gix-actor",
530− "gix-commitgraph",
482+ "gix-attributes",
531483 "gix-config",
484+ "gix-credentials",
532485 "gix-date",
533486 "gix-diff",
534487 "gix-discover",
@@ -537,25 +490,28 @@ dependencies = [
537490 "gix-glob",
538491 "gix-hash",
539492 "gix-hashtable",
493+ "gix-ignore",
494+ "gix-index",
540495 "gix-lock",
541− "gix-macros",
496+ "gix-mailmap",
542497 "gix-object",
543498 "gix-odb",
544499 "gix-pack",
545500 "gix-path",
501+ "gix-prompt",
546502 "gix-ref",
547503 "gix-refspec",
548504 "gix-revision",
549− "gix-revwalk",
550505 "gix-sec",
551506 "gix-tempfile",
552− "gix-trace",
553507 "gix-traverse",
554508 "gix-url",
555509 "gix-utils",
556510 "gix-validate",
511+ "gix-worktree",
512+ "log",
557513 "once_cell",
558− "parking_lot",
514+ "signal-hook",
559515 "smallvec",
560516 "thiserror",
561517 "unicode-normalization",
@@ -563,46 +519,67 @@ dependencies = [
563519
564520[[package]]
565521name = "gix-actor"
566−version = "0.29.0"
522+version = "0.20.0"
567523source = "registry+https://github.com/rust-lang/crates.io-index"
568−checksum = "886014c4865b93ce268f1d3eddd4fd3261242c3f3ee61eb36009f913016a9059"
524+checksum = "848efa0f1210cea8638f95691c82a46f98a74b9e3524f01d4955ebc25a8f84f3"
569525dependencies = [
570526 "bstr",
571527 "btoi",
572528 "gix-date",
573529 "itoa",
530+ "nom",
531+ "thiserror",
532+]
533+
534+[[package]]
535+name = "gix-attributes"
536+version = "0.12.0"
537+source = "registry+https://github.com/rust-lang/crates.io-index"
538+checksum = "3015baa01ad2122fbcaab7863c857a603eb7b7ec12ac8141207c42c6439805e2"
539+dependencies = [
540+ "bstr",
541+ "gix-glob",
542+ "gix-path",
543+ "gix-quote",
544+ "kstring",
545+ "log",
546+ "smallvec",
547+ "thiserror",
548+ "unicode-bom",
549+]
550+
551+[[package]]
552+name = "gix-bitmap"
553+version = "0.2.3"
554+source = "registry+https://github.com/rust-lang/crates.io-index"
555+checksum = "55a95f4942360766c3880bdb2b4b57f1ef73b190fc424755e7fdf480430af618"
556+dependencies = [
574557 "thiserror",
575− "winnow 0.5.28",
576558]
577559
578560[[package]]
579561name = "gix-chunk"
580−version = "0.4.6"
562+version = "0.4.1"
581563source = "registry+https://github.com/rust-lang/crates.io-index"
582−checksum = "2ade37ef69870de0ed966b97c57a3a947a22ff3482a52c3b99b205f77bcb08fb"
564+checksum = "b0d39583cab06464b8bf73b3f1707458270f0e7383cb24c3c9c1a16e6f792978"
583565dependencies = [
584566 "thiserror",
585567]
586568
587569[[package]]
588−name = "gix-commitgraph"
589−version = "0.23.0"
570+name = "gix-command"
571+version = "0.2.4"
590572source = "registry+https://github.com/rust-lang/crates.io-index"
591−checksum = "e7559ea9cefee188cd88b05afcc8e3ef7a3cb4a5c647bccf06b981e591b02b77"
573+checksum = "b2c6f75c1e0f924de39e750880a6e21307194bb1ab773efe3c7d2d787277f8ab"
592574dependencies = [
593575 "bstr",
594− "gix-chunk",
595− "gix-features",
596− "gix-hash",
597− "memmap2",
598− "thiserror",
599576]
600577
601578[[package]]
602579name = "gix-config"
603−version = "0.33.0"
580+version = "0.22.0"
604581source = "registry+https://github.com/rust-lang/crates.io-index"
605−checksum = "9c35dc7f9c00a42bbc9cfa1ca2ec0a78ad1b76ff0736d3d35dfd612962244467"
582+checksum = "1d252a0eddb6df74600d3d8872dc9fe98835a7da43110411d705b682f49d4ac1"
606583dependencies = [
607584 "bstr",
608585 "gix-config-value",
@@ -611,32 +588,49 @@ dependencies = [
611588 "gix-path",
612589 "gix-ref",
613590 "gix-sec",
591+ "log",
614592 "memchr",
593+ "nom",
615594 "once_cell",
616595 "smallvec",
617596 "thiserror",
618597 "unicode-bom",
619− "winnow 0.5.28",
620598]
621599
622600[[package]]
623601name = "gix-config-value"
624−version = "0.14.2"
602+version = "0.12.0"
625603source = "registry+https://github.com/rust-lang/crates.io-index"
626−checksum = "39f388cb2396aee82d6f460a2e7770659bdf8854e9e5478f7d2b1324a9698284"
604+checksum = "786861e84a5793ad5f863d846de5eb064cd23b87e61ad708c8c402608202e7be"
627605dependencies = [
628− "bitflags 2.4.1",
606+ "bitflags 2.2.1",
629607 "bstr",
630608 "gix-path",
631609 "libc",
632610 "thiserror",
633611]
634612
613+[[package]]
614+name = "gix-credentials"
615+version = "0.14.0"
616+source = "registry+https://github.com/rust-lang/crates.io-index"
617+checksum = "4874a4fc11ffa844a3c2b87a66957bda30a73b577ef1acf15ac34df5745de5ff"
618+dependencies = [
619+ "bstr",
620+ "gix-command",
621+ "gix-config-value",
622+ "gix-path",
623+ "gix-prompt",
624+ "gix-sec",
625+ "gix-url",
626+ "thiserror",
627+]
628+
635629[[package]]
636630name = "gix-date"
637−version = "0.8.2"
631+version = "0.5.0"
638632source = "registry+https://github.com/rust-lang/crates.io-index"
639−checksum = "9d85f4a01e0d05c3de585e28bae514d4baf01655e3fc3f14ce6f30bf62405345"
633+checksum = "99056f37270715f5c7584fd8b46899a2296af9cae92463bf58b8bd1f5a78e553"
640634dependencies = [
641635 "bstr",
642636 "itoa",
@@ -646,21 +640,21 @@ dependencies = [
646640
647641[[package]]
648642name = "gix-diff"
649−version = "0.39.0"
643+version = "0.29.0"
650644source = "registry+https://github.com/rust-lang/crates.io-index"
651−checksum = "ac0e75f5afd2f6c47c800b6b0a000a08045739d0450d20482e8faa42543f62d1"
645+checksum = "644a0f2768bc42d7a69289ada80c9e15c589caefc6a315d2307202df83ed1186"
652646dependencies = [
653− "bstr",
654647 "gix-hash",
655648 "gix-object",
649+ "imara-diff",
656650 "thiserror",
657651]
658652
659653[[package]]
660654name = "gix-discover"
661−version = "0.28.0"
655+version = "0.18.1"
662656source = "registry+https://github.com/rust-lang/crates.io-index"
663−checksum = "f0b63ce2ad81632ac1a84ac370f85a5e580c3261580bd85ea17ff35d3a0bba18"
657+checksum = "1a6b61363e63e7cdaa3e6f96acb0257ebdb3d8883e21eba5930c99f07f0a5fc0"
664658dependencies = [
665659 "bstr",
666660 "dunce",
@@ -673,14 +667,13 @@ dependencies = [
673667
674668[[package]]
675669name = "gix-features"
676−version = "0.37.0"
670+version = "0.29.0"
677671source = "registry+https://github.com/rust-lang/crates.io-index"
678−checksum = "befe7edea299a824504b5acc96d7a3a538125b38c42f3a8379f6912a29c90c81"
672+checksum = "cf69b0f5c701cc3ae22d3204b671907668f6437ca88862d355eaf9bc47a4f897"
679673dependencies = [
680674 "crc32fast",
681675 "flate2",
682676 "gix-hash",
683− "gix-trace",
684677 "libc",
685678 "once_cell",
686679 "prodash",
@@ -691,20 +684,20 @@ dependencies = [
691684
692685[[package]]
693686name = "gix-fs"
694−version = "0.9.0"
687+version = "0.1.1"
695688source = "registry+https://github.com/rust-lang/crates.io-index"
696−checksum = "007017ce93b819ea52c0ec68306f7d72212a2d307c3d70f1548c7141c015d0a1"
689+checksum = "9b37a1832f691fdc09910bd267f9a2e413737c1f9ec68c6e31f9e802616278a9"
697690dependencies = [
698691 "gix-features",
699692]
700693
701694[[package]]
702695name = "gix-glob"
703−version = "0.15.0"
696+version = "0.7.0"
704697source = "registry+https://github.com/rust-lang/crates.io-index"
705−checksum = "61fb116c2516d3a1170e010118f639944a6389872c875a008960cdab2a44ac72"
698+checksum = "c07c98204529ac3f24b34754540a852593d2a4c7349008df389240266627a72a"
706699dependencies = [
707− "bitflags 2.4.1",
700+ "bitflags 2.2.1",
708701 "bstr",
709702 "gix-features",
710703 "gix-path",
@@ -712,74 +705,107 @@ dependencies = [
712705
713706[[package]]
714707name = "gix-hash"
715−version = "0.14.0"
708+version = "0.11.1"
716709source = "registry+https://github.com/rust-lang/crates.io-index"
717−checksum = "c52c3170a17b6031833cd2eb4ad7bc23f2755d06e6e70f78dec21d42e8fe1b30"
710+checksum = "078eec3ac2808cc03f0bddd2704cb661da5c5dc33b41a9d7947b141d499c7c42"
718711dependencies = [
719− "faster-hex",
712+ "hex",
720713 "thiserror",
721714]
722715
723716[[package]]
724717name = "gix-hashtable"
725−version = "0.5.0"
718+version = "0.2.0"
726719source = "registry+https://github.com/rust-lang/crates.io-index"
727−checksum = "40a838e6366a5e5b84668b6997ce0981b833136468e8ba949f424c0ef2927eba"
720+checksum = "afebb85691c6a085b114e01a27f4a61364519298c5826cb87a45c304802299bc"
728721dependencies = [
729722 "gix-hash",
730− "hashbrown 0.14.3",
723+ "hashbrown 0.13.2",
731724 "parking_lot",
732725]
733726
727+[[package]]
728+name = "gix-ignore"
729+version = "0.2.0"
730+source = "registry+https://github.com/rust-lang/crates.io-index"
731+checksum = "ba205b6df563e2906768bb22834c82eb46c5fdfcd86ba2c347270bc8309a05b2"
732+dependencies = [
733+ "bstr",
734+ "gix-glob",
735+ "gix-path",
736+ "unicode-bom",
737+]
738+
739+[[package]]
740+name = "gix-index"
741+version = "0.16.0"
742+source = "registry+https://github.com/rust-lang/crates.io-index"
743+checksum = "fa282756760f79c401d4f4f42588fbb4aa27bbb4b0830f3b4d3480c21a4ac5a7"
744+dependencies = [
745+ "bitflags 2.2.1",
746+ "bstr",
747+ "btoi",
748+ "filetime",
749+ "gix-bitmap",
750+ "gix-features",
751+ "gix-hash",
752+ "gix-lock",
753+ "gix-object",
754+ "gix-traverse",
755+ "itoa",
756+ "memmap2",
757+ "smallvec",
758+ "thiserror",
759+]
760+
734761[[package]]
735762name = "gix-lock"
736−version = "12.0.0"
763+version = "5.0.0"
737764source = "registry+https://github.com/rust-lang/crates.io-index"
738−checksum = "6cf112ddee94223c119a8534dad027740dc3aba3365ac5edeef8a7f6660c74db"
765+checksum = "41b80172055c5d8017a48ddac5cc7a95421c00211047db0165c97853c4f05194"
739766dependencies = [
767+ "fastrand",
740768 "gix-tempfile",
741− "gix-utils",
742769 "thiserror",
743770]
744771
745772[[package]]
746−name = "gix-macros"
747−version = "0.1.2"
773+name = "gix-mailmap"
774+version = "0.12.0"
748775source = "registry+https://github.com/rust-lang/crates.io-index"
749−checksum = "fc207b64189cf71bcb17fc841ab99a5d63d0845546b611e2703ce467f659323a"
776+checksum = "e8856cec3bdc3610c06970d28b6cb20a0c6621621cf9a8ec48cbd23f2630f362"
750777dependencies = [
751− "proc-macro2",
752− "quote",
753− "syn 2.0.38",
778+ "bstr",
779+ "gix-actor",
780+ "thiserror",
754781]
755782
756783[[package]]
757784name = "gix-object"
758−version = "0.40.0"
785+version = "0.29.1"
759786source = "registry+https://github.com/rust-lang/crates.io-index"
760−checksum = "e4c77e47ffba92127faf632a841fce23d19547e269bd8b88e68961a70eab4e93"
787+checksum = "c9bb30ce0818d37096daa29efe361a4bc6dd0b51a5726598898be7e9a40a01e1"
761788dependencies = [
762789 "bstr",
763790 "btoi",
764791 "gix-actor",
765− "gix-date",
766792 "gix-features",
767793 "gix-hash",
768794 "gix-validate",
795+ "hex",
769796 "itoa",
797+ "nom",
770798 "smallvec",
771799 "thiserror",
772− "winnow 0.5.28",
773800]
774801
775802[[package]]
776803name = "gix-odb"
777−version = "0.56.0"
804+version = "0.45.0"
778805source = "registry+https://github.com/rust-lang/crates.io-index"
779−checksum = "a3254f2005cc7553ea78e85e816a09150c6f7a64e6b7627b8d1fdc56721bea73"
806+checksum = "bca2f324aa67672b6d0f2c0fa93f96eb6a7029d260e4c1df5dce3c015f5e5add"
780807dependencies = [
781808 "arc-swap",
782− "gix-date",
783809 "gix-features",
784810 "gix-hash",
785811 "gix-object",
@@ -793,18 +819,20 @@ dependencies = [
793819
794820[[package]]
795821name = "gix-pack"
796−version = "0.46.0"
822+version = "0.35.0"
797823source = "registry+https://github.com/rust-lang/crates.io-index"
798−checksum = "02ebc8cd657eec207d82d8f876ca402361308ecd3c87a47935b0299506257b4f"
824+checksum = "164a515900a83257ae4aa80e741655bee7a2e39113fb535d7a5ac623b445ff20"
799825dependencies = [
800826 "clru",
801827 "gix-chunk",
828+ "gix-diff",
802829 "gix-features",
803830 "gix-hash",
804831 "gix-hashtable",
805832 "gix-object",
806833 "gix-path",
807834 "gix-tempfile",
835+ "gix-traverse",
808836 "memmap2",
809837 "parking_lot",
810838 "smallvec",
@@ -813,22 +841,34 @@ dependencies = [
813841
814842[[package]]
815843name = "gix-path"
816−version = "0.10.2"
844+version = "0.8.0"
817845source = "registry+https://github.com/rust-lang/crates.io-index"
818−checksum = "10931652a3da126990ac93bce1b1c600cc99d7c268d712b6360ed52174ce1b68"
846+checksum = "4fc78f47095a0c15aea0e66103838f0748f4494bf7a9555dfe0f00425400396c"
819847dependencies = [
820848 "bstr",
821− "gix-trace",
822849 "home",
823850 "once_cell",
824851 "thiserror",
825852]
826853
854+[[package]]
855+name = "gix-prompt"
856+version = "0.5.0"
857+source = "registry+https://github.com/rust-lang/crates.io-index"
858+checksum = "330d11fdf88fff3366c2491efde2f3e454958efe7d5ddf60272e8fb1d944bb01"
859+dependencies = [
860+ "gix-command",
861+ "gix-config-value",
862+ "parking_lot",
863+ "rustix",
864+ "thiserror",
865+]
866+
827867[[package]]
828868name = "gix-quote"
829−version = "0.4.9"
869+version = "0.4.3"
830870source = "registry+https://github.com/rust-lang/crates.io-index"
831−checksum = "6c43530cb94a7759807e6f8d180e17ac9c65673b891645c6c433831dc0cf4342"
871+checksum = "a282f5a8d9ee0b09ec47390ac727350c48f2f5c76d803cd8da6b3e7ad56e0bcb"
832872dependencies = [
833873 "bstr",
834874 "btoi",
@@ -837,12 +877,11 @@ dependencies = [
837877
838878[[package]]
839879name = "gix-ref"
840−version = "0.40.0"
880+version = "0.29.1"
841881source = "registry+https://github.com/rust-lang/crates.io-index"
842−checksum = "baa951b3b850d6d1be4f900768e49af20b76bf9505beac22af723d57249a2f1d"
882+checksum = "1e03989e9d49954368e1b526578230fc7189d1634acdfbe79e9ba1de717e15d5"
843883dependencies = [
844884 "gix-actor",
845− "gix-date",
846885 "gix-features",
847886 "gix-fs",
848887 "gix-hash",
@@ -852,15 +891,15 @@ dependencies = [
852891 "gix-tempfile",
853892 "gix-validate",
854893 "memmap2",
894+ "nom",
855895 "thiserror",
856− "winnow 0.5.28",
857896]
858897
859898[[package]]
860899name = "gix-refspec"
861−version = "0.21.0"
900+version = "0.10.1"
862901source = "registry+https://github.com/rust-lang/crates.io-index"
863−checksum = "88c61f849d58c06e3068a0b601cf10127d2a07cdad00a725ed66cf303f76f6b3"
902+checksum = "0a6ea733820df67e4cd7797deb12727905824d8f5b7c59d943c456d314475892"
864903dependencies = [
865904 "bstr",
866905 "gix-hash",
@@ -872,87 +911,61 @@ dependencies = [
872911
873912[[package]]
874913name = "gix-revision"
875−version = "0.25.0"
914+version = "0.13.0"
876915source = "registry+https://github.com/rust-lang/crates.io-index"
877−checksum = "bcbbf91f4200c5c76802ef5f057b96f5a336827508881fe55a780be71a794d22"
916+checksum = "810f35e9afeccca999d5d348b239f9c162353127d2e13ff3240e31b919e35476"
878917dependencies = [
879918 "bstr",
880919 "gix-date",
881920 "gix-hash",
882921 "gix-hashtable",
883922 "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",
901923 "thiserror",
902924]
903925
904926[[package]]
905927name = "gix-sec"
906−version = "0.10.2"
928+version = "0.8.0"
907929source = "registry+https://github.com/rust-lang/crates.io-index"
908−checksum = "a9963f38a42144253ed4d571882d7db5ef644c12e6726e4b75135597cc9d0e1a"
930+checksum = "794520043d5a024dfeac335c6e520cb616f6963e30dab995892382e998c12897"
909931dependencies = [
910− "bitflags 2.4.1",
932+ "bitflags 2.2.1",
911933 "gix-path",
912934 "libc",
913− "windows 0.52.0",
935+ "windows",
914936]
915937
916938[[package]]
917939name = "gix-tempfile"
918−version = "12.0.0"
940+version = "5.0.2"
919941source = "registry+https://github.com/rust-lang/crates.io-index"
920−checksum = "e76a494bd530e1a1309188ff971825a24f159c76c2db0bf71fa5dfb469a2c915"
942+checksum = "c2ceb30a610e3f5f2d5f9a5114689fde507ba9417705a8cf3429604275b2153c"
921943dependencies = [
922− "gix-fs",
923944 "libc",
924945 "once_cell",
925946 "parking_lot",
947+ "signal-hook",
948+ "signal-hook-registry",
926949 "tempfile",
927950]
928951
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−
935952[[package]]
936953name = "gix-traverse"
937−version = "0.36.0"
954+version = "0.25.0"
938955source = "registry+https://github.com/rust-lang/crates.io-index"
939−checksum = "8661fab39985c9214e56d81a63ceb5886ac948cec2fba76c39494d1e0e307ea8"
956+checksum = "a5be1e807f288c33bb005075111886cceb43ed8a167b3182a0f62c186e2a0dd1"
940957dependencies = [
941− "gix-commitgraph",
942− "gix-date",
943958 "gix-hash",
944959 "gix-hashtable",
945960 "gix-object",
946− "gix-revwalk",
947− "smallvec",
948961 "thiserror",
949962]
950963
951964[[package]]
952965name = "gix-url"
953−version = "0.26.0"
966+version = "0.18.0"
954967source = "registry+https://github.com/rust-lang/crates.io-index"
955−checksum = "10a0129c1e8b52736d7c5128300a4485dbc85863001371e2771ac1754bd89fd7"
968+checksum = "dfc77f89054297cc81491e31f1bab4027e554b5ef742a44bd7035db9a0f78b76"
956969dependencies = [
957970 "bstr",
958971 "gix-features",
@@ -964,70 +977,93 @@ dependencies = [
964977
965978[[package]]
966979name = "gix-utils"
967−version = "0.1.7"
980+version = "0.1.1"
968981source = "registry+https://github.com/rust-lang/crates.io-index"
969−checksum = "ab9277a5e32e85d53f738096d872a4a9b76067ad471894ad31bd99c8fa2da1dc"
982+checksum = "c10b69beac219acb8df673187a1f07dde2d74092f974fb3f9eb385aeb667c909"
970983dependencies = [
971984 "fastrand",
972985]
973986
974987[[package]]
975988name = "gix-validate"
976−version = "0.8.2"
989+version = "0.7.4"
977990source = "registry+https://github.com/rust-lang/crates.io-index"
978−checksum = "f805ebbdbaa4bfd98e2ee43e6d14099b8a4d9141f5d7b8202fea4d48e44263e7"
991+checksum = "7bd629d3680773e1785e585d76fd4295b740b559cad9141517300d99a0c8c049"
979992dependencies = [
980993 "bstr",
981994 "thiserror",
982995]
983996
997+[[package]]
998+name = "gix-worktree"
999+version = "0.17.0"
1000+source = "registry+https://github.com/rust-lang/crates.io-index"
1001+checksum = "10bf56a1f5037d84293ea6cece61d9f27c4866b1e13c1c95f37cf56b7da7af25"
1002+dependencies = [
1003+ "bstr",
1004+ "filetime",
1005+ "gix-attributes",
1006+ "gix-features",
1007+ "gix-fs",
1008+ "gix-glob",
1009+ "gix-hash",
1010+ "gix-ignore",
1011+ "gix-index",
1012+ "gix-object",
1013+ "gix-path",
1014+ "io-close",
1015+ "thiserror",
1016+]
1017+
9841018[[package]]
9851019name = "globset"
986−version = "0.4.14"
1020+version = "0.4.10"
9871021source = "registry+https://github.com/rust-lang/crates.io-index"
988−checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
1022+checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
9891023dependencies = [
990− "aho-corasick",
1024+ "aho-corasick 0.7.20",
9911025 "bstr",
1026+ "fnv",
9921027 "log",
993− "regex-automata",
994− "regex-syntax",
1028+ "regex",
9951029]
9961030
9971031[[package]]
9981032name = "grep-matcher"
999−version = "0.1.7"
1033+version = "0.1.6"
10001034source = "registry+https://github.com/rust-lang/crates.io-index"
1001−checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02"
1035+checksum = "3902ca28f26945fe35cad349d776f163981d777fee382ccd6ef451126f51b319"
10021036dependencies = [
10031037 "memchr",
10041038]
10051039
10061040[[package]]
10071041name = "grep-regex"
1008−version = "0.1.12"
1042+version = "0.1.11"
10091043source = "registry+https://github.com/rust-lang/crates.io-index"
1010−checksum = "f748bb135ca835da5cbc67ca0e6955f968db9c5df74ca4f56b18e1ddbc68230d"
1044+checksum = "997598b41d53a37a2e3fc5300d5c11d825368c054420a9c65125b8fe1078463f"
10111045dependencies = [
1046+ "aho-corasick 0.7.20",
10121047 "bstr",
10131048 "grep-matcher",
10141049 "log",
1015− "regex-automata",
1016− "regex-syntax",
1050+ "regex",
1051+ "regex-syntax 0.6.29",
1052+ "thread_local",
10171053]
10181054
10191055[[package]]
10201056name = "grep-searcher"
1021−version = "0.1.13"
1057+version = "0.1.11"
10221058source = "registry+https://github.com/rust-lang/crates.io-index"
1023−checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54"
1059+checksum = "5601c4b9f480f0c9ebb40b1f6cbf447b8a50c5369223937a6c5214368c58779f"
10241060dependencies = [
10251061 "bstr",
1062+ "bytecount",
10261063 "encoding_rs",
10271064 "encoding_rs_io",
10281065 "grep-matcher",
10291066 "log",
1030− "memchr",
10311067 "memmap2",
10321068]
10331069
@@ -1036,36 +1072,36 @@ name = "hashbrown"
10361072version = "0.12.3"
10371073source = "registry+https://github.com/rust-lang/crates.io-index"
10381074checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1075+dependencies = [
1076+ "ahash 0.7.6",
1077+]
10391078
10401079[[package]]
10411080name = "hashbrown"
1042−version = "0.14.3"
1081+version = "0.13.2"
10431082source = "registry+https://github.com/rust-lang/crates.io-index"
1044−checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
1083+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
10451084dependencies = [
1046− "ahash",
1047− "allocator-api2",
1085+ "ahash 0.8.3",
10481086]
10491087
10501088[[package]]
10511089name = "helix-core"
1052−version = "23.10.0"
1090+version = "0.6.0"
10531091dependencies = [
1054− "ahash",
1092+ "ahash 0.8.3",
10551093 "arc-swap",
1056− "bitflags 2.4.1",
1094+ "bitflags 2.2.1",
10571095 "chrono",
10581096 "dunce",
10591097 "encoding_rs",
10601098 "etcetera",
1061− "hashbrown 0.14.3",
1099+ "hashbrown 0.13.2",
10621100 "helix-loader",
10631101 "imara-diff",
10641102 "indoc",
10651103 "log",
1066− "nucleo",
10671104 "once_cell",
1068− "parking_lot",
10691105 "quickcheck",
10701106 "regex",
10711107 "ropey",
@@ -1084,7 +1120,7 @@ dependencies = [
10841120
10851121[[package]]
10861122name = "helix-dap"
1087−version = "23.10.0"
1123+version = "0.6.0"
10881124dependencies = [
10891125 "anyhow",
10901126 "fern",
@@ -1097,21 +1133,12 @@ dependencies = [
10971133 "which",
10981134]
10991135
1100−[[package]]
1101−name = "helix-event"
1102−version = "23.10.0"
1103−dependencies = [
1104− "parking_lot",
1105− "tokio",
1106−]
1107−
11081136[[package]]
11091137name = "helix-loader"
1110−version = "23.10.0"
1138+version = "0.6.0"
11111139dependencies = [
11121140 "anyhow",
11131141 "cc",
1114− "dunce",
11151142 "etcetera",
11161143 "libloading",
11171144 "log",
@@ -1121,17 +1148,15 @@ dependencies = [
11211148 "threadpool",
11221149 "toml",
11231150 "tree-sitter",
1124− "which",
11251151]
11261152
11271153[[package]]
11281154name = "helix-lsp"
1129−version = "23.10.0"
1155+version = "0.6.0"
11301156dependencies = [
11311157 "anyhow",
11321158 "futures-executor",
11331159 "futures-util",
1134− "globset",
11351160 "helix-core",
11361161 "helix-loader",
11371162 "helix-parsec",
@@ -1148,11 +1173,11 @@ dependencies = [
11481173
11491174[[package]]
11501175name = "helix-parsec"
1151−version = "23.10.0"
1176+version = "0.6.0"
11521177
11531178[[package]]
11541179name = "helix-term"
1155−version = "23.10.0"
1180+version = "0.6.0"
11561181dependencies = [
11571182 "anyhow",
11581183 "arc-swap",
@@ -1161,11 +1186,11 @@ dependencies = [
11611186 "crossterm",
11621187 "fern",
11631188 "futures-util",
1189+ "fuzzy-matcher",
11641190 "grep-regex",
11651191 "grep-searcher",
11661192 "helix-core",
11671193 "helix-dap",
1168− "helix-event",
11691194 "helix-loader",
11701195 "helix-lsp",
11711196 "helix-tui",
@@ -1175,9 +1200,7 @@ dependencies = [
11751200 "indoc",
11761201 "libc",
11771202 "log",
1178− "nucleo",
11791203 "once_cell",
1180− "open",
11811204 "pulldown-cmark",
11821205 "serde",
11831206 "serde_json",
@@ -1188,15 +1211,14 @@ dependencies = [
11881211 "tokio",
11891212 "tokio-stream",
11901213 "toml",
1191− "url",
11921214 "which",
11931215]
11941216
11951217[[package]]
11961218name = "helix-tui"
1197−version = "23.10.0"
1219+version = "0.6.0"
11981220dependencies = [
1199− "bitflags 2.4.1",
1221+ "bitflags 2.2.1",
12001222 "cassowary",
12011223 "crossterm",
12021224 "helix-core",
@@ -1210,13 +1232,12 @@ dependencies = [
12101232
12111233[[package]]
12121234name = "helix-vcs"
1213−version = "23.10.0"
1235+version = "0.6.0"
12141236dependencies = [
12151237 "anyhow",
12161238 "arc-swap",
12171239 "gix",
12181240 "helix-core",
1219− "helix-event",
12201241 "imara-diff",
12211242 "log",
12221243 "parking_lot",
@@ -1226,18 +1247,17 @@ dependencies = [
12261247
12271248[[package]]
12281249name = "helix-view"
1229−version = "23.10.0"
1250+version = "0.6.0"
12301251dependencies = [
12311252 "anyhow",
12321253 "arc-swap",
1233− "bitflags 2.4.1",
1254+ "bitflags 2.2.1",
12341255 "chardetng",
12351256 "clipboard-win",
12361257 "crossterm",
12371258 "futures-util",
12381259 "helix-core",
12391260 "helix-dap",
1240− "helix-event",
12411261 "helix-loader",
12421262 "helix-lsp",
12431263 "helix-tui",
@@ -1246,7 +1266,6 @@ dependencies = [
12461266 "log",
12471267 "once_cell",
12481268 "parking_lot",
1249− "rustix",
12501269 "serde",
12511270 "serde_json",
12521271 "slotmap",
@@ -1266,13 +1285,25 @@ dependencies = [
12661285 "libc",
12671286]
12681287
1288+[[package]]
1289+name = "hermit-abi"
1290+version = "0.3.1"
1291+source = "registry+https://github.com/rust-lang/crates.io-index"
1292+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
1293+
1294+[[package]]
1295+name = "hex"
1296+version = "0.4.3"
1297+source = "registry+https://github.com/rust-lang/crates.io-index"
1298+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1299+
12691300[[package]]
12701301name = "home"
1271−version = "0.5.5"
1302+version = "0.5.4"
12721303source = "registry+https://github.com/rust-lang/crates.io-index"
1273−checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
1304+checksum = "747309b4b440c06d57b0b25f2aee03ee9b5e5397d288c60e21fc709bb98a7408"
12741305dependencies = [
1275− "windows-sys 0.48.0",
1306+ "winapi",
12761307]
12771308
12781309[[package]]
@@ -1286,7 +1317,7 @@ dependencies = [
12861317 "iana-time-zone-haiku",
12871318 "js-sys",
12881319 "wasm-bindgen",
1289− "windows 0.48.0",
1320+ "windows",
12901321]
12911322
12921323[[package]]
@@ -1301,9 +1332,9 @@ dependencies = [
13011332
13021333[[package]]
13031334name = "idna"
1304−version = "0.5.0"
1335+version = "0.3.0"
13051336source = "registry+https://github.com/rust-lang/crates.io-index"
1306−checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
1337+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
13071338dependencies = [
13081339 "unicode-bidi",
13091340 "unicode-normalization",
@@ -1311,16 +1342,17 @@ dependencies = [
13111342
13121343[[package]]
13131344name = "ignore"
1314−version = "0.4.21"
1345+version = "0.4.20"
13151346source = "registry+https://github.com/rust-lang/crates.io-index"
1316−checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
1347+checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492"
13171348dependencies = [
1318− "crossbeam-deque",
13191349 "globset",
1350+ "lazy_static",
13201351 "log",
13211352 "memchr",
1322− "regex-automata",
1353+ "regex",
13231354 "same-file",
1355+ "thread_local",
13241356 "walkdir",
13251357 "winapi-util",
13261358]
@@ -1331,43 +1363,54 @@ version = "0.1.5"
13311363source = "registry+https://github.com/rust-lang/crates.io-index"
13321364checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
13331365dependencies = [
1334− "ahash",
1366+ "ahash 0.8.3",
13351367 "hashbrown 0.12.3",
13361368]
13371369
13381370[[package]]
13391371name = "indexmap"
1340−version = "2.0.0"
1372+version = "1.9.3"
13411373source = "registry+https://github.com/rust-lang/crates.io-index"
1342−checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
1374+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
13431375dependencies = [
1344− "equivalent",
1345− "hashbrown 0.14.3",
1376+ "autocfg",
1377+ "hashbrown 0.12.3",
13461378]
13471379
13481380[[package]]
13491381name = "indoc"
1350−version = "2.0.4"
1382+version = "2.0.1"
13511383source = "registry+https://github.com/rust-lang/crates.io-index"
1352−checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
1384+checksum = "9f2cb48b81b1dc9f39676bf99f5499babfec7cd8fe14307f7b3d747208fb5690"
13531385
13541386[[package]]
1355−name = "is-docker"
1356−version = "0.2.0"
1387+name = "instant"
1388+version = "0.1.12"
13571389source = "registry+https://github.com/rust-lang/crates.io-index"
1358−checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
1390+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
13591391dependencies = [
1360− "once_cell",
1392+ "cfg-if",
13611393]
13621394
13631395[[package]]
1364−name = "is-wsl"
1365−version = "0.4.0"
1396+name = "io-close"
1397+version = "0.3.7"
13661398source = "registry+https://github.com/rust-lang/crates.io-index"
1367−checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
1399+checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc"
13681400dependencies = [
1369− "is-docker",
1370− "once_cell",
1401+ "libc",
1402+ "winapi",
1403+]
1404+
1405+[[package]]
1406+name = "io-lifetimes"
1407+version = "1.0.10"
1408+source = "registry+https://github.com/rust-lang/crates.io-index"
1409+checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
1410+dependencies = [
1411+ "hermit-abi 0.3.1",
1412+ "libc",
1413+ "windows-sys 0.48.0",
13711414]
13721415
13731416[[package]]
@@ -1385,17 +1428,32 @@ dependencies = [
13851428 "wasm-bindgen",
13861429]
13871430
1431+[[package]]
1432+name = "kstring"
1433+version = "2.0.0"
1434+source = "registry+https://github.com/rust-lang/crates.io-index"
1435+checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747"
1436+dependencies = [
1437+ "static_assertions",
1438+]
1439+
1440+[[package]]
1441+name = "lazy_static"
1442+version = "1.4.0"
1443+source = "registry+https://github.com/rust-lang/crates.io-index"
1444+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
1445+
13881446[[package]]
13891447name = "libc"
1390−version = "0.2.151"
1448+version = "0.2.144"
13911449source = "registry+https://github.com/rust-lang/crates.io-index"
1392−checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
1450+checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
13931451
13941452[[package]]
13951453name = "libloading"
1396−version = "0.8.1"
1454+version = "0.8.0"
13971455source = "registry+https://github.com/rust-lang/crates.io-index"
1398−checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
1456+checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb"
13991457dependencies = [
14001458 "cfg-if",
14011459 "windows-sys 0.48.0",
@@ -1412,9 +1470,9 @@ dependencies = [
14121470
14131471[[package]]
14141472name = "linux-raw-sys"
1415−version = "0.4.11"
1473+version = "0.3.4"
14161474source = "registry+https://github.com/rust-lang/crates.io-index"
1417−checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
1475+checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
14181476
14191477[[package]]
14201478name = "lock_api"
@@ -1428,15 +1486,18 @@ dependencies = [
14281486
14291487[[package]]
14301488name = "log"
1431−version = "0.4.20"
1489+version = "0.4.17"
14321490source = "registry+https://github.com/rust-lang/crates.io-index"
1433−checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
1491+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
1492+dependencies = [
1493+ "cfg-if",
1494+]
14341495
14351496[[package]]
14361497name = "lsp-types"
1437−version = "0.95.0"
1498+version = "0.94.0"
14381499source = "registry+https://github.com/rust-lang/crates.io-index"
1439−checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
1500+checksum = "0b63735a13a1f9cd4f4835223d828ed9c2e35c8c5e61837774399f558b6a1237"
14401501dependencies = [
14411502 "bitflags 1.3.2",
14421503 "serde",
@@ -1447,69 +1508,64 @@ dependencies = [
14471508
14481509[[package]]
14491510name = "memchr"
1450−version = "2.6.3"
1511+version = "2.5.0"
14511512source = "registry+https://github.com/rust-lang/crates.io-index"
1452−checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
1513+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
14531514
14541515[[package]]
14551516name = "memmap2"
1456−version = "0.9.0"
1517+version = "0.5.10"
14571518source = "registry+https://github.com/rust-lang/crates.io-index"
1458−checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
1519+checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
14591520dependencies = [
14601521 "libc",
14611522]
14621523
14631524[[package]]
1464−name = "memoffset"
1465−version = "0.9.0"
1525+name = "minimal-lexical"
1526+version = "0.2.1"
14661527source = "registry+https://github.com/rust-lang/crates.io-index"
1467−checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
1468−dependencies = [
1469− "autocfg",
1470−]
1528+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
14711529
14721530[[package]]
14731531name = "miniz_oxide"
1474−version = "0.7.1"
1532+version = "0.6.2"
14751533source = "registry+https://github.com/rust-lang/crates.io-index"
1476−checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
1534+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
14771535dependencies = [
14781536 "adler",
14791537]
14801538
14811539[[package]]
14821540name = "mio"
1483−version = "0.8.9"
1541+version = "0.8.6"
14841542source = "registry+https://github.com/rust-lang/crates.io-index"
1485−checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
1543+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
14861544dependencies = [
14871545 "libc",
14881546 "log",
14891547 "wasi",
1490− "windows-sys 0.48.0",
1548+ "windows-sys 0.45.0",
14911549]
14921550
14931551[[package]]
1494−name = "nucleo"
1495−version = "0.2.1"
1552+name = "nom"
1553+version = "7.1.3"
14961554source = "registry+https://github.com/rust-lang/crates.io-index"
1497−checksum = "ae5331f4bcce475cf28cb29c95366c3091af4b0aa7703f1a6bc858f29718fdf3"
1555+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
14981556dependencies = [
1499− "nucleo-matcher",
1500− "parking_lot",
1501− "rayon",
1557+ "memchr",
1558+ "minimal-lexical",
15021559]
15031560
15041561[[package]]
1505−name = "nucleo-matcher"
1506−version = "0.2.0"
1562+name = "num-integer"
1563+version = "0.1.45"
15071564source = "registry+https://github.com/rust-lang/crates.io-index"
1508−checksum = "1b702b402fe286162d1f00b552a046ce74365d2ac473a2607ff36ba650f9bd57"
1565+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
15091566dependencies = [
1510− "cov-mark",
1511− "memchr",
1512− "unicode-segmentation",
1567+ "autocfg",
1568+ "num-traits",
15131569]
15141570
15151571[[package]]
@@ -1527,7 +1583,7 @@ version = "1.15.0"
15271583source = "registry+https://github.com/rust-lang/crates.io-index"
15281584checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
15291585dependencies = [
1530− "hermit-abi",
1586+ "hermit-abi 0.2.6",
15311587 "libc",
15321588]
15331589
@@ -1540,31 +1596,11 @@ dependencies = [
15401596 "libc",
15411597]
15421598
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−
15521599[[package]]
15531600name = "once_cell"
1554−version = "1.19.0"
1601+version = "1.17.1"
15551602source = "registry+https://github.com/rust-lang/crates.io-index"
1556−checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
1557−
1558−[[package]]
1559−name = "open"
1560−version = "5.0.1"
1561−source = "registry+https://github.com/rust-lang/crates.io-index"
1562−checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349"
1563−dependencies = [
1564− "is-wsl",
1565− "libc",
1566− "pathdiff",
1567−]
1603+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
15681604
15691605[[package]]
15701606name = "parking_lot"
@@ -1589,23 +1625,17 @@ dependencies = [
15891625 "windows-sys 0.45.0",
15901626]
15911627
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−
15981628[[package]]
15991629name = "percent-encoding"
1600−version = "2.3.1"
1630+version = "2.2.0"
16011631source = "registry+https://github.com/rust-lang/crates.io-index"
1602−checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
1632+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
16031633
16041634[[package]]
16051635name = "pin-project-lite"
1606−version = "0.2.12"
1636+version = "0.2.9"
16071637source = "registry+https://github.com/rust-lang/crates.io-index"
1608−checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
1638+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
16091639
16101640[[package]]
16111641name = "pin-utils"
@@ -1615,24 +1645,24 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
16151645
16161646[[package]]
16171647name = "proc-macro2"
1618−version = "1.0.69"
1648+version = "1.0.56"
16191649source = "registry+https://github.com/rust-lang/crates.io-index"
1620−checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
1650+checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
16211651dependencies = [
16221652 "unicode-ident",
16231653]
16241654
16251655[[package]]
16261656name = "prodash"
1627−version = "28.0.0"
1657+version = "23.1.2"
16281658source = "registry+https://github.com/rust-lang/crates.io-index"
1629−checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
1659+checksum = "9516b775656bc3e8985e19cd4b8c0c0de045095074e453d2c0a513b5f978392d"
16301660
16311661[[package]]
16321662name = "pulldown-cmark"
1633−version = "0.9.3"
1663+version = "0.9.2"
16341664source = "registry+https://github.com/rust-lang/crates.io-index"
1635−checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
1665+checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
16361666dependencies = [
16371667 "bitflags 1.3.2",
16381668 "memchr",
@@ -1650,9 +1680,9 @@ dependencies = [
16501680
16511681[[package]]
16521682name = "quote"
1653−version = "1.0.29"
1683+version = "1.0.26"
16541684source = "registry+https://github.com/rust-lang/crates.io-index"
1655−checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
1685+checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
16561686dependencies = [
16571687 "proc-macro2",
16581688]
@@ -1675,28 +1705,6 @@ dependencies = [
16751705 "getrandom",
16761706]
16771707
1678−[[package]]
1679−name = "rayon"
1680−version = "1.7.0"
1681−source = "registry+https://github.com/rust-lang/crates.io-index"
1682−checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
1683−dependencies = [
1684− "either",
1685− "rayon-core",
1686−]
1687−
1688−[[package]]
1689−name = "rayon-core"
1690−version = "1.11.0"
1691−source = "registry+https://github.com/rust-lang/crates.io-index"
1692−checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
1693−dependencies = [
1694− "crossbeam-channel",
1695− "crossbeam-deque",
1696− "crossbeam-utils",
1697− "num_cpus",
1698−]
1699−
17001708[[package]]
17011709name = "redox_syscall"
17021710version = "0.2.16"
@@ -1708,69 +1716,64 @@ dependencies = [
17081716
17091717[[package]]
17101718name = "redox_syscall"
1711−version = "0.4.1"
1719+version = "0.3.5"
17121720source = "registry+https://github.com/rust-lang/crates.io-index"
1713−checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
1721+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
17141722dependencies = [
17151723 "bitflags 1.3.2",
17161724]
17171725
17181726[[package]]
17191727name = "regex"
1720−version = "1.10.2"
1728+version = "1.8.1"
17211729source = "registry+https://github.com/rust-lang/crates.io-index"
1722−checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
1730+checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
17231731dependencies = [
1724− "aho-corasick",
1732+ "aho-corasick 1.0.1",
17251733 "memchr",
1726− "regex-automata",
1727− "regex-syntax",
1734+ "regex-syntax 0.7.1",
17281735]
17291736
17301737[[package]]
17311738name = "regex-automata"
1732−version = "0.4.3"
1739+version = "0.1.10"
17331740source = "registry+https://github.com/rust-lang/crates.io-index"
1734−checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
1735−dependencies = [
1736− "aho-corasick",
1737− "memchr",
1738− "regex-syntax",
1739−]
1741+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
1742+
1743+[[package]]
1744+name = "regex-syntax"
1745+version = "0.6.29"
1746+source = "registry+https://github.com/rust-lang/crates.io-index"
1747+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
17401748
17411749[[package]]
17421750name = "regex-syntax"
1743−version = "0.8.2"
1751+version = "0.7.1"
17441752source = "registry+https://github.com/rust-lang/crates.io-index"
1745−checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
1753+checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
17461754
17471755[[package]]
17481756name = "ropey"
1749−version = "1.6.1"
1757+version = "1.6.0"
17501758source = "registry+https://github.com/rust-lang/crates.io-index"
1751−checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
1759+checksum = "53ce7a2c43a32e50d666e33c5a80251b31147bb4b49024bcab11fb6f20c671ed"
17521760dependencies = [
17531761 "smallvec",
17541762 "str_indices",
17551763]
17561764
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−
17631765[[package]]
17641766name = "rustix"
1765−version = "0.38.28"
1767+version = "0.37.15"
17661768source = "registry+https://github.com/rust-lang/crates.io-index"
1767−checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
1769+checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece"
17681770dependencies = [
1769− "bitflags 2.4.1",
1771+ "bitflags 1.3.2",
17701772 "errno",
1773+ "io-lifetimes",
17711774 "libc",
17721775 "linux-raw-sys",
1773− "windows-sys 0.52.0",
1776+ "windows-sys 0.48.0",
17741777]
17751778
17761779[[package]]
@@ -1802,29 +1805,29 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
18021805
18031806[[package]]
18041807name = "serde"
1805−version = "1.0.193"
1808+version = "1.0.163"
18061809source = "registry+https://github.com/rust-lang/crates.io-index"
1807−checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
1810+checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2"
18081811dependencies = [
18091812 "serde_derive",
18101813]
18111814
18121815[[package]]
18131816name = "serde_derive"
1814−version = "1.0.193"
1817+version = "1.0.163"
18151818source = "registry+https://github.com/rust-lang/crates.io-index"
1816−checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
1819+checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
18171820dependencies = [
18181821 "proc-macro2",
18191822 "quote",
1820− "syn 2.0.38",
1823+ "syn 2.0.15",
18211824]
18221825
18231826[[package]]
18241827name = "serde_json"
1825−version = "1.0.108"
1828+version = "1.0.96"
18261829source = "registry+https://github.com/rust-lang/crates.io-index"
1827−checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
1830+checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
18281831dependencies = [
18291832 "itoa",
18301833 "ryu",
@@ -1839,14 +1842,14 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
18391842dependencies = [
18401843 "proc-macro2",
18411844 "quote",
1842− "syn 2.0.38",
1845+ "syn 2.0.15",
18431846]
18441847
18451848[[package]]
18461849name = "serde_spanned"
1847−version = "0.6.3"
1850+version = "0.6.1"
18481851source = "registry+https://github.com/rust-lang/crates.io-index"
1849−checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
1852+checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
18501853dependencies = [
18511854 "serde",
18521855]
@@ -1859,9 +1862,9 @@ checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
18591862
18601863[[package]]
18611864name = "signal-hook"
1862−version = "0.3.17"
1865+version = "0.3.15"
18631866source = "registry+https://github.com/rust-lang/crates.io-index"
1864−checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
1867+checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
18651868dependencies = [
18661869 "libc",
18671870 "signal-hook-registry",
@@ -1910,18 +1913,18 @@ dependencies = [
19101913
19111914[[package]]
19121915name = "slotmap"
1913−version = "1.0.7"
1916+version = "1.0.6"
19141917source = "registry+https://github.com/rust-lang/crates.io-index"
1915−checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
1918+checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
19161919dependencies = [
19171920 "version_check",
19181921]
19191922
19201923[[package]]
19211924name = "smallvec"
1922−version = "1.11.2"
1925+version = "1.10.0"
19231926source = "registry+https://github.com/rust-lang/crates.io-index"
1924−checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
1927+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
19251928
19261929[[package]]
19271930name = "smartstring"
@@ -1942,12 +1945,12 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
19421945
19431946[[package]]
19441947name = "socket2"
1945−version = "0.5.5"
1948+version = "0.4.9"
19461949source = "registry+https://github.com/rust-lang/crates.io-index"
1947−checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
1950+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
19481951dependencies = [
19491952 "libc",
1950− "windows-sys 0.48.0",
1953+ "winapi",
19511954]
19521955
19531956[[package]]
@@ -1956,6 +1959,12 @@ version = "1.1.0"
19561959source = "registry+https://github.com/rust-lang/crates.io-index"
19571960checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
19581961
1962+[[package]]
1963+name = "str-buf"
1964+version = "1.0.6"
1965+source = "registry+https://github.com/rust-lang/crates.io-index"
1966+checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
1967+
19591968[[package]]
19601969name = "str_indices"
19611970version = "0.4.1"
@@ -1975,9 +1984,9 @@ dependencies = [
19751984
19761985[[package]]
19771986name = "syn"
1978−version = "2.0.38"
1987+version = "2.0.15"
19791988source = "registry+https://github.com/rust-lang/crates.io-index"
1980−checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
1989+checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
19811990dependencies = [
19821991 "proc-macro2",
19831992 "quote",
@@ -1986,15 +1995,15 @@ dependencies = [
19861995
19871996[[package]]
19881997name = "tempfile"
1989−version = "3.8.1"
1998+version = "3.5.0"
19901999source = "registry+https://github.com/rust-lang/crates.io-index"
1991−checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
2000+checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
19922001dependencies = [
19932002 "cfg-if",
19942003 "fastrand",
1995− "redox_syscall 0.4.1",
2004+ "redox_syscall 0.3.5",
19962005 "rustix",
1997− "windows-sys 0.48.0",
2006+ "windows-sys 0.45.0",
19982007]
19992008
20002009[[package]]
@@ -2028,22 +2037,32 @@ dependencies = [
20282037
20292038[[package]]
20302039name = "thiserror"
2031−version = "1.0.52"
2040+version = "1.0.40"
20322041source = "registry+https://github.com/rust-lang/crates.io-index"
2033−checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
2042+checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
20342043dependencies = [
20352044 "thiserror-impl",
20362045]
20372046
20382047[[package]]
20392048name = "thiserror-impl"
2040−version = "1.0.52"
2049+version = "1.0.40"
20412050source = "registry+https://github.com/rust-lang/crates.io-index"
2042−checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
2051+checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
20432052dependencies = [
20442053 "proc-macro2",
20452054 "quote",
2046− "syn 2.0.38",
2055+ "syn 2.0.15",
2056+]
2057+
2058+[[package]]
2059+name = "thread_local"
2060+version = "1.1.7"
2061+source = "registry+https://github.com/rust-lang/crates.io-index"
2062+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
2063+dependencies = [
2064+ "cfg-if",
2065+ "once_cell",
20472066]
20482067
20492068[[package]]
@@ -2057,9 +2076,9 @@ dependencies = [
20572076
20582077[[package]]
20592078name = "time"
2060−version = "0.3.23"
2079+version = "0.3.20"
20612080source = "registry+https://github.com/rust-lang/crates.io-index"
2062−checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
2081+checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
20632082dependencies = [
20642083 "itoa",
20652084 "libc",
@@ -2071,15 +2090,15 @@ dependencies = [
20712090
20722091[[package]]
20732092name = "time-core"
2074−version = "0.1.1"
2093+version = "0.1.0"
20752094source = "registry+https://github.com/rust-lang/crates.io-index"
2076−checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
2095+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
20772096
20782097[[package]]
20792098name = "time-macros"
2080−version = "0.2.10"
2099+version = "0.2.8"
20812100source = "registry+https://github.com/rust-lang/crates.io-index"
2082−checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
2101+checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
20832102dependencies = [
20842103 "time-core",
20852104]
@@ -2101,11 +2120,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
21012120
21022121[[package]]
21032122name = "tokio"
2104−version = "1.35.1"
2123+version = "1.28.1"
21052124source = "registry+https://github.com/rust-lang/crates.io-index"
2106−checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
2125+checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105"
21072126dependencies = [
2108− "backtrace",
2127+ "autocfg",
21092128 "bytes",
21102129 "libc",
21112130 "mio",
@@ -2120,13 +2139,13 @@ dependencies = [
21202139
21212140[[package]]
21222141name = "tokio-macros"
2123−version = "2.2.0"
2142+version = "2.1.0"
21242143source = "registry+https://github.com/rust-lang/crates.io-index"
2125−checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
2144+checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
21262145dependencies = [
21272146 "proc-macro2",
21282147 "quote",
2129− "syn 2.0.38",
2148+ "syn 2.0.15",
21302149]
21312150
21322151[[package]]
@@ -2142,9 +2161,9 @@ dependencies = [
21422161
21432162[[package]]
21442163name = "toml"
2145−version = "0.7.6"
2164+version = "0.7.3"
21462165source = "registry+https://github.com/rust-lang/crates.io-index"
2147−checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
2166+checksum = "b403acf6f2bb0859c93c7f0d967cb4a75a7ac552100f9322faf64dc047669b21"
21482167dependencies = [
21492168 "serde",
21502169 "serde_spanned",
@@ -2154,30 +2173,31 @@ dependencies = [
21542173
21552174[[package]]
21562175name = "toml_datetime"
2157−version = "0.6.3"
2176+version = "0.6.1"
21582177source = "registry+https://github.com/rust-lang/crates.io-index"
2159−checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
2178+checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
21602179dependencies = [
21612180 "serde",
21622181]
21632182
21642183[[package]]
21652184name = "toml_edit"
2166−version = "0.19.12"
2185+version = "0.19.8"
21672186source = "registry+https://github.com/rust-lang/crates.io-index"
2168−checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
2187+checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
21692188dependencies = [
21702189 "indexmap",
21712190 "serde",
21722191 "serde_spanned",
21732192 "toml_datetime",
2174− "winnow 0.4.6",
2193+ "winnow",
21752194]
21762195
21772196[[package]]
21782197name = "tree-sitter"
21792198version = "0.20.10"
2180−source = "git+https://github.com/tree-sitter/tree-sitter?rev=ab09ae20d640711174b8da8a654f6b3dec93da1a#ab09ae20d640711174b8da8a654f6b3dec93da1a"
2199+source = "registry+https://github.com/rust-lang/crates.io-index"
2200+checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d"
21812201dependencies = [
21822202 "cc",
21832203 "regex",
@@ -2218,9 +2238,13 @@ checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
22182238
22192239[[package]]
22202240name = "unicode-linebreak"
2221−version = "0.1.5"
2241+version = "0.1.4"
22222242source = "registry+https://github.com/rust-lang/crates.io-index"
2223−checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
2243+checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
2244+dependencies = [
2245+ "hashbrown 0.12.3",
2246+ "regex",
2247+]
22242248
22252249[[package]]
22262250name = "unicode-normalization"
@@ -2239,15 +2263,15 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
22392263
22402264[[package]]
22412265name = "unicode-width"
2242−version = "0.1.11"
2266+version = "0.1.10"
22432267source = "registry+https://github.com/rust-lang/crates.io-index"
2244−checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
2268+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
22452269
22462270[[package]]
22472271name = "url"
2248−version = "2.5.0"
2272+version = "2.3.1"
22492273source = "registry+https://github.com/rust-lang/crates.io-index"
2250−checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
2274+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
22512275dependencies = [
22522276 "form_urlencoded",
22532277 "idna",
@@ -2263,9 +2287,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
22632287
22642288[[package]]
22652289name = "walkdir"
2266−version = "2.4.0"
2290+version = "2.3.3"
22672291source = "registry+https://github.com/rust-lang/crates.io-index"
2268−checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
2292+checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
22692293dependencies = [
22702294 "same-file",
22712295 "winapi-util",
@@ -2333,15 +2357,13 @@ checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
23332357
23342358[[package]]
23352359name = "which"
2336−version = "5.0.0"
2360+version = "4.4.0"
23372361source = "registry+https://github.com/rust-lang/crates.io-index"
2338−checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14"
2362+checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
23392363dependencies = [
23402364 "either",
2341− "home",
2365+ "libc",
23422366 "once_cell",
2343− "rustix",
2344− "windows-sys 0.48.0",
23452367]
23462368
23472369[[package]]
@@ -2384,25 +2406,6 @@ dependencies = [
23842406 "windows-targets 0.48.0",
23852407]
23862408
2387−[[package]]
2388−name = "windows"
2389−version = "0.52.0"
2390−source = "registry+https://github.com/rust-lang/crates.io-index"
2391−checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
2392−dependencies = [
2393− "windows-core",
2394− "windows-targets 0.52.0",
2395−]
2396−
2397−[[package]]
2398−name = "windows-core"
2399−version = "0.52.0"
2400−source = "registry+https://github.com/rust-lang/crates.io-index"
2401−checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
2402−dependencies = [
2403− "windows-targets 0.52.0",
2404−]
2405−
24062409[[package]]
24072410name = "windows-sys"
24082411version = "0.45.0"
@@ -2421,15 +2424,6 @@ dependencies = [
24212424 "windows-targets 0.48.0",
24222425]
24232426
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−
24332427[[package]]
24342428name = "windows-targets"
24352429version = "0.42.2"
@@ -2460,21 +2454,6 @@ dependencies = [
24602454 "windows_x86_64_msvc 0.48.0",
24612455]
24622456
2463−[[package]]
2464−name = "windows-targets"
2465−version = "0.52.0"
2466−source = "registry+https://github.com/rust-lang/crates.io-index"
2467−checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
2468−dependencies = [
2469− "windows_aarch64_gnullvm 0.52.0",
2470− "windows_aarch64_msvc 0.52.0",
2471− "windows_i686_gnu 0.52.0",
2472− "windows_i686_msvc 0.52.0",
2473− "windows_x86_64_gnu 0.52.0",
2474− "windows_x86_64_gnullvm 0.52.0",
2475− "windows_x86_64_msvc 0.52.0",
2476−]
2477−
24782457[[package]]
24792458name = "windows_aarch64_gnullvm"
24802459version = "0.42.2"
@@ -2487,12 +2466,6 @@ version = "0.48.0"
24872466source = "registry+https://github.com/rust-lang/crates.io-index"
24882467checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
24892468
2490−[[package]]
2491−name = "windows_aarch64_gnullvm"
2492−version = "0.52.0"
2493−source = "registry+https://github.com/rust-lang/crates.io-index"
2494−checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
2495−
24962469[[package]]
24972470name = "windows_aarch64_msvc"
24982471version = "0.42.2"
@@ -2505,12 +2478,6 @@ version = "0.48.0"
25052478source = "registry+https://github.com/rust-lang/crates.io-index"
25062479checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
25072480
2508−[[package]]
2509−name = "windows_aarch64_msvc"
2510−version = "0.52.0"
2511−source = "registry+https://github.com/rust-lang/crates.io-index"
2512−checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
2513−
25142481[[package]]
25152482name = "windows_i686_gnu"
25162483version = "0.42.2"
@@ -2523,12 +2490,6 @@ version = "0.48.0"
25232490source = "registry+https://github.com/rust-lang/crates.io-index"
25242491checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
25252492
2526−[[package]]
2527−name = "windows_i686_gnu"
2528−version = "0.52.0"
2529−source = "registry+https://github.com/rust-lang/crates.io-index"
2530−checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
2531−
25322493[[package]]
25332494name = "windows_i686_msvc"
25342495version = "0.42.2"
@@ -2541,12 +2502,6 @@ version = "0.48.0"
25412502source = "registry+https://github.com/rust-lang/crates.io-index"
25422503checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
25432504
2544−[[package]]
2545−name = "windows_i686_msvc"
2546−version = "0.52.0"
2547−source = "registry+https://github.com/rust-lang/crates.io-index"
2548−checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
2549−
25502505[[package]]
25512506name = "windows_x86_64_gnu"
25522507version = "0.42.2"
@@ -2559,12 +2514,6 @@ version = "0.48.0"
25592514source = "registry+https://github.com/rust-lang/crates.io-index"
25602515checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
25612516
2562−[[package]]
2563−name = "windows_x86_64_gnu"
2564−version = "0.52.0"
2565−source = "registry+https://github.com/rust-lang/crates.io-index"
2566−checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
2567−
25682517[[package]]
25692518name = "windows_x86_64_gnullvm"
25702519version = "0.42.2"
@@ -2577,12 +2526,6 @@ version = "0.48.0"
25772526source = "registry+https://github.com/rust-lang/crates.io-index"
25782527checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
25792528
2580−[[package]]
2581−name = "windows_x86_64_gnullvm"
2582−version = "0.52.0"
2583−source = "registry+https://github.com/rust-lang/crates.io-index"
2584−checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
2585−
25862529[[package]]
25872530name = "windows_x86_64_msvc"
25882531version = "0.42.2"
@@ -2595,33 +2538,18 @@ version = "0.48.0"
25952538source = "registry+https://github.com/rust-lang/crates.io-index"
25962539checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
25972540
2598−[[package]]
2599−name = "windows_x86_64_msvc"
2600−version = "0.52.0"
2601−source = "registry+https://github.com/rust-lang/crates.io-index"
2602−checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
2603−
2604−[[package]]
2605−name = "winnow"
2606−version = "0.4.6"
2607−source = "registry+https://github.com/rust-lang/crates.io-index"
2608−checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
2609−dependencies = [
2610− "memchr",
2611−]
2612−
26132541[[package]]
26142542name = "winnow"
2615−version = "0.5.28"
2543+version = "0.4.1"
26162544source = "registry+https://github.com/rust-lang/crates.io-index"
2617−checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
2545+checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
26182546dependencies = [
26192547 "memchr",
26202548]
26212549
26222550[[package]]
26232551name = "xtask"
2624−version = "23.10.0"
2552+version = "0.6.0"
26252553dependencies = [
26262554 "helix-core",
26272555 "helix-loader",
@@ -2629,23 +2557,3 @@ dependencies = [
26292557 "helix-view",
26302558 "toml",
26312559]
2632−
2633−[[package]]
2634−name = "zerocopy"
2635−version = "0.7.31"
2636−source = "registry+https://github.com/rust-lang/crates.io-index"
2637−checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d"
2638−dependencies = [
2639− "zerocopy-derive",
2640−]
2641−
2642−[[package]]
2643−name = "zerocopy-derive"
2644−version = "0.7.31"
2645−source = "registry+https://github.com/rust-lang/crates.io-index"
2646−checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a"
2647−dependencies = [
2648− "proc-macro2",
2649− "quote",
2650− "syn 2.0.38",
2651−]
Cargo.toml−16
@@ -1,12 +1,10 @@
11[workspace]
2−resolver = "2"
32members = [
43 "helix-core",
54 "helix-view",
65 "helix-term",
76 "helix-tui",
87 "helix-lsp",
9− "helix-event",
108 "helix-dap",
119 "helix-loader",
1210 "helix-vcs",
@@ -34,17 +32,3 @@ inherits = "test"
3432package.helix-core.opt-level = 2
3533package.helix-tui.opt-level = 2
3634package.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](https://github.com/mawww/kakoune) / [Neovim](https://github.com/neovim/neovim) inspired editor, written in Rust.
21+A Kakoune / Neovim inspired editor, written in Rust.
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 [@jakenvac](https://github.com/jakenvac) for designing the logo!
64+Thanks to [@JakeHL](https://github.com/JakeHL) for designing the logo!
VERSIONnew file+1
@@ -0,0 +1 @@
1+23.05
book/book.toml+1−5
@@ -3,14 +3,10 @@ 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"
67
78[output.html]
89cname = "docs.helix-editor.com"
910default-theme = "colibri"
1011preferred-dark-theme = "colibri"
1112git-repository-url = "https://github.com/helix-editor/helix"
12−edit-url-template = "https://github.com/helix-editor/helix/edit/master/book/{path}"
13−additional-css = ["custom.css"]
14−
15−[output.html.search]
16−use-boolean-and = true
book/custom.cssdeleted−231
@@ -1,231 +0,0 @@
1−html {
2− font-family: "Inter", sans-serif;
3−}
4−
5−.sidebar .sidebar-scrollbox {
6− padding: 0;
7−}
8−
9−.chapter {
10− margin: 0.25rem 0;
11−}
12−
13−.chapter li.chapter-item {
14− line-height: initial;
15− margin: 0;
16− padding: 1rem 1.5rem;
17−}
18−
19−.chapter .section li.chapter-item {
20− line-height: inherit;
21− padding: .5rem .5rem 0 .5rem;
22−}
23−
24−.content {
25− overflow-y: auto;
26− padding: 0 15px;
27− padding-bottom: 50px;
28−}
29−
30−/* 2 1.75 1.5 1.25 1 .875 */
31−.content h1 { font-size: 2em }
32−.content h2 { font-size: 1.75em }
33−.content h3 { font-size: 1.5em }
34−.content h4 { font-size: 1.25em }
35−.content h5 { font-size: 1em }
36−.content h6 { font-size: .875em }
37−
38−.content h1,
39−.content h2,
40−.content h3,
41−.content h4 {
42− font-weight: 500;
43− margin-top: 1.275em;
44− margin-bottom: .875em;
45−}
46−
47−.content p,
48−.content ol,
49−.content ul,
50−.content table {
51− margin-top: 0;
52− margin-bottom: .875em;
53−}
54−
55−.content ul li {
56− margin-bottom: .25rem;
57−}
58−
59−.content ul {
60− list-style-type: square;
61−}
62−
63−.content ul ul,
64−.content ol ul {
65− margin-bottom: .5rem;
66−}
67−
68−.content li p {
69− margin-bottom: .5em;
70−}
71−
72−blockquote {
73− margin: 1.5rem 0;
74− padding: 1rem 1.5rem;
75− color: var(--fg);
76− opacity: .9;
77− background-color: var(--quote-bg);
78− border-left: 4px solid var(--quote-border);
79− border-top: none;
80− border-bottom: none;
81−}
82−
83−blockquote *:last-child {
84− margin-bottom: 0;
85−}
86−
87−table {
88− width: 100%;
89−}
90−
91−table thead th {
92− padding: .75rem;
93− text-align: left;
94− font-weight: 500;
95− line-height: 1.5;
96− width: auto;
97−}
98−
99−table td {
100− padding: .75rem;
101− border: none;
102−}
103−
104−table thead tr {
105− border: none;
106− border-bottom: 2px var(--table-border-color) solid;
107−}
108−
109−table tbody tr {
110− border-bottom: 1px var(--table-border-line) solid;
111−}
112−
113−table tbody tr:nth-child(2n) {
114− background: unset;
115−}
116−
117−pre code.hljs {
118− display: block;
119− overflow-x: auto;
120− padding: 1em;
121−}
122−
123−code.hljs {
124− padding: 3px 5px;
125−}
126−
127−.colibri {
128− --bg: #3b224c;
129− --fg: #bcbdd0;
130− --heading-fg: #fff;
131−
132− --sidebar-bg: #281733;
133− --sidebar-fg: #c8c9db;
134− --sidebar-non-existent: #505274;
135− --sidebar-active: #a4a0e8;
136− --sidebar-spacer: #2d334f;
137−
138− --scrollbar: var(--sidebar-fg);
139−
140− --icons: #737480;
141− --icons-hover: #b7b9cc;
142−
143− /* --links: #a4a0e8; */
144− --links: #ECCDBA;
145−
146− --inline-code-color: hsl(48.7, 7.8%, 70%);
147−
148− --theme-popup-bg: #161923;
149− --theme-popup-border: #737480;
150− --theme-hover: rgba(0, 0, 0, .2);
151−
152− --quote-bg: #281733;
153− --quote-border: hsl(226, 15%, 22%);
154−
155− --table-border-color: hsl(226, 23%, 76%);
156− --table-header-bg: hsla(226, 23%, 31%, 0);
157− --table-alternate-bg: hsl(226, 23%, 14%);
158− --table-border-line: hsla(201deg, 20%, 92%, 0.2);
159−
160− --searchbar-border-color: #aaa;
161− --searchbar-bg: #aeaec6;
162− --searchbar-fg: #000;
163− --searchbar-shadow-color: #aaa;
164− --searchresults-header-fg: #5f5f71;
165− --searchresults-border-color: #5c5c68;
166− --searchresults-li-bg: #242430;
167− --search-mark-bg: #a2cff5;
168−}
169−
170−.colibri .content .header {
171− color: #fff;
172−}
173−
174−/* highlight.js theme, :where() is used to avoid increasing specificity */
175−
176−:where(.colibri) .hljs {
177− background: #2f1e2e;
178− color: #a39e9b;
179−}
180−
181−:where(.colibri) .hljs-comment,
182−:where(.colibri) .hljs-quote {
183− color: #8d8687;
184−}
185−
186−:where(.colibri) .hljs-link,
187−:where(.colibri) .hljs-meta,
188−:where(.colibri) .hljs-name,
189−:where(.colibri) .hljs-regexp,
190−:where(.colibri) .hljs-selector-class,
191−:where(.colibri) .hljs-selector-id,
192−:where(.colibri) .hljs-tag,
193−:where(.colibri) .hljs-template-variable,
194−:where(.colibri) .hljs-variable {
195− color: #ef6155;
196−}
197−
198−:where(.colibri) .hljs-built_in,
199−:where(.colibri) .hljs-deletion,
200−:where(.colibri) .hljs-literal,
201−:where(.colibri) .hljs-number,
202−:where(.colibri) .hljs-params,
203−:where(.colibri) .hljs-type {
204− color: #f99b15;
205−}
206−
207−:where(.colibri) .hljs-attribute,
208−:where(.colibri) .hljs-section,
209−:where(.colibri) .hljs-title {
210− color: #fec418;
211−}
212−
213−:where(.colibri) .hljs-addition,
214−:where(.colibri) .hljs-bullet,
215−:where(.colibri) .hljs-string,
216−:where(.colibri) .hljs-symbol {
217− color: #48b685;
218−}
219−
220−:where(.colibri) .hljs-keyword,
221−:where(.colibri) .hljs-selector-tag {
222− color: #815ba4;
223−}
224−
225−:where(.colibri) .hljs-emphasis {
226− font-style: italic;
227−}
228−
229−:where(.colibri) .hljs-strong {
230− font-weight: 700;
231−}
book/src/configuration.md+13−43
@@ -51,8 +51,7 @@ Its settings will be merged with the configuration directory `config.toml` and t
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 | `250` |
55−| `preview-completion-insert` | Whether to apply completion item instantly when selected | `true` |
54+| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant | `400` |
5655| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
5756| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
5857| `auto-info` | Whether to display info boxes | `true` |
@@ -63,10 +62,6 @@ Its settings will be merged with the configuration directory `config.toml` and t
6362| `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` |
6463| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
6564| `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`
7065
7166### `[editor.statusline]` Section
7267
@@ -92,9 +87,9 @@ The `[editor.statusline]` key takes the following sub-keys:
9287
9388| Key | Description | Default |
9489| --- | --- | --- |
95−| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "read-only-indicator", "file-modification-indicator"]` |
90+| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name"]` |
9691| `center` | A list of elements aligned to the middle of the statusline | `[]` |
97−| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
92+| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "position", "file-encoding"]` |
9893| `separator` | The character used to separate elements in the statusline | `"│"` |
9994| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
10095| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
@@ -111,7 +106,6 @@ The following statusline elements can be configured:
111106| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
112107| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
113108| `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 |
115109| `total-line-numbers` | The total line numbers of the opened file |
116110| `file-type` | The type of the opened file |
117111| `diagnostics` | The number of warnings and/or errors |
@@ -123,7 +117,6 @@ The following statusline elements can be configured:
123117| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
124118| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
125119| `version-control` | The current branch name or detached commit hash of the opened workspace |
126−| `register` | The current selected register |
127120
128121### `[editor.lsp]` Section
129122
@@ -138,8 +131,8 @@ The following statusline elements can be configured:
138131| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
139132
140133[^1]: By default, a progress spinner is shown in the statusline beside the file path.
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!
134+[^2]: You may also have to activate them in the LSP config for them to appear, not just in Helix.
135+ Inlay hints in Helix are still being improved on and may be a little bit laggy/janky under some circumstances, please report any bugs you see so we can fix them!
143136
144137### `[editor.cursor-shape]` Section
145138
@@ -168,31 +161,16 @@ All git related options are only enabled in a git repository.
168161
169162| Key | Description | Default |
170163|--|--|---------|
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`
164+|`hidden` | Enables ignoring hidden files | true
165+|`follow-symlinks` | Follow symlinks instead of ignoring them | true
166+|`deduplicate-links` | Ignore symlinks that point at files already shown in the picker | true
167+|`parents` | Enables reading ignore files from parent directories | true
168+|`ignore` | Enables reading `.ignore` files | true
169+|`git-ignore` | Enables reading `.gitignore` files | true
170+|`git-global` | Enables reading global `.gitignore`, whose path is specified in git's config: `core.excludefile` option | true
171+|`git-exclude` | Enables reading `.git/info/exclude` files | true
179172|`max-depth` | Set with an integer value for maximum depth to recurse | Defaults to `None`.
180173
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−
196174### `[editor.auto-pairs]` Section
197175
198176Enables automatic insertion of pairs to parentheses, brackets, etc. Can be a
@@ -366,11 +344,3 @@ max-wrap = 25 # increase value to reduce forced mid-word wrapping
366344max-indent-retain = 0
367345wrap-indicator = "" # set wrap-indicator to "" to hide it
368346```
369−
370−### `[editor.smart-tab]` Section
371−
372−
373−| Key | Description | Default |
374−|------------|-------------|---------|
375−| `enable` | If set to true, then when the cursor is in a position with non-whitespace to its left, instead of inserting a tab, it will run `move_parent_node_end`. If there is only whitespace to the left, then it inserts a tab as normal. With the default bindings, to explicitly insert a tab character, press Shift-tab. | `true` |
376−| `supersede-menu` | Normally, when a menu is on screen, such as when auto complete is triggered, the tab key is bound to cycling through the items. This means when menus are on screen, one cannot use the tab key to trigger the `smart-tab` command. If this option is set to true, the `smart-tab` command always takes precedence, which means one cannot use the tab key to cycle through menu items. One of the other bindings must be used instead, such as arrow keys or `C-n`/`C-p`. | `false` |
book/src/generated/lang-support.md+18−43
@@ -1,23 +1,21 @@
11| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
22| --- | --- | --- | --- | --- |
3−| agda | ✓ | | | |
43| astro | ✓ | | | |
54| awk | ✓ | ✓ | | `awk-language-server` |
6−| bash | ✓ | ✓ | ✓ | `bash-language-server` |
5+| bash | ✓ | | ✓ | `bash-language-server` |
76| bass | ✓ | | | `bass` |
87| beancount | ✓ | | | |
98| bibtex | ✓ | | | `texlab` |
109| bicep | ✓ | | | `bicep-langserver` |
11−| blueprint | ✓ | | | `blueprint-compiler` |
1210| c | ✓ | ✓ | ✓ | `clangd` |
1311| c-sharp | ✓ | ✓ | | `OmniSharp` |
14−| cabal | | | | `haskell-language-server-wrapper` |
15−| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
12+| cabal | | | | |
13+| cairo | ✓ | | | |
1614| capnp | ✓ | | ✓ | |
1715| clojure | ✓ | | | `clojure-lsp` |
1816| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
1917| comment | ✓ | | | |
20−| common-lisp | ✓ | | ✓ | `cl-lsp` |
18+| common-lisp | ✓ | | | `cl-lsp` |
2119| cpon | ✓ | | ✓ | |
2220| cpp | ✓ | ✓ | ✓ | `clangd` |
2321| crystal | ✓ | ✓ | | `crystalline` |
@@ -25,7 +23,6 @@
2523| cue | ✓ | | | `cuelsp` |
2624| d | ✓ | ✓ | ✓ | `serve-d` |
2725| dart | ✓ | | ✓ | `dart` |
28−| dbml | ✓ | | | |
2926| devicetree | ✓ | | | |
3027| dhall | ✓ | ✓ | | `dhall-lsp-server` |
3128| diff | ✓ | | | |
@@ -43,12 +40,8 @@
4340| erlang | ✓ | ✓ | | `erlang_ls` |
4441| esdl | ✓ | | | |
4542| fish | ✓ | ✓ | ✓ | |
46−| forth | ✓ | | | `forth-lsp` |
4743| fortran | ✓ | | ✓ | `fortls` |
48−| fsharp | ✓ | | | `fsautocomplete` |
49−| gas | ✓ | ✓ | | |
5044| gdscript | ✓ | ✓ | ✓ | |
51−| gemini | ✓ | | | |
5245| git-attributes | ✓ | | | |
5346| git-commit | ✓ | ✓ | | |
5447| git-config | ✓ | | | |
@@ -56,16 +49,14 @@
5649| git-rebase | ✓ | | | |
5750| gleam | ✓ | ✓ | | `gleam` |
5851| glsl | ✓ | ✓ | ✓ | |
59−| gn | ✓ | | | |
60−| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
52+| go | ✓ | ✓ | ✓ | `gopls` |
6153| godot-resource | ✓ | | | |
6254| gomod | ✓ | | | `gopls` |
6355| gotmpl | ✓ | | | `gopls` |
6456| gowork | ✓ | | | `gopls` |
65−| graphql | ✓ | | | `graphql-lsp` |
57+| graphql | ✓ | | | |
6658| hare | ✓ | | | |
6759| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
68−| haskell-persistent | ✓ | | | |
6960| hcl | ✓ | | ✓ | `terraform-ls` |
7061| heex | ✓ | ✓ | | `elixir-ls` |
7162| hosts | ✓ | | | |
@@ -74,18 +65,15 @@
7465| idris | | | | `idris2-lsp` |
7566| iex | ✓ | | | |
7667| ini | ✓ | | | |
77−| janet | ✓ | | | |
78−| java | ✓ | ✓ | ✓ | `jdtls` |
68+| java | ✓ | ✓ | | `jdtls` |
7969| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
80−| jinja | ✓ | | | |
8170| jsdoc | ✓ | | | |
8271| json | ✓ | | ✓ | `vscode-json-language-server` |
83−| json5 | ✓ | | | |
8472| jsonnet | ✓ | | | `jsonnet-language-server` |
8573| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
8674| julia | ✓ | ✓ | ✓ | `julia` |
8775| just | ✓ | ✓ | ✓ | |
88−| kdl | ✓ | ✓ | ✓ | |
76+| kdl | ✓ | | | |
8977| kotlin | ✓ | | | `kotlin-language-server` |
9078| latex | ✓ | ✓ | | `texlab` |
9179| lean | ✓ | | | `lean` |
@@ -93,14 +81,12 @@
9381| llvm | ✓ | ✓ | ✓ | |
9482| llvm-mir | ✓ | ✓ | ✓ | |
9583| llvm-mir-yaml | ✓ | | ✓ | |
96−| log | ✓ | | | |
97−| lpf | ✓ | | | |
9884| lua | ✓ | ✓ | ✓ | `lua-language-server` |
9985| make | ✓ | | | |
10086| markdoc | ✓ | | | `markdoc-ls` |
10187| markdown | ✓ | | | `marksman` |
10288| markdown.inline | ✓ | | | |
103−| matlab | ✓ | ✓ | ✓ | |
89+| matlab | ✓ | | | |
10490| mermaid | ✓ | | | |
10591| meson | ✓ | | ✓ | |
10692| mint | | | | `mint` |
@@ -109,8 +95,7 @@
10995| nickel | ✓ | | ✓ | `nls` |
11096| nim | ✓ | ✓ | ✓ | `nimlangserver` |
11197| nix | ✓ | | | `nil` |
112−| nu | ✓ | | | `nu` |
113−| nunjucks | ✓ | | | |
98+| nu | ✓ | | | |
11499| ocaml | ✓ | | ✓ | `ocamllsp` |
115100| ocaml-interface | ✓ | | | `ocamllsp` |
116101| odin | ✓ | | ✓ | `ols` |
@@ -123,17 +108,16 @@
123108| perl | ✓ | ✓ | ✓ | `perlnavigator` |
124109| php | ✓ | ✓ | ✓ | `intelephense` |
125110| po | ✓ | ✓ | | |
126−| pod | ✓ | | | |
127111| ponylang | ✓ | ✓ | ✓ | |
128112| prisma | ✓ | | | `prisma-language-server` |
129113| prolog | | | | `swipl` |
130−| protobuf | ✓ | | ✓ | `bufls`, `pb` |
114+| protobuf | ✓ | | ✓ | |
131115| prql | ✓ | | | |
132−| purescript | ✓ | ✓ | | `purescript-language-server` |
116+| purescript | ✓ | | | `purescript-language-server` |
133117| python | ✓ | ✓ | ✓ | `pylsp` |
134118| qml | ✓ | | ✓ | `qmlls` |
135119| r | ✓ | | | `R` |
136−| racket | ✓ | | ✓ | `racket` |
120+| racket | ✓ | | | `racket` |
137121| regex | ✓ | | | |
138122| rego | ✓ | | | `regols` |
139123| rescript | ✓ | ✓ | | `rescript-language-server` |
@@ -145,49 +129,40 @@
145129| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
146130| sage | ✓ | ✓ | | |
147131| scala | ✓ | | ✓ | `metals` |
148−| scheme | ✓ | | ✓ | |
132+| scheme | ✓ | | | |
149133| scss | ✓ | | | `vscode-css-language-server` |
150134| slint | ✓ | | ✓ | `slint-lsp` |
151−| smali | ✓ | | ✓ | |
152135| smithy | ✓ | | | `cs` |
153136| sml | ✓ | | | |
154137| solidity | ✓ | | | `solc` |
155138| sql | ✓ | | | |
156139| sshclientconfig | ✓ | | | |
157140| starlark | ✓ | ✓ | | |
158−| strace | ✓ | | | |
159−| svelte | ✓ | | ✓ | `svelteserver` |
141+| svelte | ✓ | | | `svelteserver` |
160142| sway | ✓ | ✓ | ✓ | `forc` |
161143| swift | ✓ | | | `sourcekit-lsp` |
162−| t32 | ✓ | | | |
163144| tablegen | ✓ | ✓ | ✓ | |
164145| task | ✓ | | | |
165−| templ | ✓ | | | `templ` |
166146| tfvars | ✓ | | ✓ | `terraform-ls` |
167−| todotxt | ✓ | | | |
168147| toml | ✓ | | | `taplo` |
169148| tsq | ✓ | | | |
170149| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
171150| twig | ✓ | | | |
172151| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
173−| typst | ✓ | | | `typst-lsp` |
174152| ungrammar | ✓ | | | |
175−| unison | ✓ | | | |
176153| uxntal | ✓ | | | |
177−| v | ✓ | ✓ | ✓ | `v-analyzer` |
154+| v | ✓ | ✓ | ✓ | `v` |
178155| vala | ✓ | | | `vala-language-server` |
179156| verilog | ✓ | ✓ | | `svlangserver` |
180157| vhdl | ✓ | | | `vhdl_ls` |
181158| vhs | ✓ | | | |
182−| vue | ✓ | | | `vue-language-server` |
159+| vue | ✓ | | | `vls` |
183160| wast | ✓ | | | |
184161| wat | ✓ | | | |
185−| webc | ✓ | | | |
186162| wgsl | ✓ | | | `wgsl_analyzer` |
187163| wit | ✓ | | ✓ | |
188−| wren | ✓ | ✓ | ✓ | |
189164| xit | ✓ | | | |
190165| xml | ✓ | | ✓ | |
191−| yaml | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
166+| yaml | ✓ | | ✓ | `yaml-language-server` |
192167| yuck | ✓ | | | |
193168| zig | ✓ | ✓ | ✓ | `zls` |
book/src/generated/typable-cmd.md+3−8
@@ -17,14 +17,13 @@
1717| `:write-buffer-close!`, `:wbc!` | Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt) |
1818| `:new`, `:n` | Create a new scratch buffer. |
1919| `:format`, `:fmt` | Format the file using the LSP formatter. |
20−| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.) |
20+| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
2121| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
2222| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
2323| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
2424| `:write-quit`, `:wq`, `:x` | Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) |
2525| `:write-quit!`, `:wq!`, `:x!` | Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) |
2626| `:write-all`, `:wa` | Write changes from all buffers to disk. |
27−| `:write-all!`, `:wa!` | Forcefully write changes from all buffers to disk creating necessary subdirectories. |
2827| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. |
2928| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes). |
3029| `:quit-all`, `:qa` | Close all views. |
@@ -32,7 +31,6 @@
3231| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
3332| `:cquit!`, `:cq!` | Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2). |
3433| `: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. |
3634| `:clipboard-yank` | Yank main selection into system clipboard. |
3735| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
3836| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
@@ -48,14 +46,13 @@
4846| `:show-directory`, `:pwd` | Show the current working directory. |
4947| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
5048| `:character-info`, `:char` | Get info about the character under the primary cursor. |
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. |
49+| `:reload` | Discard changes and reload from the source file. |
50+| `:reload-all` | Discard changes and reload all documents from the source files. |
5351| `:update`, `:u` | Write changes only if the file has been modified. |
5452| `:lsp-workspace-command` | Open workspace command picker |
5553| `:lsp-restart` | Restarts the language servers used by the current doc |
5654| `:lsp-stop` | Stops the language servers that are used by the current doc |
5755| `: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. |
5956| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
6057| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
6158| `:debug-eval` | Evaluate expression in current debug context. |
@@ -84,5 +81,3 @@
8481| `:run-shell-command`, `:sh` | Run a shell command |
8582| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
8683| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
87−| `:redraw` | Clear and re-render the whole UI |
88−| `:move` | Move the current buffer and its corresponding file to a different path |
book/src/guides/adding_languages.md−2
@@ -36,7 +36,6 @@ below.
36363. Refer to the
3737 [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
3838 for more information on writing queries.
39−4. A list of highlight captures can be found [on the themes page](https://docs.helix-editor.com/themes.html#scopes).
4039
4140> 💡 In Helix, the first matching query takes precedence when evaluating
4241> queries, which is different from other editors such as Neovim where the last
@@ -52,4 +51,3 @@ below.
5251 grammars.
5352- If a parser is causing a segfault, or you want to remove it, make sure to
5453 remove the compiled parser located at `runtime/grammars/<name>.so`.
55−- If you are attempting to add queries and Helix is unable to locate them, ensure that the environment variable `HELIX_RUNTIME` is set to the location of the `runtime` folder you're developing in.
book/src/guides/indent.md+43−319
@@ -1,308 +1,76 @@
11# Adding indent queries
22
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.
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).
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. See [Capture types](#capture-types).
13+the total indent level by 1.
1414
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.
15+## Scopes
2316
24−## Indent queries
25−
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.
30−
31−For `o`, the inserted line is the line below the cursor, so that starting
32−position of the query is the end of the current line.
33−
34−```rust
35−fn need_hero(some_hero: Hero, life: Life) -> {
36− matches!(some_hero, Hero { // ←─────────────────╮
37− strong: true,//←╮ ↑ ↑ │
38− fast: true, // │ │ ╰── query start │
39− sure: true, // │ ╰───── cursor ├─ traversal
40− soon: true, // ╰──────── new line inserted │ start node
41− }) && // │
42−// ↑ │
43−// ╰───────────────────────────────────────────────╯
44− some_hero > life
45−}
46−```
47−
48−For `O`, the newly inserted line is the *current* line, so the starting position
49−of the query is the end of the line above the cursor.
50−
51−```rust
52−fn need_hero(some_hero: Hero, life: Life) -> { // ←─╮
53− matches!(some_hero, Hero { // ←╮ ↑ │
54− strong: true,// ↑ ╭───╯ │ │
55− fast: true, // │ │ query start ─╯ │
56− sure: true, // ╰───┼ cursor ├─ traversal
57− soon: true, // ╰ new line inserted │ start node
58− }) && // │
59− some_hero > life // │
60−} // ←──────────────────────────────────────────────╯
61−```
62−
63−From this starting node, the syntax tree is traversed up until the root node.
64−Each indent capture is collected along the way, and then combined according to
65−their [capture types](#capture-types) and [scopes](#scopes) to a final indent
66−level for the line.
67−
68−### Capture types
69−
70−- `@indent` (default scope `tail`):
71− Increase the indent level by 1. Multiple occurrences in the same line *do not*
72− stack. If there is at least one `@indent` and one `@outdent` capture on the
73− same line, the indent level isn't changed at all.
74−- `@outdent` (default scope `all`):
75− Decrease the indent level by 1. The same rules as for `@indent` apply.
76−- `@indent.always` (default scope `tail`):
77− Increase the indent level by 1. Multiple occurrences on the same line *do*
78− stack. The final indent level is `@indent.always` – `@outdent.always`. If
79− an `@indent` and an `@indent.always` are on the same line, the `@indent` is
80− ignored.
81−- `@outdent.always` (default scope `all`):
82− Decrease the indent level by 1. The same rules as for `@indent.always` apply.
83−- `@align` (default scope `all`):
84− Align everything inside this node to some anchor. The anchor is given
85− by the start of the node captured by `@anchor` in the same pattern.
86− Every pattern with an `@align` should contain exactly one `@anchor`.
87− Indent (and outdent) for nodes below (in terms of their starting line)
88− the `@align` node is added to the indentation required for alignment.
89−- `@extend`:
90− Extend the range of this node to the end of the line and to lines that are
91− indented more than the line that this node starts on. This is useful for
92− languages like Python, where for the purpose of indentation some nodes (like
93− functions or classes) should also contain indented lines that follow them.
94−- `@extend.prevent-once`:
95− Prevents the first extension of an ancestor of this node. For example, in Python
96− a return expression always ends the block that it is in. Note that this only
97− stops the extension of the next `@extend` capture. If multiple ancestors are
98− captured, only the extension of the innermost one is prevented. All other
99− ancestors are unaffected (regardless of whether the innermost ancestor would
100− actually have been extended).
101−
102−#### `@indent` / `@outdent`
103−
104−Consider this example:
105−
106−```rust
107−fn shout(things: Vec<Thing>) {
108− // ↑
109− // ├───────────────────────╮ indent level
110− // @indent ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
111− // │
112− let it_all = |out| { things.filter(|thing| { // │ 1
113− // ↑ ↑ │
114− // ├───────────────────────┼─────┼┄┄┄┄┄┄┄┄┄┄┄┄┄┄
115− // @indent @indent │
116− // │ 2
117− thing.can_do_with(out) // │
118− })}; // ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
119− //↑↑↑ │ 1
120−} //╰┼┴──────────────────────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄
121−// 3x @outdent
122−```
123−
124−```scm
125−((block) @indent)
126−["}" ")"] @outdent
127−```
128−
129−Note how on the second line, we have two blocks begin on the same line. In this
130−case, since both captures occur on the same line, they are combined and only
131−result in a net increase of 1. Also note that the closing `}`s are part of the
132−`@indent` captures, but the 3 `@outdent`s also combine into 1 and result in that
133−line losing one indent level.
134−
135−#### `@extend` / `@extend.prevent-once`
136−
137−For an example of where `@extend` can be useful, consider Python, which is
138−whitespace-sensitive.
139−
140−```scm
141−]
142− (parenthesized_expression)
143− (function_definition)
144− (class_definition)
145−] @indent
146−
147−```
148−
149−```python
150−class Hero:
151− def __init__(self, strong, fast, sure, soon):# ←─╮
152− self.is_strong = strong # │
153− self.is_fast = fast # ╭─── query start │
154− self.is_sure = sure # │ ╭─ cursor │
155− self.is_soon = soon # │ │ │
156− # ↑ ↑ │ │ │
157− # │ ╰──────╯ │ │
158− # ╰─────────────────────╯ │
159− # ├─ traversal
160− def need_hero(self, life): # │ start node
161− return ( # │
162− self.is_strong # │
163− and self.is_fast # │
164− and self.is_sure # │
165− and self.is_soon # │
166− and self > life # │
167− ) # ←─────────────────────────────────────────╯
168−```
169−
170−Without braces to catch the scope of the function, the smallest descendant of
171−the cursor on a line feed ends up being the entire inside of the class. Because
172−of this, it will miss the entire function node and its indent capture, leading
173−to an indent level one too small.
174−
175−To address this case, `@extend` tells helix to "extend" the captured node's span
176−to the line feed and every consecutive line that has a greater indent level than
177−the line of the node.
178−
179−```scm
180−(parenthesized_expression) @indent
181−
182−]
183− (function_definition)
184− (class_definition)
185−] @indent @extend
186−
187−```
188−
189−```python
190−class Hero:
191− def __init__(self, strong, fast, sure, soon):# ←─╮
192− self.is_strong = strong # │
193− self.is_fast = fast # ╭─── query start ├─ traversal
194− self.is_sure = sure # │ ╭─ cursor │ start node
195− self.is_soon = soon # │ │ ←───────────────╯
196− # ↑ ↑ │ │
197− # │ ╰──────╯ │
198− # ╰─────────────────────╯
199− def need_hero(self, life):
200− return (
201− self.is_strong
202− and self.is_fast
203− and self.is_sure
204− and self.is_soon
205− and self > life
206− )
207−```
208−
209−Furthermore, there are some cases where extending to everything with a greater
210−indent level may not be desirable. Consider the `need_hero` function above. If
211−our cursor is on the last line of the returned expression.
212−
213−```python
214−class Hero:
215− def __init__(self, strong, fast, sure, soon):
216− self.is_strong = strong
217− self.is_fast = fast
218− self.is_sure = sure
219− self.is_soon = soon
220−
221− def need_hero(self, life):
222− return (
223− self.is_strong
224− and self.is_fast
225− and self.is_sure
226− and self.is_soon
227− and self > life
228− ) # ←─── cursor
229− #←────────── where cursor should go on new line
230−```
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):
23121
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.
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.
23825
239−To help, we need to signal an end to the extension. We can do this with
240−`@extend.prevent-once`.
26+- `tail`:
27+This scope applies to everything except for the first line of the
28+captured node.
24129
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:
24233```scm
243−(parenthesized_expression) @indent
244−
245−]
246− (function_definition)
247− (class_definition)
248−] @indent @extend
249−
250−(return_statement) @extend.prevent-once
34+(assignment_expression
35+ right: (_) @indent
36+ (#set! "scope" "all"))
25137```
25238
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.
39+## Capture types
25740
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.
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.
26145
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−```
46+- `@outdent` (default scope `all`):
47+Decrease the indent level by 1. The same rules as for `@indent` apply.
27548
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.
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.
28054
281−```scm
282−((block_sequence_item) @item @indent.always @extend
283− (#not-one-line? @item))
55+- `@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).
28461
285−((block_mapping_pair
286− key: (_) @key
287− value: (_) @val
288− (#not-same-line? @key @val)
289− ) @indent.always @extend
290−)
291−```
29262
29363## Predicates
29464
29565In some cases, an S-expression cannot express exactly what pattern should be matched.
29666For that, tree-sitter allows for predicates to appear anywhere within a pattern,
29767similar to how `#set!` declarations work:
298−
29968```scm
30069(some_kind
30170 (child_kind) @indent
30271 (#predicate? arg1 arg2 ...)
30372)
30473```
305−
30674The number of arguments depends on the predicate that's used.
30775Each argument is either a capture (`@name`) or a string (`"some string"`).
30876The following predicates are supported by tree-sitter:
@@ -323,47 +91,3 @@ argument (a string).
32391
32492- `#same-line?`/`#not-same-line?`:
32593The captures given by the 2 arguments must/must not start on the same line.
326−
327−- `#one-line?`/`#not-one-line?`:
328−The captures given by the fist argument must/must span a total of one line.
329−
330−### Scopes
331−
332−Added indents don't always apply to the whole node. For example, in most
333−cases when a node should be indented, we actually only want everything
334−except for its first line to be indented. For this, there are several
335−scopes (more scopes may be added in the future if required):
336−
337−- `tail`:
338−This scope applies to everything except for the first line of the
339−captured node.
340−- `all`:
341−This scope applies to the whole captured node. This is only different from
342−`tail` when the captured node is the first node on its line.
343−
344−For example, imagine we have the following function
345−
346−```rust
347−fn aha() { // ←─────────────────────────────────────╮
348− let take = "on me"; // ←──────────────╮ scope: │
349− let take = "me on"; // ├─ "tail" ├─ (block) @indent
350− let ill = be_gone_days(1 || 2); // │ │
351−} // ←───────────────────────────────────┴──────────┴─ "}" @outdent
352− // scope: "all"
353−```
354−
355−We can write the following query with the `#set!` declaration:
356−
357− ```scm
358− ((block) @indent
359− (#set! "scope" "tail"))
360− ("}" @outdent
361− (#set! "scope" "all"))
362− ```
363−
364−As we can see, the "tail" scope covers the node, except for the first line.
365−Everything up to and including the closing brace gets an indent level of 1.
366−Then, on the closing brace, we encounter an outdent with a scope of "all", which
367−means the first line is included, and the indent level is cancelled out on this
368−line. (Note these scopes are the defaults for `@indent` and `@outdent`—they are
369−written explicitly for demonstration.)
book/src/guides/injection.md+1−1
@@ -1,7 +1,7 @@
11# Adding Injection Queries
22
33Writing language injection queries allows one to highlight a specific node as a different language.
4−In addition to the [standard][upstream-docs] language injection options used by tree-sitter, there
4+In addition to the [standard](upstream-docs) language injection options used by tree-sitter, there
55are a few Helix specific extensions that allow for more control.
66
77And example of a simple query that would highlight all strings as bash in Nix:
book/src/install.md+23−69
@@ -6,14 +6,12 @@
66- [Linux](#linux)
77 - [Ubuntu](#ubuntu)
88 - [Fedora/RHEL](#fedorarhel)
9− - [Arch Linux extra](#arch-linux-extra)
9+ - [Arch Linux community](#arch-linux-community)
1010 - [NixOS](#nixos)
1111 - [Flatpak](#flatpak)
12− - [Snap](#snap)
1312 - [AppImage](#appimage)
1413- [macOS](#macos)
1514 - [Homebrew Core](#homebrew-core)
16− - [MacPorts](#macports)
1715- [Windows](#windows)
1816 - [Winget](#winget)
1917 - [Scoop](#scoop)
@@ -65,13 +63,16 @@ sudo apt install helix
6563
6664### Fedora/RHEL
6765
66+Enable the `COPR` repository for Helix:
67+
6868```sh
69+sudo dnf copr enable varlad/helix
6970sudo dnf install helix
7071```
7172
72−### Arch Linux extra
73+### Arch Linux community
7374
74−Releases are available in the `extra` repository:
75+Releases are available in the `community` repository:
7576
7677```sh
7778sudo pacman -S helix
@@ -81,10 +82,7 @@ in the AUR, which builds the master branch.
8182
8283### NixOS
8384
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
85+Helix is available as a [flake](https://nixos.wiki/wiki/Flakes) in the project
8886root. Use `nix develop` to spin up a reproducible development shell. Outputs are
8987cached for each push to master using [Cachix](https://www.cachix.org/). The
9088flake is configured to automatically make use of this cache assuming the user
@@ -103,16 +101,6 @@ flatpak install flathub com.helix_editor.Helix
103101flatpak run com.helix_editor.Helix
104102```
105103
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−
116104### AppImage
117105
118106Install Helix using the Linux [AppImage](https://appimage.org/) format.
@@ -131,12 +119,6 @@ chmod +x helix-*.AppImage # change permission for executable mode
131119brew install helix
132120```
133121
134−### MacPorts
135−
136−```sh
137−port install helix
138−```
139−
140122## Windows
141123
142124Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
@@ -174,13 +156,9 @@ pacman -S mingw-w64-ucrt-x86_64-helix
174156
175157Requirements:
176158
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−
181159- The [Rust toolchain](https://www.rust-lang.org/tools/install)
182160- 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
161+- A c++14 compatible compiler to build the tree-sitter grammars, for example GCC or Clang
184162
185163If 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:
186164
@@ -190,19 +168,19 @@ RUSTFLAGS="-C target-feature=-crt-static"
190168
1911691. Clone the repository:
192170
193− ```sh
194− git clone https://github.com/helix-editor/helix
195− cd helix
196− ```
171+```sh
172+git clone https://github.com/helix-editor/helix
173+cd helix
174+```
197175
1981762. Compile from source:
199177
200− ```sh
201− cargo install --path helix-term --locked
202− ```
178+```sh
179+cargo install --path helix-term --locked
180+```
203181
204− This command will create the `hx` executable and construct the tree-sitter
205− grammars in the local `runtime` folder.
182+This command will create the `hx` executable and construct the tree-sitter
183+grammars in the local `runtime` folder.
206184
207185> 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch
208186> grammars with `hx --grammar fetch` and compile them with
@@ -214,22 +192,18 @@ RUSTFLAGS="-C target-feature=-crt-static"
214192
215193#### Linux and macOS
216194
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:
195+Either set the `HELIX_RUNTIME` environment variable to point to the runtime files and add it to your `~/.bashrc` or equivalent:
220196
221197```sh
222−HELIX_RUNTIME=~/src/helix/runtime
198+HELIX_RUNTIME=/home/user-name/src/helix/runtime
223199```
224200
225−Or, create a symbolic link:
201+Or, create a symlink in `~/.config/helix` that links to the source code directory:
226202
227203```sh
228−ln -Ts $PWD/runtime ~/.config/helix/runtime
204+ln -s $PWD/runtime ~/.config/helix/runtime
229205```
230206
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−
233207#### Windows
234208
235209Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
@@ -261,32 +235,12 @@ following order:
2612351. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
262236 developing and testing helix only).
2632372. `runtime/` subdirectory of OS-dependent helix user config directory.
264−3. `$HELIX_RUNTIME`
265−4. Distribution-specific fallback directory (set at compile time—not run time—
266− with the `HELIX_DEFAULT_RUNTIME` environment variable)
267−5. `runtime/` subdirectory of path to Helix executable.
238+3. `$HELIX_RUNTIME`.
239+4. `runtime/` subdirectory of path to Helix executable.
268240
269241This order also sets the priority for selecting which file will be used if multiple runtime
270242directories have files with the same name.
271243
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−
290244### Validating the installation
291245
292246To make sure everything is set up as expected you should run the Helix health
book/src/keymap.md+16−24
@@ -25,8 +25,6 @@
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−
3028### Movement
3129
3230> NOTE: Unlike Vim, `f`, `F`, `t` and `T` are not confined to the current line.
@@ -113,8 +111,7 @@ Normal mode is the default mode when you launch helix. You can return to it from
113111| `s` | Select all regex matches inside selections | `select_regex` |
114112| `S` | Split selection into sub selections on regex matches | `split_selection` |
115113| `Alt-s` | Split selection on newlines | `split_selection_on_newline` |
116−| `Alt-minus` | Merge selections | `merge_selections` |
117−| `Alt-_` | Merge consecutive selections | `merge_consecutive_selections` |
114+| `Alt-_ ` | Merge consecutive selections | `merge_consecutive_selections` |
118115| `&` | Align selection in columns | `align_selections` |
119116| `_` | Trim whitespace from the selection | `trim_selections` |
120117| `;` | Collapse selection onto a single cursor | `collapse_selection` |
@@ -291,7 +288,7 @@ This layer is a kludge of mappings, mostly pickers.
291288| `w` | Enter [window mode](#window-mode) | N/A |
292289| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
293290| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
294−| `y` | Yank selections to clipboard | `yank_to_clipboard` |
291+| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` |
295292| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
296293| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
297294| `/` | Global search in workspace folder | `global_search` |
@@ -339,8 +336,6 @@ These mappings are in the style of [vim-unimpaired](https://github.com/tpope/vim
339336
340337## Insert mode
341338
342−Accessed by typing `i` in [normal mode](#normal-mode).
343−
344339Insert mode bindings are minimal by default. Helix is designed to
345340be a modal editor, and this is reflected in the user experience and internal
346341mechanics. Changes to the text are only saved for undos when
@@ -394,11 +389,9 @@ end = "no_op"
394389
395390## Select / extend mode
396391
397−Accessed by typing `v` in [normal mode](#normal-mode).
398−
399392Select mode echoes Normal mode, but changes any movements to extend
400393selections rather than replace them. Goto motions are also changed to
401−extend, so that `vgl`, for example, extends the selection to the end of
394+extend, so that `vgl` for example extends the selection to the end of
402395the line.
403396
404397Search is also affected. By default, `n` and `N` will remove the current
@@ -411,20 +404,19 @@ you to selectively add search terms to your selections.
411404
412405Keys to use within picker. Remapping currently not supported.
413406
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 |
407+| Key | Description |
408+| ----- | ------------- |
409+| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
410+| `Tab`, `Down`, `Ctrl-n` | Next entry |
411+| `PageUp`, `Ctrl-u` | Page up |
412+| `PageDown`, `Ctrl-d` | Page down |
413+| `Home` | Go to first entry |
414+| `End` | Go to last entry |
415+| `Enter` | Open selected |
416+| `Ctrl-s` | Open horizontally |
417+| `Ctrl-v` | Open vertically |
418+| `Ctrl-t` | Toggle preview |
419+| `Escape`, `Ctrl-c` | Close picker |
428420
429421## Prompt
430422
book/src/languages.md+18−20
@@ -7,24 +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, which lives in the
10+1. In the Helix source code, this 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-server.mylang-lsp]
22− command = "mylang-lsp"
21+[language-server.mylang-lsp]
22+command = "mylang-lsp"
2323
24− [[language]]
25− name = "rust"
26− auto-format = false
27− ```
24+[[language]]
25+name = "rust"
26+auto-format = false
27+```
2828
29293. In a `.helix` folder in your project. Language configuration may also be
3030 overridden local to a project by creating a `languages.toml` file in a
@@ -64,12 +64,11 @@ These configuration keys are available:
6464| `comment-token` | The token to use as a comment-token |
6565| `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) |
6666| `language-servers` | The Language Servers used for this language. See below for more information in the section [Configuring Language Servers for a language](#configuring-language-servers-for-a-language) |
67+| `config` | Language Server configuration |
6768| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
6869| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout |
69−| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
7070| `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` |
7171| `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.
7372
7473### File-type detection and the `file-types` key
7574
@@ -129,8 +128,8 @@ These are the available options for a language server.
129128| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
130129
131130A `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:
131+[Document Formatting Requests](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-17.md#document-formatting-request--leftwards_arrow_with_hook).
132+For example with typescript:
134133
135134```toml
136135[language-server.typescript-language-server]
@@ -149,8 +148,8 @@ Different languages can use the same language server instance, e.g. `typescript-
149148In case multiple language servers are specified in the `language-servers` attribute of a `language`,
150149it's often useful to only enable/disable certain language-server features for these language servers.
151150
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).
151+For example `efm-lsp-prettier` of the previous example is used only with a formatting command `prettier`,
152+so everything else should be handled by the `typescript-language-server` (which is configured by default)
154153The language configuration for typescript could look like this:
155154
156155```toml
@@ -168,10 +167,9 @@ language-servers = [ { name = "typescript-language-server", except-features = [
168167```
169168
170169Each 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).
170+For example the first `goto-definition` supported language server (in this case `typescript-language-server`) will be taken for the relevant LSP request (command `goto_definition`).
171+If no `except-features` or `only-features` is given all features for the language server are enabled.
172+If a language server itself doesn't support a feature the next language server array entry will be tried (and so on).
175173
176174The list of supported features is:
177175
book/src/themes.md+2−10
@@ -70,7 +70,6 @@ over it and is merged into the default palette.
7070
7171| Color Name |
7272| --- |
73−| `default` |
7473| `black` |
7574| `red` |
7675| `green` |
@@ -155,7 +154,6 @@ We use a similar set of scopes as
155154
156155- `type` - Types
157156 - `builtin` - Primitive types provided by the language (`int`, `usize`)
158− - `parameter` - Generic type parameters (`T`)
159157 - `enum`
160158 - `variant`
161159- `constructor`
@@ -246,12 +244,9 @@ We use a similar set of scopes as
246244
247245- `diff` - version control changes
248246 - `plus` - additions
249− - `gutter` - gutter indicator
250247 - `minus` - deletions
251− - `gutter` - gutter indicator
252248 - `delta` - modifications
253249 - `moved` - renamed or moved files/changes
254− - `gutter` - gutter indicator
255250
256251#### Interface
257252
@@ -296,15 +291,12 @@ These scopes are used for theming the editor interface:
296291| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
297292| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
298293| `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 |
302294| `ui.popup` | Documentation popups (e.g. Space + k) |
303295| `ui.popup.info` | Prompt for multiple key options |
304296| `ui.window` | Borderlines separating splits |
305297| `ui.help` | Description box for commands |
306−| `ui.text` | Default text style, command prompts, popup text, etc. |
307−| `ui.text.focus` | The currently selected line in the picker |
298+| `ui.text` | Command prompts, popup text, etc. |
299+| `ui.text.focus` | |
308300| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
309301| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
310302| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
book/src/usage.md+10−26
@@ -37,35 +37,19 @@ If a register is selected before invoking a change or delete command, the select
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−### 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:
40+### Special registers
4441
4542| Register character | Contains |
4643| --- | --- |
4744| `/` | Last search |
4845| `:` | Last executed command |
4946| `"` | Last yanked text |
50−| `@` | Last recorded macro |
51−
52−### Special registers
53−
54−Some registers have special behavior when read from and written to.
47+| `_` | Black hole |
5548
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 |
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.
6451
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.
52+The black hole register is a no-op register, meaning that no data will be read or written to it.
6953
7054## Surround
7155
@@ -112,13 +96,13 @@ function or block of code.
11296| `(`, `[`, `'`, etc. | Specified surround pairs |
11397| `m` | The closest surround pair |
11498| `f` | Function |
115−| `t` | Type (or Class) |
99+| `c` | Class |
116100| `a` | Argument/parameter |
117−| `c` | Comment |
118−| `T` | Test |
101+| `o` | Comment |
102+| `t` | Test |
119103| `g` | Change |
120104
121−> 💡 `f`, `t`, etc. need a tree-sitter grammar active for the current
105+> 💡 `f`, `c`, etc. need a tree-sitter grammar active for the current
122106document and a special tree-sitter query file to work properly. [Only
123107some grammars][lang-support] currently have the query file implemented.
124108Contributions are welcome!
@@ -128,7 +112,7 @@ Contributions are welcome!
128112Navigating between functions, classes, parameters, and other elements is
129113possible using tree-sitter and textobject queries. For
130114example to move to the next function use `]f`, to move to previous
131−type use `[t`, and so on.
115+class use `[c`, and so on.
132116
133117![Tree-sitter-nav-demo][tree-sitter-nav-demo]
134118
book/theme/book.jsnew file+660
@@ -0,0 +1,660 @@
1+"use strict";
2+
3+// Fix back button cache problem
4+window.onunload = function () { };
5+
6+// Global variable, shared between modules
7+function playground_text(playground) {
8+ let code_block = playground.querySelector("code");
9+
10+ if (window.ace && code_block.classList.contains("editable")) {
11+ let editor = window.ace.edit(code_block);
12+ return editor.getValue();
13+ } else {
14+ return code_block.textContent;
15+ }
16+}
17+
18+(function codeSnippets() {
19+ function fetch_with_timeout(url, options, timeout = 6000) {
20+ return Promise.race([
21+ fetch(url, options),
22+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
23+ ]);
24+ }
25+
26+ var playgrounds = Array.from(document.querySelectorAll(".playground"));
27+ if (playgrounds.length > 0) {
28+ fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
29+ headers: {
30+ 'Content-Type': "application/json",
31+ },
32+ method: 'POST',
33+ mode: 'cors',
34+ })
35+ .then(response => response.json())
36+ .then(response => {
37+ // get list of crates available in the rust playground
38+ let playground_crates = response.crates.map(item => item["id"]);
39+ playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));
40+ });
41+ }
42+
43+ function handle_crate_list_update(playground_block, playground_crates) {
44+ // update the play buttons after receiving the response
45+ update_play_button(playground_block, playground_crates);
46+
47+ // and install on change listener to dynamically update ACE editors
48+ if (window.ace) {
49+ let code_block = playground_block.querySelector("code");
50+ if (code_block.classList.contains("editable")) {
51+ let editor = window.ace.edit(code_block);
52+ editor.addEventListener("change", function (e) {
53+ update_play_button(playground_block, playground_crates);
54+ });
55+ // add Ctrl-Enter command to execute rust code
56+ editor.commands.addCommand({
57+ name: "run",
58+ bindKey: {
59+ win: "Ctrl-Enter",
60+ mac: "Ctrl-Enter"
61+ },
62+ exec: _editor => run_rust_code(playground_block)
63+ });
64+ }
65+ }
66+ }
67+
68+ // updates the visibility of play button based on `no_run` class and
69+ // used crates vs ones available on http://play.rust-lang.org
70+ function update_play_button(pre_block, playground_crates) {
71+ var play_button = pre_block.querySelector(".play-button");
72+
73+ // skip if code is `no_run`
74+ if (pre_block.querySelector('code').classList.contains("no_run")) {
75+ play_button.classList.add("hidden");
76+ return;
77+ }
78+
79+ // get list of `extern crate`'s from snippet
80+ var txt = playground_text(pre_block);
81+ var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
82+ var snippet_crates = [];
83+ var item;
84+ while (item = re.exec(txt)) {
85+ snippet_crates.push(item[1]);
86+ }
87+
88+ // check if all used crates are available on play.rust-lang.org
89+ var all_available = snippet_crates.every(function (elem) {
90+ return playground_crates.indexOf(elem) > -1;
91+ });
92+
93+ if (all_available) {
94+ play_button.classList.remove("hidden");
95+ } else {
96+ play_button.classList.add("hidden");
97+ }
98+ }
99+
100+ function run_rust_code(code_block) {
101+ var result_block = code_block.querySelector(".result");
102+ if (!result_block) {
103+ result_block = document.createElement('code');
104+ result_block.className = 'result hljs language-bash';
105+
106+ code_block.append(result_block);
107+ }
108+
109+ let text = playground_text(code_block);
110+ let classes = code_block.querySelector('code').classList;
111+ let has_2018 = classes.contains("edition2018");
112+ let edition = has_2018 ? "2018" : "2015";
113+
114+ var params = {
115+ version: "stable",
116+ optimize: "0",
117+ code: text,
118+ edition: edition
119+ };
120+
121+ if (text.indexOf("#![feature") !== -1) {
122+ params.version = "nightly";
123+ }
124+
125+ result_block.innerText = "Running...";
126+
127+ fetch_with_timeout("https://play.rust-lang.org/evaluate.json", {
128+ headers: {
129+ 'Content-Type': "application/json",
130+ },
131+ method: 'POST',
132+ mode: 'cors',
133+ body: JSON.stringify(params)
134+ })
135+ .then(response => response.json())
136+ .then(response => result_block.innerText = response.result)
137+ .catch(error => result_block.innerText = "Playground Communication: " + error.message);
138+ }
139+
140+ // Syntax highlighting Configuration
141+ hljs.configure({
142+ tabReplace: ' ', // 4 spaces
143+ languages: [], // Languages used for auto-detection
144+ });
145+
146+ let code_nodes = Array
147+ .from(document.querySelectorAll('code'))
148+ // Don't highlight `inline code` blocks in headers.
149+ .filter(function (node) {return !node.parentElement.classList.contains("header"); });
150+
151+ if (window.ace) {
152+ // language-rust class needs to be removed for editable
153+ // blocks or highlightjs will capture events
154+ Array
155+ .from(document.querySelectorAll('code.editable'))
156+ .forEach(function (block) { block.classList.remove('language-rust'); });
157+
158+ Array
159+ .from(document.querySelectorAll('code:not(.editable)'))
160+ .forEach(function (block) { hljs.highlightBlock(block); });
161+ } else {
162+ code_nodes.forEach(function (block) { hljs.highlightBlock(block); });
163+ }
164+
165+ // Adding the hljs class gives code blocks the color css
166+ // even if highlighting doesn't apply
167+ code_nodes.forEach(function (block) { block.classList.add('hljs'); });
168+
169+ Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
170+
171+ var lines = Array.from(block.querySelectorAll('.boring'));
172+ // If no lines were hidden, return
173+ if (!lines.length) { return; }
174+ block.classList.add("hide-boring");
175+
176+ var buttons = document.createElement('div');
177+ buttons.className = 'buttons';
178+ buttons.innerHTML = "<button class=\"fa fa-eye\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
179+
180+ // add expand button
181+ var pre_block = block.parentNode;
182+ pre_block.insertBefore(buttons, pre_block.firstChild);
183+
184+ pre_block.querySelector('.buttons').addEventListener('click', function (e) {
185+ if (e.target.classList.contains('fa-eye')) {
186+ e.target.classList.remove('fa-eye');
187+ e.target.classList.add('fa-eye-slash');
188+ e.target.title = 'Hide lines';
189+ e.target.setAttribute('aria-label', e.target.title);
190+
191+ block.classList.remove('hide-boring');
192+ } else if (e.target.classList.contains('fa-eye-slash')) {
193+ e.target.classList.remove('fa-eye-slash');
194+ e.target.classList.add('fa-eye');
195+ e.target.title = 'Show hidden lines';
196+ e.target.setAttribute('aria-label', e.target.title);
197+
198+ block.classList.add('hide-boring');
199+ }
200+ });
201+ });
202+
203+ if (window.playground_copyable) {
204+ Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
205+ var pre_block = block.parentNode;
206+ if (!pre_block.classList.contains('playground')) {
207+ var buttons = pre_block.querySelector(".buttons");
208+ if (!buttons) {
209+ buttons = document.createElement('div');
210+ buttons.className = 'buttons';
211+ pre_block.insertBefore(buttons, pre_block.firstChild);
212+ }
213+
214+ var clipButton = document.createElement('button');
215+ clipButton.className = 'fa fa-copy clip-button';
216+ clipButton.title = 'Copy to clipboard';
217+ clipButton.setAttribute('aria-label', clipButton.title);
218+ clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
219+
220+ buttons.insertBefore(clipButton, buttons.firstChild);
221+ }
222+ });
223+ }
224+
225+ // Process playground code blocks
226+ Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) {
227+ // Add play button
228+ var buttons = pre_block.querySelector(".buttons");
229+ if (!buttons) {
230+ buttons = document.createElement('div');
231+ buttons.className = 'buttons';
232+ pre_block.insertBefore(buttons, pre_block.firstChild);
233+ }
234+
235+ var runCodeButton = document.createElement('button');
236+ runCodeButton.className = 'fa fa-play play-button';
237+ runCodeButton.hidden = true;
238+ runCodeButton.title = 'Run this code';
239+ runCodeButton.setAttribute('aria-label', runCodeButton.title);
240+
241+ buttons.insertBefore(runCodeButton, buttons.firstChild);
242+ runCodeButton.addEventListener('click', function (e) {
243+ run_rust_code(pre_block);
244+ });
245+
246+ if (window.playground_copyable) {
247+ var copyCodeClipboardButton = document.createElement('button');
248+ copyCodeClipboardButton.className = 'fa fa-copy clip-button';
249+ copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
250+ copyCodeClipboardButton.title = 'Copy to clipboard';
251+ copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
252+
253+ buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
254+ }
255+
256+ let code_block = pre_block.querySelector("code");
257+ if (window.ace && code_block.classList.contains("editable")) {
258+ var undoChangesButton = document.createElement('button');
259+ undoChangesButton.className = 'fa fa-history reset-button';
260+ undoChangesButton.title = 'Undo changes';
261+ undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
262+
263+ buttons.insertBefore(undoChangesButton, buttons.firstChild);
264+
265+ undoChangesButton.addEventListener('click', function () {
266+ let editor = window.ace.edit(code_block);
267+ editor.setValue(editor.originalCode);
268+ editor.clearSelection();
269+ });
270+ }
271+ });
272+})();
273+
274+(function themes() {
275+ var html = document.querySelector('html');
276+ var themeToggleButton = document.getElementById('theme-toggle');
277+ var themePopup = document.getElementById('theme-list');
278+ var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
279+ var stylesheets = {
280+ ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
281+ tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
282+ highlight: document.querySelector("[href$='highlight.css']"),
283+ };
284+
285+ function showThemes() {
286+ themePopup.style.display = 'block';
287+ themeToggleButton.setAttribute('aria-expanded', true);
288+ themePopup.querySelector("button#" + get_theme()).focus();
289+ }
290+
291+ function hideThemes() {
292+ themePopup.style.display = 'none';
293+ themeToggleButton.setAttribute('aria-expanded', false);
294+ themeToggleButton.focus();
295+ }
296+
297+ function get_theme() {
298+ var theme;
299+ try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
300+ if (theme === null || theme === undefined) {
301+ return default_theme;
302+ } else {
303+ return theme;
304+ }
305+ }
306+
307+ function set_theme(theme, store = true) {
308+ let ace_theme;
309+
310+ if (theme == 'coal' || theme == 'navy') {
311+ stylesheets.ayuHighlight.disabled = true;
312+ stylesheets.tomorrowNight.disabled = false;
313+ stylesheets.highlight.disabled = true;
314+
315+ ace_theme = "ace/theme/tomorrow_night";
316+ } else if (theme == 'ayu') {
317+ stylesheets.ayuHighlight.disabled = false;
318+ stylesheets.tomorrowNight.disabled = true;
319+ stylesheets.highlight.disabled = true;
320+ ace_theme = "ace/theme/tomorrow_night";
321+ } else {
322+ stylesheets.ayuHighlight.disabled = true;
323+ stylesheets.tomorrowNight.disabled = true;
324+ stylesheets.highlight.disabled = false;
325+ ace_theme = "ace/theme/dawn";
326+ }
327+
328+ setTimeout(function () {
329+ themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;
330+ }, 1);
331+
332+ if (window.ace && window.editors) {
333+ window.editors.forEach(function (editor) {
334+ editor.setTheme(ace_theme);
335+ });
336+ }
337+
338+ var previousTheme = get_theme();
339+
340+ if (store) {
341+ try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
342+ }
343+
344+ html.classList.remove(previousTheme);
345+ html.classList.add(theme);
346+ }
347+
348+ // Set theme
349+ var theme = get_theme();
350+
351+ set_theme(theme, false);
352+
353+ themeToggleButton.addEventListener('click', function () {
354+ if (themePopup.style.display === 'block') {
355+ hideThemes();
356+ } else {
357+ showThemes();
358+ }
359+ });
360+
361+ themePopup.addEventListener('click', function (e) {
362+ var theme = e.target.id || e.target.parentElement.id;
363+ set_theme(theme);
364+ });
365+
366+ themePopup.addEventListener('focusout', function(e) {
367+ // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
368+ if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
369+ hideThemes();
370+ }
371+ });
372+
373+ // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
374+ document.addEventListener('click', function(e) {
375+ if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
376+ hideThemes();
377+ }
378+ });
379+
380+ document.addEventListener('keydown', function (e) {
381+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
382+ if (!themePopup.contains(e.target)) { return; }
383+
384+ switch (e.key) {
385+ case 'Escape':
386+ e.preventDefault();
387+ hideThemes();
388+ break;
389+ case 'ArrowUp':
390+ e.preventDefault();
391+ var li = document.activeElement.parentElement;
392+ if (li && li.previousElementSibling) {
393+ li.previousElementSibling.querySelector('button').focus();
394+ }
395+ break;
396+ case 'ArrowDown':
397+ e.preventDefault();
398+ var li = document.activeElement.parentElement;
399+ if (li && li.nextElementSibling) {
400+ li.nextElementSibling.querySelector('button').focus();
401+ }
402+ break;
403+ case 'Home':
404+ e.preventDefault();
405+ themePopup.querySelector('li:first-child button').focus();
406+ break;
407+ case 'End':
408+ e.preventDefault();
409+ themePopup.querySelector('li:last-child button').focus();
410+ break;
411+ }
412+ });
413+})();
414+
415+(function sidebar() {
416+ var html = document.querySelector("html");
417+ var sidebar = document.getElementById("sidebar");
418+ var sidebarLinks = document.querySelectorAll('#sidebar a');
419+ var sidebarToggleButton = document.getElementById("sidebar-toggle");
420+ var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
421+ var firstContact = null;
422+
423+ function showSidebar() {
424+ html.classList.remove('sidebar-hidden')
425+ html.classList.add('sidebar-visible');
426+ Array.from(sidebarLinks).forEach(function (link) {
427+ link.setAttribute('tabIndex', 0);
428+ });
429+ sidebarToggleButton.setAttribute('aria-expanded', true);
430+ sidebar.setAttribute('aria-hidden', false);
431+ try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
432+ }
433+
434+
435+ var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
436+
437+ function toggleSection(ev) {
438+ ev.currentTarget.parentElement.classList.toggle('expanded');
439+ }
440+
441+ Array.from(sidebarAnchorToggles).forEach(function (el) {
442+ el.addEventListener('click', toggleSection);
443+ });
444+
445+ function hideSidebar() {
446+ html.classList.remove('sidebar-visible')
447+ html.classList.add('sidebar-hidden');
448+ Array.from(sidebarLinks).forEach(function (link) {
449+ link.setAttribute('tabIndex', -1);
450+ });
451+ sidebarToggleButton.setAttribute('aria-expanded', false);
452+ sidebar.setAttribute('aria-hidden', true);
453+ try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
454+ }
455+
456+ // Toggle sidebar
457+ sidebarToggleButton.addEventListener('click', function sidebarToggle() {
458+ if (html.classList.contains("sidebar-hidden")) {
459+ var current_width = parseInt(
460+ document.documentElement.style.getPropertyValue('--sidebar-width'), 10);
461+ if (current_width < 150) {
462+ document.documentElement.style.setProperty('--sidebar-width', '150px');
463+ }
464+ showSidebar();
465+ } else if (html.classList.contains("sidebar-visible")) {
466+ hideSidebar();
467+ } else {
468+ if (getComputedStyle(sidebar)['transform'] === 'none') {
469+ hideSidebar();
470+ } else {
471+ showSidebar();
472+ }
473+ }
474+ });
475+
476+ sidebarResizeHandle.addEventListener('mousedown', initResize, false);
477+
478+ function initResize(e) {
479+ window.addEventListener('mousemove', resize, false);
480+ window.addEventListener('mouseup', stopResize, false);
481+ html.classList.add('sidebar-resizing');
482+ }
483+ function resize(e) {
484+ var pos = (e.clientX - sidebar.offsetLeft);
485+ if (pos < 20) {
486+ hideSidebar();
487+ } else {
488+ if (html.classList.contains("sidebar-hidden")) {
489+ showSidebar();
490+ }
491+ pos = Math.min(pos, window.innerWidth - 100);
492+ document.documentElement.style.setProperty('--sidebar-width', pos + 'px');
493+ }
494+ }
495+ //on mouseup remove windows functions mousemove & mouseup
496+ function stopResize(e) {
497+ html.classList.remove('sidebar-resizing');
498+ window.removeEventListener('mousemove', resize, false);
499+ window.removeEventListener('mouseup', stopResize, false);
500+ }
501+
502+ document.addEventListener('touchstart', function (e) {
503+ firstContact = {
504+ x: e.touches[0].clientX,
505+ time: Date.now()
506+ };
507+ }, { passive: true });
508+
509+ document.addEventListener('touchmove', function (e) {
510+ if (!firstContact)
511+ return;
512+
513+ var curX = e.touches[0].clientX;
514+ var xDiff = curX - firstContact.x,
515+ tDiff = Date.now() - firstContact.time;
516+
517+ if (tDiff < 250 && Math.abs(xDiff) >= 150) {
518+ if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
519+ showSidebar();
520+ else if (xDiff < 0 && curX < 300)
521+ hideSidebar();
522+
523+ firstContact = null;
524+ }
525+ }, { passive: true });
526+
527+ // Scroll sidebar to current active section
528+ var activeSection = document.getElementById("sidebar").querySelector(".active");
529+ if (activeSection) {
530+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
531+ activeSection.scrollIntoView({ block: 'center' });
532+ }
533+})();
534+
535+(function chapterNavigation() {
536+ document.addEventListener('keydown', function (e) {
537+ if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
538+ if (window.search && window.search.hasFocus()) { return; }
539+
540+ switch (e.key) {
541+ case 'ArrowRight':
542+ e.preventDefault();
543+ var nextButton = document.querySelector('.nav-chapters.next');
544+ if (nextButton) {
545+ window.location.href = nextButton.href;
546+ }
547+ break;
548+ case 'ArrowLeft':
549+ e.preventDefault();
550+ var previousButton = document.querySelector('.nav-chapters.previous');
551+ if (previousButton) {
552+ window.location.href = previousButton.href;
553+ }
554+ break;
555+ }
556+ });
557+})();
558+
559+(function clipboard() {
560+ var clipButtons = document.querySelectorAll('.clip-button');
561+
562+ function hideTooltip(elem) {
563+ elem.firstChild.innerText = "";
564+ elem.className = 'fa fa-copy clip-button';
565+ }
566+
567+ function showTooltip(elem, msg) {
568+ elem.firstChild.innerText = msg;
569+ elem.className = 'fa fa-copy tooltipped';
570+ }
571+
572+ var clipboardSnippets = new ClipboardJS('.clip-button', {
573+ text: function (trigger) {
574+ hideTooltip(trigger);
575+ let playground = trigger.closest("pre");
576+ return playground_text(playground);
577+ }
578+ });
579+
580+ Array.from(clipButtons).forEach(function (clipButton) {
581+ clipButton.addEventListener('mouseout', function (e) {
582+ hideTooltip(e.currentTarget);
583+ });
584+ });
585+
586+ clipboardSnippets.on('success', function (e) {
587+ e.clearSelection();
588+ showTooltip(e.trigger, "Copied!");
589+ });
590+
591+ clipboardSnippets.on('error', function (e) {
592+ showTooltip(e.trigger, "Clipboard error!");
593+ });
594+})();
595+
596+(function scrollToTop () {
597+ var menuTitle = document.querySelector('.menu-title');
598+
599+ menuTitle.addEventListener('click', function () {
600+ document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
601+ });
602+})();
603+
604+(function controlMenu() {
605+ var menu = document.getElementById('menu-bar');
606+
607+ (function controlPosition() {
608+ var scrollTop = document.scrollingElement.scrollTop;
609+ var prevScrollTop = scrollTop;
610+ var minMenuY = -menu.clientHeight - 50;
611+ // When the script loads, the page can be at any scroll (e.g. if you reforesh it).
612+ menu.style.top = scrollTop + 'px';
613+ // Same as parseInt(menu.style.top.slice(0, -2), but faster
614+ var topCache = menu.style.top.slice(0, -2);
615+ menu.classList.remove('sticky');
616+ var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster
617+ document.addEventListener('scroll', function () {
618+ scrollTop = Math.max(document.scrollingElement.scrollTop, 0);
619+ // `null` means that it doesn't need to be updated
620+ var nextSticky = null;
621+ var nextTop = null;
622+ var scrollDown = scrollTop > prevScrollTop;
623+ var menuPosAbsoluteY = topCache - scrollTop;
624+ if (scrollDown) {
625+ nextSticky = false;
626+ if (menuPosAbsoluteY > 0) {
627+ nextTop = prevScrollTop;
628+ }
629+ } else {
630+ if (menuPosAbsoluteY > 0) {
631+ nextSticky = true;
632+ } else if (menuPosAbsoluteY < minMenuY) {
633+ nextTop = prevScrollTop + minMenuY;
634+ }
635+ }
636+ if (nextSticky === true && stickyCache === false) {
637+ menu.classList.add('sticky');
638+ stickyCache = true;
639+ } else if (nextSticky === false && stickyCache === true) {
640+ menu.classList.remove('sticky');
641+ stickyCache = false;
642+ }
643+ if (nextTop !== null) {
644+ menu.style.top = nextTop + 'px';
645+ topCache = nextTop;
646+ }
647+ prevScrollTop = scrollTop;
648+ }, { passive: true });
649+ })();
650+ (function controlBorder() {
651+ menu.classList.remove('bordered');
652+ document.addEventListener('scroll', function () {
653+ if (menu.offsetTop === 0) {
654+ menu.classList.remove('bordered');
655+ } else {
656+ menu.classList.add('bordered');
657+ }
658+ }, { passive: true });
659+ })();
660+})();
book/theme/css/chrome.cssnew file+499
@@ -0,0 +1,499 @@
1+/* CSS for UI elements (a.k.a. chrome) */
2+
3+@import 'variables.css';
4+
5+::-webkit-scrollbar {
6+ background: var(--bg);
7+}
8+::-webkit-scrollbar-thumb {
9+ background: var(--scrollbar);
10+}
11+html {
12+ scrollbar-color: var(--scrollbar) var(--bg);
13+}
14+#searchresults a,
15+.content a:link,
16+a:visited,
17+a > .hljs {
18+ color: var(--links);
19+}
20+.content a:hover {
21+ text-decoration: underline;
22+}
23+
24+/* Menu Bar */
25+
26+#menu-bar,
27+#menu-bar-hover-placeholder {
28+ z-index: 101;
29+ margin: auto calc(0px - var(--page-padding));
30+}
31+#menu-bar {
32+ position: relative;
33+ display: flex;
34+ flex-wrap: wrap;
35+ background-color: var(--bg);
36+ border-bottom-color: var(--bg);
37+ border-bottom-width: 1px;
38+ border-bottom-style: solid;
39+}
40+#menu-bar.sticky,
41+.js #menu-bar-hover-placeholder:hover + #menu-bar,
42+.js #menu-bar:hover,
43+.js.sidebar-visible #menu-bar {
44+ position: -webkit-sticky;
45+ position: sticky;
46+ top: 0 !important;
47+}
48+#menu-bar-hover-placeholder {
49+ position: sticky;
50+ position: -webkit-sticky;
51+ top: 0;
52+ height: var(--menu-bar-height);
53+}
54+#menu-bar.bordered {
55+ border-bottom-color: var(--table-border-color);
56+}
57+#menu-bar i, #menu-bar .icon-button {
58+ position: relative;
59+ padding: 0 8px;
60+ z-index: 10;
61+ line-height: var(--menu-bar-height);
62+ cursor: pointer;
63+ transition: color 0.5s;
64+}
65+@media only screen and (max-width: 420px) {
66+ #menu-bar i, #menu-bar .icon-button {
67+ padding: 0 5px;
68+ }
69+}
70+
71+.icon-button {
72+ border: none;
73+ background: none;
74+ padding: 0;
75+ color: inherit;
76+}
77+.icon-button i {
78+ margin: 0;
79+}
80+
81+.right-buttons {
82+ margin: 0 15px;
83+}
84+.right-buttons a {
85+ text-decoration: none;
86+}
87+
88+.left-buttons {
89+ display: flex;
90+ margin: 0 5px;
91+}
92+.no-js .left-buttons {
93+ display: none;
94+}
95+
96+.menu-title {
97+ display: inline-block;
98+ font-weight: 200;
99+ font-size: 2.4rem;
100+ line-height: var(--menu-bar-height);
101+ text-align: center;
102+ margin: 0;
103+ flex: 1;
104+ white-space: nowrap;
105+ overflow: hidden;
106+ text-overflow: ellipsis;
107+}
108+.js .menu-title {
109+ cursor: pointer;
110+}
111+
112+.menu-bar,
113+.menu-bar:visited,
114+.nav-chapters,
115+.nav-chapters:visited,
116+.mobile-nav-chapters,
117+.mobile-nav-chapters:visited,
118+.menu-bar .icon-button,
119+.menu-bar a i {
120+ color: var(--icons);
121+}
122+
123+.menu-bar i:hover,
124+.menu-bar .icon-button:hover,
125+.nav-chapters:hover,
126+.mobile-nav-chapters i:hover {
127+ color: var(--icons-hover);
128+}
129+
130+/* Nav Icons */
131+
132+.nav-chapters {
133+ font-size: 2.5em;
134+ text-align: center;
135+ text-decoration: none;
136+
137+ position: fixed;
138+ top: 0;
139+ bottom: 0;
140+ margin: 0;
141+ max-width: 150px;
142+ min-width: 90px;
143+
144+ display: flex;
145+ justify-content: center;
146+ align-content: center;
147+ flex-direction: column;
148+
149+ transition: color 0.5s, background-color 0.5s;
150+}
151+
152+.nav-chapters:hover {
153+ text-decoration: none;
154+ background-color: var(--theme-hover);
155+ transition: background-color 0.15s, color 0.15s;
156+}
157+
158+.nav-wrapper {
159+ margin-top: 50px;
160+ display: none;
161+}
162+
163+.mobile-nav-chapters {
164+ font-size: 2.5em;
165+ text-align: center;
166+ text-decoration: none;
167+ width: 90px;
168+ border-radius: 5px;
169+ background-color: var(--sidebar-bg);
170+}
171+
172+.previous {
173+ float: left;
174+}
175+
176+.next {
177+ float: right;
178+ right: var(--page-padding);
179+}
180+
181+@media only screen and (max-width: 1080px) {
182+ .nav-wide-wrapper { display: none; }
183+ .nav-wrapper { display: block; }
184+}
185+
186+@media only screen and (max-width: 1380px) {
187+ .sidebar-visible .nav-wide-wrapper { display: none; }
188+ .sidebar-visible .nav-wrapper { display: block; }
189+}
190+
191+/* Inline code */
192+
193+:not(pre) > .hljs {
194+ display: inline;
195+ padding: 0.1em 0.3em;
196+ border-radius: 3px;
197+}
198+
199+:not(pre):not(a):not(td):not(p) > .hljs {
200+ color: var(--inline-code-color);
201+ overflow-x: initial;
202+}
203+
204+a:hover > .hljs {
205+ text-decoration: underline;
206+}
207+
208+pre {
209+ position: relative;
210+}
211+pre > .buttons {
212+ position: absolute;
213+ z-index: 100;
214+ right: 5px;
215+ top: 5px;
216+
217+ color: var(--sidebar-fg);
218+ cursor: pointer;
219+}
220+pre > .buttons :hover {
221+ color: var(--sidebar-active);
222+}
223+pre > .buttons i {
224+ margin-left: 8px;
225+}
226+pre > .buttons button {
227+ color: inherit;
228+ background: transparent;
229+ border: none;
230+ cursor: inherit;
231+}
232+pre > .result {
233+ margin-top: 10px;
234+}
235+
236+/* Search */
237+
238+#searchresults a {
239+ text-decoration: none;
240+}
241+
242+mark {
243+ border-radius: 2px;
244+ padding: 0 3px 1px 3px;
245+ margin: 0 -3px -1px -3px;
246+ background-color: var(--search-mark-bg);
247+ transition: background-color 300ms linear;
248+ cursor: pointer;
249+}
250+
251+mark.fade-out {
252+ background-color: rgba(0,0,0,0) !important;
253+ cursor: auto;
254+}
255+
256+.searchbar-outer {
257+ margin-left: auto;
258+ margin-right: auto;
259+ max-width: var(--content-max-width);
260+}
261+
262+#searchbar {
263+ width: 100%;
264+ margin: 5px auto 0px auto;
265+ padding: 10px 16px;
266+ transition: box-shadow 300ms ease-in-out;
267+ border: 1px solid var(--searchbar-border-color);
268+ border-radius: 3px;
269+ background-color: var(--searchbar-bg);
270+ color: var(--searchbar-fg);
271+}
272+#searchbar:focus,
273+#searchbar.active {
274+ box-shadow: 0 0 3px var(--searchbar-shadow-color);
275+}
276+
277+.searchresults-header {
278+ font-weight: bold;
279+ font-size: 1em;
280+ padding: 18px 0 0 5px;
281+ color: var(--searchresults-header-fg);
282+}
283+
284+.searchresults-outer {
285+ margin-left: auto;
286+ margin-right: auto;
287+ max-width: var(--content-max-width);
288+ border-bottom: 1px dashed var(--searchresults-border-color);
289+}
290+
291+ul#searchresults {
292+ list-style: none;
293+ padding-left: 20px;
294+}
295+ul#searchresults li {
296+ margin: 10px 0px;
297+ padding: 2px;
298+ border-radius: 2px;
299+}
300+ul#searchresults li.focus {
301+ background-color: var(--searchresults-li-bg);
302+}
303+ul#searchresults span.teaser {
304+ display: block;
305+ clear: both;
306+ margin: 5px 0 0 20px;
307+ font-size: 0.8em;
308+}
309+ul#searchresults span.teaser em {
310+ font-weight: bold;
311+ font-style: normal;
312+}
313+
314+/* Sidebar */
315+
316+.sidebar {
317+ position: fixed;
318+ left: 0;
319+ top: 0;
320+ bottom: 0;
321+ width: var(--sidebar-width);
322+ font-size: 0.875em;
323+ box-sizing: border-box;
324+ -webkit-overflow-scrolling: touch;
325+ overscroll-behavior-y: contain;
326+ background-color: var(--sidebar-bg);
327+ color: var(--sidebar-fg);
328+}
329+.sidebar-resizing {
330+ -moz-user-select: none;
331+ -webkit-user-select: none;
332+ -ms-user-select: none;
333+ user-select: none;
334+}
335+.js:not(.sidebar-resizing) .sidebar {
336+ transition: transform 0.3s; /* Animation: slide away */
337+}
338+.sidebar code {
339+ line-height: 2em;
340+}
341+.sidebar .sidebar-scrollbox {
342+ overflow-y: auto;
343+ position: absolute;
344+ top: 0;
345+ bottom: 0;
346+ left: 0;
347+ right: 0;
348+}
349+.sidebar .sidebar-resize-handle {
350+ position: absolute;
351+ cursor: col-resize;
352+ width: 0;
353+ right: 0;
354+ top: 0;
355+ bottom: 0;
356+}
357+.js .sidebar .sidebar-resize-handle {
358+ cursor: col-resize;
359+ width: 5px;
360+}
361+.sidebar-hidden .sidebar {
362+ transform: translateX(calc(0px - var(--sidebar-width)));
363+}
364+.sidebar::-webkit-scrollbar {
365+ background: var(--sidebar-bg);
366+}
367+.sidebar::-webkit-scrollbar-thumb {
368+ background: var(--scrollbar);
369+}
370+
371+.sidebar-visible .page-wrapper {
372+ transform: translateX(var(--sidebar-width));
373+}
374+@media only screen and (min-width: 620px) {
375+ .sidebar-visible .page-wrapper {
376+ transform: none;
377+ margin-left: var(--sidebar-width);
378+ }
379+}
380+
381+.chapter {
382+ list-style: none outside none;
383+ padding-left: 0;
384+ margin: .25rem 0;
385+}
386+
387+.chapter ol {
388+ width: 100%;
389+}
390+
391+.chapter li {
392+ display: flex;
393+ color: var(--sidebar-non-existent);
394+}
395+.chapter li a {
396+ display: block;
397+ text-decoration: none;
398+ color: var(--sidebar-fg);
399+}
400+
401+.chapter li a:hover {
402+ color: var(--sidebar-active);
403+}
404+
405+.chapter li a.active {
406+ color: var(--sidebar-active);
407+}
408+
409+.chapter li > a.toggle {
410+ cursor: pointer;
411+ display: block;
412+ margin-left: auto;
413+ padding: 0 10px;
414+ user-select: none;
415+ opacity: 0.68;
416+}
417+
418+.chapter li > a.toggle div {
419+ transition: transform 0.5s;
420+}
421+
422+/* collapse the section */
423+.chapter li:not(.expanded) + li > ol {
424+ display: none;
425+}
426+
427+.chapter li.chapter-item {
428+ padding: 1rem 1.5rem;
429+}
430+
431+.chapter .section li.chapter-item {
432+ padding: .5rem .5rem 0 .5rem;
433+}
434+
435+.chapter li.expanded > a.toggle div {
436+ transform: rotate(90deg);
437+}
438+
439+.spacer {
440+ width: 100%;
441+ height: 3px;
442+ margin: 5px 0px;
443+}
444+.chapter .spacer {
445+ background-color: var(--sidebar-spacer);
446+}
447+
448+@media (-moz-touch-enabled: 1), (pointer: coarse) {
449+ .chapter li a { padding: 5px 0; }
450+ .spacer { margin: 10px 0; }
451+}
452+
453+.section {
454+ list-style: none outside none;
455+ padding-left: 2rem;
456+ line-height: 1.9em;
457+}
458+
459+/* Theme Menu Popup */
460+
461+.theme-popup {
462+ position: absolute;
463+ left: 10px;
464+ top: var(--menu-bar-height);
465+ z-index: 1000;
466+ border-radius: 4px;
467+ font-size: 0.7em;
468+ color: var(--fg);
469+ background: var(--theme-popup-bg);
470+ border: 1px solid var(--theme-popup-border);
471+ margin: 0;
472+ padding: 0;
473+ list-style: none;
474+ display: none;
475+}
476+.theme-popup .default {
477+ color: var(--icons);
478+}
479+.theme-popup .theme {
480+ width: 100%;
481+ border: 0;
482+ margin: 0;
483+ padding: 2px 10px;
484+ line-height: 25px;
485+ white-space: nowrap;
486+ text-align: left;
487+ cursor: pointer;
488+ color: inherit;
489+ background: inherit;
490+ font-size: inherit;
491+}
492+.theme-popup .theme:hover {
493+ background-color: var(--theme-hover);
494+}
495+.theme-popup .theme:hover:first-child,
496+.theme-popup .theme:hover:last-child {
497+ border-top-left-radius: inherit;
498+ border-top-right-radius: inherit;
499+}
book/theme/css/general.cssnew file+233
@@ -0,0 +1,233 @@
1+/* Base styles and content styles */
2+
3+@import 'variables.css';
4+
5+:root {
6+ /* Browser default font-size is 16px, this way 1 rem = 10px */
7+ font-size: 62.5%;
8+}
9+
10+/* TODO: replace with self hosted fonts */
11+
12+html {
13+ font-family: "Inter", sans-serif;
14+ color: var(--fg);
15+ background-color: var(--bg);
16+ text-size-adjust: none;
17+}
18+
19+/* @supports (font-variation-settings: normal) { */
20+/* html { font-family: 'Inter var', sans-serif; } */
21+/* } */
22+
23+body {
24+ margin: 0;
25+ font-size: 1.6rem;
26+ overflow-x: hidden;
27+}
28+
29+code {
30+ font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
31+ font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
32+}
33+
34+/* Don't change font size in headers. */
35+h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
36+ font-size: unset;
37+}
38+
39+.left { float: left; }
40+.right { float: right; }
41+.boring { opacity: 0.6; }
42+.hide-boring .boring { display: none; }
43+.hidden { display: none !important; }
44+
45+h2, h3 { margin-top: 2.5em; }
46+h4, h5 { margin-top: 2em; }
47+
48+.header + .header h3,
49+.header + .header h4,
50+.header + .header h5 {
51+ margin-top: 1em;
52+}
53+
54+h1:target::before,
55+h2:target::before,
56+h3:target::before,
57+h4:target::before,
58+h5:target::before,
59+h6:target::before {
60+ display: inline-block;
61+ content: "»";
62+ margin-left: -30px;
63+ width: 30px;
64+}
65+
66+/* This is broken on Safari as of version 14, but is fixed
67+ in Safari Technology Preview 117 which I think will be Safari 14.2.
68+ https://bugs.webkit.org/show_bug.cgi?id=218076
69+*/
70+:target {
71+ scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
72+}
73+
74+.page {
75+ outline: 0;
76+ padding: 0 var(--page-padding);
77+ margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */
78+}
79+.page-wrapper {
80+ box-sizing: border-box;
81+}
82+.js:not(.sidebar-resizing) .page-wrapper {
83+ transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */
84+}
85+
86+.content {
87+ overflow-y: auto;
88+ padding: 0 15px;
89+ padding-bottom: 50px;
90+}
91+.content main {
92+ margin-left: auto;
93+ margin-right: auto;
94+ max-width: var(--content-max-width);
95+}
96+
97+
98+/* 2 1.75 1.5 1.25 1 .875 */
99+
100+.content h1 { font-size: 2em }
101+.content h2 { font-size: 1.75em }
102+.content h3 { font-size: 1.5em }
103+.content h4 { font-size: 1.25em }
104+.content h5 { font-size: 1em }
105+.content h6 { font-size: .875em }
106+
107+.content h1, .content h2, .content h3, .content h4 {
108+ font-weight: 500;
109+ margin-top: 1.275em;
110+ margin-bottom: .875em;
111+}
112+.content p, .content ol, .content ul, .content table {
113+ margin-top: 0;
114+ margin-bottom: .875em;
115+}
116+
117+.content ul li {
118+margin-bottom: .25rem;
119+}
120+.content ul {
121+ list-style-type: square;
122+}
123+.content ul ul, .content ol ul {
124+ margin-bottom: .5rem;
125+}
126+.content li p {
127+ margin-bottom: .5em;
128+}
129+
130+.content p { line-height: 1.45em; }
131+.content ol { line-height: 1.45em; }
132+.content ul { line-height: 1.45em; }
133+.content a { text-decoration: none; }
134+.content a:hover { text-decoration: underline; }
135+.content img { max-width: 100%; }
136+.content .header:link,
137+.content .header:visited {
138+ color: var(--fg);
139+ color: var(--heading-fg);
140+}
141+.content .header:link,
142+.content .header:visited:hover {
143+ text-decoration: none;
144+}
145+
146+table {
147+ margin: 0 auto;
148+ border-collapse: collapse;
149+ width: 100%;
150+}
151+table td {
152+ padding: .75rem;
153+ width: auto;
154+}
155+table thead {
156+ background: var(--table-header-bg);
157+}
158+table thead td {
159+ font-weight: 700;
160+ border: none;
161+}
162+table thead th {
163+ padding: .75rem;
164+ text-align: left;
165+ font-weight: 500;
166+ line-height: 1.5;
167+ width: auto;
168+}
169+table thead tr {
170+ border-bottom: 2px var(--table-border-color) solid;
171+}
172+table tbody tr {
173+ border-bottom: 1px var(--table-border-line) solid;
174+}
175+/* Alternate background colors for rows */
176+table tbody tr:nth-child(2n) {
177+ /* background: var(--table-alternate-bg); */
178+}
179+
180+
181+blockquote {
182+ margin: 1.5rem 0;
183+ padding: 1rem 1.5rem;
184+ color: var(--fg);
185+ opacity: .9;
186+ background-color: var(--quote-bg);
187+ border-left: 4px solid var(--quote-border);
188+}
189+blockquote *:last-child {
190+ margin-bottom: 0;
191+}
192+
193+
194+:not(.footnote-definition) + .footnote-definition,
195+.footnote-definition + :not(.footnote-definition) {
196+ margin-top: 2em;
197+}
198+.footnote-definition {
199+ font-size: 0.9em;
200+ margin: 0.5em 0;
201+}
202+.footnote-definition p {
203+ display: inline;
204+}
205+
206+.tooltiptext {
207+ position: absolute;
208+ visibility: hidden;
209+ color: #fff;
210+ background-color: #333;
211+ transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */
212+ left: -8px; /* Half of the width of the icon */
213+ top: -35px;
214+ font-size: 0.8em;
215+ text-align: center;
216+ border-radius: 6px;
217+ padding: 5px 8px;
218+ margin: 5px;
219+ z-index: 1000;
220+}
221+.tooltipped .tooltiptext {
222+ visibility: visible;
223+}
224+
225+.chapter li.part-title {
226+ color: var(--sidebar-fg);
227+ margin: 5px 0px;
228+ font-weight: bold;
229+}
230+
231+.result-no-output {
232+ font-style: italic;
233+}
book/theme/css/print.cssnew file+54
@@ -0,0 +1,54 @@
1+
2+#sidebar,
3+#menu-bar,
4+.nav-chapters,
5+.mobile-nav-chapters {
6+ display: none;
7+}
8+
9+#page-wrapper.page-wrapper {
10+ transform: none;
11+ margin-left: 0px;
12+ overflow-y: initial;
13+}
14+
15+#content {
16+ max-width: none;
17+ margin: 0;
18+ padding: 0;
19+}
20+
21+.page {
22+ overflow-y: initial;
23+}
24+
25+code {
26+ background-color: #666666;
27+ border-radius: 5px;
28+
29+ /* Force background to be printed in Chrome */
30+ -webkit-print-color-adjust: exact;
31+}
32+
33+pre > .buttons {
34+ z-index: 2;
35+}
36+
37+a, a:visited, a:active, a:hover {
38+ color: #4183c4;
39+ text-decoration: none;
40+}
41+
42+h1, h2, h3, h4, h5, h6 {
43+ page-break-inside: avoid;
44+ page-break-after: avoid;
45+}
46+
47+pre, code {
48+ page-break-inside: avoid;
49+ white-space: pre-wrap;
50+}
51+
52+.fa {
53+ display: none !important;
54+}
book/theme/css/variables.cssnew file+411
@@ -0,0 +1,411 @@
1+
2+/* Globals */
3+
4+:root {
5+ --sidebar-width: 300px;
6+ --page-padding: 15px;
7+ --content-max-width: 750px;
8+ --menu-bar-height: 50px;
9+}
10+
11+/* Themes */
12+
13+.ayu {
14+ --bg: hsl(210, 25%, 8%);
15+ --fg: #c5c5c5;
16+
17+ --sidebar-bg: #14191f;
18+ --sidebar-fg: #c8c9db;
19+ --sidebar-non-existent: #5c6773;
20+ --sidebar-active: #ffb454;
21+ --sidebar-spacer: #2d334f;
22+
23+ --scrollbar: var(--sidebar-fg);
24+
25+ --icons: #737480;
26+ --icons-hover: #b7b9cc;
27+
28+ --links: #0096cf;
29+
30+ --inline-code-color: #ffb454;
31+
32+ --theme-popup-bg: #14191f;
33+ --theme-popup-border: #5c6773;
34+ --theme-hover: #191f26;
35+
36+ --quote-bg: hsl(226, 15%, 17%);
37+ --quote-border: hsl(226, 15%, 22%);
38+
39+ --table-border-color: hsl(210, 25%, 13%);
40+ --table-header-bg: hsl(210, 25%, 28%);
41+ --table-alternate-bg: hsl(210, 25%, 11%);
42+
43+ --searchbar-border-color: #848484;
44+ --searchbar-bg: #424242;
45+ --searchbar-fg: #fff;
46+ --searchbar-shadow-color: #d4c89f;
47+ --searchresults-header-fg: #666;
48+ --searchresults-border-color: #888;
49+ --searchresults-li-bg: #252932;
50+ --search-mark-bg: #e3b171;
51+ --hljs-background: #191f26;
52+ --hljs-color: #e6e1cf;
53+ --hljs-quote: #5c6773;
54+ --hljs-variable: #ff7733;
55+ --hljs-type: #ffee99;
56+ --hljs-title: #b8cc52;
57+ --hljs-symbol: #ffb454;
58+ --hljs-selector-tag: #ff7733;
59+ --hljs-selector-tag: #36a3d9;
60+ --hljs-selector-tag: #00568d;
61+ --hljs-selector-tag: #91b362;
62+ --hljs-selector-tag: #d96c75;
63+}
64+
65+.coal {
66+ --bg: hsl(200, 7%, 8%);
67+ --fg: #98a3ad;
68+
69+ --sidebar-bg: #292c2f;
70+ --sidebar-fg: #a1adb8;
71+ --sidebar-non-existent: #505254;
72+ --sidebar-active: #3473ad;
73+ --sidebar-spacer: #393939;
74+
75+ --scrollbar: var(--sidebar-fg);
76+
77+ --icons: #43484d;
78+ --icons-hover: #b3c0cc;
79+
80+ --links: #2b79a2;
81+
82+ --inline-code-color: #c5c8c6;
83+
84+ --theme-popup-bg: #141617;
85+ --theme-popup-border: #43484d;
86+ --theme-hover: #1f2124;
87+
88+ --quote-bg: hsl(234, 21%, 18%);
89+ --quote-border: hsl(234, 21%, 23%);
90+
91+ --table-border-color: hsl(200, 7%, 13%);
92+ --table-header-bg: hsl(200, 7%, 28%);
93+ --table-alternate-bg: hsl(200, 7%, 11%);
94+
95+ --searchbar-border-color: #aaa;
96+ --searchbar-bg: #b7b7b7;
97+ --searchbar-fg: #000;
98+ --searchbar-shadow-color: #aaa;
99+ --searchresults-header-fg: #666;
100+ --searchresults-border-color: #98a3ad;
101+ --searchresults-li-bg: #2b2b2f;
102+ --search-mark-bg: #355c7d;
103+ --hljs-background: #969896;
104+ --hljs-color: #cc6666;
105+ --hljs-quote: #de935f;
106+ --hljs-variable: #f0c674;
107+ --hljs-type: #b5bd68;
108+ --hljs-title: #8abeb7;
109+ --hljs-symbol: #81a2be;
110+ --hljs-selector-tag: #b294bb;
111+ --hljs-selector-tag: #1d1f21;
112+ --hljs-selector-tag: #c5c8c6;
113+ --hljs-selector-tag: #718c00;
114+ --hljs-selector-tag: #c82829;
115+}
116+
117+.light {
118+ --bg: hsl(0, 0%, 100%);
119+ --fg: hsl(0, 0%, 0%);
120+
121+ --sidebar-bg: #fafafa;
122+ --sidebar-fg: hsl(0, 0%, 0%);
123+ --sidebar-non-existent: #aaaaaa;
124+ --sidebar-active: #1f1fff;
125+ --sidebar-spacer: #f4f4f4;
126+
127+ --scrollbar: #8F8F8F;
128+
129+ --icons: #747474;
130+ --icons-hover: #000000;
131+
132+ --links: #20609f;
133+
134+ --inline-code-color: #301900;
135+
136+ --theme-popup-bg: #fafafa;
137+ --theme-popup-border: #cccccc;
138+ --theme-hover: #e6e6e6;
139+
140+ --quote-bg: hsl(197, 37%, 96%);
141+ --quote-border: hsl(197, 37%, 91%);
142+
143+ --table-border-color: hsl(0, 0%, 95%);
144+ --table-header-bg: hsl(0, 0%, 80%);
145+ --table-alternate-bg: hsl(0, 0%, 97%);
146+
147+ --searchbar-border-color: #aaa;
148+ --searchbar-bg: #fafafa;
149+ --searchbar-fg: #000;
150+ --searchbar-shadow-color: #aaa;
151+ --searchresults-header-fg: #666;
152+ --searchresults-border-color: #888;
153+ --searchresults-li-bg: #e4f2fe;
154+ --search-mark-bg: #a2cff5;
155+ --hljs-background: #f6f7f6;
156+ --hljs-color: #000;
157+ --hljs-quote: #575757;
158+ --hljs-variable: #d70025;
159+ --hljs-type: #b21e00;
160+ --hljs-title: #0030f2;
161+ --hljs-symbol: #008200;
162+ --hljs-selector-tag: #9d00ec;
163+}
164+
165+.navy {
166+ --bg: hsl(226, 23%, 11%);
167+ --fg: #bcbdd0;
168+
169+ --sidebar-bg: #282d3f;
170+ --sidebar-fg: #c8c9db;
171+ --sidebar-non-existent: #505274;
172+ --sidebar-active: #2b79a2;
173+ --sidebar-spacer: #2d334f;
174+
175+ --scrollbar: var(--sidebar-fg);
176+
177+ --icons: #737480;
178+ --icons-hover: #b7b9cc;
179+
180+ --links: #2b79a2;
181+
182+ --inline-code-color: #c5c8c6;
183+
184+ --theme-popup-bg: #161923;
185+ --theme-popup-border: #737480;
186+ --theme-hover: #282e40;
187+
188+ --quote-bg: hsl(226, 15%, 17%);
189+ --quote-border: hsl(226, 15%, 22%);
190+
191+ --table-border-color: hsl(226, 23%, 16%);
192+ --table-header-bg: hsl(226, 23%, 31%);
193+ --table-alternate-bg: hsl(226, 23%, 14%);
194+
195+ --searchbar-border-color: #aaa;
196+ --searchbar-bg: #aeaec6;
197+ --searchbar-fg: #000;
198+ --searchbar-shadow-color: #aaa;
199+ --searchresults-header-fg: #5f5f71;
200+ --searchresults-border-color: #5c5c68;
201+ --searchresults-li-bg: #242430;
202+ --search-mark-bg: #a2cff5;
203+
204+ --hljs-background: #969896;
205+ --hljs-color: #cc6666;
206+ --hljs-quote: #de935f;
207+ --hljs-variable: #f0c674;
208+ --hljs-type: #b5bd68;
209+ --hljs-title: #8abeb7;
210+ --hljs-symbol: #81a2be;
211+ --hljs-selector-tag: #b294bb;
212+ --hljs-selector-tag: #1d1f21;
213+ --hljs-selector-tag: #c5c8c6;
214+ --hljs-selector-tag: #718c00;
215+ --hljs-selector-tag: #c82829;
216+}
217+
218+.rust {
219+ --bg: hsl(60, 9%, 87%);
220+ --fg: #262625;
221+
222+ --sidebar-bg: #3b2e2a;
223+ --sidebar-fg: #c8c9db;
224+ --sidebar-non-existent: #505254;
225+ --sidebar-active: #e69f67;
226+ --sidebar-spacer: #45373a;
227+
228+ --scrollbar: var(--sidebar-fg);
229+
230+ --icons: #737480;
231+ --icons-hover: #262625;
232+
233+ --links: #2b79a2;
234+
235+ --inline-code-color: #6e6b5e;
236+
237+ --theme-popup-bg: #e1e1db;
238+ --theme-popup-border: #b38f6b;
239+ --theme-hover: #99908a;
240+
241+ --quote-bg: hsl(60, 5%, 75%);
242+ --quote-border: hsl(60, 5%, 70%);
243+
244+ --table-border-color: hsl(60, 9%, 82%);
245+ --table-header-bg: #b3a497;
246+ --table-alternate-bg: hsl(60, 9%, 84%);
247+
248+ --searchbar-border-color: #aaa;
249+ --searchbar-bg: #fafafa;
250+ --searchbar-fg: #000;
251+ --searchbar-shadow-color: #aaa;
252+ --searchresults-header-fg: #666;
253+ --searchresults-border-color: #888;
254+ --searchresults-li-bg: #dec2a2;
255+ --search-mark-bg: #e69f67;
256+ --hljs-background: #f6f7f6;
257+ --hljs-color: #000;
258+ --hljs-quote: #575757;
259+ --hljs-variable: #d70025;
260+ --hljs-type: #b21e00;
261+ --hljs-title: #0030f2;
262+ --hljs-symbol: #008200;
263+ --hljs-selector-tag: #9d00ec;
264+}
265+
266+@media (prefers-color-scheme: dark) {
267+ .light.no-js {
268+ --bg: hsl(200, 7%, 8%);
269+ --fg: #98a3ad;
270+
271+ --sidebar-bg: #292c2f;
272+ --sidebar-fg: #a1adb8;
273+ --sidebar-non-existent: #505254;
274+ --sidebar-active: #3473ad;
275+ --sidebar-spacer: #393939;
276+
277+ --scrollbar: var(--sidebar-fg);
278+
279+ --icons: #43484d;
280+ --icons-hover: #b3c0cc;
281+
282+ --links: #2b79a2;
283+
284+ --inline-code-color: #c5c8c6;
285+
286+ --theme-popup-bg: #141617;
287+ --theme-popup-border: #43484d;
288+ --theme-hover: #1f2124;
289+
290+ --quote-bg: hsl(234, 21%, 18%);
291+ --quote-border: hsl(234, 21%, 23%);
292+
293+ --table-border-color: hsl(200, 7%, 13%);
294+ --table-header-bg: hsl(200, 7%, 28%);
295+ --table-alternate-bg: hsl(200, 7%, 11%);
296+
297+ --searchbar-border-color: #aaa;
298+ --searchbar-bg: #b7b7b7;
299+ --searchbar-fg: #000;
300+ --searchbar-shadow-color: #aaa;
301+ --searchresults-header-fg: #666;
302+ --searchresults-border-color: #98a3ad;
303+ --searchresults-li-bg: #2b2b2f;
304+ --search-mark-bg: #355c7d;
305+ }
306+}
307+
308+.colibri {
309+ --bg: #3b224c;
310+ --fg: #bcbdd0;
311+ --heading-fg: #fff;
312+
313+ --sidebar-bg: #281733;
314+ --sidebar-fg: #c8c9db;
315+ --sidebar-non-existent: #505274;
316+ --sidebar-active: #a4a0e8;
317+ --sidebar-spacer: #2d334f;
318+
319+ --scrollbar: var(--sidebar-fg);
320+
321+ --icons: #737480;
322+ --icons-hover: #b7b9cc;
323+
324+ /* --links: #a4a0e8; */
325+ --links: #ECCDBA;
326+
327+ --inline-code-color: hsl(48.7, 7.8%, 70%);
328+
329+ --theme-popup-bg: #161923;
330+ --theme-popup-border: #737480;
331+ --theme-hover: rgba(0,0,0, .2);
332+
333+ --quote-bg: #281733;
334+ --quote-border: hsl(226, 15%, 22%);
335+
336+ --table-border-color: hsl(226, 23%, 76%);
337+ --table-header-bg: hsla(226, 23%, 31%, 0);
338+ --table-alternate-bg: hsl(226, 23%, 14%);
339+ --table-border-line: hsla(201deg, 20%, 92%, 0.2);
340+
341+ --searchbar-border-color: #aaa;
342+ --searchbar-bg: #aeaec6;
343+ --searchbar-fg: #000;
344+ --searchbar-shadow-color: #aaa;
345+ --searchresults-header-fg: #5f5f71;
346+ --searchresults-border-color: #5c5c68;
347+ --searchresults-li-bg: #242430;
348+ --search-mark-bg: #acff5;
349+ --hljs-background: #2f1e2e;
350+ --hljs-color: #a39e9b;
351+ --hljs-quote: #8d8687;
352+ --hljs-variable: #ef6155;
353+ --hljs-type: #f99b15;
354+ --hljs-title: #fec418;
355+ --hljs-symbol: #48b685;
356+ --hljs-selector-tag: #815ba4;
357+}
358+
359+.colibri {
360+/*
361+ --bg: #ffffff;
362+ --fg: #452859;
363+ --fg: #5a5977;
364+ --heading-fg: #281733;
365+
366+ --sidebar-bg: #281733;
367+ --sidebar-fg: #c8c9db;
368+ --sidebar-non-existent: #505274;
369+ --sidebar-active: #a4a0e8;
370+ --sidebar-spacer: #2d334f;
371+
372+ --scrollbar: var(--sidebar-fg);
373+
374+ --icons: #737480;
375+ --icons-hover: #b7b9cc;
376+
377+ --links: #6F44F0;
378+
379+ --inline-code-color: #a39e9b;
380+
381+ --theme-popup-bg: #161923;
382+ --theme-popup-border: #737480;
383+ --theme-hover: rgba(0,0,0, .2);
384+
385+ --quote-bg: rgba(0, 0, 0, 0);
386+ --quote-border: hsl(226, 15%, 75%);
387+
388+ --table-border-color: #5a5977;
389+ --table-border-color: hsl(201deg 10% 67%);
390+ --table-header-bg: hsl(0, 0%, 100%);
391+ --table-alternate-bg: hsl(0, 0%, 97%);
392+ --table-border-line: hsl(201deg, 20%, 92%);
393+
394+ --searchbar-border-color: #aaa;
395+ --searchbar-bg: #aeaec6;
396+ --searchbar-fg: #000;
397+ --searchbar-shadow-color: #aaa;
398+ --searchresults-header-fg: #5f5f71;
399+ --searchresults-border-color: #5c5c68;
400+ --searchresults-li-bg: #242430;
401+ --search-mark-bg: #a2cff5;
402+ --hljs-background: #TODO;
403+ --hljs-color: #TODO;
404+ --hljs-quote: #TODO;
405+ --hljs-variable: #TODO;
406+ --hljs-type: #TODO;
407+ --hljs-title: #TODO;
408+ --hljs-symbol: #TODO;
409+ --hljs-selector-tag: #TODO;
410+*/
411+}
book/theme/highlight.cssnew file+56
@@ -0,0 +1,56 @@
1+pre code.hljs {
2+ display:block;
3+ overflow-x:auto;
4+ padding:1em
5+}
6+code.hljs {
7+ padding:3px 5px
8+}
9+.hljs {
10+ background: var(--hljs-background);
11+ color: var(--hljs-color);
12+}
13+.hljs-comment,
14+.hljs-quote {
15+ color: var(--hljs-quote)
16+}
17+.hljs-link,
18+.hljs-meta,
19+.hljs-name,
20+.hljs-regexp,
21+.hljs-selector-class,
22+.hljs-selector-id,
23+.hljs-tag,
24+.hljs-template-variable,
25+.hljs-variable {
26+ color: var(--hljs-variable)
27+}
28+.hljs-built_in,
29+.hljs-deletion,
30+.hljs-literal,
31+.hljs-number,
32+.hljs-params,
33+.hljs-type {
34+ color: var(--hljs-type)
35+}
36+.hljs-attribute,
37+.hljs-section,
38+.hljs-title {
39+ color: var(--hljs-title)
40+}
41+.hljs-addition,
42+.hljs-bullet,
43+.hljs-string,
44+.hljs-symbol {
45+ color: var(--hljs-symbol)
46+}
47+.hljs-keyword,
48+.hljs-selector-tag {
49+ color: var(--hljs-selector-tag)
50+}
51+.hljs-emphasis {
52+ font-style:italic
53+}
54+.hljs-strong {
55+ font-weight:700
56+}
book/theme/highlight.jsnew file+6
@@ -0,0 +1,6 @@
1+/*
2+ Highlight.js 10.1.1 (93fd0d73)
3+ License: BSD-3-Clause
4+ Copyright (c) 2006-2020, Ivan Sagalaev
5+*/
6+var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset<n[0].offset?e:n:"start"===n[0].event?e:n:e.length?e:n}function c(e){s+="<"+a(e)+[].map.call(e.attributes,(function(e){return" "+e.nodeName+'="'+t(e.value)+'"'})).join("")+">"}function u(e){s+="</"+a(e)+">"}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="</span>",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=`<span class="${e}">`}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:k,mergeStreams:O}=i,M=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void O.addText(A);e=m(y.subLanguage,A,!0,k[y.subLanguage]),k[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void O.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),O.addText(t)}(),A=""}function h(e){return e.className&&O.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"<unnamed>")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&O.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i<e.length;i++){var s=r+=1,o=d(e[i]);for(i>0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"<br>":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ /]*>/g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:"</?",end:">"}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}());hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin:/</,end:/>/},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%<?\^\+\*]/}]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,i]},a={className:"variable",begin:/\$\([\w-]+\s/,end:/\)/,keywords:{built_in:"subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value"},contains:[i]},r={begin:"^"+e.UNDERSCORE_IDENT_RE+"\\s*(?=[:+?]?=)"},s={className:"section",begin:/^[^\s]+:/,end:/$/,contains:[i]};return{name:"Makefile",aliases:["mk","mak"],keywords:{$pattern:/[\w-]+/,keyword:"define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath"},contains:[e.HASH_COMMENT_MODE,i,n,a,r,{className:"meta",begin:/^\.PHONY:/,end:/$/,keywords:{$pattern:/[\.\w]+/,"meta-keyword":".PHONY"}},s]}}}());hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("xml",function(){"use strict";return function(e){var n={className:"symbol",begin:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},a={begin:"\\s",contains:[{className:"meta-keyword",begin:"#?[a-z_][a-z1-9_-]+",illegal:"\\n"}]},s=e.inherit(a,{begin:"\\(",end:"\\)"}),t=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),i=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),c={endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:"attr",begin:"[A-Za-z0-9\\._:-]+",relevance:0},{begin:/=\s*/,relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/,contains:[n]},{begin:/'/,end:/'/,contains:[n]},{begin:/[^\s"'=<>`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"<![a-z]",end:">",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"<![a-z]",end:">",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:"<style(?=\\s|>)",end:">",keywords:{name:"style"},contains:[c],starts:{end:"</style>",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:"<script(?=\\s|>)",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"</?",end:"/?>",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"</",contains:[].concat(_,u,d,[s,{begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",end:">",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin:/</,end:/>/,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}());hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}());hljs.registerLanguage("d",function(){"use strict";return function(e){var a={$pattern:e.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("haskell",function(){"use strict";return function(e){var n={variants:[e.COMMENT("--","$"),e.COMMENT("{-","-}",{contains:["self"]})]},i={className:"meta",begin:"{-#",end:"#-}"},a={className:"meta",begin:"^#",end:"$"},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",end:"\\)",illegal:'"',contains:[i,a,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[s,l,n]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[i,s,l,{begin:"{",end:"}",contains:l.contains},n]},{beginKeywords:"default",end:"$",contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}}());hljs.registerLanguage("handlebars",function(){"use strict";function e(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(n){const a={"builtin-name":"action bindattr collection component concat debugger each each-in get hash if in input link-to loc log lookup mut outlet partial query-params render template textarea unbound unless view with yield"},t=/\[.*?\]/,s=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,i=e("(",/'.*?'/,"|",/".*?"/,"|",t,"|",s,"|",/\.|\//,")+"),r=e("(",t,"|",s,")(?==)"),l={begin:i,lexemes:/[\w.\/]+/},c=n.inherit(l,{keywords:{literal:"true false undefined null"}}),o={begin:/\(/,end:/\)/},m={className:"attr",begin:r,relevance:0,starts:{begin:/=/,end:/=/,starts:{contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,c,o]}}},d={contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{begin:/as\s+\|/,keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},m,c,o],returnEnd:!0},g=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/\)/})});o.contains=[g];const u=n.inherit(l,{keywords:a,className:"name",starts:n.inherit(d,{end:/}}/})}),b=n.inherit(l,{keywords:a,className:"name"}),h=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/}}/})});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},n.COMMENT(/\{\{!--/,/--\}\}/),n.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[u],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[b]},{className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[u]},{className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[b]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[h]},{className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[h]}]}}}());hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:"</",contains:[e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*","\\*/",{contains:["self"]}),e.inherit(e.QUOTE_STRING_MODE,{begin:/b?"/,illegal:null}),{className:"string",variants:[{begin:/r(#*)"(.|\n)*?"\1(?!#)/},{begin:/b?'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/}]},{className:"symbol",begin:/'[a-zA-Z_][a-zA-Z0-9_]*/},{className:"number",variants:[{begin:"\\b0b([01_]+)"+n},{begin:"\\b0o([0-7_]+)"+n},{begin:"\\b0x([A-Fa-f0-9_]+)"+n},{begin:"\\b(\\d[\\d_]*(\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)"+n}],relevance:0},{className:"function",beginKeywords:"fn",end:"(\\(|<)",excludeEnd:!0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"meta",begin:"#\\!?\\[",end:"\\]",contains:[{className:"meta-string",begin:/"/,end:/"/}]},{className:"class",beginKeywords:"type",end:";",contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"\\S"},{className:"class",beginKeywords:"trait enum struct union",end:"{",contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"[\\w\\d]"},{begin:e.IDENT_RE+"::",keywords:{built_in:t}},{begin:"->"}]}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("ini",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(...n){return n.map(n=>e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}());hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"</",contains:[{className:"built_in",begin:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.C_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"string",variants:[{begin:'@"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]}]},{className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}());hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"</?",end:">",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}());hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}());hljs.registerLanguage("x86asm",function(){"use strict";return function(s){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+s.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}());hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin:/</,end:/>/,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin:/</,end:/>/,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}());hljs.registerLanguage("armasm",function(){"use strict";return function(s){const e={variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),s.COMMENT("[;@]","$",{relevance:0}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}());hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:"</",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"string",variants:[e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{begin:"`",end:"`"}]},{className:"number",variants:[{begin:e.C_NUMBER_RE+"[i]",relevance:1},e.C_NUMBER_MODE]},{begin:/:=/},{className:"function",beginKeywords:"func",end:"\\s*(\\{|$)",excludeEnd:!0,contains:[e.TITLE_MODE,{className:"params",begin:/\(/,end:/\)/,keywords:n,illegal:/["']/}]}]}}}());hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}());hljs.registerLanguage("python",function(){"use strict";return function(e){var n={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}());hljs.registerLanguage("scala",function(){"use strict";return function(e){var n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},a={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},i={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[t]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}());hljs.registerLanguage("julia",function(){"use strict";return function(e){var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},a={keywords:t,illegal:/<\//},n={className:"subst",begin:/\$\(/,end:/\)/,keywords:t},o={className:"variable",begin:"\\$"+r},i={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]},l={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],begin:"`",end:"`"},s={className:"meta",begin:"@"+r};return a.name="Julia",a.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,s,{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}],n.contains=a.contains,a}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("r",function(){"use strict";return function(e){var n="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{name:"R",contains:[e.HASH_COMMENT_MODE,{begin:n,keywords:{$pattern:n,keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}());hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:"</>"},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}());hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}());
book/theme/index.hbs+51−87
@@ -1,11 +1,11 @@
11<!DOCTYPE HTML>
2−<html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
2+<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
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,9 +15,10 @@
1515 <!-- Custom HTML head -->
1616 {{> head}}
1717
18+ <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
1819 <meta name="description" content="{{ description }}">
1920 <meta name="viewport" content="width=device-width, initial-scale=1">
20− <meta name="theme-color" content="#ffffff">
21+ <meta name="theme-color" content="#ffffff" />
2122
2223 {{#if favicon_svg}}
2324 <link rel="icon" href="{{ path_to_root }}favicon.svg">
@@ -34,6 +35,8 @@
3435
3536 <!-- Fonts -->
3637 <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">
3740 {{#if copy_fonts}}
3841 <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
3942 {{/if}}
@@ -50,19 +53,18 @@
5053
5154 {{#if mathjax_support}}
5255 <!-- MathJax -->
53− <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
56+ <script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
5457 {{/if}}
5558 </head>
56− <body class="sidebar-visible no-js">
57− <div id="body-container">
59+ <body>
5860 <!-- Provide site root to javascript -->
59− <script>
61+ <script type="text/javascript">
6062 var path_to_root = "{{ path_to_root }}";
6163 var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
6264 </script>
6365
6466 <!-- Work around some values being stored in localStorage wrapped in quotes -->
65− <script>
67+ <script type="text/javascript">
6668 try {
6769 var theme = localStorage.getItem('mdbook-theme');
6870 var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -78,34 +80,27 @@
7880 </script>
7981
8082 <!-- Set the theme before any content is loaded, prevents flash -->
81− <script>
83+ <script type="text/javascript">
8284 var theme;
8385 try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
8486 if (theme === null || theme === undefined) { theme = default_theme; }
8587 var html = document.querySelector('html');
88+ html.classList.remove('no-js')
8689 html.classList.remove('{{ default_theme }}')
8790 html.classList.add(theme);
88− var body = document.querySelector('body');
89− body.classList.remove('no-js')
90− body.classList.add('js');
91+ html.classList.add('js');
9192 </script>
9293
93− <input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
94−
9594 <!-- Hide / unhide sidebar before it is displayed -->
96− <script>
97− var body = document.querySelector('body');
98− var sidebar = null;
99− var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
95+ <script type="text/javascript">
96+ var html = document.querySelector('html');
97+ var sidebar = 'hidden';
10098 if (document.body.clientWidth >= 1080) {
10199 try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
102100 sidebar = sidebar || 'visible';
103− } else {
104− sidebar = 'hidden';
105101 }
106− sidebar_toggle.checked = sidebar === 'visible';
107− body.classList.remove('sidebar-visible');
108− body.classList.add("sidebar-" + sidebar);
102+ html.classList.remove('sidebar-visible');
103+ html.classList.add("sidebar-" + sidebar);
109104 </script>
110105
111106 <nav id="sidebar" class="sidebar" aria-label="Table of contents">
@@ -115,48 +110,26 @@
115110 <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
116111 </nav>
117112
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−
140113 <div id="page-wrapper" class="page-wrapper">
141114
142115 <div class="page">
143116 {{> header}}
144117 <div id="menu-bar-hover-placeholder"></div>
145− <div id="menu-bar" class="menu-bar sticky">
118+ <div id="menu-bar" class="menu-bar sticky bordered">
146119 <div class="left-buttons">
147− <label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
120+ <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
148121 <i class="fa fa-bars"></i>
149− </label>
122+ </button>
150123 <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">
151124 <i class="fa fa-paint-brush"></i>
152125 </button>
153126 <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
154− <li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
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>
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>
160133 </ul>
161134 {{#if search_enabled}}
162135 <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
@@ -178,19 +151,13 @@
178151 <i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
179152 </a>
180153 {{/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−
187154 </div>
188155 </div>
189156
190157 {{#if search_enabled}}
191158 <div id="search-wrapper" class="hidden">
192159 <form id="searchbar-outer" class="searchbar-outer">
193− <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
160+ <input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
194161 </form>
195162 <div id="searchresults-outer" class="searchresults-outer hidden">
196163 <div id="searchresults-header" class="searchresults-header"></div>
@@ -201,7 +168,7 @@
201168 {{/if}}
202169
203170 <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
204− <script>
171+ <script type="text/javascript">
205172 document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
206173 document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
207174 Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -223,7 +190,7 @@
223190 {{/previous}}
224191
225192 {{#next}}
226− <a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
193+ <a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
227194 <i class="fa fa-angle-right"></i>
228195 </a>
229196 {{/next}}
@@ -241,7 +208,7 @@
241208 {{/previous}}
242209
243210 {{#next}}
244− <a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
211+ <a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
245212 <i class="fa fa-angle-right"></i>
246213 </a>
247214 {{/next}}
@@ -249,12 +216,10 @@
249216
250217 </div>
251218
252− {{#if live_reload_endpoint}}
219+ {{#if livereload}}
253220 <!-- Livereload script (if served using the cli tool) -->
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);
221+ <script type="text/javascript">
222+ var socket = new WebSocket("{{{livereload}}}");
258223 socket.onmessage = function (event) {
259224 if (event.data === "reload") {
260225 socket.close();
@@ -270,7 +235,7 @@
270235
271236 {{#if google_analytics}}
272237 <!-- Google Analytics Tag -->
273− <script>
238+ <script type="text/javascript">
274239 var localAddrs = ["localhost", "127.0.0.1", ""];
275240
276241 // make sure we don't activate google analytics if the developer is
@@ -288,43 +253,43 @@
288253 {{/if}}
289254
290255 {{#if playground_line_numbers}}
291− <script>
256+ <script type="text/javascript">
292257 window.playground_line_numbers = true;
293258 </script>
294259 {{/if}}
295260
296261 {{#if playground_copyable}}
297− <script>
262+ <script type="text/javascript">
298263 window.playground_copyable = true;
299264 </script>
300265 {{/if}}
301266
302267 {{#if playground_js}}
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>
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>
308273 {{/if}}
309274
310275 {{#if search_js}}
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>
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>
314279 {{/if}}
315280
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>
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>
319284
320285 <!-- Custom JS scripts -->
321286 {{#each additional_js}}
322− <script src="{{ ../path_to_root }}{{this}}"></script>
287+ <script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
323288 {{/each}}
324289
325290 {{#if is_print}}
326291 {{#if mathjax_support}}
327− <script>
292+ <script type="text/javascript">
328293 window.addEventListener('load', function() {
329294 MathJax.Hub.Register.StartupHook('End', function() {
330295 window.setTimeout(window.print, 100);
@@ -332,7 +297,7 @@
332297 });
333298 </script>
334299 {{else}}
335− <script>
300+ <script type="text/javascript">
336301 window.addEventListener('load', function() {
337302 window.setTimeout(window.print, 100);
338303 });
@@ -340,6 +305,5 @@
340305 {{/if}}
341306 {{/if}}
342307
343− </div>
344308 </body>
345309</html>
contrib/Helix.appdata.xml−15
@@ -5,7 +5,6 @@
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>
98
109 <description>
1110 <p>
@@ -18,17 +17,6 @@
1817 <li>Smart, incremental syntax highlighting and code editing via tree-sitter</li>
1918 </ul>
2019 </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>
3220
3321 <launchable type="desktop-id">Helix.desktop</launchable>
3422
@@ -48,9 +36,6 @@
4836 <content_rating type="oars-1.1" />
4937
5038 <releases>
51− <release version="23.10" date="2023-10-24">
52− <url>https://helix-editor.com/news/release-23-10-highlights/</url>
53− </release>
5439 <release version="23.05" date="2023-05-18">
5540 <url>https://github.com/helix-editor/helix/releases/tag/23.05</url>
5641 </release>
contrib/Helix.desktop+1−3
@@ -1,7 +1,6 @@
11[Desktop Entry]
22Name=Helix
33GenericName=Text Editor
4−GenericName[ar]=مُحَرِّرُ نُصُوص
54GenericName[de]=Texteditor
65GenericName[fr]=Éditeur de texte
76GenericName[ru]=Текстовый редактор
@@ -10,7 +9,7 @@ GenericName[tr]=Metin Düzenleyici
109Comment=Edit text files
1110Comment[af]=Redigeer tekslêers
1211Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ
13−Comment[ar]=مُحَرِّرُ مِلَفَّاتٍ نَصِّيَّة
12+Comment[ar]=حرّر ملفات نصية
1413Comment[az]=Mətn fayllarını redaktə edin
1514Comment[be]=Рэдагаваньне тэкставых файлаў
1615Comment[bg]=Редактиране на текстови файлове
@@ -80,7 +79,6 @@ Exec=hx %F
8079Terminal=true
8180Type=Application
8281Keywords=Text;editor;
83−Keywords[ar]=نص;نصوص;محرر;
8482Keywords[fr]=Texte;éditeur;
8583Keywords[ru]=текст;текстовый редактор;
8684Keywords[sr]=Текст;едитор;
docs/CONTRIBUTING.md+3−10
@@ -29,15 +29,9 @@ files, run
2929cargo xtask docgen
3030```
3131
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).
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).
4135
4236# Testing
4337
@@ -64,5 +58,4 @@ The current MSRV and future changes to the MSRV are listed in the [Firefox docum
6458[architecture.md]: ./architecture.md
6559[docs]: https://docs.helix-editor.com/
6660[xtask]: https://github.com/matklad/cargo-xtask
67−[mdbook]: https://rust-lang.github.io/mdBook/guide/installation.html
6861[helpers.rs]: ../helix-term/tests/test/helpers.rs
docs/releases.md+6−13
@@ -1,23 +1,16 @@
11## Checklist
22
33Helix releases are versioned in the Calendar Versioning scheme:
4−`YY.0M(.MICRO)`, for example, `22.05` for May of 2022, or in a patch release,
5−`22.05.1`. In these instructions we'll use `<tag>` as a placeholder for the tag
6−being published.
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.
76
87* Merge the changelog PR
98* 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)
109* Tag and push
1110 * `git tag -s -m "<tag>" -a <tag> && git push`
1211 * Make sure to switch to master and pull first
13−* Edit the `Cargo.toml` file and change the date in the `version` field to the next planned release
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`
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`
2114* Wait for the Release CI to finish
2215 * It will automatically turn the git tag into a GitHub release when it uploads artifacts
2316* Edit the new release
@@ -37,7 +30,7 @@ being published.
3730
3831The changelog is currently created manually by reading through commits in the
3932log since the last release. GitHub's compare view is a nice way to approach
40−this. For example, when creating the 22.07 release notes, this compare link
33+this. For example when creating the 22.07 release notes, this compare link
4134may be used
4235
4336```
@@ -64,4 +57,4 @@ versions for convenience:
6457> release. For the full log, check out the git log.
6558
6659Typically, small changes like dependencies or documentation updates, refactors,
67−or meta changes like GitHub Actions work are left out.
60+or meta changes like GitHub Actions work are left out.
docs/vision.md+1−1
@@ -20,5 +20,5 @@ Vision statements are all well and good, but are also vague and subjective. Her
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.
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.
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+237−21
@@ -1,22 +1,126 @@
11{
22 "nodes": {
33 "crane": {
4+ "flake": false,
5+ "locked": {
6+ "lastModified": 1681175776,
7+ "narHash": "sha256-7SsUy9114fryHAZ8p1L6G6YSu7jjz55FddEwa2U8XZc=",
8+ "owner": "ipetkov",
9+ "repo": "crane",
10+ "rev": "445a3d222947632b5593112bb817850e8a9cf737",
11+ "type": "github"
12+ },
13+ "original": {
14+ "owner": "ipetkov",
15+ "ref": "v0.12.1",
16+ "repo": "crane",
17+ "type": "github"
18+ }
19+ },
20+ "dream2nix": {
421 "inputs": {
22+ "all-cabal-json": [
23+ "nci"
24+ ],
25+ "crane": "crane",
26+ "devshell": [
27+ "nci"
28+ ],
29+ "drv-parts": "drv-parts",
30+ "flake-compat": "flake-compat",
31+ "flake-parts": [
32+ "nci",
33+ "parts"
34+ ],
35+ "flake-utils-pre-commit": [
36+ "nci"
37+ ],
38+ "ghc-utils": [
39+ "nci"
40+ ],
41+ "gomod2nix": [
42+ "nci"
43+ ],
44+ "mach-nix": [
45+ "nci"
46+ ],
47+ "nix-pypi-fetcher": [
48+ "nci"
49+ ],
550 "nixpkgs": [
51+ "nci",
652 "nixpkgs"
53+ ],
54+ "nixpkgsV1": "nixpkgsV1",
55+ "poetry2nix": [
56+ "nci"
57+ ],
58+ "pre-commit-hooks": [
59+ "nci"
60+ ],
61+ "pruned-racket-catalog": [
62+ "nci"
763 ]
864 },
965 "locked": {
10− "lastModified": 1701025348,
11− "narHash": "sha256-42GHmYH+GF7VjwGSt+fVT1CQuNpGanJbNgVHTAZppUM=",
12− "owner": "ipetkov",
13− "repo": "crane",
14− "rev": "42afaeb1a0325194a7cdb526332d2cb92fddd07b",
66+ "lastModified": 1683212002,
67+ "narHash": "sha256-EObtqyQsv9v+inieRY5cvyCMCUI5zuU5qu+1axlJCPM=",
68+ "owner": "nix-community",
69+ "repo": "dream2nix",
70+ "rev": "fbfb09d2ab5ff761d822dd40b4a1def81651d096",
1571 "type": "github"
1672 },
1773 "original": {
18− "owner": "ipetkov",
19− "repo": "crane",
74+ "owner": "nix-community",
75+ "repo": "dream2nix",
76+ "type": "github"
77+ }
78+ },
79+ "drv-parts": {
80+ "inputs": {
81+ "flake-compat": [
82+ "nci",
83+ "dream2nix",
84+ "flake-compat"
85+ ],
86+ "flake-parts": [
87+ "nci",
88+ "dream2nix",
89+ "flake-parts"
90+ ],
91+ "nixpkgs": [
92+ "nci",
93+ "dream2nix",
94+ "nixpkgs"
95+ ]
96+ },
97+ "locked": {
98+ "lastModified": 1680698112,
99+ "narHash": "sha256-FgnobN/DvCjEsc0UAZEAdPLkL4IZi2ZMnu2K2bUaElc=",
100+ "owner": "davhau",
101+ "repo": "drv-parts",
102+ "rev": "e8c2ec1157dc1edb002989669a0dbd935f430201",
103+ "type": "github"
104+ },
105+ "original": {
106+ "owner": "davhau",
107+ "repo": "drv-parts",
108+ "type": "github"
109+ }
110+ },
111+ "flake-compat": {
112+ "flake": false,
113+ "locked": {
114+ "lastModified": 1673956053,
115+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
116+ "owner": "edolstra",
117+ "repo": "flake-compat",
118+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
119+ "type": "github"
120+ },
121+ "original": {
122+ "owner": "edolstra",
123+ "repo": "flake-compat",
20124 "type": "github"
21125 }
22126 },
@@ -25,11 +129,11 @@
25129 "systems": "systems"
26130 },
27131 "locked": {
28− "lastModified": 1694529238,
29− "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
132+ "lastModified": 1681202837,
133+ "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
30134 "owner": "numtide",
31135 "repo": "flake-utils",
32− "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
136+ "rev": "cfacdce06f30d2b68473a46042957675eebb3401",
33137 "type": "github"
34138 },
35139 "original": {
@@ -38,13 +142,55 @@
38142 "type": "github"
39143 }
40144 },
145+ "mk-naked-shell": {
146+ "flake": false,
147+ "locked": {
148+ "lastModified": 1681286841,
149+ "narHash": "sha256-3XlJrwlR0nBiREnuogoa5i1b4+w/XPe0z8bbrJASw0g=",
150+ "owner": "yusdacra",
151+ "repo": "mk-naked-shell",
152+ "rev": "7612f828dd6f22b7fb332cc69440e839d7ffe6bd",
153+ "type": "github"
154+ },
155+ "original": {
156+ "owner": "yusdacra",
157+ "repo": "mk-naked-shell",
158+ "type": "github"
159+ }
160+ },
161+ "nci": {
162+ "inputs": {
163+ "dream2nix": "dream2nix",
164+ "mk-naked-shell": "mk-naked-shell",
165+ "nixpkgs": [
166+ "nixpkgs"
167+ ],
168+ "parts": "parts",
169+ "rust-overlay": [
170+ "rust-overlay"
171+ ]
172+ },
173+ "locked": {
174+ "lastModified": 1683699050,
175+ "narHash": "sha256-UWKQpzVcSshB+sU2O8CCHjOSTQrNS7Kk9V3+UeBsJpg=",
176+ "owner": "yusdacra",
177+ "repo": "nix-cargo-integration",
178+ "rev": "ed27173cd1b223f598343ea3c15aacb1d140feac",
179+ "type": "github"
180+ },
181+ "original": {
182+ "owner": "yusdacra",
183+ "repo": "nix-cargo-integration",
184+ "type": "github"
185+ }
186+ },
41187 "nixpkgs": {
42188 "locked": {
43− "lastModified": 1700794826,
44− "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=",
189+ "lastModified": 1683408522,
190+ "narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=",
45191 "owner": "nixos",
46192 "repo": "nixpkgs",
47− "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8",
193+ "rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7",
48194 "type": "github"
49195 },
50196 "original": {
@@ -54,29 +200,99 @@
54200 "type": "github"
55201 }
56202 },
203+ "nixpkgs-lib": {
204+ "locked": {
205+ "dir": "lib",
206+ "lastModified": 1682879489,
207+ "narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=",
208+ "owner": "NixOS",
209+ "repo": "nixpkgs",
210+ "rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0",
211+ "type": "github"
212+ },
213+ "original": {
214+ "dir": "lib",
215+ "owner": "NixOS",
216+ "ref": "nixos-unstable",
217+ "repo": "nixpkgs",
218+ "type": "github"
219+ }
220+ },
221+ "nixpkgsV1": {
222+ "locked": {
223+ "lastModified": 1678500271,
224+ "narHash": "sha256-tRBLElf6f02HJGG0ZR7znMNFv/Uf7b2fFInpTHiHaSE=",
225+ "owner": "NixOS",
226+ "repo": "nixpkgs",
227+ "rev": "5eb98948b66de29f899c7fe27ae112a47964baf8",
228+ "type": "github"
229+ },
230+ "original": {
231+ "id": "nixpkgs",
232+ "ref": "nixos-22.11",
233+ "type": "indirect"
234+ }
235+ },
236+ "parts": {
237+ "inputs": {
238+ "nixpkgs-lib": [
239+ "nci",
240+ "nixpkgs"
241+ ]
242+ },
243+ "locked": {
244+ "lastModified": 1683560683,
245+ "narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=",
246+ "owner": "hercules-ci",
247+ "repo": "flake-parts",
248+ "rev": "006c75898cf814ef9497252b022e91c946ba8e17",
249+ "type": "github"
250+ },
251+ "original": {
252+ "owner": "hercules-ci",
253+ "repo": "flake-parts",
254+ "type": "github"
255+ }
256+ },
257+ "parts_2": {
258+ "inputs": {
259+ "nixpkgs-lib": "nixpkgs-lib"
260+ },
261+ "locked": {
262+ "lastModified": 1683560683,
263+ "narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=",
264+ "owner": "hercules-ci",
265+ "repo": "flake-parts",
266+ "rev": "006c75898cf814ef9497252b022e91c946ba8e17",
267+ "type": "github"
268+ },
269+ "original": {
270+ "owner": "hercules-ci",
271+ "repo": "flake-parts",
272+ "type": "github"
273+ }
274+ },
57275 "root": {
58276 "inputs": {
59− "crane": "crane",
60− "flake-utils": "flake-utils",
277+ "nci": "nci",
61278 "nixpkgs": "nixpkgs",
279+ "parts": "parts_2",
62280 "rust-overlay": "rust-overlay"
63281 }
64282 },
65283 "rust-overlay": {
66284 "inputs": {
67− "flake-utils": [
68− "flake-utils"
69− ],
285+ "flake-utils": "flake-utils",
70286 "nixpkgs": [
71287 "nixpkgs"
72288 ]
73289 },
74290 "locked": {
75− "lastModified": 1701137803,
76− "narHash": "sha256-0LcPAdql5IhQSUXJx3Zna0dYTgdIoYO7zUrsKgiBd04=",
291+ "lastModified": 1683771545,
292+ "narHash": "sha256-we0GYcKTo2jRQGmUGrzQ9VH0OYAUsJMCsK8UkF+vZUA=",
77293 "owner": "oxalica",
78294 "repo": "rust-overlay",
79− "rev": "9dd940c967502f844eacea52a61e9596268d4f70",
295+ "rev": "c57e210faf68e5d5386f18f1b17ad8365d25e4ed",
80296 "type": "github"
81297 },
82298 "original": {
flake.nix+140−171
@@ -3,193 +3,162 @@
33
44 inputs = {
55 nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
6− flake-utils.url = "github:numtide/flake-utils";
76 rust-overlay = {
87 url = "github:oxalica/rust-overlay";
9− inputs = {
10− nixpkgs.follows = "nixpkgs";
11− flake-utils.follows = "flake-utils";
12− };
8+ inputs.nixpkgs.follows = "nixpkgs";
139 };
14− crane = {
15− url = "github:ipetkov/crane";
10+ nci = {
11+ url = "github:yusdacra/nix-cargo-integration";
1612 inputs.nixpkgs.follows = "nixpkgs";
13+ inputs.rust-overlay.follows = "rust-overlay";
1714 };
15+ parts.url = "github:hercules-ci/flake-parts";
1816 };
1917
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)];
18+ outputs = inp: let
19+ mkRootPath = rel:
20+ builtins.path {
21+ path = "${toString ./.}/${rel}";
22+ name = rel;
3223 };
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;
68− in
69− lib.all (p: ! (lib.hasPrefix p relPath)) pathsToIgnore;
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;
7056 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 {};
57+ lib.all (p: ! (lib.hasPrefix p relPath)) pathsToIgnore;
58+ in
59+ builtins.path {
60+ name = "helix-source";
61+ path = toString ./.;
62+ # filter out unnecessary paths
63+ filter = ignorePaths;
64+ };
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+ '';
107+ in
108+ helix-wrapped
109+ // {
110+ override = makeOverridableHelix old;
91111 passthru =
92− (old.passthru or {})
112+ helix-wrapped.passthru
93113 // {
94− unwrapped = old;
114+ wrapper = old: makeOverridableHelix old config;
95115 };
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;
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";
111136 };
137+ disable-tests = {checkPhase = ":";};
138+ set-stdenv.override = _: {inherit stdenv;};
139+ set-filtered-src.override = _: {src = filteredSource;};
140+ };
112141 };
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− };
152142
153− checks = {
154− # Build the crate itself
155− inherit (self.packages.${system}) helix;
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;
156148
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;
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+ });
193162 };
194163 };
195164
grammars.nix+21−24
@@ -5,7 +5,6 @@
55 runCommand,
66 yj,
77 includeGrammarIf ? _: true,
8− grammarOverlays ? [],
98 ...
109}: let
1110 # HACK: nix < 2.6 has a bug in the toml parser, so we convert to JSON
@@ -49,22 +48,22 @@
4948 then sourceGitHub
5049 else sourceGit;
5150 in
52− stdenv.mkDerivation {
51+ stdenv.mkDerivation rec {
5352 # see https://github.com/NixOS/nixpkgs/blob/fbdd1a7c0bc29af5325e0d7dd70e804a972eb465/pkgs/development/tools/parsing/tree-sitter/grammar.nix
5453
5554 pname = "helix-tree-sitter-${grammar.name}";
5655 version = grammar.source.rev;
5756
58− src = source;
59− sourceRoot = if builtins.hasAttr "subpath" grammar.source then
60− "source/${grammar.source.subpath}"
61− else
62− "source";
57+ src =
58+ if builtins.hasAttr "subpath" grammar.source
59+ then "${source}/${grammar.source.subpath}"
60+ else source;
6361
62+ dontUnpack = true;
6463 dontConfigure = true;
6564
6665 FLAGS = [
67− "-Isrc"
66+ "-I${src}/src"
6867 "-g"
6968 "-O3"
7069 "-fPIC"
@@ -77,13 +76,13 @@
7776 buildPhase = ''
7877 runHook preBuild
7978
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
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
8483 fi
8584
86− $CC -c src/parser.c -o parser.o $FLAGS
85+ $CC -c "$src/src/parser.c" -o parser.o $FLAGS
8786 $CXX -shared -o $NAME.so *.o
8887
8988 ls -al
@@ -106,17 +105,15 @@
106105 '';
107106 };
108107 grammarsToBuild = builtins.filter includeGrammarIf gitGrammars;
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);
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;
120117in
121118 runCommand "consolidated-helix-grammars" {} ''
122119 mkdir -p $out
helix-core/Cargo.toml+16−20
@@ -1,38 +1,37 @@
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"
37description = "Helix editor core editing primitives"
8+categories = ["editor"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
411include = ["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
1312
1413[features]
1514unicode-lines = ["ropey/unicode_lines"]
1615integration = []
1716
1817[dependencies]
19−helix-loader = { path = "../helix-loader" }
18+helix-loader = { version = "0.6", path = "../helix-loader" }
2019
21−ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
22−smallvec = "1.11"
20+ropey = { version = "1.6.0", default-features = false, features = ["simd"] }
21+smallvec = "1.10"
2322smartstring = "1.0.1"
2423unicode-segmentation = "1.10"
2524unicode-width = "0.1"
2625unicode-general-category = "0.6"
2726# slab = "0.4.2"
2827slotmap = "1.0"
29−tree-sitter.workspace = true
30−once_cell = "1.19"
28+tree-sitter = "0.20"
29+once_cell = "1.17"
3130arc-swap = "1"
3231regex = "1"
33−bitflags = "2.4"
34−ahash = "0.8.6"
35−hashbrown = { version = "0.14.3", features = ["raw"] }
32+bitflags = "2.2"
33+ahash = "0.8.3"
34+hashbrown = { version = "0.13.2", features = ["raw"] }
3635dunce = "1.0"
3736
3837log = "0.4"
@@ -49,9 +48,6 @@ chrono = { version = "0.4", default-features = false, features = ["alloc", "std"
4948etcetera = "0.8"
5049textwrap = "0.16.0"
5150
52−nucleo.workspace = true
53−parking_lot = "0.12"
54−
5551[dev-dependencies]
5652quickcheck = { version = "1", default-features = false }
57−indoc = "2.0.4"
53+indoc = "2.0.1"
helix-core/src/diagnostic.rs−4
@@ -39,10 +39,6 @@ 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,
4642 pub line: usize,
4743 pub message: String,
4844 pub severity: Option<Severity>,
helix-core/src/fuzzy.rsdeleted−43
@@ -1,43 +0,0 @@
1−use std::ops::DerefMut;
2−
3−use nucleo::pattern::{Atom, AtomKind, CaseMatching};
4−use nucleo::Config;
5−use parking_lot::Mutex;
6−
7−pub struct LazyMutex<T> {
8− inner: Mutex<Option<T>>,
9− init: fn() -> T,
10−}
11−
12−impl<T> LazyMutex<T> {
13− pub const fn new(init: fn() -> T) -> Self {
14− Self {
15− inner: Mutex::new(None),
16− init,
17− }
18− }
19−
20− pub fn lock(&self) -> impl DerefMut<Target = T> + '_ {
21− parking_lot::MutexGuard::map(self.inner.lock(), |val| val.get_or_insert_with(self.init))
22− }
23−}
24−
25−pub static MATCHER: LazyMutex<nucleo::Matcher> = LazyMutex::new(nucleo::Matcher::default);
26−
27−/// convenience function to easily fuzzy match
28−/// on a (relatively small list of inputs). This is not recommended for building a full tui
29−/// application that can match large numbers of matches as all matching is done on the current
30−/// thread, effectively blocking the UI
31−pub fn fuzzy_match<T: AsRef<str>>(
32− pattern: &str,
33− items: impl IntoIterator<Item = T>,
34− path: bool,
35−) -> Vec<(T, u16)> {
36− let mut matcher = MATCHER.lock();
37− matcher.config = Config::DEFAULT;
38− if path {
39− matcher.config.set_match_paths();
40− }
41− let pattern = Atom::new(pattern, CaseMatching::Smart, AtomKind::Fuzzy, false);
42− pattern.match_list(items, &mut matcher)
43−}
helix-core/src/graphemes.rs+1−1
@@ -481,7 +481,7 @@ impl<'a> From<String> for GraphemeStr<'a> {
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) | Self::MASK_OWNED,
484+ len: i32::try_from(len).unwrap() as u32,
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("".into())),
76− inversion: Transaction::from(ChangeSet::new("".into())),
75+ transaction: Transaction::from(ChangeSet::new(&Rope::new())),
76+ inversion: Transaction::from(ChangeSet::new(&Rope::new())),
7777 timestamp: Instant::now(),
7878 }],
7979 current: 0,
helix-core/src/indent.rs+145−617
@@ -1,14 +1,13 @@
1−use std::{borrow::Cow, collections::HashMap};
1+use std::collections::HashMap;
22
33use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
44
55use crate::{
66 chars::{char_is_line_ending, char_is_whitespace},
7− find_first_non_whitespace_char,
8− graphemes::{grapheme_width, tab_width_at},
9− syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
7+ graphemes::tab_width_at,
8+ syntax::{LanguageConfiguration, RopeProvider, Syntax},
109 tree_sitter::Node,
11− Position, Rope, RopeGraphemes, RopeSlice,
10+ Rope, RopeSlice,
1211};
1312
1413/// Enum representing indentation style.
@@ -20,10 +19,6 @@ pub enum IndentStyle {
2019 Spaces(u8),
2120}
2221
23−// 16 spaces
24−const INDENTS: &str = " ";
25−pub const MAX_INDENT: u8 = 16;
26−
2722impl IndentStyle {
2823 /// Creates an `IndentStyle` from an indentation string.
2924 ///
@@ -32,10 +27,10 @@ impl IndentStyle {
3227 #[inline]
3328 pub fn from_str(indent: &str) -> Self {
3429 // XXX: do we care about validating the input more than this? Probably not...?
35− debug_assert!(!indent.is_empty() && indent.len() <= MAX_INDENT as usize);
30+ debug_assert!(!indent.is_empty() && indent.len() <= 8);
3631
3732 if indent.starts_with(' ') {
38− IndentStyle::Spaces(indent.len().clamp(1, MAX_INDENT as usize) as u8)
33+ IndentStyle::Spaces(indent.len() as u8)
3934 } else {
4035 IndentStyle::Tabs
4136 }
@@ -45,13 +40,20 @@ impl IndentStyle {
4540 pub fn as_str(&self) -> &'static str {
4641 match *self {
4742 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.
4854 IndentStyle::Spaces(n) => {
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]
55+ debug_assert!(n > 0 && n <= 8); // Always triggers. `debug_panic!()` wanted.
56+ " "
5557 }
5658 }
5759 }
@@ -73,9 +75,9 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
7375 // Build a histogram of the indentation *increases* between
7476 // subsequent lines, ignoring lines that are all whitespace.
7577 //
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];
78+ // Index 0 is for tabs, the rest are 1-8 spaces.
79+ let histogram: [usize; 9] = {
80+ let mut histogram = [0; 9];
7981 let mut prev_line_is_tabs = false;
8082 let mut prev_line_leading_count = 0usize;
8183
@@ -134,7 +136,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
134136 histogram[0] += 1;
135137 } else {
136138 let amount = leading_count - prev_line_leading_count;
137− if amount <= MAX_INDENT as usize {
139+ if amount <= 8 {
138140 histogram[amount] += 1;
139141 }
140142 }
@@ -197,56 +199,6 @@ pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: us
197199 len / indent_width
198200}
199201
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−
250202/// Computes for node and all ancestors whether they are the first node on their line.
251203/// The first entry in the return value represents the root node, the last one the node itself
252204fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bool> {
@@ -288,140 +240,68 @@ fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bo
288240/// This is usually constructed in one of 2 ways:
289241/// - Successively add indent captures to get the (added) indent from a single line
290242/// - Successively add the indent results for each line
291−/// The string that this indentation defines starts with the string contained in the align field (unless it is None), followed by:
292−/// - max(0, indent - outdent) tabs, if tabs are used for indentation
293−/// - max(0, indent - outdent)*indent_width spaces, if spaces are used for indentation
294−#[derive(Default, Debug, PartialEq, Eq, Clone)]
295−pub struct Indentation<'a> {
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.)
296247 indent: usize,
297− indent_always: usize,
298248 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>>,
303249}
304−
305−impl<'a> Indentation<'a> {
250+impl Indentation {
306251 /// Add some other [Indentation] to this.
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;
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;
313258 }
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;
322259 }
323−
324260 /// Add an indent capture to this indent.
325− /// Only captures that apply to the same line should be added together in this way (otherwise use `add_line`)
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>) {
261+ /// All the captures that are added in this way should be on the same line.
262+ fn add_capture(&mut self, added: IndentCaptureType) {
329263 match added {
330264 IndentCaptureType::Indent => {
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;
265+ self.indent = 1;
340266 }
341267 IndentCaptureType::Outdent => {
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− }
268+ self.outdent = 1;
354269 }
355270 }
356271 }
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− ))
272+ fn as_string(&self, indent_style: &IndentStyle) -> String {
273+ let indent_level = if self.indent >= self.outdent {
274+ self.indent - self.outdent
380275 } else {
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− )
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)
393280 }
394281}
395282
396283/// An indent definition which corresponds to a capture from the indent query
397−#[derive(Debug)]
398−struct IndentCapture<'a> {
399− capture_type: IndentCaptureType<'a>,
284+struct IndentCapture {
285+ capture_type: IndentCaptureType,
400286 scope: IndentScope,
401287}
402−#[derive(Debug, Clone, PartialEq)]
403−enum IndentCaptureType<'a> {
288+#[derive(Clone, Copy)]
289+enum IndentCaptureType {
404290 Indent,
405− IndentAlways,
406291 Outdent,
407− OutdentAlways,
408− /// Alignment given as a string of whitespace
409− Align(RopeSlice<'a>),
410292}
411−
412−impl<'a> IndentCaptureType<'a> {
293+impl IndentCaptureType {
413294 fn default_scope(&self) -> IndentScope {
414295 match self {
415− IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
416− IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All,
417− IndentCaptureType::Align(_) => IndentScope::All,
296+ IndentCaptureType::Indent => IndentScope::Tail,
297+ IndentCaptureType::Outdent => IndentScope::All,
418298 }
419299 }
420300}
421301/// This defines which part of a node an [IndentCapture] applies to.
422302/// Each [IndentCaptureType] has a default scope, but the scope can be changed
423303/// with `#set!` property declarations.
424−#[derive(Debug, Clone, Copy)]
304+#[derive(Clone, Copy)]
425305enum IndentScope {
426306 /// The indent applies to the whole node
427307 All,
@@ -431,7 +311,6 @@ enum IndentScope {
431311
432312/// A capture from the indent query which does not define an indent but extends
433313/// the range of a node. This is used before the indent is calculated.
434−#[derive(Debug)]
435314enum ExtendCapture {
436315 Extend,
437316 PreventOnce,
@@ -440,41 +319,24 @@ enum ExtendCapture {
440319/// The result of running a tree-sitter indent query. This stores for
441320/// each node (identified by its ID) the relevant captures (already filtered
442321/// by predicates).
443−#[derive(Debug)]
444−struct IndentQueryResult<'a> {
445− indent_captures: HashMap<usize, Vec<IndentCapture<'a>>>,
322+struct IndentQueryResult {
323+ indent_captures: HashMap<usize, Vec<IndentCapture>>,
446324 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
447325}
448326
449−fn get_node_start_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
450− let mut node_line = node.start_position().row;
451− // Adjust for the new line that will be inserted
452− if new_line_byte_pos.map_or(false, |pos| node.start_byte() >= pos) {
453− node_line += 1;
454− }
455− node_line
456−}
457−fn get_node_end_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
458− let mut node_line = node.end_position().row;
459− // Adjust for the new line that will be inserted (with a strict inequality since end_byte is exclusive)
460− if new_line_byte_pos.map_or(false, |pos| node.end_byte() > pos) {
461− node_line += 1;
462− }
463− node_line
464−}
465−
466−fn query_indents<'a>(
327+fn query_indents(
467328 query: &Query,
468329 syntax: &Syntax,
469330 cursor: &mut QueryCursor,
470− text: RopeSlice<'a>,
331+ text: RopeSlice,
471332 range: std::ops::Range<usize>,
472− new_line_byte_pos: Option<usize>,
473−) -> IndentQueryResult<'a> {
333+ // Position of the (optional) newly inserted line break.
334+ // Given as (line, byte_pos)
335+ new_line_break: Option<(usize, usize)>,
336+) -> IndentQueryResult {
474337 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
475338 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
476339 cursor.set_byte_range(range);
477−
478340 // Iterate over all captures from the query
479341 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
480342 // Skip matches where not all custom predicates are fulfilled
@@ -501,13 +363,21 @@ fn query_indents<'a>(
501363 Some(QueryPredicateArg::Capture(capt1)),
502364 Some(QueryPredicateArg::Capture(capt2))
503365 ) => {
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+ };
504376 let n1 = m.nodes_for_capture_index(*capt1).next();
505377 let n2 = m.nodes_for_capture_index(*capt2).next();
506378 match (n1, n2) {
507379 (Some(n1), Some(n2)) => {
508− let n1_line = get_node_start_line(n1, new_line_byte_pos);
509− let n2_line = get_node_start_line(n2, new_line_byte_pos);
510− let same_line = n1_line == n2_line;
380+ let same_line = get_line_num(n1)==get_line_num(n2);
511381 same_line==(pred.operator.as_ref()=="same-line?")
512382 }
513383 _ => true,
@@ -518,23 +388,6 @@ fn query_indents<'a>(
518388 }
519389 }
520390 }
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− },
538391 _ => {
539392 panic!(
540393 "Invalid indent query: Unknown predicate (\"{}\")",
@@ -545,28 +398,11 @@ fn query_indents<'a>(
545398 }) {
546399 continue;
547400 }
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;
553401 for capture in m.captures {
554402 let capture_name = query.capture_names()[capture.index as usize].as_str();
555403 let capture_type = match capture_name {
556404 "indent" => IndentCaptureType::Indent,
557− "indent.always" => IndentCaptureType::IndentAlways,
558405 "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− }
570406 "extend" => {
571407 extend_captures
572408 .entry(capture.node.id())
@@ -616,40 +452,17 @@ fn query_indents<'a>(
616452 }
617453 }
618454 }
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− }
638455 indent_captures
639− .entry(node_id)
456+ .entry(capture.node.id())
457+ // Most entries only need to contain a single IndentCapture
640458 .or_insert_with(|| Vec::with_capacity(1))
641− .push(capture);
459+ .push(indent_capture);
642460 }
643461 }
644−
645− let result = IndentQueryResult {
462+ IndentQueryResult {
646463 indent_captures,
647464 extend_captures,
648− };
649−
650− log::trace!("indent result = {:?}", result);
651−
652− result
465+ }
653466}
654467
655468/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
@@ -719,27 +532,61 @@ fn extend_nodes<'a>(
719532 }
720533}
721534
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.
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+/// ```
725571#[allow(clippy::too_many_arguments)]
726−fn init_indent_query<'a, 'b>(
572+pub fn treesitter_indent_for_pos(
727573 query: &Query,
728− syntax: &'a Syntax,
729− text: RopeSlice<'b>,
574+ syntax: &Syntax,
575+ indent_style: &IndentStyle,
730576 tab_width: usize,
731577 indent_width: usize,
578+ text: RopeSlice,
732579 line: usize,
733− byte_pos: usize,
734− new_line_byte_pos: Option<usize>,
735−) -> Option<(Node<'a>, HashMap<usize, Vec<IndentCapture<'b>>>)> {
580+ pos: usize,
581+ new_line: bool,
582+) -> Option<String> {
583+ let byte_pos = text.char_to_byte(pos);
736584 // The innermost tree-sitter node which is considered for the indent
737585 // computation. It may change if some predeceding node is extended
738586 let mut node = syntax
739587 .tree()
740588 .root_node()
741589 .descendant_for_byte_range(byte_pos, byte_pos)?;
742−
743590 let (query_result, deepest_preceding) = {
744591 // The query range should intersect with all nodes directly preceding
745592 // the position of the indent query in case one of them is extended.
@@ -770,12 +617,13 @@ fn init_indent_query<'a, 'b>(
770617 &mut cursor,
771618 text,
772619 query_range,
773− new_line_byte_pos,
620+ new_line.then_some((line, byte_pos)),
774621 );
775622 ts_parser.cursors.push(cursor);
776623 (query_result, deepest_preceding)
777624 })
778625 };
626+ let indent_captures = query_result.indent_captures;
779627 let extend_captures = query_result.extend_captures;
780628
781629 // Check for extend captures, potentially changing the node that the indent calculation starts with
@@ -790,68 +638,6 @@ fn init_indent_query<'a, 'b>(
790638 indent_width,
791639 );
792640 }
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− )?;
855641 let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos));
856642
857643 let mut result = Indentation::default();
@@ -859,16 +645,12 @@ pub fn treesitter_indent_for_pos<'a>(
859645 // even if there are multiple "indent" nodes on the same line
860646 let mut indent_for_line = Indentation::default();
861647 let mut indent_for_line_below = Indentation::default();
862−
863648 loop {
864649 // This can safely be unwrapped because `first_in_line` contains
865650 // one entry for each ancestor of the node (which is what we iterate over)
866651 let is_first = *first_in_line.last().unwrap();
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()) {
652+ // Apply all indent definitions for this node
653+ if let Some(definitions) = indent_captures.get(&node.id()) {
872654 for definition in definitions {
873655 match definition.scope {
874656 IndentScope::All => {
@@ -886,22 +668,28 @@ pub fn treesitter_indent_for_pos<'a>(
886668 }
887669
888670 if let Some(parent) = node.parent() {
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−
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+ };
892682 if node_line != parent_line {
893− // Don't add indent for the line below the line of the query
894683 if node_line < line + (new_line as usize) {
895− result.add_line(indent_for_line_below);
684+ // Don't add indent for the line below the line of the query
685+ result.add_line(&indent_for_line_below);
896686 }
897−
898687 if node_line == parent_line + 1 {
899688 indent_for_line_below = indent_for_line;
900689 } else {
901− result.add_line(indent_for_line);
690+ result.add_line(&indent_for_line);
902691 indent_for_line_below = Indentation::default();
903692 }
904−
905693 indent_for_line = Indentation::default();
906694 }
907695
@@ -913,13 +701,13 @@ pub fn treesitter_indent_for_pos<'a>(
913701 if (node.start_position().row < line)
914702 || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
915703 {
916− result.add_line(indent_for_line_below);
704+ result.add_line(&indent_for_line_below);
917705 }
918− result.add_line(indent_for_line);
706+ result.add_line(&indent_for_line);
919707 break;
920708 }
921709 }
922− Some(result)
710+ Some(result.as_string(indent_style))
923711}
924712
925713/// Returns the indentation for a new line.
@@ -928,7 +716,6 @@ pub fn treesitter_indent_for_pos<'a>(
928716pub fn indent_for_newline(
929717 language_config: Option<&LanguageConfiguration>,
930718 syntax: Option<&Syntax>,
931− indent_heuristic: &IndentationHeuristic,
932719 indent_style: &IndentStyle,
933720 tab_width: usize,
934721 text: RopeSlice,
@@ -937,18 +724,14 @@ pub fn indent_for_newline(
937724 current_line: usize,
938725) -> String {
939726 let indent_width = indent_style.indent_width(tab_width);
940− if let (
941− IndentationHeuristic::TreeSitter | IndentationHeuristic::Hybrid,
942− Some(query),
943− Some(syntax),
944− ) = (
945− indent_heuristic,
727+ if let (Some(query), Some(syntax)) = (
946728 language_config.and_then(|config| config.indent_query()),
947729 syntax,
948730 ) {
949731 if let Some(indent) = treesitter_indent_for_pos(
950732 query,
951733 syntax,
734+ indent_style,
952735 tab_width,
953736 indent_width,
954737 text,
@@ -956,57 +739,9 @@ pub fn indent_for_newline(
956739 line_before_end_pos,
957740 true,
958741 ) {
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);
742+ return indent;
1007743 };
1008744 }
1009− // Fallback in case we either don't have indent queries or they failed for some reason
1010745 let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
1011746 indent_style.as_str().repeat(indent_level)
1012747}
@@ -1062,211 +797,4 @@ mod test {
1062797 3
1063798 );
1064799 }
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− }
1272800}
helix-core/src/lib.rs+2−4
@@ -7,7 +7,6 @@ pub mod config;
77pub mod diagnostic;
88pub mod diff;
99pub mod doc_formatter;
10−pub mod fuzzy;
1110pub mod graphemes;
1211pub mod history;
1312pub mod increment;
@@ -19,6 +18,7 @@ pub mod movement;
1918pub mod object;
2019pub mod path;
2120mod position;
21+pub mod register;
2222pub mod search;
2323pub mod selection;
2424pub mod shellwords;
@@ -41,9 +41,7 @@ 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;
4544
46−pub use rope_reader::RopeReader;
4745pub use ropey::{self, str_utils, Rope, RopeBuilder, RopeSlice};
4846
4947// pub use tendril::StrTendril as Tendril;
@@ -68,5 +66,5 @@ pub use syntax::Syntax;
6866
6967pub use diagnostic::Diagnostic;
7068
71−pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
69+pub use line_ending::{LineEnding, DEFAULT_LINE_ENDING};
7270pub 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 NATIVE_LINE_ENDING: LineEnding = LineEnding::Crlf;
4+pub const DEFAULT_LINE_ENDING: LineEnding = LineEnding::Crlf;
55#[cfg(not(target_os = "windows"))]
6−pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::LF;
6+pub const DEFAULT_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+29−232
@@ -1,15 +1,7 @@
1−use std::iter;
2−
3−use ropey::RopeSlice;
41use tree_sitter::Node;
52
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;
3+use crate::{Rope, Syntax};
114
12−// Limit matching pairs to only ( ) { } [ ] < > ' ' " "
135const PAIRS: &[(char, char)] = &[
146 ('(', ')'),
157 ('{', '}'),
@@ -19,17 +11,17 @@ const PAIRS: &[(char, char)] = &[
1911 ('\"', '\"'),
2012];
2113
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.
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.
3123#[must_use]
32−pub fn find_matching_bracket(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
24+pub fn find_matching_bracket(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
3325 if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) {
3426 return None;
3527 }
@@ -47,159 +39,46 @@ pub fn find_matching_bracket(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Opt
4739//
4840// If no surrounding scope is found, the function returns `None`.
4941#[must_use]
50−pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
42+pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
5143 find_pair(syntax, doc, pos, true)
5244}
5345
54−fn find_pair(
55− syntax: &Syntax,
56− doc: RopeSlice,
57− pos_: usize,
58− traverse_parents: bool,
59−) -> Option<usize> {
46+fn find_pair(syntax: &Syntax, doc: &Rope, pos: usize, traverse_parents: bool) -> Option<usize> {
6047 let tree = syntax.tree();
61− let pos = doc.char_to_byte(pos_);
48+ let pos = doc.char_to_byte(pos);
6249
63− let mut node = tree.root_node().descendant_for_byte_range(pos, pos)?;
50+ let mut node = tree.root_node().named_descendant_for_byte_range(pos, pos)?;
6451
6552 loop {
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− }
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));
7455
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− }
56+ if is_valid_pair(doc, start_char, end_char) {
57+ if end_byte == pos {
58+ return Some(start_char);
8059 }
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);
8163 }
82− // this node itselt wasn't a pair but maybe its siblings are
8364
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− }
65+ if traverse_parents {
66+ node = node.parent()?;
67+ } else {
68+ return None;
9069 }
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;
12470 }
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
18871}
18972
19073fn is_valid_bracket(c: char) -> bool {
19174 PAIRS.iter().any(|(l, r)| *l == c || *r == c)
19275}
19376
194−fn is_forward_bracket(c: char) -> bool {
195− PAIRS.iter().any(|(l, _)| *l == c)
196−}
197−
198−fn is_valid_pair(doc: RopeSlice, start_char: usize, end_char: usize) -> bool {
77+fn is_valid_pair(doc: &Rope, start_char: usize, end_char: usize) -> bool {
19978 PAIRS.contains(&(doc.char(start_char), doc.char(end_char)))
20079}
20180
202−fn surrounding_bytes(doc: RopeSlice, node: &Node) -> Option<(usize, usize)> {
81+fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> {
20382 let len = doc.len_bytes();
20483
20584 let start_byte = node.start_byte();
@@ -211,85 +90,3 @@ fn surrounding_bytes(doc: RopeSlice, node: &Node) -> Option<(usize, usize)> {
21190
21291 Some((start_byte, end_byte))
21392}
214−
215−/// Tests if this node is a pair close char and returns the expected open char
216−fn as_close_pair(doc: RopeSlice, node: &Node) -> Option<char> {
217− let close = as_char(doc, node)?.1;
218− PAIRS
219− .iter()
220− .find_map(|&(open, close_)| (close_ == close).then_some(open))
221−}
222−
223−/// Checks if `node` or its siblings (at most MATCH_LIMIT nodes) is the specified closing char
224−///
225−/// # Returns
226−///
227−/// The position of the found node or `None` otherwise
228−fn find_pair_end(
229− doc: RopeSlice,
230− node: Option<Node>,
231− end_char: char,
232− direction: Direction,
233−) -> Option<usize> {
234− let advance = match direction {
235− Forward => Node::next_sibling,
236− Backward => Node::prev_sibling,
237− };
238− iter::successors(node, advance)
239− .take(MATCH_LIMIT)
240− .find_map(|node| {
241− let (pos, c) = as_char(doc, &node)?;
242− (end_char == c).then_some(pos)
243− })
244−}
245−
246−/// Tests if this node is a pair close char and returns the expected open char
247−fn as_open_pair(doc: RopeSlice, node: &Node) -> Option<char> {
248− let open = as_char(doc, node)?.1;
249− PAIRS
250− .iter()
251− .find_map(|&(open_, close)| (open_ == open).then_some(close))
252−}
253−
254−/// If node is a single char return it (and its char position)
255−fn as_char(doc: RopeSlice, node: &Node) -> Option<(usize, char)> {
256− // TODO: multi char/non ASCII pairs
257− if node.byte_range().len() != 1 {
258− return None;
259− }
260− let pos = doc.try_byte_to_char(node.start_byte()).ok()?;
261− Some((pos, doc.char(pos)))
262−}
263−
264−#[cfg(test)]
265−mod tests {
266− use super::*;
267−
268− #[test]
269− fn test_find_matching_bracket_current_line_plaintext() {
270− let assert = |input: &str, pos, expected| {
271− let input = RopeSlice::from(input);
272− let actual = find_matching_bracket_plaintext(input, pos);
273− assert_eq!(expected, actual.unwrap());
274−
275− let actual = find_matching_bracket_plaintext(input, expected);
276− assert_eq!(pos, actual.unwrap(), "expected symmetrical behaviour");
277− };
278−
279− assert("(hello)", 0, 6);
280− assert("((hello))", 0, 8);
281− assert("((hello))", 1, 7);
282− assert("(((hello)))", 2, 8);
283−
284− assert("key: ${value}", 6, 12);
285− assert("key: ${value} # (some comment)", 16, 29);
286−
287− assert("(paren (paren {bracket}))", 0, 24);
288− assert("(paren (paren {bracket}))", 7, 23);
289− assert("(paren (paren {bracket}))", 14, 22);
290−
291− assert("(prev line\n ) (middle) ( \n next line)", 0, 12);
292− assert("(prev line\n ) (middle) ( \n next line)", 14, 21);
293− assert("(prev line\n ) (middle) ( \n next line)", 23, 36);
294− }
295−}
helix-core/src/movement.rs+7−187
@@ -1,4 +1,4 @@
1−use std::{cmp::Reverse, iter};
1+use std::iter;
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, Selection, Syntax,
19+ visual_offset_from_block, Range, RopeSlice,
2020};
2121
2222#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -177,10 +177,6 @@ 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−
184180pub fn move_next_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
185181 word_move(slice, range, count, WordMotionTarget::NextLongWordStart)
186182}
@@ -193,8 +189,8 @@ pub fn move_prev_long_word_start(slice: RopeSlice, range: Range, count: usize) -
193189 word_move(slice, range, count, WordMotionTarget::PrevLongWordStart)
194190}
195191
196−pub fn move_prev_long_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
197− word_move(slice, range, count, WordMotionTarget::PrevLongWordEnd)
192+pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
193+ word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
198194}
199195
200196fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
@@ -203,7 +199,6 @@ fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTar
203199 WordMotionTarget::PrevWordStart
204200 | WordMotionTarget::PrevLongWordStart
205201 | WordMotionTarget::PrevWordEnd
206− | WordMotionTarget::PrevLongWordEnd
207202 );
208203
209204 // Special-case early-out.
@@ -382,7 +377,6 @@ pub enum WordMotionTarget {
382377 NextLongWordStart,
383378 NextLongWordEnd,
384379 PrevLongWordStart,
385− PrevLongWordEnd,
386380}
387381
388382pub trait CharHelpers {
@@ -399,7 +393,6 @@ impl CharHelpers for Chars<'_> {
399393 WordMotionTarget::PrevWordStart
400394 | WordMotionTarget::PrevLongWordStart
401395 | WordMotionTarget::PrevWordEnd
402− | WordMotionTarget::PrevLongWordEnd
403396 );
404397
405398 // Reverse the iterator if needed for the motion direction.
@@ -486,7 +479,7 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
486479 is_word_boundary(prev_ch, next_ch)
487480 && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
488481 }
489− WordMotionTarget::NextLongWordStart | WordMotionTarget::PrevLongWordEnd => {
482+ WordMotionTarget::NextLongWordStart => {
490483 is_long_word_boundary(prev_ch, next_ch)
491484 && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
492485 }
@@ -527,10 +520,10 @@ pub fn goto_treesitter_object(
527520 let node = match dir {
528521 Direction::Forward => nodes
529522 .filter(|n| n.start_byte() > byte_pos)
530− .min_by_key(|n| (n.start_byte(), Reverse(n.end_byte())))?,
523+ .min_by_key(|n| n.start_byte())?,
531524 Direction::Backward => nodes
532525 .filter(|n| n.end_byte() < byte_pos)
533− .max_by_key(|n| (n.end_byte(), Reverse(n.start_byte())))?,
526+ .max_by_key(|n| n.end_byte())?,
534527 };
535528
536529 let len = slice.len_bytes();
@@ -556,85 +549,6 @@ pub fn goto_treesitter_object(
556549 last_range
557550}
558551
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−
638552#[cfg(test)]
639553mod test {
640554 use ropey::Rope;
@@ -1531,100 +1445,6 @@ mod test {
15311445 }
15321446 }
15331447
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−
16281448 #[test]
16291449 fn test_behaviour_when_moving_to_prev_paragraph_single() {
16301450 let tests = [
helix-core/src/path.rs+7−5
@@ -85,21 +85,23 @@ 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) -> PathBuf {
88+pub fn get_canonicalized_path(path: &Path) -> std::io::Result<PathBuf> {
8989 let path = expand_tilde(path);
9090 let path = if path.is_relative() {
91− helix_loader::current_working_dir().join(path)
91+ std::env::current_dir().map(|current_dir| current_dir.join(path))?
9292 } else {
9393 path
9494 };
9595
96− get_normalized_path(path.as_path())
96+ Ok(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 = get_normalized_path(&helix_loader::current_working_dir());
102+ let cwdir = std::env::current_dir()
103+ .map(|path| get_normalized_path(&path))
104+ .expect("couldn't determine current directory");
103105 get_normalized_path(&path)
104106 .strip_prefix(cwdir)
105107 .map(PathBuf::from)
@@ -140,7 +142,7 @@ pub fn get_relative_path(path: &Path) -> PathBuf {
140142/// ```
141143///
142144pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
143− let cwd = helix_loader::current_working_dir();
145+ let cwd = std::env::current_dir().unwrap_or_default();
144146 let path = path
145147 .as_ref()
146148 .strip_prefix(cwd)
helix-core/src/register.rsnew file+89
@@ -0,0 +1,89 @@
1+use std::collections::HashMap;
2+
3+#[derive(Debug)]
4+pub struct Register {
5+ name: char,
6+ values: Vec<String>,
7+}
8+
9+impl Register {
10+ pub const fn new(name: char) -> Self {
11+ Self {
12+ name,
13+ values: Vec::new(),
14+ }
15+ }
16+
17+ pub fn new_with_values(name: char, values: Vec<String>) -> Self {
18+ Self { name, values }
19+ }
20+
21+ pub const fn name(&self) -> char {
22+ self.name
23+ }
24+
25+ pub fn read(&self) -> &[String] {
26+ &self.values
27+ }
28+
29+ pub fn write(&mut self, values: Vec<String>) {
30+ self.values = values;
31+ }
32+
33+ pub fn push(&mut self, value: String) {
34+ self.values.push(value);
35+ }
36+}
37+
38+/// Currently just wraps a `HashMap` of `Register`s
39+#[derive(Debug, Default)]
40+pub struct Registers {
41+ inner: HashMap<char, Register>,
42+}
43+
44+impl Registers {
45+ pub fn get(&self, name: char) -> Option<&Register> {
46+ self.inner.get(&name)
47+ }
48+
49+ pub fn read(&self, name: char) -> Option<&[String]> {
50+ self.get(name).map(|reg| reg.read())
51+ }
52+
53+ pub fn write(&mut self, name: char, values: Vec<String>) {
54+ if name != '_' {
55+ self.inner
56+ .insert(name, Register::new_with_values(name, values));
57+ }
58+ }
59+
60+ pub fn push(&mut self, name: char, value: String) {
61+ if name != '_' {
62+ if let Some(r) = self.inner.get_mut(&name) {
63+ r.push(value);
64+ } else {
65+ self.write(name, vec![value]);
66+ }
67+ }
68+ }
69+
70+ pub fn first(&self, name: char) -> Option<&String> {
71+ self.read(name).and_then(|entries| entries.first())
72+ }
73+
74+ pub fn last(&self, name: char) -> Option<&String> {
75+ self.read(name).and_then(|entries| entries.last())
76+ }
77+
78+ pub fn inner(&self) -> &HashMap<char, Register> {
79+ &self.inner
80+ }
81+
82+ pub fn clear(&mut self) {
83+ self.inner.clear();
84+ }
85+
86+ pub fn remove(&mut self, name: char) -> Option<Register> {
87+ self.inner.remove(&name)
88+ }
89+}
helix-core/src/rope_reader.rsdeleted−37
@@ -1,37 +0,0 @@
1−use std::io;
2−
3−use ropey::iter::Chunks;
4−use ropey::RopeSlice;
5−
6−pub struct RopeReader<'a> {
7− current_chunk: &'a [u8],
8− chunks: Chunks<'a>,
9−}
10−
11−impl<'a> RopeReader<'a> {
12− pub fn new(rope: RopeSlice<'a>) -> RopeReader<'a> {
13− RopeReader {
14− current_chunk: &[],
15− chunks: rope.chunks(),
16− }
17− }
18−}
19−
20−impl io::Read for RopeReader<'_> {
21− fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
22− let buf_len = buf.len();
23− loop {
24− let read_bytes = self.current_chunk.read(buf)?;
25− buf = &mut buf[read_bytes..];
26− if buf.is_empty() {
27− return Ok(buf_len);
28− }
29−
30− if let Some(next_chunk) = self.chunks.next() {
31− self.current_chunk = next_chunk.as_bytes();
32− } else {
33− return Ok(buf_len - buf.len());
34− }
35− }
36− }
37−}
helix-core/src/selection.rs+43−76
@@ -161,35 +161,34 @@ impl Range {
161161 self.from() <= pos && pos < self.to()
162162 }
163163
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 {
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 {
171167 use std::cmp::Ordering;
172− if changes.is_empty() {
173− return self;
174− }
175−
176− let positions_to_map = match self.anchor.cmp(&self.head) {
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− ],
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+ ),
189181 };
190− changes.update_positions(positions_to_map.into_iter());
191− self.old_visual_position = None;
192− self
182+
183+ // We want to return a new `Range` with `horiz == None` every time,
184+ // even if the anchor and head haven't changed, because we don't
185+ // know if the *visual* position hasn't changed due to
186+ // character-width or grapheme changes earlier in the text.
187+ Self {
188+ anchor,
189+ head,
190+ old_visual_position: None,
191+ }
193192 }
194193
195194 /// Extend the range to cover at least `from` `to`.
@@ -452,36 +451,17 @@ impl Selection {
452451 /// Map selections over a set of changes. Useful for adjusting the selection position after
453452 /// applying changes to a document.
454453 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 {
461454 if changes.is_empty() {
462455 return self;
463456 }
464457
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
458+ Self::new(
459+ self.ranges
460+ .into_iter()
461+ .map(|range| range.map(changes))
462+ .collect(),
463+ self.primary_index,
464+ )
485465 }
486466
487467 pub fn ranges(&self) -> &[Range] {
@@ -517,9 +497,6 @@ impl Selection {
517497
518498 /// Normalizes a `Selection`.
519499 fn normalize(mut self) -> Self {
520− if self.len() < 2 {
521− return self;
522− }
523500 let mut primary = self.ranges[self.primary_index];
524501 self.ranges.sort_unstable_by_key(Range::from);
525502
@@ -545,14 +522,7 @@ impl Selection {
545522 self
546523 }
547524
548− /// Replaces ranges with one spanning from first to last range.
549− pub fn merge_ranges(self) -> Self {
550− let first = self.ranges.first().unwrap();
551− let last = self.ranges.last().unwrap();
552− Selection::new(smallvec![first.merge(*last)], 0)
553− }
554−
555− /// Merges all ranges that are consecutive.
525+ // Merges all ranges that are consecutive
556526 pub fn merge_consecutive_ranges(mut self) -> Self {
557527 let mut primary = self.ranges[self.primary_index];
558528
@@ -584,12 +554,17 @@ impl Selection {
584554 assert!(!ranges.is_empty());
585555 debug_assert!(primary_index < ranges.len());
586556
587− let selection = Self {
557+ let mut selection = Self {
588558 ranges,
589559 primary_index,
590560 };
591561
592− selection.normalize()
562+ if selection.ranges.len() > 1 {
563+ // TODO: only normalize if needed (any ranges out of order)
564+ selection = selection.normalize();
565+ }
566+
567+ selection
593568 }
594569
595570 /// Takes a closure and maps each `Range` over the closure.
@@ -630,19 +605,11 @@ impl Selection {
630605 self.transform(|range| Range::point(range.cursor(text)))
631606 }
632607
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− {
608+ pub fn fragments<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = Cow<str>> + 'a {
638609 self.ranges.iter().map(move |range| range.fragment(text))
639610 }
640611
641− pub fn slices<'a>(
642− &'a self,
643− text: RopeSlice<'a>,
644− ) -> impl DoubleEndedIterator<Item = RopeSlice<'a>> + ExactSizeIterator<Item = RopeSlice<'a>> + 'a
645− {
612+ pub fn slices<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = RopeSlice> + 'a {
646613 self.ranges.iter().map(move |range| range.slice(text))
647614 }
648615
helix-core/src/syntax.rs+81−105
@@ -4,7 +4,7 @@ use crate::{
44 diagnostic::Severity,
55 regex::Regex,
66 transaction::{ChangeSet, Operation},
7− RopeSlice, Tendril,
7+ Rope, RopeSlice, Tendril,
88};
99
1010use ahash::RandomState;
@@ -48,21 +48,6 @@ 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−
6651pub fn deserialize_auto_pairs<'de, D>(deserializer: D) -> Result<Option<AutoPairs>, D::Error>
6752where
6853 D: serde::Deserializer<'de>,
@@ -101,8 +86,7 @@ pub struct LanguageConfiguration {
10186 pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
10287 #[serde(default)]
10388 pub shebangs: Vec<String>, // interpreter(s) associated with language
104− #[serde(default)]
105− pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
89+ pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
10690 pub comment_token: Option<String>,
10791 pub text_width: Option<usize>,
10892 pub soft_wrap: Option<SoftWrap>,
@@ -155,8 +139,6 @@ pub struct LanguageConfiguration {
155139 /// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
156140 /// Falling back to the current working directory if none are configured.
157141 pub workspace_lsp_roots: Option<Vec<PathBuf>>,
158− #[serde(default)]
159− pub persistent_diagnostic_sources: Vec<String>,
160142}
161143
162144#[derive(Debug, PartialEq, Eq, Hash)]
@@ -214,7 +196,10 @@ impl<'de> Deserialize<'de> for FileType {
214196 {
215197 match map.next_entry::<String, String>()? {
216198 Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
217− suffix.replace('/', std::path::MAIN_SEPARATOR_STR)
199+ // FIXME: use `suffix.replace('/', std::path::MAIN_SEPARATOR_STR)`
200+ // if MSRV is updated to 1.68
201+ let mut seperator = [0; 1];
202+ suffix.replace('/', std::path::MAIN_SEPARATOR.encode_utf8(&mut seperator))
218203 })),
219204 Some((key, _value)) => Err(serde::de::Error::custom(format!(
220205 "unknown key in `file-types` list: {}",
@@ -439,27 +424,10 @@ pub struct DebuggerQuirks {
439424#[derive(Debug, Serialize, Deserialize)]
440425#[serde(rename_all = "kebab-case")]
441426pub struct IndentationConfiguration {
442− #[serde(deserialize_with = "deserialize_tab_width")]
443427 pub tab_width: usize,
444428 pub unit: String,
445429}
446430
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−
463431/// Configuration for auto pairs
464432#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
465433#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
@@ -834,10 +802,7 @@ impl Loader {
834802 // TODO: content_regex handling conflict resolution
835803 }
836804
837− pub fn language_config_for_shebang(
838− &self,
839− source: RopeSlice,
840− ) -> Option<Arc<LanguageConfiguration>> {
805+ pub fn language_config_for_shebang(&self, source: &Rope) -> Option<Arc<LanguageConfiguration>> {
841806 let line = Cow::from(source.line(0));
842807 static SHEBANG_REGEX: Lazy<Regex> =
843808 Lazy::new(|| Regex::new(&["^", SHEBANG].concat()).unwrap());
@@ -947,7 +912,7 @@ fn byte_range_to_str(range: std::ops::Range<usize>, source: RopeSlice) -> Cow<st
947912
948913impl Syntax {
949914 pub fn new(
950− source: RopeSlice,
915+ source: &Rope,
951916 config: Arc<HighlightConfiguration>,
952917 loader: Arc<Loader>,
953918 ) -> Option<Self> {
@@ -978,7 +943,7 @@ impl Syntax {
978943 let res = syntax.update(source, source, &ChangeSet::new(source));
979944
980945 if res.is_err() {
981− log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
946+ log::error!("TS parser failed, disabeling TS for the current buffer: {res:?}");
982947 return None;
983948 }
984949 Some(syntax)
@@ -986,8 +951,8 @@ impl Syntax {
986951
987952 pub fn update(
988953 &mut self,
989− old_source: RopeSlice,
990− source: RopeSlice,
954+ old_source: &Rope,
955+ source: &Rope,
991956 changeset: &ChangeSet,
992957 ) -> Result<(), Error> {
993958 let mut queue = VecDeque::new();
@@ -1154,38 +1119,12 @@ impl Syntax {
11541119 layer.tree().root_node(),
11551120 RopeProvider(source_slice),
11561121 );
1157− let mut combined_injections = vec![
1158− (None, Vec::new(), IncludedChildren::default());
1159− layer.config.combined_injections_patterns.len()
1160− ];
11611122 let mut injections = Vec::new();
1162− let mut last_injection_end = 0;
11631123 for mat in matches {
11641124 let (injection_capture, content_node, included_children) = layer
11651125 .config
11661126 .injection_for_match(&layer.config.injections_query, &mat, source_slice);
11671127
1168− // in case this is a combined injection save it for more processing later
1169− if let Some(combined_injection_idx) = layer
1170− .config
1171− .combined_injections_patterns
1172− .iter()
1173− .position(|&pattern| pattern == mat.pattern_index)
1174− {
1175− let entry = &mut combined_injections[combined_injection_idx];
1176− if injection_capture.is_some() {
1177− entry.0 = injection_capture;
1178− }
1179− if let Some(content_node) = content_node {
1180− if content_node.start_byte() >= last_injection_end {
1181− entry.1.push(content_node);
1182− last_injection_end = content_node.end_byte();
1183− }
1184− }
1185− entry.2 = included_children;
1186− continue;
1187− }
1188−
11891128 // Explicitly remove this match so that none of its other captures will remain
11901129 // in the stream of captures.
11911130 mat.remove();
@@ -1200,23 +1139,49 @@ impl Syntax {
12001139 intersect_ranges(&layer.ranges, &[content_node], included_children);
12011140
12021141 if !ranges.is_empty() {
1203− if content_node.start_byte() < last_injection_end {
1204− continue;
1205− }
1206− last_injection_end = content_node.end_byte();
12071142 injections.push((config, ranges));
12081143 }
12091144 }
12101145 }
12111146 }
12121147
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));
1148+ // Process combined injections.
1149+ if let Some(combined_injections_query) = &layer.config.combined_injections_query {
1150+ let mut injections_by_pattern_index =
1151+ vec![
1152+ (None, Vec::new(), IncludedChildren::default());
1153+ combined_injections_query.pattern_count()
1154+ ];
1155+ let matches = cursor.matches(
1156+ combined_injections_query,
1157+ layer.tree().root_node(),
1158+ RopeProvider(source_slice),
1159+ );
1160+ for mat in matches {
1161+ let entry = &mut injections_by_pattern_index[mat.pattern_index];
1162+ let (injection_capture, content_node, included_children) = layer
1163+ .config
1164+ .injection_for_match(combined_injections_query, &mat, source_slice);
1165+ if injection_capture.is_some() {
1166+ entry.0 = injection_capture;
1167+ }
1168+ if let Some(content_node) = content_node {
1169+ entry.1.push(content_node);
1170+ }
1171+ entry.2 = included_children;
1172+ }
1173+ for (lang_name, content_nodes, included_children) in injections_by_pattern_index
1174+ {
1175+ if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) {
1176+ if let Some(config) = (injection_callback)(&lang_name) {
1177+ let ranges = intersect_ranges(
1178+ &layer.ranges,
1179+ &content_nodes,
1180+ included_children,
1181+ );
1182+ if !ranges.is_empty() {
1183+ injections.push((config, ranges));
1184+ }
12201185 }
12211186 }
12221187 }
@@ -1406,7 +1371,7 @@ impl LanguageLayer {
14061371 self.tree.as_ref().unwrap()
14071372 }
14081373
1409− fn parse(&mut self, parser: &mut Parser, source: RopeSlice) -> Result<(), Error> {
1374+ fn parse(&mut self, parser: &mut Parser, source: &Rope) -> Result<(), Error> {
14101375 parser
14111376 .set_included_ranges(&self.ranges)
14121377 .map_err(|_| Error::InvalidRanges)?;
@@ -1421,7 +1386,7 @@ impl LanguageLayer {
14211386 &mut |byte, _| {
14221387 if byte <= source.len_bytes() {
14231388 let (chunk, start_byte, _, _) = source.chunk_at_byte(byte);
1424− &chunk.as_bytes()[byte - start_byte..]
1389+ chunk[byte - start_byte..].as_bytes()
14251390 } else {
14261391 // out of range
14271392 &[]
@@ -1437,7 +1402,7 @@ impl LanguageLayer {
14371402}
14381403
14391404pub(crate) fn generate_edits(
1440− old_text: RopeSlice,
1405+ old_text: &Rope,
14411406 changeset: &ChangeSet,
14421407) -> Vec<tree_sitter::InputEdit> {
14431408 use Operation::*;
@@ -1453,7 +1418,7 @@ pub(crate) fn generate_edits(
14531418
14541419 // TODO; this is a lot easier with Change instead of Operation.
14551420
1456− fn point_at_pos(text: RopeSlice, pos: usize) -> (usize, Point) {
1421+ fn point_at_pos(text: &Rope, pos: usize) -> (usize, Point) {
14571422 let byte = text.char_to_byte(pos); // <- attempted to index past end
14581423 let line = text.char_to_line(pos);
14591424 let line_start_byte = text.line_to_byte(line);
@@ -1579,7 +1544,7 @@ pub struct HighlightConfiguration {
15791544 pub language: Grammar,
15801545 pub query: Query,
15811546 injections_query: Query,
1582− combined_injections_patterns: Vec<usize>,
1547+ combined_injections_query: Option<Query>,
15831548 highlights_pattern_index: usize,
15841549 highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
15851550 non_local_variable_patterns: Vec<bool>,
@@ -1630,7 +1595,7 @@ impl<'a> Iterator for ChunksBytes<'a> {
16301595}
16311596
16321597pub struct RopeProvider<'a>(pub RopeSlice<'a>);
1633−impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> {
1598+impl<'a> TextProvider<'a> for RopeProvider<'a> {
16341599 type I = ChunksBytes<'a>;
16351600
16361601 fn text(&mut self, node: Node) -> Self::I {
@@ -1644,7 +1609,7 @@ impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> {
16441609struct HighlightIterLayer<'a> {
16451610 _tree: Option<Tree>,
16461611 cursor: QueryCursor,
1647− captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>, &'a [u8]>>>,
1612+ captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>>>>,
16481613 config: &'a HighlightConfiguration,
16491614 highlight_end_stack: Vec<usize>,
16501615 scope_stack: Vec<LocalScope<'a>>,
@@ -1695,15 +1660,26 @@ impl HighlightConfiguration {
16951660 }
16961661 }
16971662
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();
1663+ let mut injections_query = Query::new(language, injection_query)?;
1664+
1665+ // Construct a separate query just for dealing with the 'combined injections'.
1666+ // Disable the combined injection patterns in the main query.
1667+ let mut combined_injections_query = Query::new(language, injection_query)?;
1668+ let mut has_combined_queries = false;
1669+ for pattern_index in 0..injections_query.pattern_count() {
1670+ let settings = injections_query.property_settings(pattern_index);
1671+ if settings.iter().any(|s| &*s.key == "injection.combined") {
1672+ has_combined_queries = true;
1673+ injections_query.disable_pattern(pattern_index);
1674+ } else {
1675+ combined_injections_query.disable_pattern(pattern_index);
1676+ }
1677+ }
1678+ let combined_injections_query = if has_combined_queries {
1679+ Some(combined_injections_query)
1680+ } else {
1681+ None
1682+ };
17071683
17081684 // Find all of the highlighting patterns that are disabled for nodes that
17091685 // have been identified as local variables.
@@ -1752,7 +1728,7 @@ impl HighlightConfiguration {
17521728 language,
17531729 query,
17541730 injections_query,
1755− combined_injections_patterns,
1731+ combined_injections_query,
17561732 highlights_pattern_index,
17571733 highlight_indices,
17581734 non_local_variable_patterns,
@@ -2548,7 +2524,7 @@ mod test {
25482524 let mut cursor = QueryCursor::new();
25492525
25502526 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2551− let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
2527+ let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader)).unwrap();
25522528
25532529 let root = syntax.tree().root_node();
25542530 let mut test = |capture, range| {
@@ -2622,7 +2598,7 @@ mod test {
26222598 fn main() {}
26232599 ",
26242600 );
2625− let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
2601+ let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader)).unwrap();
26262602 let tree = syntax.tree();
26272603 let root = tree.root_node();
26282604 assert_eq!(root.kind(), "source_file");
@@ -2649,7 +2625,7 @@ mod test {
26492625 &doc,
26502626 vec![(6, 11, Some("test".into())), (12, 17, None)].into_iter(),
26512627 );
2652− let edits = generate_edits(doc.slice(..), transaction.changes());
2628+ let edits = generate_edits(&doc, transaction.changes());
26532629 // transaction.apply(&mut state);
26542630
26552631 assert_eq!(
@@ -2678,7 +2654,7 @@ mod test {
26782654 let mut doc = Rope::from("fn test() {}");
26792655 let transaction =
26802656 Transaction::change(&doc, vec![(8, 8, Some("a: u32".into()))].into_iter());
2681− let edits = generate_edits(doc.slice(..), transaction.changes());
2657+ let edits = generate_edits(&doc, transaction.changes());
26822658 transaction.apply(&mut doc);
26832659
26842660 assert_eq!(doc, "fn test(a: u32) {}");
@@ -2712,7 +2688,7 @@ mod test {
27122688 let language = get_language(language_name).unwrap();
27132689
27142690 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2715− let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
2691+ let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader)).unwrap();
27162692
27172693 let root = syntax
27182694 .tree()
helix-core/src/transaction.rs+51−191
@@ -1,8 +1,7 @@
1−use ropey::RopeSlice;
21use smallvec::SmallVec;
32
4−use crate::{chars::char_is_word, Range, Rope, Selection, Tendril};
5−use std::{borrow::Cow, iter::once};
3+use crate::{Range, Rope, Selection, Tendril};
4+use std::borrow::Cow;
65
76/// (from, to, replacement)
87pub type Change = (usize, usize, Option<Tendril>);
@@ -23,30 +22,6 @@ pub enum Operation {
2322pub enum Assoc {
2423 Before,
2524 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− }
5025}
5126
5227#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -67,7 +42,7 @@ impl ChangeSet {
6742 }
6843
6944 #[must_use]
70− pub fn new(doc: RopeSlice) -> Self {
45+ pub fn new(doc: &Rope) -> Self {
7146 let len = doc.len_chars();
7247 Self {
7348 changes: Vec::new(),
@@ -351,75 +326,20 @@ impl ChangeSet {
351326 self.changes.is_empty() || self.changes == [Operation::Retain(self.len)]
352327 }
353328
354− /// Map a (mostly) *sorted* list of positions through the changes.
355− ///
356− /// This is equivalent to updating each position with `map_pos`:
329+ /// Map a position through the changes.
357330 ///
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)>) {
331+ /// `assoc` indicates which size to associate the position with. `Before` will keep the
332+ /// position close to the character before, and will place it before insertions over that
333+ /// range, or at that point. `After` will move it forward, placing it at the end of such
334+ /// insertions.
335+ pub fn map_pos(&self, pos: usize, assoc: Assoc) -> usize {
369336 use Operation::*;
370−
371− let mut positions = positions.peekable();
372−
373337 let mut old_pos = 0;
374338 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− }
414339
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− };
340+ let mut iter = self.changes.iter().peekable();
422341
342+ while let Some(change) = iter.next() {
423343 let len = match change {
424344 Delete(i) | Retain(i) => *i,
425345 Insert(_) => 0,
@@ -428,65 +348,61 @@ impl ChangeSet {
428348
429349 match change {
430350 Retain(_) => {
431− map!(
432− |pos, _| (old_end > pos).then_some(new_pos + (pos - old_pos)),
433− i
434− );
351+ if old_end > pos {
352+ return new_pos + (pos - old_pos);
353+ }
435354 new_pos += len;
436355 }
437356 Delete(_) => {
438357 // in range
439− map!(|pos, _| (old_end > pos).then_some(new_pos), i);
358+ if old_end > pos {
359+ return new_pos;
360+ }
440361 }
441362 Insert(s) => {
363+ let ins = s.chars().count();
364+
442365 // a subsequent delete means a replace, consume it
443− if let Some((_, Delete(len))) = iter.peek() {
366+ if let Some(Delete(len)) = iter.peek() {
444367 iter.next();
445368
446369 old_end = old_pos + len;
447370 // in range of replaced text
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− );
371+ if old_end > pos {
372+ // at point or tracking before
373+ if pos == old_pos || assoc == Assoc::Before {
374+ return new_pos;
375+ } else {
376+ // place to end of insert
377+ return new_pos + ins;
378+ }
379+ }
460380 } else {
461381 // at insert point
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− );
382+ if old_pos == pos {
383+ // return position before inserted text
384+ if assoc == Assoc::Before {
385+ return new_pos;
386+ } else {
387+ // after text
388+ return new_pos + ins;
389+ }
390+ }
469391 }
470392
471− new_pos += s.chars().count();
393+ new_pos += ins;
472394 }
473395 }
474396 old_pos = old_end;
475397 }
476− let out_of_bounds: Vec<_> = positions.collect();
477398
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
399+ if pos > old_pos {
400+ panic!(
401+ "Position {} is out of range for changeset len {}!",
402+ pos, old_pos
403+ )
404+ }
405+ new_pos
490406 }
491407
492408 pub fn changes_iter(&self) -> ChangeIterator {
@@ -506,7 +422,7 @@ impl Transaction {
506422 /// Create a new, empty transaction.
507423 pub fn new(doc: &Rope) -> Self {
508424 Self {
509− changes: ChangeSet::new(doc.slice(..)),
425+ changes: ChangeSet::new(doc),
510426 selection: None,
511427 }
512428 }
@@ -887,62 +803,6 @@ mod test {
887803 };
888804 assert_eq!(cs.map_pos(2, Assoc::Before), 2);
889805 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);
946806 }
947807
948808 #[test]
@@ -1009,9 +869,9 @@ mod test {
1009869 #[test]
1010870 fn combine_with_empty() {
1011871 let empty = Rope::from("");
1012− let a = ChangeSet::new(empty.slice(..));
872+ let a = ChangeSet::new(&empty);
1013873
1014− let mut b = ChangeSet::new(empty.slice(..));
874+ let mut b = ChangeSet::new(&empty);
1015875 b.insert("a".into());
1016876
1017877 let changes = a.compose(b);
@@ -1025,9 +885,9 @@ mod test {
1025885 const TEST_CASE: &str = "Hello, これはヘリックスエディターです!";
1026886
1027887 let empty = Rope::from("");
1028− let a = ChangeSet::new(empty.slice(..));
888+ let a = ChangeSet::new(&empty);
1029889
1030− let mut b = ChangeSet::new(empty.slice(..));
890+ let mut b = ChangeSet::new(&empty);
1031891 b.insert(TEST_CASE.into());
1032892
1033893 let changes = a.compose(b);
helix-core/src/wrap.rs+1−3
@@ -1,9 +1,7 @@
11use smartstring::{LazyCompact, SmartString};
2−use textwrap::{Options, WordSplitter::NoHyphenation};
32
43/// Given a slice of text, return the text re-wrapped to fit it
54/// within the given width.
65pub fn reflow_hard_wrap(text: &str, text_width: usize) -> SmartString<LazyCompact> {
7− let options = Options::new(text_width).word_splitter(NoHyphenation);
8− textwrap::refill(text, options).into()
6+ textwrap::refill(text, text_width).into()
97}
helix-core/tests/data/indent/cpp.cppdeleted−48
@@ -1,48 +0,0 @@
1−std::vector<std::string>
2−fn_with_many_parameters(int parm1, long parm2, float parm3, double parm4,
3− char* parm5, bool parm6);
4−
5−std::vector<std::string>
6−fn_with_many_parameters(int parm1, long parm2, float parm3, double parm4,
7− char* parm5, bool parm6) {
8− auto lambda = []() {
9− return 0;
10− };
11− auto lambda_with_a_really_long_name_that_uses_a_whole_line
12− = [](int some_more_aligned_parameters,
13− std::string parm2) {
14− do_smth();
15− };
16− if (brace_on_same_line) {
17− do_smth();
18− } else if (brace_on_next_line)
19− {
20− do_smth();
21− } else if (another_condition) {
22− do_smth();
23− }
24− else {
25− do_smth();
26− }
27− if (inline_if_statement)
28− do_smth();
29− if (another_inline_if_statement)
30− return [](int parm1, char* parm2) {
31− this_is_a_really_pointless_lambda();
32− };
33−
34− switch (var) {
35− case true:
36− return -1;
37− case false:
38− return 42;
39− }
40−}
41−
42−class MyClass : public MyBaseClass {
43−public:
44− MyClass();
45− void public_fn();
46−private:
47− super_secret_private_fn();
48−}
helix-core/tests/data/indent/indent.rsnew file+1
@@ -0,0 +1 @@
1+../../../src/indent.rs
helix-core/tests/data/indent/languages.toml−13
@@ -11,16 +11,3 @@ indent = { tab-width = 4, unit = " " }
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+25−153
@@ -1,122 +1,20 @@
11use helix_core::{
22 indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle},
3− syntax::{Configuration, Loader},
3+ syntax::Loader,
44 Syntax,
55};
6−use ropey::Rope;
7−use std::{ops::Range, path::PathBuf, process::Command};
6+use std::path::PathBuf;
87
98#[test]
109fn test_treesitter_indent_rust() {
11− standard_treesitter_test("rust.rs", "source.rust");
10+ test_treesitter_indent("rust.rs", "source.rust");
1211}
13−
1412#[test]
15−fn test_treesitter_indent_cpp() {
16− standard_treesitter_test("cpp.cpp", "source.cpp");
17−}
18−
19−#[test]
20−fn test_treesitter_indent_rust_helix() {
21− // We pin a specific git revision to prevent unrelated changes from causing the indent tests to fail.
22− // Ideally, someone updates this once in a while and fixes any errors that occur.
23− let rev = "af382768cdaf89ff547dbd8f644a1bddd90e7c8f";
24− let files = Command::new("git")
25− .args([
26− "ls-tree",
27− "-r",
28− "--name-only",
29− "--full-tree",
30− rev,
31− "helix-term/src",
32− ])
33− .output()
34− .unwrap();
35− let files = String::from_utf8(files.stdout).unwrap();
36−
37− let ignored_files = vec![
38− // Contains many macros that tree-sitter does not parse in a meaningful way and is otherwise not very interesting
39− "helix-term/src/health.rs",
40− ];
41−
42− for file in files.split_whitespace() {
43− if ignored_files.contains(&file) {
44− continue;
45− }
46− let ignored_lines: Vec<Range<usize>> = match file {
47− "helix-term/src/application.rs" => vec![
48− // We can't handle complicated indent rules inside macros (`json!` in this case) since
49− // the tree-sitter grammar only parses them as `token_tree` and `identifier` nodes.
50− 1045..1051,
51− ],
52− "helix-term/src/commands.rs" => vec![
53− // This is broken because of the current handling of `call_expression`
54− // (i.e. having an indent query for it but outdenting again in specific cases).
55− // The indent query is needed to correctly handle multi-line arguments in function calls
56− // inside indented `field_expression` nodes (which occurs fairly often).
57− //
58− // Once we have the `@indent.always` capture type, it might be possible to just have an indent
59− // capture for the `arguments` field of a call expression. That could enable us to correctly
60− // handle this.
61− 2226..2230,
62− ],
63− "helix-term/src/commands/dap.rs" => vec![
64− // Complex `format!` macro
65− 46..52,
66− ],
67− "helix-term/src/commands/lsp.rs" => vec![
68− // Macro
69− 624..627,
70− // Return type declaration of a closure. `cargo fmt` adds an additional space here,
71− // which we cannot (yet) model with our indent queries.
72− 878..879,
73− // Same as in `helix-term/src/commands.rs`
74− 1335..1343,
75− ],
76− "helix-term/src/config.rs" => vec![
77− // Multiline string
78− 146..152,
79− ],
80− "helix-term/src/keymap.rs" => vec![
81− // Complex macro (see above)
82− 456..470,
83− // Multiline string without indent
84− 563..567,
85− ],
86− "helix-term/src/main.rs" => vec![
87− // Multiline string
88− 44..70,
89− ],
90− "helix-term/src/ui/completion.rs" => vec![
91− // Macro
92− 218..232,
93− ],
94− "helix-term/src/ui/editor.rs" => vec![
95− // The chained function calls here are not indented, probably because of the comment
96− // in between. Since `cargo fmt` doesn't even attempt to format it, there's probably
97− // no point in trying to indent this correctly.
98− 342..350,
99− ],
100− "helix-term/src/ui/lsp.rs" => vec![
101− // Macro
102− 56..61,
103− ],
104− "helix-term/src/ui/statusline.rs" => vec![
105− // Same as in `helix-term/src/commands.rs`
106− 436..442,
107− 450..456,
108− ],
109− _ => Vec::new(),
110− };
111−
112− let git_object = rev.to_string() + ":" + file;
113− let content = Command::new("git")
114− .args(["cat-file", "blob", &git_object])
115− .output()
116− .unwrap();
117− let doc = Rope::from_reader(&mut content.stdout.as_slice()).unwrap();
118− test_treesitter_indent(file, doc, "source.rust", ignored_lines);
119− }
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");
12018}
12119
12220#[test]
@@ -152,41 +50,20 @@ fn test_indent_level_for_line_with_spaces_and_tabs() {
15250 assert_eq!(indent_level, 2)
15351}
15452
155−fn indent_tests_dir() -> PathBuf {
53+fn test_treesitter_indent(file_name: &str, lang_scope: &str) {
15654 let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
15755 test_dir.push("tests/data/indent");
158− test_dir
159−}
16056
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();
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();
17760 let doc = ropey::Rope::from_reader(test_file).unwrap();
178− test_treesitter_indent(file_name, doc, lang_scope, Vec::new())
179−}
18061
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());
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);
19067
19168 // set runtime path so we can find the queries
19269 let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
@@ -194,35 +71,30 @@ fn test_treesitter_indent(
19471 std::env::set_var("HELIX_RUNTIME", runtime.to_str().unwrap());
19572
19673 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);
19874 let highlight_config = language_config.highlight_config(&[]).unwrap();
199− let text = doc.slice(..);
200− let syntax = Syntax::new(text, highlight_config, std::sync::Arc::new(loader)).unwrap();
75+ let syntax = Syntax::new(&doc, highlight_config, std::sync::Arc::new(loader)).unwrap();
20176 let indent_query = language_config.indent_query().unwrap();
77+ let text = doc.slice(..);
20278
20379 for i in 0..doc.len_lines() {
20480 let line = text.line(i);
205− if ignored_lines.iter().any(|range| range.contains(&(i + 1))) {
206− continue;
207− }
20881 if let Some(pos) = helix_core::find_first_non_whitespace_char(line) {
209− let tab_width: usize = 4;
82+ let tab_and_indent_width: usize = 4;
21083 let suggested_indent = treesitter_indent_for_pos(
21184 indent_query,
21285 &syntax,
213− tab_width,
214− indent_style.indent_width(tab_width),
86+ &IndentStyle::Spaces(tab_and_indent_width as u8),
87+ tab_and_indent_width,
88+ tab_and_indent_width,
21589 text,
21690 i,
21791 text.line_to_char(i) + pos,
21892 false,
21993 )
220− .unwrap()
221− .to_string(&indent_style, tab_width);
94+ .unwrap();
22295 assert!(
22396 line.get_slice(..pos).map_or(false, |s| s == suggested_indent),
224− "Wrong indentation for file {:?} on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
225− test_name,
97+ "Wrong indentation on line {}:\n\"{}\" (original line)\n\"{}\" (suggested indentation)\n",
22698 i+1,
22799 line.slice(..line.len_chars()-1),
228100 suggested_indent,
helix-dap/Cargo.toml+9−11
@@ -1,27 +1,25 @@
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"
37description = "DAP client implementation for Helix project"
4−version.workspace = true
5−authors.workspace = true
6−edition.workspace = true
7−license.workspace = true
8−rust-version.workspace = true
9−categories.workspace = true
10−repository.workspace = true
11−homepage.workspace = true
8+categories = ["editor"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
1211
1312# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1413
1514[dependencies]
16−helix-core = { path = "../helix-core" }
17−
15+helix-core = { version = "0.6", path = "../helix-core" }
1816anyhow = "1.0"
1917log = "0.4"
2018serde = { version = "1.0", features = ["derive"] }
2119serde_json = "1.0"
2220thiserror = "1.0"
2321tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "net", "sync"] }
24−which = "5.0.0"
22+which = "4.4"
2523
2624[dev-dependencies]
2725fern = "0.6"
helix-event/Cargo.tomldeleted−16
@@ -1,16 +0,0 @@
1−[package]
2−name = "helix-event"
3−version.workspace = true
4−authors.workspace = true
5−edition.workspace = true
6−license.workspace = true
7−rust-version.workspace = true
8−categories.workspace = true
9−repository.workspace = true
10−homepage.workspace = true
11−
12−# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13−
14−[dependencies]
15−tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "parking_lot"] }
16−parking_lot = { version = "0.12", features = ["send_guard"] }
helix-event/src/lib.rsdeleted−8
@@ -1,8 +0,0 @@
1−//! `helix-event` contains systems that allow (often async) communication between
2−//! different editor components without strongly coupling them. Currently this
3−//! crate only contains some smaller facilities but the intend is to add more
4−//! functionality in the future ( like a generic hook system)
5−
6−pub use redraw::{lock_frame, redraw_requested, request_redraw, start_frame, RenderLockGuard};
7−
8−mod redraw;
helix-event/src/redraw.rsdeleted−49
@@ -1,49 +0,0 @@
1−//! Signals that control when/if the editor redraws
2−
3−use std::future::Future;
4−
5−use parking_lot::{RwLock, RwLockReadGuard};
6−use tokio::sync::Notify;
7−
8−/// A `Notify` instance that can be used to (asynchronously) request
9−/// the editor the render a new frame.
10−static REDRAW_NOTIFY: Notify = Notify::const_new();
11−
12−/// A `RwLock` that prevents the next frame from being
13−/// drawn until an exclusive (write) lock can be acquired.
14−/// This allows asynchsonous tasks to acquire `non-exclusive`
15−/// locks (read) to prevent the next frame from being drawn
16−/// until a certain computation has finished.
17−static RENDER_LOCK: RwLock<()> = RwLock::new(());
18−
19−pub type RenderLockGuard = RwLockReadGuard<'static, ()>;
20−
21−/// Requests that the editor is redrawn. The redraws are debounced (currently to
22−/// 30FPS) so this can be called many times without causing a ton of frames to
23−/// be rendered.
24−pub fn request_redraw() {
25− REDRAW_NOTIFY.notify_one();
26−}
27−
28−/// Returns a future that will yield once a redraw has been asynchronously
29−/// requested using [`request_redraw`].
30−pub fn redraw_requested() -> impl Future<Output = ()> {
31− REDRAW_NOTIFY.notified()
32−}
33−
34−/// Wait until all locks acquired with [`lock_frame`] have been released.
35−/// This function is called before rendering and is intended to allow the frame
36−/// to wait for async computations that should be included in the current frame.
37−pub fn start_frame() {
38− drop(RENDER_LOCK.write());
39− // exhaust any leftover redraw notifications
40− let notify = REDRAW_NOTIFY.notified();
41− tokio::pin!(notify);
42− notify.enable();
43−}
44−
45−/// Acquires the render lock which will prevent the next frame from being drawn
46−/// until the returned guard is dropped.
47−pub fn lock_frame() -> RenderLockGuard {
48− RENDER_LOCK.read()
49−}
helix-loader/Cargo.toml+11−14
@@ -1,14 +1,13 @@
11[package]
22name = "helix-loader"
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
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"
1211
1312[[bin]]
1413name = "hx-loader"
@@ -19,18 +18,16 @@ anyhow = "1"
1918serde = { version = "1.0", features = ["derive"] }
2019toml = "0.7"
2120etcetera = "0.8"
22−tree-sitter.workspace = true
23−once_cell = "1.19"
21+tree-sitter = "0.20"
22+once_cell = "1.17"
2423log = "0.4"
25−which = "5.0.0"
2624
2725# TODO: these two should be on !wasm32 only
2826
2927# cloning/compiling tree-sitter grammars
3028cc = { version = "1" }
3129threadpool = { version = "1.0" }
32−tempfile = "3.8.1"
33−dunce = "1.0.4"
30+tempfile = "3.5.0"
3431
3532[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
3633libloading = "0.8"
helix-loader/build.rs+6−20
@@ -2,17 +2,7 @@ use std::borrow::Cow;
22use std::path::Path;
33use std::process::Command;
44
5−const MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR");
6−const MINOR: &str = env!("CARGO_PKG_VERSION_MINOR");
7−const PATCH: &str = env!("CARGO_PKG_VERSION_PATCH");
8−
9−fn get_calver() -> String {
10− if PATCH == "0" {
11− format!("{MAJOR}.{MINOR}")
12− } else {
13− format!("{MAJOR}.{MINOR}.{PATCH}")
14− }
15−}
5+const VERSION: &str = include_str!("../VERSION");
166
177fn main() {
188 let git_hash = Command::new("git")
@@ -22,10 +12,9 @@ fn main() {
2212 .filter(|output| output.status.success())
2313 .and_then(|x| String::from_utf8(x.stdout).ok());
2414
25− let calver = get_calver();
2615 let version: Cow<_> = match &git_hash {
27− Some(git_hash) => format!("{} ({})", calver, &git_hash[..8]).into(),
28− None => calver.into(),
16+ Some(git_hash) => format!("{} ({})", VERSION, &git_hash[..8]).into(),
17+ None => VERSION.into(),
2918 };
3019
3120 println!(
@@ -33,6 +22,7 @@ fn main() {
3322 std::env::var("TARGET").unwrap()
3423 );
3524
25+ println!("cargo:rerun-if-changed=../VERSION");
3626 println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version);
3727
3828 if git_hash.is_none() {
@@ -50,9 +40,7 @@ fn main() {
5040 .ok()
5141 .filter(|output| output.status.success())
5242 .and_then(|x| String::from_utf8(x.stdout).ok())
53− else {
54− return;
55− };
43+ else{ return; };
5644 // If heads starts pointing at something else (different branch)
5745 // we need to return
5846 let head = Path::new(&git_dir).join("HEAD");
@@ -67,9 +55,7 @@ fn main() {
6755 .ok()
6856 .filter(|output| output.status.success())
6957 .and_then(|x| String::from_utf8(x.stdout).ok())
70− else {
71− return;
72− };
58+ else{ return; };
7359 let head_ref = Path::new(&git_dir).join(head_ref);
7460 if head_ref.exists() {
7561 println!("cargo:rerun-if-changed={}", head_ref.display());
helix-loader/src/grammar.rs−11
@@ -85,16 +85,7 @@ pub fn get_language(name: &str) -> Result<Language> {
8585 Ok(language)
8686}
8787
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−
9588pub fn fetch_grammars() -> Result<()> {
96− ensure_git_is_available()?;
97−
9889 // We do not need to fetch local grammars.
9990 let mut grammars = get_grammar_configs()?;
10091 grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. }));
@@ -154,8 +145,6 @@ pub fn fetch_grammars() -> Result<()> {
154145}
155146
156147pub fn build_grammars(target: Option<String>) -> Result<()> {
157− ensure_git_is_available()?;
158−
159148 let grammars = get_grammar_configs()?;
160149 println!("Building {} grammars", grammars.len());
161150 let results = run_parallel(grammars, move |grammar| {
helix-loader/src/lib.rs+17−78
@@ -3,56 +3,29 @@ pub mod grammar;
33
44use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
55use std::path::{Path, PathBuf};
6−use std::sync::RwLock;
76
87pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH");
98
10−static CWD: RwLock<Option<PathBuf>> = RwLock::new(None);
11−
129static RUNTIME_DIRS: once_cell::sync::Lazy<Vec<PathBuf>> =
1310 once_cell::sync::Lazy::new(prioritize_runtime_dirs);
1411
1512static CONFIG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
1613
17−static LOG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
18−
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− }
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();
2617
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());
18+ if !config_dir.exists() {
19+ std::fs::create_dir_all(&config_dir).ok();
20+ }
3221
33− path
34−}
22+ config_dir.join("config.toml")
23+ });
3524
36−pub fn set_current_working_dir(path: impl AsRef<Path>) -> std::io::Result<()> {
37− let path = dunce::canonicalize(path)?;
38− std::env::set_current_dir(&path)?;
39− let mut cwd = CWD.write().unwrap();
40− *cwd = Some(path);
41− Ok(())
42−}
43−
44−pub fn initialize_config_file(specified_file: Option<PathBuf>) {
45− let config_file = specified_file.unwrap_or_else(default_config_file);
46− ensure_parent_dir(&config_file);
25+ // We should only initialize this value once.
4726 CONFIG_FILE.set(config_file).ok();
4827}
4928
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−
5629/// A list of runtime directories from highest to lowest priority
5730///
5831/// The priority is:
@@ -60,8 +33,7 @@ pub fn initialize_log_file(specified_file: Option<PathBuf>) {
6033/// 1. sibling directory to `CARGO_MANIFEST_DIR` (if environment variable is set)
6134/// 2. subdirectory of user config directory (always included)
6235/// 3. `HELIX_RUNTIME` (if environment variable is set)
63−/// 4. `HELIX_DEFAULT_RUNTIME` (if environment variable is set *at build time*)
64−/// 5. subdirectory of path to helix executable (always included)
36+/// 4. subdirectory of path to helix executable (always included)
6537///
6638/// Postcondition: returns at least two paths (they might not exist).
6739fn prioritize_runtime_dirs() -> Vec<PathBuf> {
@@ -82,14 +54,6 @@ fn prioritize_runtime_dirs() -> Vec<PathBuf> {
8254 rt_dirs.push(dir.into());
8355 }
8456
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−
9357 // fallback to location of the executable being run
9458 // canonicalize the path in case the executable is symlinked
9559 let exe_rt_dir = std::env::current_exe()
@@ -158,11 +122,10 @@ pub fn cache_dir() -> PathBuf {
158122}
159123
160124pub fn config_file() -> PathBuf {
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()
125+ CONFIG_FILE
126+ .get()
127+ .map(|path| path.to_path_buf())
128+ .unwrap_or_else(|| config_dir().join("config.toml"))
166129}
167130
168131pub fn workspace_config_file() -> PathBuf {
@@ -173,7 +136,7 @@ pub fn lang_config_file() -> PathBuf {
173136 config_dir().join("languages.toml")
174137}
175138
176−pub fn default_log_file() -> PathBuf {
139+pub fn log_file() -> PathBuf {
177140 cache_dir().join("helix.log")
178141}
179142
@@ -254,7 +217,7 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi
254217/// If no workspace was found returns (CWD, true).
255218/// Otherwise (workspace, false) is returned
256219pub fn find_workspace() -> (PathBuf, bool) {
257− let current_dir = current_working_dir();
220+ let current_dir = std::env::current_dir().expect("unable to determine current directory");
258221 for ancestor in current_dir.ancestors() {
259222 if ancestor.join(".git").exists() || ancestor.join(".helix").exists() {
260223 return (ancestor.to_owned(), false);
@@ -264,37 +227,13 @@ pub fn find_workspace() -> (PathBuf, bool) {
264227 (current_dir, true)
265228}
266229
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−
279230#[cfg(test)]
280231mod merge_toml_tests {
281232 use std::str;
282233
283− use super::{current_working_dir, merge_toml_values, set_current_working_dir};
234+ use super::merge_toml_values;
284235 use toml::Value;
285236
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−
298237 #[test]
299238 fn language_toml_map_merges() {
300239 const USER: &str = r#"
helix-lsp/Cargo.toml+13−15
@@ -1,32 +1,30 @@
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"
37description = "LSP client implementation for Helix project"
4−version.workspace = true
5−authors.workspace = true
6−edition.workspace = true
7−license.workspace = true
8−rust-version.workspace = true
9−categories.workspace = true
10−repository.workspace = true
11−homepage.workspace = true
8+categories = ["editor"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
1211
1312# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1413
1514[dependencies]
16−helix-core = { path = "../helix-core" }
17−helix-loader = { path = "../helix-loader" }
18−helix-parsec = { path = "../helix-parsec" }
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" }
1918
2019anyhow = "1.0"
2120futures-executor = "0.3"
2221futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
23−globset = "0.4.14"
2422log = "0.4"
25−lsp-types = { version = "0.95" }
23+lsp-types = { version = "0.94" }
2624serde = { version = "1.0", features = ["derive"] }
2725serde_json = "1.0"
2826thiserror = "1.0"
29−tokio = { version = "1.35", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
27+tokio = { version = "1.28", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
3028tokio-stream = "0.1.14"
31−which = "5.0.0"
29+which = "4.4"
3230parking_lot = "0.12.1"
helix-lsp/src/client.rs+40−146
@@ -7,9 +7,8 @@ use crate::{
77use helix_core::{find_workspace, path, syntax::LanguageServerFeature, ChangeSet, Rope};
88use helix_loader::{self, VERSION_AND_GIT_HASH};
99use lsp::{
10− notification::DidChangeWorkspaceFolders, CodeActionCapabilityResolveSupport,
11− DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, WorkspaceFolder,
12− WorkspaceFoldersChangeEvent,
10+ notification::DidChangeWorkspaceFolders, DidChangeWorkspaceFoldersParams, OneOf,
11+ PositionEncodingKind, WorkspaceFolder, WorkspaceFoldersChangeEvent,
1312};
1413use lsp_types as lsp;
1514use parking_lot::Mutex;
@@ -277,62 +276,65 @@ impl Client {
277276 .expect("language server not yet initialized!")
278277 }
279278
280− /// Client has to be initialized otherwise this function panics
281− #[inline]
279+ #[inline] // TODO inline?
282280 pub fn supports_feature(&self, feature: LanguageServerFeature) -> bool {
283− let capabilities = self.capabilities();
284−
285− use lsp::*;
281+ let capabilities = match self.capabilities.get() {
282+ Some(capabilities) => capabilities,
283+ None => return false, // not initialized, TODO unwrap/expect instead?
284+ };
286285 match feature {
287286 LanguageServerFeature::Format => matches!(
288287 capabilities.document_formatting_provider,
289− Some(OneOf::Left(true) | OneOf::Right(_))
288+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
290289 ),
291290 LanguageServerFeature::GotoDeclaration => matches!(
292291 capabilities.declaration_provider,
293292 Some(
294− DeclarationCapability::Simple(true)
295− | DeclarationCapability::RegistrationOptions(_)
296− | DeclarationCapability::Options(_),
293+ lsp::DeclarationCapability::Simple(true)
294+ | lsp::DeclarationCapability::RegistrationOptions(_)
295+ | lsp::DeclarationCapability::Options(_),
297296 )
298297 ),
299298 LanguageServerFeature::GotoDefinition => matches!(
300299 capabilities.definition_provider,
301− Some(OneOf::Left(true) | OneOf::Right(_))
300+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
302301 ),
303302 LanguageServerFeature::GotoTypeDefinition => matches!(
304303 capabilities.type_definition_provider,
305304 Some(
306− TypeDefinitionProviderCapability::Simple(true)
307− | TypeDefinitionProviderCapability::Options(_),
305+ lsp::TypeDefinitionProviderCapability::Simple(true)
306+ | lsp::TypeDefinitionProviderCapability::Options(_),
308307 )
309308 ),
310309 LanguageServerFeature::GotoReference => matches!(
311310 capabilities.references_provider,
312− Some(OneOf::Left(true) | OneOf::Right(_))
311+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
313312 ),
314313 LanguageServerFeature::GotoImplementation => matches!(
315314 capabilities.implementation_provider,
316315 Some(
317− ImplementationProviderCapability::Simple(true)
318− | ImplementationProviderCapability::Options(_),
316+ lsp::ImplementationProviderCapability::Simple(true)
317+ | lsp::ImplementationProviderCapability::Options(_),
319318 )
320319 ),
321320 LanguageServerFeature::SignatureHelp => capabilities.signature_help_provider.is_some(),
322321 LanguageServerFeature::Hover => matches!(
323322 capabilities.hover_provider,
324− Some(HoverProviderCapability::Simple(true) | HoverProviderCapability::Options(_),)
323+ Some(
324+ lsp::HoverProviderCapability::Simple(true)
325+ | lsp::HoverProviderCapability::Options(_),
326+ )
325327 ),
326328 LanguageServerFeature::DocumentHighlight => matches!(
327329 capabilities.document_highlight_provider,
328− Some(OneOf::Left(true) | OneOf::Right(_))
330+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
329331 ),
330332 LanguageServerFeature::Completion => capabilities.completion_provider.is_some(),
331333 LanguageServerFeature::CodeAction => matches!(
332334 capabilities.code_action_provider,
333335 Some(
334− CodeActionProviderCapability::Simple(true)
335− | CodeActionProviderCapability::Options(_),
336+ lsp::CodeActionProviderCapability::Simple(true)
337+ | lsp::CodeActionProviderCapability::Options(_),
336338 )
337339 ),
338340 LanguageServerFeature::WorkspaceCommand => {
@@ -340,20 +342,23 @@ impl Client {
340342 }
341343 LanguageServerFeature::DocumentSymbols => matches!(
342344 capabilities.document_symbol_provider,
343− Some(OneOf::Left(true) | OneOf::Right(_))
345+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
344346 ),
345347 LanguageServerFeature::WorkspaceSymbols => matches!(
346348 capabilities.workspace_symbol_provider,
347− Some(OneOf::Left(true) | OneOf::Right(_))
349+ Some(lsp::OneOf::Left(true) | lsp::OneOf::Right(_))
348350 ),
349351 LanguageServerFeature::Diagnostics => true, // there's no extra server capability
350352 LanguageServerFeature::RenameSymbol => matches!(
351353 capabilities.rename_provider,
352− Some(OneOf::Left(true)) | Some(OneOf::Right(_))
354+ Some(lsp::OneOf::Left(true)) | Some(lsp::OneOf::Right(_))
353355 ),
354356 LanguageServerFeature::InlayHints => matches!(
355357 capabilities.inlay_hint_provider,
356− Some(OneOf::Left(true) | OneOf::Right(InlayHintServerCapabilities::Options(_)))
358+ Some(
359+ lsp::OneOf::Left(true)
360+ | lsp::OneOf::Right(lsp::InlayHintServerCapabilities::Options(_))
361+ )
357362 ),
358363 }
359364 }
@@ -401,22 +406,12 @@ impl Client {
401406 &self,
402407 params: R::Params,
403408 ) -> 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>>
415409 where
416410 R::Params: serde::Serialize,
417411 {
418412 let server_tx = self.server_tx.clone();
419413 let id = self.next_request_id();
414+ let timeout_secs = self.req_timeout;
420415
421416 async move {
422417 use std::time::Duration;
@@ -554,15 +549,6 @@ impl Client {
554549 normalizes_line_endings: Some(false),
555550 change_annotation_support: None,
556551 }),
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− }),
566552 ..Default::default()
567553 }),
568554 text_document: Some(lsp::TextDocumentClientCapabilities {
@@ -629,12 +615,6 @@ impl Client {
629615 .collect(),
630616 },
631617 }),
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− }),
638618 ..Default::default()
639619 }),
640620 publish_diagnostics: Some(lsp::PublishDiagnosticsClientCapabilities {
@@ -667,7 +647,6 @@ impl Client {
667647 version: Some(String::from(VERSION_AND_GIT_HASH)),
668648 }),
669649 locale: None, // TODO
670− work_done_progress_params: lsp::WorkDoneProgressParams::default(),
671650 };
672651
673652 self.request::<lsp::request::Initialize>(params).await
@@ -716,65 +695,6 @@ impl Client {
716695 })
717696 }
718697
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−
778698 // -------------------------------------------------------------------------------------------
779699 // Text document
780700 // -------------------------------------------------------------------------------------------
@@ -970,19 +890,20 @@ impl Client {
970890 ) -> Option<impl Future<Output = Result<()>>> {
971891 let capabilities = self.capabilities.get().unwrap();
972892
973− let include_text = match &capabilities.text_document_sync.as_ref()? {
974− lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
975− save: options,
893+ let include_text = match &capabilities.text_document_sync {
894+ Some(lsp::TextDocumentSyncCapability::Options(lsp::TextDocumentSyncOptions {
895+ save: Some(options),
976896 ..
977− }) => match options.as_ref()? {
897+ })) => match options {
978898 lsp::TextDocumentSyncSaveOptions::Supported(true) => false,
979899 lsp::TextDocumentSyncSaveOptions::SaveOptions(lsp_types::SaveOptions {
980900 include_text,
981901 }) => include_text.unwrap_or(false),
982− lsp::TextDocumentSyncSaveOptions::Supported(false) => return None,
902+ // Supported(false)
903+ _ => return None,
983904 },
984− // see: https://github.com/microsoft/language-server-protocol/issues/288
985− lsp::TextDocumentSyncCapability::Kind(..) => false,
905+ // unsupported
906+ _ => return None,
986907 };
987908
988909 Some(self.notify::<lsp::notification::DidSaveTextDocument>(
@@ -1039,24 +960,6 @@ impl Client {
1039960 Some(self.call::<lsp::request::ResolveCompletionItem>(completion_item))
1040961 }
1041962
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−
1060963 pub fn text_document_signature_help(
1061964 &self,
1062965 text_document: lsp::TextDocumentIdentifier,
@@ -1531,13 +1434,4 @@ impl Client {
15311434
15321435 Some(self.call::<lsp::request::ExecuteCommand>(params))
15331436 }
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− }
15431437}
helix-lsp/src/file_event.rsdeleted−193
@@ -1,193 +0,0 @@
1−use std::{collections::HashMap, path::PathBuf, sync::Weak};
2−
3−use globset::{GlobBuilder, GlobSetBuilder};
4−use tokio::sync::mpsc;
5−
6−use crate::{lsp, Client};
7−
8−enum Event {
9− FileChanged {
10− path: PathBuf,
11− },
12− Register {
13− client_id: usize,
14− client: Weak<Client>,
15− registration_id: String,
16− options: lsp::DidChangeWatchedFilesRegistrationOptions,
17− },
18− Unregister {
19− client_id: usize,
20− registration_id: String,
21− },
22− RemoveClient {
23− client_id: usize,
24− },
25−}
26−
27−#[derive(Default)]
28−struct ClientState {
29− client: Weak<Client>,
30− registered: HashMap<String, globset::GlobSet>,
31−}
32−
33−/// The Handler uses a dedicated tokio task to respond to file change events by
34−/// forwarding changes to LSPs that have registered for notifications with a
35−/// matching glob.
36−///
37−/// When an LSP registers for the DidChangeWatchedFiles notification, the
38−/// Handler is notified by sending the registration details in addition to a
39−/// weak reference to the LSP client. This is done so that the Handler can have
40−/// access to the client without preventing the client from being dropped if it
41−/// is closed and the Handler isn't properly notified.
42−#[derive(Clone, Debug)]
43−pub struct Handler {
44− tx: mpsc::UnboundedSender<Event>,
45−}
46−
47−impl Default for Handler {
48− fn default() -> Self {
49− Self::new()
50− }
51−}
52−
53−impl Handler {
54− pub fn new() -> Self {
55− let (tx, rx) = mpsc::unbounded_channel();
56− tokio::spawn(Self::run(rx));
57− Self { tx }
58− }
59−
60− pub fn register(
61− &self,
62− client_id: usize,
63− client: Weak<Client>,
64− registration_id: String,
65− options: lsp::DidChangeWatchedFilesRegistrationOptions,
66− ) {
67− let _ = self.tx.send(Event::Register {
68− client_id,
69− client,
70− registration_id,
71− options,
72− });
73− }
74−
75− pub fn unregister(&self, client_id: usize, registration_id: String) {
76− let _ = self.tx.send(Event::Unregister {
77− client_id,
78− registration_id,
79− });
80− }
81−
82− pub fn file_changed(&self, path: PathBuf) {
83− let _ = self.tx.send(Event::FileChanged { path });
84− }
85−
86− pub fn remove_client(&self, client_id: usize) {
87− let _ = self.tx.send(Event::RemoveClient { client_id });
88− }
89−
90− async fn run(mut rx: mpsc::UnboundedReceiver<Event>) {
91− let mut state: HashMap<usize, ClientState> = HashMap::new();
92− while let Some(event) = rx.recv().await {
93− match event {
94− Event::FileChanged { path } => {
95− log::debug!("Received file event for {:?}", &path);
96−
97− state.retain(|id, client_state| {
98− if !client_state
99− .registered
100− .values()
101− .any(|glob| glob.is_match(&path))
102− {
103− return true;
104− }
105− let Some(client) = client_state.client.upgrade() else {
106− log::warn!("LSP client was dropped: {id}");
107− return false;
108− };
109− let Ok(uri) = lsp::Url::from_file_path(&path) else {
110− return true;
111− };
112− log::debug!(
113− "Sending didChangeWatchedFiles notification to client '{}'",
114− client.name()
115− );
116− if let Err(err) = crate::block_on(client
117− .did_change_watched_files(vec![lsp::FileEvent {
118− uri,
119− // We currently always send the CHANGED state
120− // since we don't actually have more context at
121− // the moment.
122− typ: lsp::FileChangeType::CHANGED,
123− }]))
124− {
125− log::warn!("Failed to send didChangeWatchedFiles notification to client: {err}");
126− }
127− true
128− });
129− }
130− Event::Register {
131− client_id,
132− client,
133− registration_id,
134− options: ops,
135− } => {
136− log::debug!(
137− "Registering didChangeWatchedFiles for client '{}' with id '{}'",
138− client_id,
139− registration_id
140− );
141−
142− let entry = state.entry(client_id).or_insert_with(ClientState::default);
143− entry.client = client;
144−
145− let mut builder = GlobSetBuilder::new();
146− for watcher in ops.watchers {
147− if let lsp::GlobPattern::String(pattern) = watcher.glob_pattern {
148− if let Ok(glob) = GlobBuilder::new(&pattern).build() {
149− builder.add(glob);
150− }
151− }
152− }
153− match builder.build() {
154− Ok(globset) => {
155− entry.registered.insert(registration_id, globset);
156− }
157− Err(err) => {
158− // Remove any old state for that registration id and
159− // remove the entire client if it's now empty.
160− entry.registered.remove(&registration_id);
161− if entry.registered.is_empty() {
162− state.remove(&client_id);
163− }
164− log::warn!(
165− "Unable to build globset for LSP didChangeWatchedFiles {err}"
166− )
167− }
168− }
169− }
170− Event::Unregister {
171− client_id,
172− registration_id,
173− } => {
174− log::debug!(
175− "Unregistering didChangeWatchedFiles with id '{}' for client '{}'",
176− registration_id,
177− client_id
178− );
179− if let Some(client_state) = state.get_mut(&client_id) {
180− client_state.registered.remove(&registration_id);
181− if client_state.registered.is_empty() {
182− state.remove(&client_id);
183− }
184− }
185− }
186− Event::RemoveClient { client_id } => {
187− log::debug!("Removing LSP client: {client_id}");
188− state.remove(&client_id);
189− }
190− }
191− }
192− }
193−}
helix-lsp/src/lib.rs+41−57
@@ -1,5 +1,4 @@
11mod client;
2−pub mod file_event;
32pub mod jsonrpc;
43pub mod snippet;
54mod transport;
@@ -378,7 +377,7 @@ pub mod util {
378377 .expect("transaction must be valid for primary selection");
379378 let removed_text = text.slice(removed_start..removed_end);
380379
381− let (transaction, mut selection) = Transaction::change_by_selection_ignore_overlapping(
380+ let (transaction, selection) = Transaction::change_by_selection_ignore_overlapping(
382381 doc,
383382 selection,
384383 |range| {
@@ -421,11 +420,6 @@ pub mod util {
421420 return transaction;
422421 }
423422
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);
429423 let mut mapped_selection = SmallVec::with_capacity(selection.len());
430424 let mut mapped_primary_idx = 0;
431425 let primary_range = selection.primary();
@@ -434,8 +428,9 @@ pub mod util {
434428 mapped_primary_idx = mapped_selection.len()
435429 }
436430
431+ let range = range.map(changes);
437432 let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty());
438− let Some(tabstops) = tabstops else {
433+ let Some(tabstops) = tabstops else{
439434 // no tabstop normal mapping
440435 mapped_selection.push(range);
441436 continue;
@@ -445,36 +440,36 @@ pub mod util {
445440 // the tabstop closest to the range simply replaces `head` while anchor remains in place
446441 // the remaining tabstops receive their own single-width cursor
447442 if range.head < range.anchor {
448− let last_idx = tabstops.len() - 1;
449− let last_tabstop = tabstop_anchor + tabstops[last_idx].0;
443+ let first_tabstop = tabstop_anchor + tabstops[0].1;
450444
451445 // if selection is forward but was moved to the right it is
452446 // contained entirely in the replacement text, just do a point
453447 // selection (fallback below)
454− if range.anchor > last_tabstop {
455− let range = Range::new(range.anchor, last_tabstop);
448+ if range.anchor >= first_tabstop {
449+ let range = Range::new(range.anchor, first_tabstop);
456450 mapped_selection.push(range);
457− let rem_tabstops = tabstops[..last_idx]
451+ let rem_tabstops = tabstops[1..]
458452 .iter()
459− .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
453+ .map(|tabstop| Range::point(tabstop_anchor + tabstop.1));
460454 mapped_selection.extend(rem_tabstops);
461455 continue;
462456 }
463457 } else {
464− let first_tabstop = tabstop_anchor + tabstops[0].0;
458+ let last_idx = tabstops.len() - 1;
459+ let last_tabstop = tabstop_anchor + tabstops[last_idx].1;
465460
466461 // if selection is forward but was moved to the right it is
467462 // contained entirely in the replacement text, just do a point
468463 // selection (fallback below)
469− if range.anchor < first_tabstop {
464+ if range.anchor <= last_tabstop {
470465 // we can't properly compute the the next grapheme
471466 // here because the transaction hasn't been applied yet
472467 // that is not a problem because the range gets grapheme aligned anyway
473468 // tough so just adding one will always cause head to be grapheme
474469 // aligned correctly when applied to the document
475− let range = Range::new(range.anchor, first_tabstop + 1);
470+ let range = Range::new(range.anchor, last_tabstop + 1);
476471 mapped_selection.push(range);
477− let rem_tabstops = tabstops[1..]
472+ let rem_tabstops = tabstops[..last_idx]
478473 .iter()
479474 .map(|tabstop| Range::point(tabstop_anchor + tabstop.0));
480475 mapped_selection.extend(rem_tabstops);
@@ -548,7 +543,6 @@ pub enum MethodCall {
548543 WorkspaceFolders,
549544 WorkspaceConfiguration(lsp::ConfigurationParams),
550545 RegisterCapability(lsp::RegistrationParams),
551− UnregisterCapability(lsp::UnregistrationParams),
552546}
553547
554548impl MethodCall {
@@ -572,10 +566,6 @@ impl MethodCall {
572566 let params: lsp::RegistrationParams = params.parse()?;
573567 Self::RegisterCapability(params)
574568 }
575− lsp::request::UnregisterCapability::METHOD => {
576− let params: lsp::UnregistrationParams = params.parse()?;
577− Self::UnregisterCapability(params)
578− }
579569 _ => {
580570 return Err(Error::Unhandled);
581571 }
@@ -635,7 +625,6 @@ pub struct Registry {
635625 syn_loader: Arc<helix_core::syntax::Loader>,
636626 counter: usize,
637627 pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
638− pub file_event_handler: file_event::Handler,
639628}
640629
641630impl Registry {
@@ -645,7 +634,6 @@ impl Registry {
645634 syn_loader,
646635 counter: 0,
647636 incoming: SelectAll::new(),
648− file_event_handler: file_event::Handler::new(),
649637 }
650638 }
651639
@@ -658,7 +646,6 @@ impl Registry {
658646 }
659647
660648 pub fn remove_by_id(&mut self, id: usize) {
661− self.file_event_handler.remove_client(id);
662649 self.inner.retain(|_, language_servers| {
663650 language_servers.retain(|ls| id != ls.id());
664651 !language_servers.is_empty()
@@ -678,8 +665,8 @@ impl Registry {
678665 .language_server_configs()
679666 .get(&name)
680667 .ok_or_else(|| anyhow::anyhow!("Language server '{name}' not defined"))?;
681− let id = self.counter;
682668 self.counter += 1;
669+ let id = self.counter;
683670 let NewClient(client, incoming) = start_client(
684671 id,
685672 name,
@@ -693,9 +680,6 @@ impl Registry {
693680 Ok(client)
694681 }
695682
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
699683 pub fn restart(
700684 &mut self,
701685 language_config: &LanguageConfiguration,
@@ -707,6 +691,7 @@ impl Registry {
707691 .language_servers
708692 .iter()
709693 .filter_map(|LanguageServerFeatures { name, .. }| {
694+ // #[allow(clippy::map_entry)]
710695 if self.inner.contains_key(name) {
711696 let client = match self.start_client(
712697 name.clone(),
@@ -723,8 +708,9 @@ impl Registry {
723708 .insert(name.clone(), vec![client.clone()])
724709 .unwrap();
725710
711+ // TODO what if there are different language servers for different workspaces,
712+ // I think the language servers will be stopped without being restarted, which is not intended
726713 for old_client in old_clients {
727− self.file_event_handler.remove_client(old_client.id());
728714 tokio::spawn(async move {
729715 let _ = old_client.force_shutdown().await;
730716 });
@@ -741,7 +727,6 @@ impl Registry {
741727 pub fn stop(&mut self, name: &str) {
742728 if let Some(clients) = self.inner.remove(name) {
743729 for client in clients {
744− self.file_event_handler.remove_client(client.id());
745730 tokio::spawn(async move {
746731 let _ = client.force_shutdown().await;
747732 });
@@ -749,40 +734,39 @@ impl Registry {
749734 }
750735 }
751736
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],
737+ pub fn get(
738+ &mut self,
739+ language_config: &LanguageConfiguration,
740+ doc_path: Option<&std::path::PathBuf>,
741+ root_dirs: &[PathBuf],
757742 enable_snippets: bool,
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)| {
743+ ) -> Result<HashMap<LanguageServerName, Arc<Client>>> {
744+ language_config
745+ .language_servers
746+ .iter()
747+ .map(|LanguageServerFeatures { name, .. }| {
748+ if let Some(clients) = self.inner.get_mut(name) {
749+ // clients.find(
750+
751+ if let Some((_, client)) = clients.iter_mut().enumerate().find(|(i, client)| {
763752 client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
764753 }) {
765− return (name.to_owned(), Ok(client.clone()));
754+ return Ok((name.to_owned(), client.clone()));
766755 }
756+ // return Ok((name.clone(), clients.clone()));
767757 }
768− match self.start_client(
758+ let client = self.start_client(
769759 name.clone(),
770760 language_config,
771761 doc_path,
772762 root_dirs,
773763 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− )
764+ )?;
765+ let clients = self.inner.entry(name.clone()).or_default();
766+ clients.push(client.clone());
767+ Ok((name.clone(), client))
768+ })
769+ .collect()
786770 }
787771
788772 pub fn iter_clients(&self) -> impl Iterator<Item = &Arc<Client>> {
@@ -946,7 +930,7 @@ pub fn find_lsp_workspace(
946930 let mut file = if file.is_absolute() {
947931 file.to_path_buf()
948932 } else {
949− let current_dir = helix_loader::current_working_dir();
933+ let current_dir = std::env::current_dir().expect("unable to determine current directory");
950934 current_dir.join(file)
951935 };
952936 file = path::get_normalized_path(&file);
helix-lsp/src/transport.rs+1−9
@@ -353,11 +353,6 @@ impl Transport {
353353 }
354354 }
355355
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−
361356 // TODO: events that use capabilities need to do the right thing
362357
363358 loop {
@@ -396,10 +391,7 @@ impl Transport {
396391 }
397392 msg = client_rx.recv() => {
398393 if let Some(msg) = msg {
399− if is_pending && is_shutdown(&msg) {
400− log::info!("Language server not initialized, shutting down");
401− break;
402− } else if is_pending && !is_initialize(&msg) {
394+ if is_pending && !is_initialize(&msg) {
403395 // ignore notifications
404396 if let Payload::Notification(_) = msg {
405397 continue;
helix-parsec/Cargo.toml+7−8
@@ -1,14 +1,13 @@
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"
37description = "Parser combinators for Helix"
8+categories = ["editor"]
9+repository = "https://github.com/helix-editor/helix"
10+homepage = "https://helix-editor.com"
411include = ["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
1312
1413[dependencies]
helix-term/Cargo.toml+25−33
@@ -1,16 +1,16 @@
11[package]
22name = "helix-term"
3+version = "0.6.0"
34description = "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"
411include = ["src/**/*", "README.md"]
512default-run = "hx"
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
13+rust-version = "1.65"
1414
1515[features]
1616default = ["git"]
@@ -23,22 +23,21 @@ name = "hx"
2323path = "src/main.rs"
2424
2525[dependencies]
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" }
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" }
3332
3433anyhow = "1"
35−once_cell = "1.19"
34+once_cell = "1.17"
3635
37−which = "5.0.0"
36+which = "4.4"
3837
3938tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
4039tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
41−crossterm = { version = "0.27", features = ["event-stream"] }
40+crossterm = { version = "0.26", features = ["event-stream"] }
4241signal-hook = "0.3"
4342tokio-stream = "0.1"
4443futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
@@ -50,17 +49,13 @@ chrono = { version = "0.4", default-features = false, features = ["clock"] }
5049log = "0.4"
5150
5251# File picker
53−nucleo.workspace = true
52+fuzzy-matcher = "0.3"
5453ignore = "0.4"
5554# markdown doc rendering
5655pulldown-cmark = { version = "0.9", default-features = false }
5756# file type detection
5857content_inspector = "0.2.4"
5958
60−# opening URLs
61−open = "5.0.1"
62−url = "2.5.0"
63−
6459# config
6560toml = "0.7"
6661
@@ -68,20 +63,17 @@ serde_json = "1.0"
6863serde = { version = "1.0", features = ["derive"] }
6964
7065# ripgrep for global search
71−grep-regex = "0.1.12"
72−grep-searcher = "0.1.13"
66+grep-regex = "0.1.11"
67+grep-searcher = "0.1.11"
7368
7469[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
7570signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
76−libc = "0.2.151"
77−
78−[target.'cfg(target_os = "macos")'.dependencies]
79−crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] }
71+libc = "0.2.144"
8072
8173[build-dependencies]
82−helix-loader = { path = "../helix-loader" }
74+helix-loader = { version = "0.6", path = "../helix-loader" }
8375
8476[dev-dependencies]
85−smallvec = "1.11"
86−indoc = "2.0.4"
87−tempfile = "3.8.1"
77+smallvec = "1.10"
78+indoc = "2.0.1"
79+tempfile = "3.4.0"
helix-term/src/application.rs+142−239
@@ -1,16 +1,11 @@
11use arc_swap::{access::Map, ArcSwap};
22use futures_util::Stream;
33use helix_core::{
4− chars::char_is_word,
54 diagnostic::{DiagnosticTag, NumberOrString},
65 path::get_relative_path,
76 pos_at_coords, syntax, Selection,
87};
9−use helix_lsp::{
10− lsp::{self, notification::Notification},
11− util::lsp_pos_to_pos,
12− LspProgressMap,
13−};
8+use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
149use helix_view::{
1510 align_view,
1611 document::DocumentSavedEventResult,
@@ -34,9 +29,13 @@ use crate::{
3429};
3530
3631use log::{debug, error, warn};
37−#[cfg(not(feature = "integration"))]
38−use std::io::stdout;
39−use std::{collections::btree_map::Entry, io::stdin, path::Path, sync::Arc};
32+use std::{
33+ collections::btree_map::Entry,
34+ io::{stdin, stdout},
35+ path::Path,
36+ sync::Arc,
37+ time::{Duration, Instant},
38+};
4039
4140use anyhow::{Context, Error};
4241
@@ -46,6 +45,8 @@ use {signal_hook::consts::signal, signal_hook_tokio::Signals};
4645#[cfg(windows)]
4746type Signals = futures_util::stream::Empty<()>;
4847
48+const LSP_DEADLINE: Duration = Duration::from_millis(16);
49+
4950#[cfg(not(feature = "integration"))]
5051use tui::backend::CrosstermBackend;
5152
@@ -75,6 +76,7 @@ pub struct Application {
7576 signals: Signals,
7677 jobs: Jobs,
7778 lsp_progress: LspProgressMap,
79+ last_render: Instant,
7880}
7981
8082#[cfg(feature = "integration")]
@@ -161,21 +163,17 @@ impl Application {
161163 let path = helix_loader::runtime_file(Path::new("tutor"));
162164 editor.open(&path, Action::VerticalSplit)?;
163165 // Unset path to prevent accidentally saving to the original tutor file.
164− doc_mut!(editor).set_path(None);
166+ doc_mut!(editor).set_path(None)?;
165167 } else if !args.files.is_empty() {
166− let mut files_it = args.files.into_iter().peekable();
167−
168− // If the first file is a directory, skip it and open a picker
169− if let Some((first, _)) = files_it.next_if(|(p, _)| p.is_dir()) {
170− let picker = ui::file_picker(first, &config.load().editor);
168+ let first = &args.files[0].0; // we know it's not empty
169+ if first.is_dir() {
170+ std::env::set_current_dir(first).context("set current dir")?;
171+ editor.new_file(Action::VerticalSplit);
172+ let picker = ui::file_picker(".".into(), &config.load().editor);
171173 compositor.push(Box::new(overlaid(picker)));
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;
174+ } else {
175+ let nr_of_files = args.files.len();
176+ for (i, (file, pos)) in args.files.into_iter().enumerate() {
179177 if file.is_dir() {
180178 return Err(anyhow::anyhow!(
181179 "expected a path to file, found a directory. (to open a directory pass it as first argument)"
@@ -187,7 +185,7 @@ impl Application {
187185 // option. If neither of those two arguments are passed
188186 // in, just load the files normally.
189187 let action = match args.split {
190− _ if nr_of_files == 1 => Action::VerticalSplit,
188+ _ if i == 0 => Action::VerticalSplit,
191189 Some(Layout::Vertical) => Action::VerticalSplit,
192190 Some(Layout::Horizontal) => Action::HorizontalSplit,
193191 None => Action::Load,
@@ -214,11 +212,14 @@ impl Application {
214212 // does not affect views without pos since it is at the top
215213 let (view, doc) = current!(editor);
216214 align_view(doc, view, Align::Center);
217− } else {
218− editor.new_file(Action::VerticalSplit);
219215 }
220216 } else if stdin().is_tty() || cfg!(feature = "integration") {
221217 editor.new_file(Action::VerticalSplit);
218+ } else if cfg!(target_os = "macos") {
219+ // On Linux and Windows, we allow the output of a command to be piped into the new buffer.
220+ // This doesn't currently work on macOS because of the following issue:
221+ // https://github.com/crossterm-rs/crossterm/issues/500
222+ anyhow::bail!("Piping into helix-term is currently not supported on macOS");
222223 } else {
223224 editor
224225 .new_file_from_stdin(Action::VerticalSplit)
@@ -230,14 +231,8 @@ impl Application {
230231 #[cfg(windows)]
231232 let signals = futures_util::stream::empty();
232233 #[cfg(not(windows))]
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")?;
234+ let signals = Signals::new([signal::SIGTSTP, signal::SIGCONT, signal::SIGUSR1])
235+ .context("build signal handler")?;
241236
242237 let app = Self {
243238 compositor,
@@ -252,25 +247,29 @@ impl Application {
252247 signals,
253248 jobs: Jobs::new(),
254249 lsp_progress: LspProgressMap::new(),
250+ last_render: Instant::now(),
255251 };
256252
257253 Ok(app)
258254 }
259255
260256 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−
266257 let mut cx = crate::compositor::Context {
267258 editor: &mut self.editor,
268259 jobs: &mut self.jobs,
269260 scroll: None,
270261 };
271262
272− helix_event::start_frame();
263+ // Acquire mutable access to the redraw_handle lock
264+ // to ensure that there are no tasks running that want to block rendering
265+ drop(cx.editor.redraw_handle.1.write().await);
273266 cx.editor.needs_redraw = false;
267+ {
268+ // exhaust any leftover redraw notifications
269+ let notify = cx.editor.redraw_handle.0.notified();
270+ tokio::pin!(notify);
271+ notify.enable();
272+ }
274273
275274 let area = self
276275 .terminal
@@ -292,9 +291,10 @@ impl Application {
292291
293292 pub async fn event_loop<S>(&mut self, input_stream: &mut S)
294293 where
295− S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
294+ S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
296295 {
297296 self.render().await;
297+ self.last_render = Instant::now();
298298
299299 loop {
300300 if !self.event_loop_until_idle(input_stream).await {
@@ -305,7 +305,7 @@ impl Application {
305305
306306 pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool
307307 where
308− S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
308+ S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
309309 {
310310 loop {
311311 if self.editor.should_close() {
@@ -318,9 +318,7 @@ impl Application {
318318 biased;
319319
320320 Some(signal) = self.signals.next() => {
321− if !self.handle_signals(signal).await {
322− return false;
323− };
321+ self.handle_signals(signal).await;
324322 }
325323 Some(event) = input_stream.next() => {
326324 self.handle_terminal_events(event).await;
@@ -444,12 +442,10 @@ impl Application {
444442
445443 #[cfg(windows)]
446444 // no signal handling available on windows
447− pub async fn handle_signals(&mut self, _signal: ()) -> bool {
448− true
449− }
445+ pub async fn handle_signals(&mut self, _signal: ()) {}
450446
451447 #[cfg(not(windows))]
452− pub async fn handle_signals(&mut self, signal: i32) -> bool {
448+ pub async fn handle_signals(&mut self, signal: i32) {
453449 match signal {
454450 signal::SIGTSTP => {
455451 self.restore_term().unwrap();
@@ -503,14 +499,8 @@ impl Application {
503499 self.refresh_config();
504500 self.render().await;
505501 }
506− signal::SIGTERM | signal::SIGINT => {
507− self.restore_term().unwrap();
508− return false;
509− }
510502 _ => unreachable!(),
511503 }
512−
513− true
514504 }
515505
516506 pub async fn handle_idle_timeout(&mut self) {
@@ -558,7 +548,16 @@ impl Application {
558548 let bytes = doc_save_event.text.len_bytes();
559549
560550 if doc.path() != Some(&doc_save_event.path) {
561− doc.set_path(Some(&doc_save_event.path));
551+ if let Err(err) = doc.set_path(Some(&doc_save_event.path)) {
552+ log::error!(
553+ "error setting path for doc '{:?}': {}",
554+ doc.path(),
555+ err.to_string(),
556+ );
557+
558+ self.editor.set_error(err.to_string());
559+ return;
560+ }
562561
563562 let loader = self.editor.syn_loader.clone();
564563
@@ -594,7 +593,12 @@ impl Application {
594593 EditorEvent::LanguageServerMessage((id, call)) => {
595594 self.handle_language_server_message(call, id).await;
596595 // limit render calls for fast language server messages
597− helix_event::request_redraw();
596+ let last = self.editor.language_servers.incoming.is_empty();
597+
598+ if last || self.last_render.elapsed() > LSP_DEADLINE {
599+ self.render().await;
600+ self.last_render = Instant::now();
601+ }
598602 }
599603 EditorEvent::DebuggerEvent(payload) => {
600604 let needs_render = self.editor.handle_debugger_message(payload).await;
@@ -602,9 +606,6 @@ impl Application {
602606 self.render().await;
603607 }
604608 }
605− EditorEvent::Redraw => {
606− self.render().await;
607− }
608609 EditorEvent::IdleTimer => {
609610 self.editor.clear_idle_timer();
610611 self.handle_idle_timeout().await;
@@ -619,7 +620,10 @@ impl Application {
619620 false
620621 }
621622
622− pub async fn handle_terminal_events(&mut self, event: std::io::Result<CrosstermEvent>) {
623+ pub async fn handle_terminal_events(
624+ &mut self,
625+ event: Result<CrosstermEvent, crossterm::ErrorKind>,
626+ ) {
623627 let mut cx = crate::compositor::Context {
624628 editor: &mut self.editor,
625629 jobs: &mut self.jobs,
@@ -661,7 +665,7 @@ impl Application {
661665
662666 macro_rules! language_server {
663667 () => {
664− match self.editor.language_server_by_id(server_id) {
668+ match self.editor.language_servers.get_by_id(server_id) {
665669 Some(language_server) => language_server,
666670 None => {
667671 warn!("can't find language server with id `{}`", server_id);
@@ -718,7 +722,7 @@ impl Application {
718722 ));
719723 }
720724 }
721− Notification::PublishDiagnostics(mut params) => {
725+ Notification::PublishDiagnostics(params) => {
722726 let path = match params.uri.to_file_path() {
723727 Ok(path) => path,
724728 Err(_) => {
@@ -726,75 +730,32 @@ impl Application {
726730 return;
727731 }
728732 };
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− }
733+ let offset_encoding = language_server!().offset_encoding();
734+ let doc = self.editor.document_by_path_mut(&path).filter(|doc| {
735+ if let Some(version) = params.version {
736+ if version != doc.version() {
737+ log::info!("Version ({version}) is out of date for {path:?} (expected ({}), dropping PublishDiagnostic notification", doc.version());
738+ return false;
744739 }
745− true
746− });
740+ }
741+
742+ true
743+ });
747744
748745 if let Some(doc) = doc {
749− let lang_conf = doc.language.clone();
750− let text = doc.text().clone();
751−
752− let mut unchaged_diag_sources_ = Vec::new();
753− if let Some(lang_conf) = &lang_conf {
754− if let Some(old_diagnostics) =
755− self.editor.diagnostics.get(&params.uri)
756− {
757− if !lang_conf.persistent_diagnostic_sources.is_empty() {
758− // Sort diagnostics first by severity and then by line numbers.
759− // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
760− params
761− .diagnostics
762− .sort_unstable_by_key(|d| (d.severity, d.range.start));
763− }
764− for source in &lang_conf.persistent_diagnostic_sources {
765− let new_diagnostics = params
766− .diagnostics
767− .iter()
768− .filter(|d| d.source.as_ref() == Some(source));
769− let old_diagnostics = old_diagnostics
770− .iter()
771− .filter(|(d, d_server)| {
772− *d_server == server_id
773− && d.source.as_ref() == Some(source)
774− })
775− .map(|(d, _)| d);
776− if new_diagnostics.eq(old_diagnostics) {
777− unchaged_diag_sources_.push(source.clone())
778− }
779− }
780− }
781− }
746+ let lang_conf = doc.language_config();
747+ let text = doc.text();
782748
783− let unchaged_diag_sources = &unchaged_diag_sources_;
784− let diagnostics =
785− params.diagnostics.iter().filter_map(move |diagnostic| {
749+ let diagnostics = params
750+ .diagnostics
751+ .iter()
752+ .filter_map(|diagnostic| {
786753 use helix_core::diagnostic::{Diagnostic, Range, Severity::*};
787754 use lsp::DiagnosticSeverity;
788755
789− if diagnostic.source.as_ref().map_or(false, |source| {
790− unchaged_diag_sources.contains(source)
791− }) {
792− return None;
793− }
794−
795756 // TODO: convert inside server
796757 let start = if let Some(start) = lsp_pos_to_pos(
797− &text,
758+ text,
798759 diagnostic.range.start,
799760 offset_encoding,
800761 ) {
@@ -805,13 +766,14 @@ impl Application {
805766 };
806767
807768 let end = if let Some(end) =
808− lsp_pos_to_pos(&text, diagnostic.range.end, offset_encoding)
769+ lsp_pos_to_pos(text, diagnostic.range.end, offset_encoding)
809770 {
810771 end
811772 } else {
812773 log::warn!("lsp position out of bounds - {:?}", diagnostic);
813774 return None;
814775 };
776+
815777 let severity =
816778 diagnostic.severity.map(|severity| match severity {
817779 DiagnosticSeverity::ERROR => Error,
@@ -824,7 +786,7 @@ impl Application {
824786 ),
825787 });
826788
827− if let Some(lang_conf) = &lang_conf {
789+ if let Some(lang_conf) = lang_conf {
828790 if let Some(severity) = severity {
829791 if severity < lang_conf.diagnostic_severity {
830792 return None;
@@ -863,17 +825,8 @@ impl Application {
863825 Vec::new()
864826 };
865827
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−
872828 Some(Diagnostic {
873829 range: Range { start, end },
874− ends_at_word,
875− starts_at_word,
876− zero_width: start == end,
877830 line: diagnostic.range.start.line as usize,
878831 message: diagnostic.message.clone(),
879832 severity,
@@ -883,33 +836,38 @@ impl Application {
883836 data: diagnostic.data.clone(),
884837 language_server_id: server_id,
885838 })
886− });
839+ })
840+ .collect();
887841
888− doc.replace_diagnostics(diagnostics, unchaged_diag_sources, server_id);
842+ doc.replace_diagnostics(diagnostics, server_id);
889843 }
890844
891− let diagnostics = params.diagnostics.into_iter().map(|d| (d, server_id));
845+ let mut diagnostics = params
846+ .diagnostics
847+ .into_iter()
848+ .map(|d| (d, server_id))
849+ .collect();
892850
893851 // Insert the original lsp::Diagnostics here because we may have no open document
894852 // for diagnosic message and so we can't calculate the exact position.
895853 // When using them later in the diagnostics picker, we calculate them on-demand.
896− let diagnostics = match self.editor.diagnostics.entry(params.uri) {
854+ match self.editor.diagnostics.entry(params.uri) {
897855 Entry::Occupied(o) => {
898856 let current_diagnostics = o.into_mut();
899857 // there may entries of other language servers, which is why we can't overwrite the whole entry
900858 current_diagnostics.retain(|(_, lsp_id)| *lsp_id != server_id);
901− current_diagnostics.extend(diagnostics);
902− current_diagnostics
859+ current_diagnostics.append(&mut diagnostics);
903860 // Sort diagnostics first by severity and then by line numbers.
861+ // Note: The `lsp::DiagnosticSeverity` enum is already defined in decreasing order
862+ current_diagnostics
863+ .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
864+ }
865+ Entry::Vacant(v) => {
866+ diagnostics
867+ .sort_unstable_by_key(|(d, _)| (d.severity, d.range.start));
868+ v.insert(diagnostics);
904869 }
905− Entry::Vacant(v) => v.insert(diagnostics.collect()),
906870 };
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− });
913871 }
914872 Notification::ShowMessage(params) => {
915873 log::warn!("unhandled window/showMessage: {:?}", params);
@@ -1006,18 +964,22 @@ impl Application {
1006964 Notification::Exit => {
1007965 self.editor.set_status("Language server exited");
1008966
1009− // LSPs may produce diagnostics for files that haven't been opened in helix,
1010− // we need to clear those and remove the entries from the list if this leads to
1011− // an empty diagnostic list for said files
1012− for diags in self.editor.diagnostics.values_mut() {
1013− diags.retain(|(_, lsp_id)| *lsp_id != server_id);
1014− }
1015−
1016− self.editor.diagnostics.retain(|_, diags| !diags.is_empty());
1017−
1018967 // Clear any diagnostics for documents with this server open.
1019− for doc in self.editor.documents_mut() {
1020− doc.clear_diagnostics(server_id);
968+ let urls: Vec<_> = self
969+ .editor
970+ .documents_mut()
971+ .filter_map(|doc| {
972+ if doc.supports_language_server(server_id) {
973+ doc.clear_diagnostics(server_id);
974+ doc.url()
975+ } else {
976+ None
977+ }
978+ })
979+ .collect();
980+
981+ for url in urls {
982+ self.editor.diagnostics.remove(&url);
1021983 }
1022984
1023985 // Remove the language server from the registry.
@@ -1068,31 +1030,20 @@ impl Application {
10681030 Ok(serde_json::Value::Null)
10691031 }
10701032 Ok(MethodCall::ApplyWorkspaceEdit(params)) => {
1071− let language_server = language_server!();
1072− if language_server.is_initialized() {
1073− let offset_encoding = language_server.offset_encoding();
1074− let res = apply_workspace_edit(
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− }
1033+ let res = apply_workspace_edit(
1034+ &mut self.editor,
1035+ helix_lsp::OffsetEncoding::Utf8,
1036+ &params.edit,
1037+ );
1038+
1039+ Ok(json!(lsp::ApplyWorkspaceEditResponse {
1040+ applied: res.is_ok(),
1041+ failure_reason: res.as_ref().err().map(|err| err.kind.to_string()),
1042+ failed_change: res
1043+ .as_ref()
1044+ .err()
1045+ .map(|err| err.failed_change_idx as u32),
1046+ }))
10961047 }
10971048 Ok(MethodCall::WorkspaceFolders) => {
10981049 Ok(json!(&*language_server!().workspace_folders().await))
@@ -1117,65 +1068,17 @@ impl Application {
11171068 .collect();
11181069 Ok(json!(result))
11191070 }
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− }
1071+ Ok(MethodCall::RegisterCapability(_params)) => {
1072+ log::warn!("Ignoring a client/registerCapability request because dynamic capability registration is not enabled. Please report this upstream to the language server");
1073+ // Language Servers based on the `vscode-languageserver-node` library often send
1074+ // client/registerCapability even though we do not enable dynamic registration
1075+ // for any capabilities. We should send a MethodNotFound JSONRPC error in this
1076+ // case but that rejects the registration promise in the server which causes an
1077+ // exit. So we work around this by ignoring the request and sending back an OK
1078+ // response.
11601079
11611080 Ok(serde_json::Value::Null)
11621081 }
1163− Ok(MethodCall::UnregisterCapability(params)) => {
1164− for unreg in params.unregisterations {
1165− match unreg.method.as_str() {
1166− lsp::notification::DidChangeWatchedFiles::METHOD => {
1167− self.editor
1168− .language_servers
1169− .file_event_handler
1170− .unregister(server_id, unreg.id);
1171− }
1172− _ => {
1173− log::warn!("Received unregistration request for unsupported method: {}", unreg.method);
1174− }
1175− }
1176− }
1177− Ok(serde_json::Value::Null)
1178− }
11791082 };
11801083
11811084 tokio::spawn(language_server!().reply(id, reply));
@@ -1201,7 +1104,7 @@ impl Application {
12011104
12021105 pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
12031106 where
1204− S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
1107+ S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin,
12051108 {
12061109 self.claim_term().await?;
12071110
helix-term/src/args.rs−29
@@ -17,14 +17,12 @@ 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>,
2120}
2221
2322impl Args {
2423 pub fn parse_args() -> Result<Args> {
2524 let mut args = Args::default();
2625 let mut argv = std::env::args().peekable();
27− let mut line_number = 0;
2826
2927 argv.next(); // skip the program, we don't care about that
3028
@@ -61,20 +59,6 @@ impl Args {
6159 Some(path) => args.log_file = Some(path.into()),
6260 None => anyhow::bail!("--log must specify a path to write"),
6361 },
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− },
7862 arg if arg.starts_with("--") => {
7963 anyhow::bail!("unexpected double dash argument: {}", arg)
8064 }
@@ -89,13 +73,6 @@ impl Args {
8973 }
9074 }
9175 }
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− }
9976 arg => args.files.push(parse_file(arg)),
10077 }
10178 }
@@ -105,12 +82,6 @@ impl Args {
10582 args.files.push(parse_file(&arg));
10683 }
10784
108− if let Some(file) = args.files.first_mut() {
109− if line_number != 0 {
110− file.1.row = line_number;
111− }
112− }
113−
11485 Ok(args)
11586 }
11687}
helix-term/src/commands.rs+435−810
@@ -29,11 +29,12 @@ use helix_core::{
2929 tree_sitter::Node,
3030 unicode::width::UnicodeWidthChar,
3131 visual_offset_from_block, Deletion, LineEnding, Position, Range, Rope, RopeGraphemes,
32− RopeReader, RopeSlice, Selection, SmallVec, Tendril, Transaction,
32+ RopeSlice, Selection, SmallVec, Tendril, Transaction,
3333};
3434use helix_view::{
35+ clipboard::ClipboardType,
3536 document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
36− editor::{Action, CompleteAction},
37+ editor::{Action, CompleteAction, Motion},
3738 info::Info,
3839 input::KeyEvent,
3940 keyboard::KeyCode,
@@ -43,6 +44,7 @@ use helix_view::{
4344};
4445
4546use anyhow::{anyhow, bail, ensure, Context as _};
47+use fuzzy_matcher::FuzzyMatcher;
4648use insert::*;
4749use movement::Movement;
4850
@@ -53,20 +55,15 @@ use crate::{
5355 job::Callback,
5456 keymap::ReverseKeymap,
5557 ui::{
56− self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, CompletionItem, Picker,
57− Popup, Prompt, PromptEvent,
58+ self, editor::InsertEvent, lsp::SignatureHelp, overlay::overlaid, CompletionItem,
59+ FilePicker, Picker, Popup, Prompt, PromptEvent,
5860 },
5961};
6062
6163use crate::job::{self, Jobs};
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−};
64+use futures_util::{stream::FuturesUnordered, StreamExt, TryStreamExt};
65+use std::{collections::HashMap, fmt, future::Future};
66+use std::{collections::HashSet, num::NonZeroUsize};
7067
7168use std::{
7269 borrow::Cow,
@@ -75,11 +72,11 @@ use std::{
7572
7673use once_cell::sync::Lazy;
7774use serde::de::{self, Deserialize, Deserializer};
78−use url::Url;
7975
8076use grep_regex::RegexMatcherBuilder;
8177use grep_searcher::{sinks, BinaryDetection, SearcherBuilder};
8278use ignore::{DirEntry, WalkBuilder, WalkState};
79+use tokio_stream::wrappers::UnboundedReceiverStream;
8380
8481pub type OnKeyCallback = Box<dyn FnOnce(&mut Context, KeyEvent)>;
8582
@@ -250,9 +247,6 @@ impl MappableCommand {
250247 move_next_long_word_start, "Move to start of next long word",
251248 move_prev_long_word_start, "Move to start of previous long word",
252249 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",
256250 extend_next_word_start, "Extend to start of next word",
257251 extend_prev_word_start, "Extend to start of previous word",
258252 extend_next_word_end, "Extend to end of next word",
@@ -260,9 +254,6 @@ impl MappableCommand {
260254 extend_next_long_word_start, "Extend to start of next long word",
261255 extend_prev_long_word_start, "Extend to start of previous long word",
262256 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",
266257 find_till_char, "Move till next occurrence of char",
267258 find_next_char, "Move to next occurrence of char",
268259 extend_till_char, "Extend till next occurrence of char",
@@ -284,7 +275,6 @@ impl MappableCommand {
284275 select_regex, "Select all regex matches inside selections",
285276 split_selection, "Split selections on regex matches",
286277 split_selection_on_newline, "Split selection on newlines",
287− merge_selections, "Merge selections",
288278 merge_consecutive_selections, "Merge consecutive selections",
289279 search, "Search for regex pattern",
290280 rsearch, "Reverse search for regex pattern",
@@ -337,7 +327,7 @@ impl MappableCommand {
337327 goto_implementation, "Goto implementation",
338328 goto_file_start, "Goto line number <n> else file start",
339329 goto_file_end, "Goto file end",
340− goto_file, "Goto files/URLs in selection",
330+ goto_file, "Goto files in selection",
341331 goto_file_hsplit, "Goto files in selection (hsplit)",
342332 goto_file_vsplit, "Goto files in selection (vsplit)",
343333 goto_reference, "Goto references",
@@ -369,7 +359,6 @@ impl MappableCommand {
369359 extend_to_line_end, "Extend to line end",
370360 extend_to_line_end_newline, "Extend to line end",
371361 signature_help, "Show signature help",
372− smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.",
373362 insert_tab, "Insert tab char",
374363 insert_newline, "Insert newline char",
375364 delete_char_backward, "Delete previous char",
@@ -384,9 +373,6 @@ impl MappableCommand {
384373 later, "Move forward in history",
385374 commit_undo_checkpoint, "Commit changes to new checkpoint",
386375 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",
390376 yank_joined_to_clipboard, "Join and yank selections to clipboard",
391377 yank_main_selection_to_clipboard, "Yank main selection to clipboard",
392378 yank_joined_to_primary_clipboard, "Join and yank selections to primary clipboard",
@@ -417,7 +403,6 @@ impl MappableCommand {
417403 rotate_selections_backward, "Rotate selections backward",
418404 rotate_selection_contents_forward, "Rotate selection contents forward",
419405 rotate_selection_contents_backward, "Rotate selections contents backward",
420− reverse_selection_contents, "Reverse selections contents",
421406 expand_selection, "Expand selection to parent syntax node",
422407 shrink_selection, "Shrink selection to previously expanded syntax node",
423408 select_next_sibling, "Select next sibling in syntax tree",
@@ -990,7 +975,6 @@ fn align_selections(cx: &mut Context) {
990975
991976 let transaction = Transaction::change(doc.text(), changes.into_iter());
992977 doc.apply(&transaction, view.id);
993− exit_select_mode(cx);
994978}
995979
996980fn goto_window(cx: &mut Context, align: Align) {
@@ -1082,10 +1066,6 @@ fn move_prev_long_word_start(cx: &mut Context) {
10821066 move_word_impl(cx, movement::move_prev_long_word_start)
10831067}
10841068
1085−fn move_prev_long_word_end(cx: &mut Context) {
1086− move_word_impl(cx, movement::move_prev_long_word_end)
1087−}
1088−
10891069fn move_next_long_word_end(cx: &mut Context) {
10901070 move_word_impl(cx, movement::move_next_long_word_end)
10911071}
@@ -1110,7 +1090,8 @@ where
11101090 .transform(|range| move_fn(text, range, count, behavior));
11111091 doc.set_selection(view.id, selection);
11121092 };
1113− cx.editor.apply_motion(motion)
1093+ motion(cx.editor);
1094+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
11141095}
11151096
11161097fn goto_prev_paragraph(cx: &mut Context) {
@@ -1165,10 +1146,6 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11651146 let (view, doc) = current_ref!(cx.editor);
11661147 let text = doc.text();
11671148 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();
11721149 let mut paths: Vec<_> = selections
11731150 .iter()
11741151 .map(|r| text.slice(r.from()..r.to()).to_string())
@@ -1196,80 +1173,16 @@ fn goto_file_impl(cx: &mut Context, action: Action) {
11961173 .to_string(),
11971174 );
11981175 }
1199−
12001176 for sel in paths {
12011177 let p = sel.trim();
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) {
1178+ if !p.is_empty() {
1179+ if let Err(e) = cx.editor.open(&PathBuf::from(p), action) {
12501180 cx.editor.set_error(format!("Open file failed: {:?}", e));
12511181 }
12521182 }
12531183 }
12541184}
12551185
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−
12731186fn extend_word_impl<F>(cx: &mut Context, extend_fn: F)
12741187where
12751188 F: Fn(RopeSlice, Range, usize) -> Range,
@@ -1310,74 +1223,14 @@ fn extend_prev_long_word_start(cx: &mut Context) {
13101223 extend_word_impl(cx, movement::move_prev_long_word_start)
13111224}
13121225
1313−fn extend_prev_long_word_end(cx: &mut Context) {
1314− extend_word_impl(cx, movement::move_prev_long_word_end)
1315−}
1316−
13171226fn extend_next_long_word_end(cx: &mut Context) {
13181227 extend_word_impl(cx, movement::move_next_long_word_end)
13191228}
13201229
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) {
1230+fn will_find_char<F>(cx: &mut Context, search_fn: F, inclusive: bool, extend: bool)
1231+where
1232+ F: Fn(RopeSlice, char, usize, usize, bool) -> Option<usize> + 'static,
1233+{
13811234 // TODO: count is reset to 1 before next key so we move it into the closure here.
13821235 // Would be nice to carry over.
13831236 let count = cx.count();
@@ -1390,9 +1243,13 @@ fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bo
13901243 KeyEvent {
13911244 code: KeyCode::Enter,
13921245 ..
1393− } => {
1394− find_char_line_ending(cx, count, direction, inclusive, extend);
1395− return;
1246+ } =>
1247+ // TODO: this isn't quite correct when CRLF is involved.
1248+ // This hack will work in most cases, since documents don't
1249+ // usually mix line endings. But we should fix it eventually
1250+ // anyway.
1251+ {
1252+ doc!(cx.editor).line_ending.as_str().chars().next().unwrap()
13961253 }
13971254
13981255 KeyEvent {
@@ -1405,18 +1262,11 @@ fn find_char(cx: &mut Context, direction: Direction, inclusive: bool, extend: bo
14051262 } => ch,
14061263 _ => return,
14071264 };
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− };
14181265
1419− cx.editor.apply_motion(motion);
1266+ find_char_impl(cx.editor, &search_fn, inclusive, extend, ch, count);
1267+ cx.editor.last_motion = Some(Motion(Box::new(move |editor: &mut Editor| {
1268+ find_char_impl(editor, &search_fn, inclusive, true, ch, 1);
1269+ })));
14201270 })
14211271}
14221272
@@ -1495,39 +1345,46 @@ fn find_prev_char_impl(
14951345}
14961346
14971347fn find_till_char(cx: &mut Context) {
1498− find_char(cx, Direction::Forward, false, false);
1348+ will_find_char(cx, find_next_char_impl, false, false)
14991349}
15001350
15011351fn find_next_char(cx: &mut Context) {
1502− find_char(cx, Direction::Forward, true, false)
1352+ will_find_char(cx, find_next_char_impl, true, false)
15031353}
15041354
15051355fn extend_till_char(cx: &mut Context) {
1506− find_char(cx, Direction::Forward, false, true)
1356+ will_find_char(cx, find_next_char_impl, false, true)
15071357}
15081358
15091359fn extend_next_char(cx: &mut Context) {
1510− find_char(cx, Direction::Forward, true, true)
1360+ will_find_char(cx, find_next_char_impl, true, true)
15111361}
15121362
15131363fn till_prev_char(cx: &mut Context) {
1514− find_char(cx, Direction::Backward, false, false)
1364+ will_find_char(cx, find_prev_char_impl, false, false)
15151365}
15161366
15171367fn find_prev_char(cx: &mut Context) {
1518− find_char(cx, Direction::Backward, true, false)
1368+ will_find_char(cx, find_prev_char_impl, true, false)
15191369}
15201370
15211371fn extend_till_prev_char(cx: &mut Context) {
1522− find_char(cx, Direction::Backward, false, true)
1372+ will_find_char(cx, find_prev_char_impl, false, true)
15231373}
15241374
15251375fn extend_prev_char(cx: &mut Context) {
1526− find_char(cx, Direction::Backward, true, true)
1376+ will_find_char(cx, find_prev_char_impl, true, true)
15271377}
15281378
15291379fn repeat_last_motion(cx: &mut Context) {
1530− cx.editor.repeat_last_motion(cx.count())
1380+ let count = cx.count();
1381+ let last_motion = cx.editor.last_motion.take();
1382+ if let Some(m) = &last_motion {
1383+ for _ in 0..count {
1384+ m.run(cx.editor);
1385+ }
1386+ cx.editor.last_motion = last_motion;
1387+ }
15311388}
15321389
15331390fn replace(cx: &mut Context) {
@@ -1594,7 +1451,6 @@ where
15941451 });
15951452
15961453 doc.apply(&transaction, view.id);
1597− exit_select_mode(cx);
15981454}
15991455
16001456fn switch_case(cx: &mut Context) {
@@ -1836,8 +1692,8 @@ fn select_regex(cx: &mut Context) {
18361692 "select:".into(),
18371693 Some(reg),
18381694 ui::completers::none,
1839− move |cx, regex, event| {
1840− let (view, doc) = current!(cx.editor);
1695+ move |editor, regex, event| {
1696+ let (view, doc) = current!(editor);
18411697 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
18421698 return;
18431699 }
@@ -1858,8 +1714,8 @@ fn split_selection(cx: &mut Context) {
18581714 "split:".into(),
18591715 Some(reg),
18601716 ui::completers::none,
1861− move |cx, regex, event| {
1862− let (view, doc) = current!(cx.editor);
1717+ move |editor, regex, event| {
1718+ let (view, doc) = current!(editor);
18631719 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
18641720 return;
18651721 }
@@ -1881,12 +1737,6 @@ fn split_selection_on_newline(cx: &mut Context) {
18811737 doc.set_selection(view.id, selection);
18821738}
18831739
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−
18901740fn merge_consecutive_selections(cx: &mut Context) {
18911741 let (view, doc) = current!(cx.editor);
18921742 let selection = doc.selection(view.id).clone().merge_consecutive_ranges();
@@ -1982,11 +1832,11 @@ fn search_impl(
19821832
19831833fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
19841834 let mut items = reg
1985− .and_then(|reg| cx.editor.registers.read(reg, cx.editor))
1986− .map_or(Vec::new(), |reg| reg.take(200).collect());
1835+ .and_then(|reg| cx.editor.registers.get(reg))
1836+ .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect());
19871837 items.sort_unstable();
19881838 items.dedup();
1989− items.into_iter().map(|value| value.to_string()).collect()
1839+ items.into_iter().cloned().collect()
19901840}
19911841
19921842fn search(cx: &mut Context) {
@@ -2023,14 +1873,12 @@ fn searcher(cx: &mut Context, direction: Direction) {
20231873 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
20241874 .collect()
20251875 },
2026− move |cx, regex, event| {
2027− if event == PromptEvent::Validate {
2028− cx.editor.registers.last_search_register = reg;
2029− } else if event != PromptEvent::Update {
1876+ move |editor, regex, event| {
1877+ if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
20301878 return;
20311879 }
20321880 search_impl(
2033− cx.editor,
1881+ editor,
20341882 &contents,
20351883 &regex,
20361884 Movement::Move,
@@ -2045,13 +1893,11 @@ fn searcher(cx: &mut Context, direction: Direction) {
20451893
20461894fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Direction) {
20471895 let count = cx.count();
2048− let register = cx
2049− .register
2050− .unwrap_or(cx.editor.registers.last_search_register);
20511896 let config = cx.editor.config();
20521897 let scrolloff = config.scrolloff;
2053− if let Some(query) = cx.editor.registers.first(register, cx.editor) {
2054− let doc = doc!(cx.editor);
1898+ let (_, doc) = current!(cx.editor);
1899+ let registers = &cx.editor.registers;
1900+ if let Some(query) = registers.read('/').and_then(|query| query.last()) {
20551901 let contents = doc.text().slice(..).to_string();
20561902 let search_config = &config.search;
20571903 let case_insensitive = if search_config.smart_case {
@@ -2060,7 +1906,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
20601906 false
20611907 };
20621908 let wrap_around = search_config.wrap_around;
2063− if let Ok(regex) = RegexBuilder::new(&query)
1909+ if let Ok(regex) = RegexBuilder::new(query)
20641910 .case_insensitive(case_insensitive)
20651911 .multi_line(true)
20661912 .build()
@@ -2100,7 +1946,6 @@ fn extend_search_prev(cx: &mut Context) {
21001946}
21011947
21021948fn search_selection(cx: &mut Context) {
2103− let register = cx.register.unwrap_or('/');
21041949 let (view, doc) = current!(cx.editor);
21051950 let contents = doc.text().slice(..);
21061951
@@ -2113,24 +1958,13 @@ fn search_selection(cx: &mut Context) {
21131958 .collect::<Vec<_>>()
21141959 .join("|");
21151960
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− }
1961+ let msg = format!("register '{}' set to '{}'", '/', &regex);
1962+ cx.editor.registers.push('/', regex);
1963+ cx.editor.set_status(msg);
21241964}
21251965
21261966fn make_search_word_bounded(cx: &mut Context) {
2127− // Defaults to the active search register instead `/` to be more ergonomic assuming most people
2128− // would use this command following `search_selection`. This avoids selecting the register
2129− // twice.
2130− let register = cx
2131− .register
2132− .unwrap_or(cx.editor.registers.last_search_register);
2133− let regex = match cx.editor.registers.first(register, cx.editor) {
1967+ let regex = match cx.editor.registers.last('/') {
21341968 Some(regex) => regex,
21351969 None => return,
21361970 };
@@ -2148,19 +1982,14 @@ fn make_search_word_bounded(cx: &mut Context) {
21481982 if !start_anchored {
21491983 new_regex.push_str("\\b");
21501984 }
2151− new_regex.push_str(&regex);
1985+ new_regex.push_str(regex);
21521986 if !end_anchored {
21531987 new_regex.push_str("\\b");
21541988 }
21551989
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− }
1990+ let msg = format!("register '{}' set to '{}'", '/', &new_regex);
1991+ cx.editor.registers.push('/', new_regex);
1992+ cx.editor.set_status(msg);
21641993}
21651994
21661995fn global_search(cx: &mut Context) {
@@ -2199,11 +2028,13 @@ fn global_search(cx: &mut Context) {
21992028 }
22002029 }
22012030
2031+ let (all_matches_sx, all_matches_rx) = tokio::sync::mpsc::unbounded_channel::<FileResult>();
22022032 let config = cx.editor.config();
22032033 let smart_case = config.search.smart_case;
22042034 let file_picker_config = config.file_picker.clone();
22052035
22062036 let reg = cx.register.unwrap_or('/');
2037+
22072038 let completions = search_completions(cx, Some(reg));
22082039 ui::regex_prompt(
22092040 cx,
@@ -2216,67 +2047,43 @@ fn global_search(cx: &mut Context) {
22162047 .map(|comp| (0.., std::borrow::Cow::Owned(comp.clone())))
22172048 .collect()
22182049 },
2219− move |cx, regex, event| {
2050+ move |_editor, regex, event| {
22202051 if event != PromptEvent::Validate {
22212052 return;
22222053 }
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();
22312054
22322055 if let Ok(matcher) = RegexMatcherBuilder::new()
22332056 .case_smart(smart_case)
22342057 .build(regex.as_str())
22352058 {
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);
2059+ let searcher = SearcherBuilder::new()
2060+ .binary_detection(BinaryDetection::quit(b'\x00'))
2061+ .build();
22442062
2063+ let search_root = std::env::current_dir()
2064+ .expect("Global search error: Failed to get current dir");
22452065 let dedup_symlinks = file_picker_config.deduplicate_links;
22462066 let absolute_root = search_root
22472067 .canonicalize()
22482068 .unwrap_or_else(|_| search_root.clone());
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(|| {
2069+
2070+ WalkBuilder::new(search_root)
2071+ .hidden(file_picker_config.hidden)
2072+ .parents(file_picker_config.parents)
2073+ .ignore(file_picker_config.ignore)
2074+ .follow_links(file_picker_config.follow_symlinks)
2075+ .git_ignore(file_picker_config.git_ignore)
2076+ .git_global(file_picker_config.git_global)
2077+ .git_exclude(file_picker_config.git_exclude)
2078+ .max_depth(file_picker_config.max_depth)
2079+ .filter_entry(move |entry| {
2080+ filter_picker_entry(entry, &absolute_root, dedup_symlinks)
2081+ })
2082+ .build_parallel()
2083+ .run(|| {
22762084 let mut searcher = searcher.clone();
22772085 let matcher = matcher.clone();
2278− let injector = injector_.clone();
2279− let documents = &documents;
2086+ let all_matches_sx = all_matches_sx.clone();
22802087 Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState {
22812088 let entry = match entry {
22822089 Ok(entry) => entry,
@@ -2289,39 +2096,17 @@ fn global_search(cx: &mut Context) {
22892096 _ => return WalkState::Continue,
22902097 };
22912098
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− };
2099+ let result = searcher.search_path(
2100+ &matcher,
2101+ entry.path(),
2102+ sinks::UTF8(|line_num, _| {
2103+ all_matches_sx
2104+ .send(FileResult::new(entry.path(), line_num as usize - 1))
2105+ .unwrap();
2106+
2107+ Ok(true)
2108+ }),
2109+ );
23252110
23262111 if let Err(err) = result {
23272112 log::error!(
@@ -2330,66 +2115,67 @@ fn global_search(cx: &mut Context) {
23302115 err
23312116 );
23322117 }
2333− if stop {
2334− WalkState::Quit
2335− } else {
2336− WalkState::Continue
2337− }
2118+ WalkState::Continue
23382119 })
23392120 });
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− })
23872121 } else {
23882122 // Otherwise do nothing
23892123 // log::warn!("Global Search Invalid Pattern")
23902124 }
23912125 },
23922126 );
2127+
2128+ let current_path = doc_mut!(cx.editor).path().cloned();
2129+
2130+ let show_picker = async move {
2131+ let all_matches: Vec<FileResult> =
2132+ UnboundedReceiverStream::new(all_matches_rx).collect().await;
2133+ let call: job::Callback = Callback::EditorCompositor(Box::new(
2134+ move |editor: &mut Editor, compositor: &mut Compositor| {
2135+ if all_matches.is_empty() {
2136+ editor.set_status("No matches found");
2137+ return;
2138+ }
2139+
2140+ let picker = FilePicker::new(
2141+ all_matches,
2142+ current_path,
2143+ move |cx, FileResult { path, line_num }, action| {
2144+ match cx.editor.open(path, action) {
2145+ Ok(_) => {}
2146+ Err(e) => {
2147+ cx.editor.set_error(format!(
2148+ "Failed to open file '{}': {}",
2149+ path.display(),
2150+ e
2151+ ));
2152+ return;
2153+ }
2154+ }
2155+
2156+ let line_num = *line_num;
2157+ let (view, doc) = current!(cx.editor);
2158+ let text = doc.text();
2159+ if line_num >= text.len_lines() {
2160+ cx.editor.set_error("The line you jumped to does not exist anymore because the file has changed.");
2161+ return;
2162+ }
2163+ let start = text.line_to_char(line_num);
2164+ let end = text.line_to_char((line_num + 1).min(text.len_lines()));
2165+
2166+ doc.set_selection(view.id, Selection::single(start, end));
2167+ align_view(doc, view, Align::Center);
2168+ },
2169+ |_editor, FileResult { path, line_num }| {
2170+ Some((path.clone().into(), Some((*line_num, *line_num))))
2171+ },
2172+ );
2173+ compositor.push(Box::new(overlaid(picker)));
2174+ },
2175+ ));
2176+ Ok(call)
2177+ };
2178+ cx.jobs.callback(show_picker);
23932179}
23942180
23952181enum Extend {
@@ -2512,35 +2298,17 @@ enum Operation {
25122298 Change,
25132299}
25142300
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−
25292301fn delete_selection_impl(cx: &mut Context, op: Operation) {
25302302 let (view, doc) = current!(cx.editor);
25312303
25322304 let selection = doc.selection(view.id);
2533− let only_whole_lines = selection_is_linewise(selection, doc.text());
25342305
25352306 if cx.register != Some('_') {
25362307 // first yank the selection
25372308 let text = doc.text().slice(..);
25382309 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
25392310 let reg_name = cx.register.unwrap_or('"');
2540− if let Err(err) = cx.editor.registers.write(reg_name, values) {
2541− cx.editor.set_error(err.to_string());
2542− return;
2543− }
2311+ cx.editor.registers.write(reg_name, values);
25442312 };
25452313
25462314 // then delete
@@ -2554,11 +2322,7 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
25542322 exit_select_mode(cx);
25552323 }
25562324 Operation::Change => {
2557− if only_whole_lines {
2558− open_above(cx);
2559− } else {
2560− enter_insert_mode(cx);
2561− }
2325+ enter_insert_mode(cx);
25622326 }
25632327 }
25642328}
@@ -2680,10 +2444,6 @@ fn insert_mode(cx: &mut Context) {
26802444 .transform(|range| Range::new(range.to(), range.from()));
26812445
26822446 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();
26872447}
26882448
26892449// inserts at the end of each selection
@@ -2720,10 +2480,6 @@ fn append_mode(cx: &mut Context) {
27202480
27212481fn file_picker(cx: &mut Context) {
27222482 let root = find_workspace().0;
2723− if !root.exists() {
2724− cx.editor.set_error("Workspace directory does not exist");
2725− return;
2726− }
27272483 let picker = ui::file_picker(root, &cx.editor.config());
27282484 cx.push_layer(Box::new(overlaid(picker)));
27292485}
@@ -2744,14 +2500,8 @@ fn file_picker_in_current_buffer_directory(cx: &mut Context) {
27442500 let picker = ui::file_picker(path, &cx.editor.config());
27452501 cx.push_layer(Box::new(overlaid(picker)));
27462502}
2747−
27482503fn file_picker_in_current_directory(cx: &mut Context) {
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− }
2504+ let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("./"));
27552505 let picker = ui::file_picker(cwd, &cx.editor.config());
27562506 cx.push_layer(Box::new(overlaid(picker)));
27572507}
@@ -2810,18 +2560,22 @@ fn buffer_picker(cx: &mut Context) {
28102560 // mru
28112561 items.sort_unstable_by_key(|item| std::cmp::Reverse(item.focused_at));
28122562
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− });
2563+ let picker = FilePicker::new(
2564+ items,
2565+ (),
2566+ |cx, meta, action| {
2567+ cx.editor.switch(meta.id, action);
2568+ },
2569+ |editor, meta| {
2570+ let doc = &editor.documents.get(&meta.id)?;
2571+ let &view_id = doc.selections().keys().next()?;
2572+ let line = doc
2573+ .selection(view_id)
2574+ .primary()
2575+ .cursor_line(doc.text().slice(..));
2576+ Some((meta.id.into(), Some((line, line))))
2577+ },
2578+ );
28252579 cx.push_layer(Box::new(overlaid(picker)));
28262580}
28272581
@@ -2887,7 +2641,7 @@ fn jumplist_picker(cx: &mut Context) {
28872641 }
28882642 };
28892643
2890− let picker = Picker::new(
2644+ let picker = FilePicker::new(
28912645 cx.editor
28922646 .tree
28932647 .views()
@@ -2901,18 +2655,16 @@ fn jumplist_picker(cx: &mut Context) {
29012655 |cx, meta, action| {
29022656 cx.editor.switch(meta.id, action);
29032657 let config = cx.editor.config();
2904− let (view, doc) = (view_mut!(cx.editor), doc_mut!(cx.editor, &meta.id));
2658+ let (view, doc) = current!(cx.editor);
29052659 doc.set_selection(view.id, meta.selection.clone());
2906− if action.align_view(view, doc.id()) {
2907− view.ensure_cursor_in_view_center(doc, config.scrolloff);
2908− }
2660+ view.ensure_cursor_in_view_center(doc, config.scrolloff);
29092661 },
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− });
2662+ |editor, meta| {
2663+ let doc = &editor.documents.get(&meta.id)?;
2664+ let line = meta.selection.primary().cursor_line(doc.text().slice(..));
2665+ Some((meta.path.clone()?.into(), Some((line, line))))
2666+ },
2667+ );
29162668 cx.push_layer(Box::new(overlaid(picker)));
29172669}
29182670
@@ -2946,9 +2698,6 @@ impl ui::menu::Item for MappableCommand {
29462698}
29472699
29482700pub fn command_palette(cx: &mut Context) {
2949− let register = cx.register;
2950− let count = cx.count;
2951−
29522701 cx.callback = Some(Box::new(
29532702 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
29542703 let keymap = compositor.find::<ui::EditorView>().unwrap().keymaps.map()
@@ -2966,8 +2715,8 @@ pub fn command_palette(cx: &mut Context) {
29662715
29672716 let picker = Picker::new(commands, keymap, move |cx, command, _action| {
29682717 let mut ctx = Context {
2969− register,
2970− count,
2718+ register: None,
2719+ count: std::num::NonZeroUsize::new(1),
29712720 editor: cx.editor,
29722721 callback: None,
29732722 on_next_key_callback: None,
@@ -3006,88 +2755,24 @@ fn last_picker(cx: &mut Context) {
30062755 }));
30072756}
30082757
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.
2758+// I inserts at the first nonwhitespace character of each line with a selection
30172759fn insert_at_line_start(cx: &mut Context) {
3018− insert_with_indent(cx, IndentFallbackPos::LineStart);
2760+ goto_first_nonwhitespace(cx);
2761+ enter_insert_mode(cx);
30192762}
30202763
3021−// `A` inserts at the end of each line with a selection.
3022−// If the line is empty, automatically indent.
2764+// A inserts at the end of each line with a selection
30232765fn 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) {
30302766 enter_insert_mode(cx);
3031−
30322767 let (view, doc) = current!(cx.editor);
30332768
3034− let text = doc.text().slice(..);
3035− let contents = doc.text();
3036− let selection = doc.selection(view.id);
3037−
3038− let language_config = doc.language_config();
3039− let syntax = doc.syntax();
3040− let tab_width = doc.tab_width();
3041−
3042− let mut ranges = SmallVec::with_capacity(selection.len());
3043− let mut offs = 0;
3044−
3045− let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
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− }
2769+ let selection = doc.selection(view.id).clone().transform(|range| {
2770+ let text = doc.text().slice(..);
2771+ let line = range.cursor_line(text);
2772+ let pos = line_end_char_index(&text, line);
2773+ Range::new(pos, pos)
30872774 });
3088−
3089− transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
3090− doc.apply(&transaction, view.id);
2775+ doc.set_selection(view.id, selection);
30912776}
30922777
30932778// Creates an LspCallback that waits for formatting changes to be computed. When they're done,
@@ -3158,7 +2843,6 @@ fn open(cx: &mut Context, open: Open) {
31582843 Open::Below => graphemes::prev_grapheme_boundary(text, range.to()),
31592844 Open::Above => range.from(),
31602845 });
3161−
31622846 let new_line = match open {
31632847 // adjust position to the end of the line (next line - 1)
31642848 Open::Below => cursor_line + 1,
@@ -3166,15 +2850,13 @@ fn open(cx: &mut Context, open: Open) {
31662850 Open::Above => cursor_line,
31672851 };
31682852
3169− let line_num = new_line.saturating_sub(1);
3170−
31712853 // Index to insert newlines after, as well as the char width
31722854 // to use to compensate for those inserted newlines.
31732855 let (line_end_index, line_end_offset_width) = if new_line == 0 {
31742856 (0, 0)
31752857 } else {
31762858 (
3177− line_end_char_index(&text, line_num),
2859+ line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
31782860 doc.line_ending.len_chars(),
31792861 )
31802862 };
@@ -3182,15 +2864,13 @@ fn open(cx: &mut Context, open: Open) {
31822864 let indent = indent::indent_for_newline(
31832865 doc.language_config(),
31842866 doc.syntax(),
3185− &doc.config.load().indent_heuristic,
31862867 &doc.indent_style,
31872868 doc.tab_width(),
31882869 text,
3189− line_num,
2870+ new_line.saturating_sub(1),
31902871 line_end_index,
31912872 cursor_line,
31922873 );
3193−
31942874 let indent_len = indent.len();
31952875 let mut text = String::with_capacity(1 + indent_len);
31962876 text.push_str(doc.line_ending.as_str());
@@ -3489,7 +3169,8 @@ fn goto_next_change_impl(cx: &mut Context, direction: Direction) {
34893169
34903170 doc.set_selection(view.id, selection)
34913171 };
3492− cx.editor.apply_motion(motion);
3172+ motion(cx.editor);
3173+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
34933174}
34943175
34953176/// Returns the [Range] for a [Hunk] in the given text.
@@ -3616,7 +3297,6 @@ pub mod insert {
36163297 }
36173298
36183299 use helix_core::auto_pairs;
3619− use helix_view::editor::SmartTabConfig;
36203300
36213301 pub fn insert_char(cx: &mut Context, c: char) {
36223302 let (view, doc) = current_ref!(cx.editor);
@@ -3642,31 +3322,6 @@ pub mod insert {
36423322 }
36433323 }
36443324
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−
36703325 pub fn insert_tab(cx: &mut Context) {
36713326 let (view, doc) = current!(cx.editor);
36723327 // TODO: round out to nearest indentation level (for example a line with 3 spaces should
@@ -3722,7 +3377,6 @@ pub mod insert {
37223377 let indent = indent::indent_for_newline(
37233378 doc.language_config(),
37243379 doc.syntax(),
3725− &doc.config.load().indent_heuristic,
37263380 &doc.indent_style,
37273381 doc.tab_width(),
37283382 text,
@@ -3963,21 +3617,34 @@ fn commit_undo_checkpoint(cx: &mut Context) {
39633617// Yank / Paste
39643618
39653619fn yank(cx: &mut Context) {
3966− yank_impl(cx.editor, cx.register.unwrap_or('"'));
3967− exit_select_mode(cx);
3968−}
3620+ let (view, doc) = current!(cx.editor);
3621+ let text = doc.text().slice(..);
39693622
3970−fn yank_to_clipboard(cx: &mut Context) {
3971− yank_impl(cx.editor, '+');
3972− exit_select_mode(cx);
3973−}
3623+ let values: Vec<String> = doc
3624+ .selection(view.id)
3625+ .fragments(text)
3626+ .map(Cow::into_owned)
3627+ .collect();
3628+
3629+ let msg = format!(
3630+ "yanked {} selection(s) to register {}",
3631+ values.len(),
3632+ cx.register.unwrap_or('"')
3633+ );
39743634
3975−fn yank_to_primary_clipboard(cx: &mut Context) {
3976− yank_impl(cx.editor, '*');
3635+ cx.editor
3636+ .registers
3637+ .write(cx.register.unwrap_or('"'), values);
3638+
3639+ cx.editor.set_status(msg);
39773640 exit_select_mode(cx);
39783641}
39793642
3980−fn yank_impl(editor: &mut Editor, register: char) {
3643+fn yank_joined_to_clipboard_impl(
3644+ editor: &mut Editor,
3645+ separator: &str,
3646+ clipboard_type: ClipboardType,
3647+) -> anyhow::Result<()> {
39813648 let (view, doc) = current!(editor);
39823649 let text = doc.text().slice(..);
39833650
@@ -3986,79 +3653,74 @@ fn yank_impl(editor: &mut Editor, register: char) {
39863653 .fragments(text)
39873654 .map(Cow::into_owned)
39883655 .collect();
3989− let selections = values.len();
39903656
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−}
3657+ let clipboard_text = match clipboard_type {
3658+ ClipboardType::Clipboard => "system clipboard",
3659+ ClipboardType::Selection => "primary clipboard",
3660+ };
39993661
4000−fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
4001− let (view, doc) = current!(editor);
4002− let text = doc.text().slice(..);
3662+ let msg = format!(
3663+ "joined and yanked {} selection(s) to {}",
3664+ values.len(),
3665+ clipboard_text,
3666+ );
40033667
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− });
3668+ let joined = values.join(separator);
40153669
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−}
3670+ editor
3671+ .clipboard_provider
3672+ .set_contents(joined, clipboard_type)
3673+ .context("Couldn't set system clipboard content")?;
40243674
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);
4029−}
3675+ editor.set_status(msg);
40303676
4031−fn yank_joined_to_clipboard(cx: &mut Context) {
4032− let line_ending = doc!(cx.editor).line_ending;
4033− yank_joined_impl(cx.editor, line_ending.as_str(), '+');
4034− exit_select_mode(cx);
3677+ Ok(())
40353678}
40363679
4037−fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3680+fn yank_joined_to_clipboard(cx: &mut Context) {
40383681 let line_ending = doc!(cx.editor).line_ending;
4039− yank_joined_impl(cx.editor, line_ending.as_str(), '*');
3682+ let _ =
3683+ yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Clipboard);
40403684 exit_select_mode(cx);
40413685}
40423686
4043−fn yank_primary_selection_impl(editor: &mut Editor, register: char) {
3687+fn yank_main_selection_to_clipboard_impl(
3688+ editor: &mut Editor,
3689+ clipboard_type: ClipboardType,
3690+) -> anyhow::Result<()> {
40443691 let (view, doc) = current!(editor);
40453692 let text = doc.text().slice(..);
40463693
4047− let selection = doc.selection(view.id).primary().fragment(text).to_string();
3694+ let message_text = match clipboard_type {
3695+ ClipboardType::Clipboard => "yanked main selection to system clipboard",
3696+ ClipboardType::Selection => "yanked main selection to primary clipboard",
3697+ };
3698+
3699+ let value = doc.selection(view.id).primary().fragment(text);
40483700
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()),
3701+ if let Err(e) = editor
3702+ .clipboard_provider
3703+ .set_contents(value.into_owned(), clipboard_type)
3704+ {
3705+ bail!("Couldn't set system clipboard content: {}", e);
40523706 }
3707+
3708+ editor.set_status(message_text);
3709+ Ok(())
40533710}
40543711
40553712fn yank_main_selection_to_clipboard(cx: &mut Context) {
4056− yank_primary_selection_impl(cx.editor, '+');
4057− exit_select_mode(cx);
3713+ let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard);
3714+}
3715+
3716+fn yank_joined_to_primary_clipboard(cx: &mut Context) {
3717+ let line_ending = doc!(cx.editor).line_ending;
3718+ let _ =
3719+ yank_joined_to_clipboard_impl(cx.editor, line_ending.as_str(), ClipboardType::Selection);
40583720}
40593721
40603722fn yank_main_selection_to_primary_clipboard(cx: &mut Context) {
4061− yank_primary_selection_impl(cx.editor, '*');
3723+ let _ = yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection);
40623724 exit_select_mode(cx);
40633725}
40643726
@@ -4156,99 +3818,146 @@ pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) {
41563818 };
41573819 let (view, doc) = current!(cx.editor);
41583820 paste_impl(&[contents], doc, view, paste, count, cx.editor.mode);
4159− exit_select_mode(cx);
3821+}
3822+
3823+fn paste_clipboard_impl(
3824+ editor: &mut Editor,
3825+ action: Paste,
3826+ clipboard_type: ClipboardType,
3827+ count: usize,
3828+) -> anyhow::Result<()> {
3829+ let (view, doc) = current!(editor);
3830+ match editor.clipboard_provider.get_contents(clipboard_type) {
3831+ Ok(contents) => {
3832+ paste_impl(&[contents], doc, view, action, count, editor.mode);
3833+ Ok(())
3834+ }
3835+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
3836+ }
41603837}
41613838
41623839fn paste_clipboard_after(cx: &mut Context) {
4163− paste(cx.editor, '+', Paste::After, cx.count());
4164− exit_select_mode(cx);
3840+ let _ = paste_clipboard_impl(
3841+ cx.editor,
3842+ Paste::After,
3843+ ClipboardType::Clipboard,
3844+ cx.count(),
3845+ );
41653846}
41663847
41673848fn paste_clipboard_before(cx: &mut Context) {
4168− paste(cx.editor, '+', Paste::Before, cx.count());
4169− exit_select_mode(cx);
3849+ let _ = paste_clipboard_impl(
3850+ cx.editor,
3851+ Paste::Before,
3852+ ClipboardType::Clipboard,
3853+ cx.count(),
3854+ );
41703855}
41713856
41723857fn paste_primary_clipboard_after(cx: &mut Context) {
4173− paste(cx.editor, '*', Paste::After, cx.count());
4174− exit_select_mode(cx);
3858+ let _ = paste_clipboard_impl(
3859+ cx.editor,
3860+ Paste::After,
3861+ ClipboardType::Selection,
3862+ cx.count(),
3863+ );
41753864}
41763865
41773866fn paste_primary_clipboard_before(cx: &mut Context) {
4178− paste(cx.editor, '*', Paste::Before, cx.count());
4179− exit_select_mode(cx);
3867+ let _ = paste_clipboard_impl(
3868+ cx.editor,
3869+ Paste::Before,
3870+ ClipboardType::Selection,
3871+ cx.count(),
3872+ );
41803873}
41813874
41823875fn replace_with_yanked(cx: &mut Context) {
4183− replace_with_yanked_impl(cx.editor, cx.register.unwrap_or('"'), cx.count());
4184− exit_select_mode(cx);
3876+ let count = cx.count();
3877+ let reg_name = cx.register.unwrap_or('"');
3878+ let (view, doc) = current!(cx.editor);
3879+ let registers = &mut cx.editor.registers;
3880+
3881+ if let Some(values) = registers.read(reg_name) {
3882+ if !values.is_empty() {
3883+ let repeat = std::iter::repeat(
3884+ values
3885+ .last()
3886+ .map(|value| Tendril::from(&value.repeat(count)))
3887+ .unwrap(),
3888+ );
3889+ let mut values = values
3890+ .iter()
3891+ .map(|value| Tendril::from(&value.repeat(count)))
3892+ .chain(repeat);
3893+ let selection = doc.selection(view.id);
3894+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3895+ if !range.is_empty() {
3896+ (range.from(), range.to(), Some(values.next().unwrap()))
3897+ } else {
3898+ (range.from(), range.to(), None)
3899+ }
3900+ });
3901+
3902+ doc.apply(&transaction, view.id);
3903+ exit_select_mode(cx);
3904+ }
3905+ }
41853906}
41863907
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();
3908+fn replace_selections_with_clipboard_impl(
3909+ cx: &mut Context,
3910+ clipboard_type: ClipboardType,
3911+) -> anyhow::Result<()> {
3912+ let count = cx.count();
3913+ let (view, doc) = current!(cx.editor);
41923914
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)
3915+ match cx.editor.clipboard_provider.get_contents(clipboard_type) {
3916+ Ok(contents) => {
3917+ let selection = doc.selection(view.id);
3918+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
3919+ (
3920+ range.from(),
3921+ range.to(),
3922+ Some(contents.repeat(count).as_str().into()),
3923+ )
3924+ });
3925+
3926+ doc.apply(&transaction, view.id);
3927+ doc.append_changes_to_history(view);
42103928 }
4211− });
3929+ Err(e) => return Err(e.context("Couldn't get system clipboard contents")),
3930+ }
42123931
4213− doc.apply(&transaction, view.id);
3932+ exit_select_mode(cx);
3933+ Ok(())
42143934}
42153935
42163936fn replace_selections_with_clipboard(cx: &mut Context) {
4217− replace_with_yanked_impl(cx.editor, '+', cx.count());
4218− exit_select_mode(cx);
3937+ let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard);
42193938}
42203939
42213940fn replace_selections_with_primary_clipboard(cx: &mut Context) {
4222− replace_with_yanked_impl(cx.editor, '*', cx.count());
4223− exit_select_mode(cx);
3941+ let _ = replace_selections_with_clipboard_impl(cx, ClipboardType::Selection);
42243942}
42253943
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();
3944+fn paste(cx: &mut Context, pos: Paste) {
3945+ let count = cx.count();
3946+ let reg_name = cx.register.unwrap_or('"');
3947+ let (view, doc) = current!(cx.editor);
3948+ let registers = &mut cx.editor.registers;
42293949
4230− let (view, doc) = current!(editor);
4231− paste_impl(&values, doc, view, pos, count, editor.mode);
3950+ if let Some(values) = registers.read(reg_name) {
3951+ paste_impl(values, doc, view, pos, count, cx.editor.mode);
3952+ }
42323953}
42333954
42343955fn paste_after(cx: &mut Context) {
4235− paste(
4236− cx.editor,
4237− cx.register.unwrap_or('"'),
4238− Paste::After,
4239− cx.count(),
4240− );
4241− exit_select_mode(cx);
3956+ paste(cx, Paste::After)
42423957}
42433958
42443959fn paste_before(cx: &mut Context) {
4245− paste(
4246− cx.editor,
4247− cx.register.unwrap_or('"'),
4248− Paste::Before,
4249− cx.count(),
4250− );
4251− exit_select_mode(cx);
3960+ paste(cx, Paste::Before)
42523961}
42533962
42543963fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
@@ -4287,7 +3996,6 @@ fn indent(cx: &mut Context) {
42873996 }),
42883997 );
42893998 doc.apply(&transaction, view.id);
4290− exit_select_mode(cx);
42913999}
42924000
42934001fn unindent(cx: &mut Context) {
@@ -4327,7 +4035,6 @@ fn unindent(cx: &mut Context) {
43274035 let transaction = Transaction::change(doc.text(), changes.into_iter());
43284036
43294037 doc.apply(&transaction, view.id);
4330− exit_select_mode(cx);
43314038}
43324039
43334040fn format_selections(cx: &mut Context) {
@@ -4357,7 +4064,7 @@ fn format_selections(cx: &mut Context) {
43574064 })
43584065 else {
43594066 cx.editor
4360− .set_error("No configured language server supports range formatting");
4067+ .set_error("No configured language server does not support range formatting");
43614068 return;
43624069 };
43634070
@@ -4394,9 +4101,10 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
43944101 use movement::skip_while;
43954102 let (view, doc) = current!(cx.editor);
43964103 let text = doc.text();
4397− let slice = text.slice(..);
4104+ let slice = doc.text().slice(..);
43984105
43994106 let mut changes = Vec::new();
4107+ let fragment = Tendril::from(" ");
44004108
44014109 for selection in doc.selection(view.id) {
44024110 let (start, mut end) = selection.line_range(slice);
@@ -4412,13 +4120,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
44124120 let mut end = text.line_to_char(line + 1);
44134121 end = skip_while(slice, end, |ch| matches!(ch, ' ' | '\t')).unwrap_or(end);
44144122
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));
4123+ // need to skip from start, not end
4124+ let change = (start, end, Some(fragment.clone()));
4125+ changes.push(change);
44224126 }
44234127 }
44244128
@@ -4430,6 +4134,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
44304134 changes.sort_unstable_by_key(|(from, _to, _text)| *from);
44314135 changes.dedup();
44324136
4137+ // TODO: joining multiple empty lines should be replaced by a single space.
4138+ // need to merge change ranges that touch
4139+
44334140 // select inserted spaces
44344141 let transaction = if select_space {
44354142 let ranges: SmallVec<_> = changes
@@ -4441,9 +4148,9 @@ fn join_selections_impl(cx: &mut Context, select_space: bool) {
44414148 })
44424149 .collect();
44434150 let selection = Selection::new(ranges, 0);
4444− Transaction::change(text, changes.into_iter()).with_selection(selection)
4151+ Transaction::change(doc.text(), changes.into_iter()).with_selection(selection)
44454152 } else {
4446− Transaction::change(text, changes.into_iter())
4153+ Transaction::change(doc.text(), changes.into_iter())
44474154 };
44484155
44494156 doc.apply(&transaction, view.id);
@@ -4457,8 +4164,8 @@ fn keep_or_remove_selections_impl(cx: &mut Context, remove: bool) {
44574164 if remove { "remove:" } else { "keep:" }.into(),
44584165 Some(reg),
44594166 ui::completers::none,
4460− move |cx, regex, event| {
4461− let (view, doc) = current!(cx.editor);
4167+ move |editor, regex, event| {
4168+ let (view, doc) = current!(editor);
44624169 if !matches!(event, PromptEvent::Update | PromptEvent::Validate) {
44634170 return;
44644171 }
@@ -4535,7 +4242,7 @@ pub fn completion(cx: &mut Context) {
45354242 .map(|language_server| {
45364243 let language_server_id = language_server.id();
45374244 let offset_encoding = language_server.offset_encoding();
4538− let pos = pos_to_lsp_pos(&text, cursor, offset_encoding);
4245+ let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding);
45394246 let doc_id = doc.identifier();
45404247 let completion_request = language_server.completion(doc_id, pos, None).unwrap();
45414248
@@ -4692,13 +4399,7 @@ fn rotate_selections_backward(cx: &mut Context) {
46924399 rotate_selections(cx, Direction::Backward)
46934400}
46944401
4695−enum ReorderStrategy {
4696− RotateForward,
4697− RotateBackward,
4698− Reverse,
4699−}
4700−
4701−fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
4402+fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
47024403 let count = cx.count;
47034404 let (view, doc) = current!(cx.editor);
47044405 let text = doc.text().slice(..);
@@ -4716,10 +4417,9 @@ fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
47164417
47174418 for chunk in fragments.chunks_mut(group) {
47184419 // TODO: also modify main index
4719− match strategy {
4720− ReorderStrategy::RotateForward => chunk.rotate_right(1),
4721− ReorderStrategy::RotateBackward => chunk.rotate_left(1),
4722− ReorderStrategy::Reverse => chunk.reverse(),
4420+ match direction {
4421+ Direction::Forward => chunk.rotate_right(1),
4422+ Direction::Backward => chunk.rotate_left(1),
47234423 };
47244424 }
47254425
@@ -4736,13 +4436,10 @@ fn reorder_selection_contents(cx: &mut Context, strategy: ReorderStrategy) {
47364436}
47374437
47384438fn rotate_selection_contents_forward(cx: &mut Context) {
4739− reorder_selection_contents(cx, ReorderStrategy::RotateForward)
4439+ rotate_selection_contents(cx, Direction::Forward)
47404440}
47414441fn rotate_selection_contents_backward(cx: &mut Context) {
4742− reorder_selection_contents(cx, ReorderStrategy::RotateBackward)
4743−}
4744−fn reverse_selection_contents(cx: &mut Context) {
4745− reorder_selection_contents(cx, ReorderStrategy::Reverse)
4442+ rotate_selection_contents(cx, Direction::Backward)
47464443}
47474444
47484445// tree sitter node selection
@@ -4766,7 +4463,8 @@ fn expand_selection(cx: &mut Context) {
47664463 }
47674464 }
47684465 };
4769− cx.editor.apply_motion(motion);
4466+ motion(cx.editor);
4467+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
47704468}
47714469
47724470fn shrink_selection(cx: &mut Context) {
@@ -4790,7 +4488,8 @@ fn shrink_selection(cx: &mut Context) {
47904488 doc.set_selection(view.id, selection);
47914489 }
47924490 };
4793− cx.editor.apply_motion(motion);
4491+ motion(cx.editor);
4492+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
47944493}
47954494
47964495fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
@@ -4808,7 +4507,8 @@ where
48084507 doc.set_selection(view.id, selection);
48094508 }
48104509 };
4811− cx.editor.apply_motion(motion);
4510+ motion(cx.editor);
4511+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
48124512}
48134513
48144514fn select_next_sibling(cx: &mut Context) {
@@ -4819,68 +4519,22 @@ fn select_prev_sibling(cx: &mut Context) {
48194519 select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
48204520}
48214521
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−
48654522fn match_brackets(cx: &mut Context) {
48664523 let (view, doc) = current!(cx.editor);
4867− let is_select = cx.editor.mode == Mode::Select;
4868− let text = doc.text();
4869− let text_slice = text.slice(..);
4870−
4871− let selection = doc.selection(view.id).clone().transform(|range| {
4872− let pos = range.cursor(text_slice);
4873− if let Some(matched_pos) = doc.syntax().map_or_else(
4874− || match_brackets::find_matching_bracket_plaintext(text.slice(..), pos),
4875− |syntax| match_brackets::find_matching_bracket_fuzzy(syntax, text.slice(..), pos),
4876− ) {
4877− range.put_cursor(text_slice, matched_pos, is_select)
4878− } else {
4879− range
4880− }
4881− });
48824524
4883− doc.set_selection(view.id, selection);
4525+ if let Some(syntax) = doc.syntax() {
4526+ let text = doc.text().slice(..);
4527+ let selection = doc.selection(view.id).clone().transform(|range| {
4528+ if let Some(pos) =
4529+ match_brackets::find_matching_bracket_fuzzy(syntax, doc.text(), range.cursor(text))
4530+ {
4531+ range.put_cursor(text, pos, cx.editor.mode == Mode::Select)
4532+ } else {
4533+ range
4534+ }
4535+ });
4536+ doc.set_selection(view.id, selection);
4537+ }
48844538}
48854539
48864540//
@@ -4975,19 +4629,17 @@ fn transpose_view(cx: &mut Context) {
49754629 cx.editor.transpose_view()
49764630}
49774631
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);
4632+// split helper, clear it later
4633+fn split(cx: &mut Context, action: Action) {
4634+ let (view, doc) = current!(cx.editor);
49834635 let id = doc.id();
49844636 let selection = doc.selection(view.id).clone();
49854637 let offset = view.offset;
49864638
4987− editor.switch(id, action);
4639+ cx.editor.switch(id, action);
49884640
49894641 // match the selection in the previous view
4990− let (view, doc) = current!(editor);
4642+ let (view, doc) = current!(cx.editor);
49914643 doc.set_selection(view.id, selection);
49924644 // match the view scroll offset (switch doesn't handle this fully
49934645 // since the selection is only matched after the split)
@@ -4995,7 +4647,7 @@ fn split(editor: &mut Editor, action: Action) {
49954647}
49964648
49974649fn hsplit(cx: &mut Context) {
4998− split(cx.editor, Action::HorizontalSplit);
4650+ split(cx, Action::HorizontalSplit);
49994651}
50004652
50014653fn hsplit_new(cx: &mut Context) {
@@ -5003,7 +4655,7 @@ fn hsplit_new(cx: &mut Context) {
50034655}
50044656
50054657fn vsplit(cx: &mut Context) {
5006− split(cx.editor, Action::VerticalSplit);
4658+ split(cx, Action::VerticalSplit);
50074659}
50084660
50094661fn vsplit_new(cx: &mut Context) {
@@ -5052,12 +4704,7 @@ fn insert_register(cx: &mut Context) {
50524704 if let Some(ch) = event.char() {
50534705 cx.editor.autoinfo = None;
50544706 cx.register = Some(ch);
5055− paste(
5056− cx.editor,
5057− cx.register.unwrap_or('"'),
5058− Paste::Cursor,
5059− cx.count(),
5060− );
4707+ paste(cx, Paste::Cursor);
50614708 }
50624709 })
50634710}
@@ -5141,7 +4788,8 @@ fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direct
51414788 editor.set_status("Syntax-tree is not available in current buffer");
51424789 }
51434790 };
5144− cx.editor.apply_motion(motion);
4791+ motion(cx.editor);
4792+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
51454793}
51464794
51474795fn goto_next_function(cx: &mut Context) {
@@ -5262,7 +4910,8 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
52624910 });
52634911 doc.set_selection(view.id, selection);
52644912 };
5265− cx.editor.apply_motion(textobject);
4913+ textobject(cx.editor);
4914+ cx.editor.last_motion = Some(Motion(Box::new(textobject)));
52664915 }
52674916 });
52684917
@@ -5352,18 +5001,11 @@ fn surround_replace(cx: &mut Context) {
53525001 }
53535002 };
53545003
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−
53625004 cx.on_next_key(move |cx, event| {
53635005 let (view, doc) = current!(cx.editor);
53645006 let to = match event.char() {
53655007 Some(to) => to,
5366− None => return doc.set_selection(view.id, selection),
5008+ None => return,
53675009 };
53685010 let (open, close) = surround::get_pair(to);
53695011 let transaction = Transaction::change(
@@ -5374,7 +5016,6 @@ fn surround_replace(cx: &mut Context) {
53745016 (pos, pos + 1, Some(t))
53755017 }),
53765018 );
5377− doc.set_selection(view.id, selection);
53785019 doc.apply(&transaction, view.id);
53795020 exit_select_mode(cx);
53805021 });
@@ -5609,18 +5250,12 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
56095250 };
56105251
56115252 // These `usize`s cannot underflow because selection ranges cannot overlap.
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");
5253+ // Once the MSRV is 1.66.0 (mixed_integer_ops is stabilized), we can use checked
5254+ // arithmetic to assert this.
5255+ let anchor = (to as isize + offset - deleted_len as isize) as usize;
56175256 let new_range = Range::new(anchor, anchor + output_len).with_direction(range.direction());
56185257 ranges.push(new_range);
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");
5258+ offset = offset + output_len as isize - deleted_len as isize;
56245259
56255260 changes.push((from, to, Some(output)));
56265261 }
@@ -5642,7 +5277,7 @@ fn shell_prompt(cx: &mut Context, prompt: Cow<'static, str>, behavior: ShellBeha
56425277 cx,
56435278 prompt,
56445279 Some('|'),
5645− ui::completers::filename,
5280+ ui::completers::none,
56465281 move |cx, input: &str, event: PromptEvent| {
56475282 if event != PromptEvent::Validate {
56485283 return;
@@ -5759,7 +5394,6 @@ fn increment_impl(cx: &mut Context, increment_direction: IncrementDirection) {
57595394 let transaction = Transaction::change(doc.text(), changes.into_iter());
57605395 let transaction = transaction.with_selection(new_selection);
57615396 doc.apply(&transaction, view.id);
5762− exit_select_mode(cx);
57635397 }
57645398}
57655399
@@ -5778,12 +5412,9 @@ fn record_macro(cx: &mut Context) {
57785412 }
57795413 })
57805414 .collect::<String>();
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− }
5415+ cx.editor.registers.write(reg, vec![s]);
5416+ cx.editor
5417+ .set_status(format!("Recorded to register [{}]", reg));
57875418 } else {
57885419 let reg = cx.register.take().unwrap_or('@');
57895420 cx.editor.macro_recording = Some((reg, Vec::new()));
@@ -5803,14 +5434,8 @@ fn replay_macro(cx: &mut Context) {
58035434 return;
58045435 }
58055436
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) {
5437+ let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
5438+ match helix_view::input::parse_macro(keys_str) {
58145439 Ok(keys) => keys,
58155440 Err(err) => {
58165441 cx.editor.set_error(format!("Invalid macro: {}", err));
helix-term/src/commands/dap.rs+55−58
@@ -2,13 +2,13 @@ use super::{Context, Editor};
22use crate::{
33 compositor::{self, Compositor},
44 job::{Callback, Jobs},
5− ui::{self, overlay::overlaid, Picker, Popup, Prompt, PromptEvent, Text},
5+ ui::{self, overlay::overlaid, FilePicker, 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, graphics::Margin};
11+use helix_view::editor::Breakpoint;
1212
1313use serde_json::{to_value, Value};
1414use tokio_stream::wrappers::UnboundedReceiverStream;
@@ -73,19 +73,21 @@ fn thread_picker(
7373 let debugger = debugger!(editor);
7474
7575 let thread_states = debugger.thread_states.clone();
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− });
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+ );
8991 compositor.push(Box::new(picker));
9092 },
9193 );
@@ -217,7 +219,7 @@ pub fn dap_start_impl(
217219 }
218220 }
219221
220− args.insert("cwd", to_value(helix_loader::current_working_dir())?);
222+ args.insert("cwd", to_value(std::env::current_dir().unwrap())?);
221223
222224 let args = to_value(args).unwrap();
223225
@@ -339,12 +341,8 @@ fn debug_parameter_prompt(
339341 .to_owned();
340342
341343 let completer = match field_type {
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− },
344+ "filename" => ui::completers::filename,
345+ "directory" => ui::completers::directory,
348346 _ => ui::completers::none,
349347 };
350348
@@ -581,12 +579,7 @@ pub fn dap_variables(cx: &mut Context) {
581579 }
582580
583581 let contents = Text::from(tui::text::Text::from(variables));
584− let margin = if cx.editor.popup_border() {
585− Margin::all(1)
586− } else {
587− Margin::none()
588− };
589− let popup = Popup::new("dap-variables", contents).margin(margin);
582+ let popup = Popup::new("dap-variables", contents);
590583 cx.replace_or_push_layer("dap-variables", popup);
591584}
592585
@@ -735,35 +728,39 @@ pub fn dap_switch_stack_frame(cx: &mut Context) {
735728
736729 let frames = debugger.stack_frames[&thread_id].clone();
737730
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− });
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+ );
768765 cx.push_layer(Box::new(picker))
769766}
helix-term/src/commands/lsp.rs+94−94
@@ -23,7 +23,6 @@ use helix_core::{
2323use helix_view::{
2424 document::{DocumentInlayHints, DocumentInlayHintsId, Mode},
2525 editor::Action,
26− graphics::Margin,
2726 theme::Style,
2827 Document, View,
2928};
@@ -32,8 +31,8 @@ use crate::{
3231 compositor::{self, Compositor},
3332 job::Callback,
3433 ui::{
35− self, lsp::SignatureHelp, overlay::overlaid, DynamicPicker, FileLocation, Picker, Popup,
36− PromptEvent,
34+ self, lsp::SignatureHelp, overlay::overlaid, DynamicPicker, FileLocation, FilePicker,
35+ Popup, PromptEvent,
3736 },
3837};
3938
@@ -50,7 +49,7 @@ use std::{
5049/// If there is no configured language server that supports the feature, this displays a status message.
5150/// Using this macro in a context where the editor automatically queries the LSP
5251/// (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.
52+/// will spam the "No configured language server supports <feature>" status message confusingly.
5453#[macro_export]
5554macro_rules! language_server_with_feature {
5655 ($editor:expr, $doc:expr, $feature:expr) => {{
@@ -196,6 +195,7 @@ fn location_to_file_location(location: &lsp::Location) -> FileLocation {
196195 (path.into(), line)
197196}
198197
198+// TODO: share with symbol picker(symbol.location)
199199fn jump_to_location(
200200 editor: &mut Editor,
201201 location: &lsp::Location,
@@ -213,16 +213,15 @@ fn jump_to_location(
213213 return;
214214 }
215215 };
216−
217− let doc = match editor.open(&path, action) {
218− Ok(id) => doc_mut!(editor, &id),
216+ match editor.open(&path, action) {
217+ Ok(_) => (),
219218 Err(err) => {
220219 let err = format!("failed to open path: {:?}: {:?}", location.uri, err);
221220 editor.set_error(err);
222221 return;
223222 }
224− };
225− let view = view_mut!(editor);
223+ }
224+ let (view, doc) = current!(editor);
226225 // TODO: convert inside server
227226 let new_range =
228227 if let Some(new_range) = lsp_range_to_range(doc.text(), location.range, offset_encoding) {
@@ -234,24 +233,51 @@ fn jump_to_location(
234233 // we flip the range so that the cursor sits on the start of the symbol
235234 // (for example start of the function).
236235 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− }
236+ align_view(doc, view, Align::Center);
240237}
241238
242−type SymbolPicker = Picker<SymbolInformationItem>;
239+type SymbolPicker = FilePicker<SymbolInformationItem>;
243240
244241fn sym_picker(symbols: Vec<SymbolInformationItem>, current_path: Option<lsp::Url>) -> SymbolPicker {
245242 // 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)))
243+ FilePicker::new(
244+ symbols,
245+ current_path.clone(),
246+ move |cx, item, action| {
247+ let (view, doc) = current!(cx.editor);
248+ push_jump(view, doc);
249+
250+ if current_path.as_ref() != Some(&item.symbol.location.uri) {
251+ let uri = &item.symbol.location.uri;
252+ let path = match uri.to_file_path() {
253+ Ok(path) => path,
254+ Err(_) => {
255+ let err = format!("unable to convert URI to filepath: {}", uri);
256+ cx.editor.set_error(err);
257+ return;
258+ }
259+ };
260+ if let Err(err) = cx.editor.open(&path, action) {
261+ let err = format!("failed to open document: {}: {}", uri, err);
262+ log::error!("{}", err);
263+ cx.editor.set_error(err);
264+ return;
265+ }
266+ }
267+
268+ let (view, doc) = current!(cx.editor);
269+
270+ if let Some(range) =
271+ lsp_range_to_range(doc.text(), item.symbol.location.range, item.offset_encoding)
272+ {
273+ // we flip the range so that the cursor sits on the start of the symbol
274+ // (for example start of the function).
275+ doc.set_selection(view.id, Selection::single(range.head, range.anchor));
276+ align_view(doc, view, Align::Center);
277+ }
278+ },
279+ move |_editor, item| Some(location_to_file_location(&item.symbol.location)),
280+ )
255281 .truncate_start(false)
256282}
257283
@@ -264,9 +290,9 @@ enum DiagnosticsFormat {
264290fn diag_picker(
265291 cx: &Context,
266292 diagnostics: BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize)>>,
267− _current_path: Option<lsp::Url>,
293+ current_path: Option<lsp::Url>,
268294 format: DiagnosticsFormat,
269−) -> Picker<PickerDiagnostic> {
295+) -> FilePicker<PickerDiagnostic> {
270296 // TODO: drop current_path comparison and instead use workspace: bool flag?
271297
272298 // flatten the map to a vec of (url, diag) pairs
@@ -275,7 +301,7 @@ fn diag_picker(
275301 flat_diag.reserve(diags.len());
276302
277303 for (diag, ls) in diags {
278− if let Some(ls) = cx.editor.language_server_by_id(ls) {
304+ if let Some(ls) = cx.editor.language_servers.get_by_id(ls) {
279305 flat_diag.push(PickerDiagnostic {
280306 url: url.clone(),
281307 diag,
@@ -292,7 +318,7 @@ fn diag_picker(
292318 error: cx.editor.theme.get("error"),
293319 };
294320
295− Picker::new(
321+ FilePicker::new(
296322 flat_diag,
297323 (styles, format),
298324 move |cx,
@@ -302,18 +328,28 @@ fn diag_picker(
302328 offset_encoding,
303329 },
304330 action| {
305− jump_to_location(
306− cx.editor,
307− &lsp::Location::new(url.clone(), diag.range),
308− *offset_encoding,
309− action,
310− )
331+ if current_path.as_ref() == Some(url) {
332+ let (view, doc) = current!(cx.editor);
333+ push_jump(view, doc);
334+ } else {
335+ let path = url.to_file_path().unwrap();
336+ cx.editor.open(&path, action).expect("editor.open failed");
337+ }
338+
339+ let (view, doc) = current!(cx.editor);
340+
341+ if let Some(range) = lsp_range_to_range(doc.text(), diag.range, *offset_encoding) {
342+ // we flip the range so that the cursor sits on the start of the symbol
343+ // (for example start of the function).
344+ doc.set_selection(view.id, Selection::single(range.head, range.anchor));
345+ align_view(doc, view, Align::Center);
346+ }
347+ },
348+ move |_editor, PickerDiagnostic { url, diag, .. }| {
349+ let location = lsp::Location::new(url.clone(), diag.range);
350+ Some(location_to_file_location(&location))
311351 },
312352 )
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− })
317353 .truncate_start(false)
318354}
319355
@@ -406,15 +442,6 @@ pub fn symbol_picker(cx: &mut Context) {
406442
407443pub fn workspace_symbol_picker(cx: &mut Context) {
408444 let doc = doc!(cx.editor);
409− if doc
410− .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
411− .count()
412− == 0
413− {
414− cx.editor
415− .set_error("No configured language server supports workspace symbols");
416− return;
417− }
418445
419446 let get_symbols = move |pattern: String, editor: &mut Editor| {
420447 let doc = doc!(editor);
@@ -508,6 +535,7 @@ pub fn workspace_diagnostics_picker(cx: &mut Context) {
508535
509536struct CodeActionOrCommandItem {
510537 lsp_item: lsp::CodeActionOrCommand,
538+ offset_encoding: OffsetEncoding,
511539 language_server_id: usize,
512540}
513541
@@ -609,9 +637,9 @@ pub fn code_action(cx: &mut Context) {
609637 };
610638 let code_action_request =
611639 language_server.code_actions(doc.identifier(), range, code_action_context)?;
612− Some((code_action_request, language_server_id))
640+ Some((code_action_request, offset_encoding, language_server_id))
613641 })
614− .map(|(request, ls_id)| async move {
642+ .map(|(request, offset_encoding, ls_id)| async move {
615643 let json = request.await?;
616644 let response: Option<lsp::CodeActionResponse> = serde_json::from_value(json)?;
617645 let mut actions = match response {
@@ -667,6 +695,7 @@ pub fn code_action(cx: &mut Context) {
667695 .into_iter()
668696 .map(|lsp_item| CodeActionOrCommandItem {
669697 lsp_item,
698+ offset_encoding,
670699 language_server_id: ls_id,
671700 })
672701 .collect())
@@ -698,12 +727,7 @@ pub fn code_action(cx: &mut Context) {
698727
699728 // always present here
700729 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();
730+ let offset_encoding = action.offset_encoding;
707731
708732 match &action.lsp_item {
709733 lsp::CodeActionOrCommand::Command(command) => {
@@ -712,25 +736,7 @@ pub fn code_action(cx: &mut Context) {
712736 }
713737 lsp::CodeActionOrCommand::CodeAction(code_action) => {
714738 log::debug!("code action: {:?}", code_action);
715− // we support lsp "codeAction/resolve" for `edit` and `command` fields
716− let mut resolved_code_action = None;
717− if code_action.edit.is_none() || code_action.command.is_none() {
718− if let Some(future) =
719− language_server.resolve_code_action(code_action.clone())
720− {
721− if let Ok(response) = helix_lsp::block_on(future) {
722− if let Ok(code_action) =
723− serde_json::from_value::<CodeAction>(response)
724− {
725− resolved_code_action = Some(code_action);
726− }
727− }
728− }
729− }
730− let resolved_code_action =
731− resolved_code_action.as_ref().unwrap_or(code_action);
732−
733− if let Some(ref workspace_edit) = resolved_code_action.edit {
739+ if let Some(ref workspace_edit) = code_action.edit {
734740 log::debug!("edit: {:?}", workspace_edit);
735741 let _ = apply_workspace_edit(editor, offset_encoding, workspace_edit);
736742 }
@@ -745,16 +751,7 @@ pub fn code_action(cx: &mut Context) {
745751 });
746752 picker.move_down(); // pre-select the first item
747753
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−
754+ let popup = Popup::new("code-action", picker).with_scrollbar(false);
758755 compositor.replace_or_push("code-action", popup);
759756 };
760757
@@ -773,7 +770,8 @@ pub fn execute_lsp_command(editor: &mut Editor, language_server_id: usize, cmd:
773770 // the command is executed on the server and communicated back
774771 // to the client asynchronously using workspace edits
775772 let future = match editor
776− .language_server_by_id(language_server_id)
773+ .language_servers
774+ .get_by_id(language_server_id)
777775 .and_then(|language_server| language_server.command(cmd))
778776 {
779777 Some(future) => future,
@@ -1029,7 +1027,7 @@ fn goto_impl(
10291027 locations: Vec<lsp::Location>,
10301028 offset_encoding: OffsetEncoding,
10311029) {
1032− let cwdir = helix_loader::current_working_dir();
1030+ let cwdir = std::env::current_dir().unwrap_or_default();
10331031
10341032 match locations.as_slice() {
10351033 [location] => {
@@ -1039,10 +1037,14 @@ fn goto_impl(
10391037 editor.set_error("No definition found.");
10401038 }
10411039 _locations => {
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)));
1040+ let picker = FilePicker::new(
1041+ locations,
1042+ cwdir,
1043+ move |cx, location, action| {
1044+ jump_to_location(cx.editor, location, offset_encoding, action)
1045+ },
1046+ move |_editor, location| Some(location_to_file_location(location)),
1047+ );
10461048 compositor.push(Box::new(overlaid(picker)));
10471049 }
10481050 }
@@ -1169,8 +1171,7 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
11691171 // Do not show the message if signature help was invoked
11701172 // automatically on backspace, trigger characters, etc.
11711173 if invoked == SignatureHelpInvoked::Manual {
1172− cx.editor
1173− .set_error("No configured language server supports signature-help");
1174+ cx.editor.set_error("No configured language server supports signature-help");
11741175 }
11751176 return;
11761177 };
@@ -1393,10 +1394,9 @@ pub fn rename_symbol(cx: &mut Context) {
13931394
13941395 let Some(language_server) = doc
13951396 .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
1396− .find(|ls| language_server_id.map_or(true, |id| id == ls.id()))
1397+ .find(|ls| language_server_id.is_none() || Some(ls.id()) == language_server_id)
13971398 else {
1398− cx.editor
1399− .set_error("No configured language server supports symbol renaming");
1399+ cx.editor.set_error("No configured language server supports symbol renaming");
14001400 return;
14011401 };
14021402
@@ -1483,7 +1483,7 @@ pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
14831483 };
14841484 let (view, doc) = current!(editor);
14851485 let text = doc.text();
1486− let pos = doc.selection(view.id).primary().cursor(text.slice(..));
1486+ let pos = doc.selection(view.id).primary().head;
14871487
14881488 // We must find the range that contains our primary cursor to prevent our primary cursor to move
14891489 let mut primary_index = 0;
helix-term/src/commands/typed.rs+704−987
@@ -5,10 +5,7 @@ use crate::job::Job;
55
66use super::*;
77
8−use helix_core::fuzzy::fuzzy_match;
9−use helix_core::indent::MAX_INDENT;
10−use helix_core::{encoding, line_ending, path::get_canonicalized_path, shellwords::Shellwords};
11−use helix_lsp::{OffsetEncoding, Url};
8+use helix_core::{encoding, shellwords::Shellwords};
129use helix_view::document::DEFAULT_LANGUAGE_NAME;
1310use helix_view::editor::{Action, CloseError, ConfigEvent};
1411use serde_json::Value;
@@ -332,16 +329,12 @@ fn write_impl(
332329 path: Option<&Cow<str>>,
333330 force: bool,
334331) -> anyhow::Result<()> {
335− let config = cx.editor.config();
332+ let editor_auto_fmt = cx.editor.config().auto_format;
336333 let jobs = &mut cx.jobs;
337334 let (view, doc) = current!(cx.editor);
338335 let path = path.map(AsRef::as_ref);
339336
340− if config.insert_final_newline {
341− insert_final_newline(doc, view);
342− }
343−
344− let fmt = if config.auto_format {
337+ let fmt = if editor_auto_fmt {
345338 doc.auto_format().map(|fmt| {
346339 let callback = make_format_callback(
347340 doc.id(),
@@ -365,16 +358,6 @@ fn write_impl(
365358 Ok(())
366359}
367360
368−fn insert_final_newline(doc: &mut Document, view: &mut View) {
369− let text = doc.text();
370− if line_ending::get_line_ending(&text.slice(..)).is_none() {
371− let eof = Selection::point(text.len_chars());
372− let insert = Transaction::insert(text, &eof, doc.line_ending.as_str().into());
373− doc.apply(&insert, view.id);
374− doc.append_changes_to_history(view);
375− }
376−}
377−
378361fn write(
379362 cx: &mut compositor::Context,
380363 args: &[Cow<str>],
@@ -477,7 +460,8 @@ fn set_indent_style(
477460 cx.editor.set_status(match style {
478461 Tabs => "tabs".to_owned(),
479462 Spaces(1) => "1 space".to_owned(),
480− Spaces(n) => format!("{} spaces", n),
463+ Spaces(n) if (2..=8).contains(&n) => format!("{} spaces", n),
464+ _ => unreachable!(), // Shouldn't happen.
481465 });
482466 return Ok(());
483467 }
@@ -489,7 +473,7 @@ fn set_indent_style(
489473 Some(arg) => arg
490474 .parse::<u8>()
491475 .ok()
492− .filter(|n| (1..=MAX_INDENT).contains(n))
476+ .filter(|n| (1..=8).contains(n))
493477 .map(Spaces),
494478 _ => None,
495479 };
@@ -574,6 +558,7 @@ fn set_line_ending(
574558
575559 Ok(())
576560}
561+
577562fn earlier(
578563 cx: &mut compositor::Context,
579564 args: &[Cow<str>],
@@ -672,10 +657,11 @@ pub fn write_all_impl(
672657 write_scratch: bool,
673658) -> anyhow::Result<()> {
674659 let mut errors: Vec<&'static str> = Vec::new();
675− let config = cx.editor.config();
660+ let auto_format = cx.editor.config().auto_format;
676661 let jobs = &mut cx.jobs;
677662 let current_view = view!(cx.editor);
678663
664+ // save all documents
679665 let saves: Vec<_> = cx
680666 .editor
681667 .documents
@@ -686,7 +672,7 @@ pub fn write_all_impl(
686672 }
687673 if doc.path().is_none() {
688674 if write_scratch {
689− errors.push("cannot write a buffer without a filename");
675+ errors.push("cannot write a buffer without a filename\n");
690676 }
691677 return None;
692678 }
@@ -706,35 +692,32 @@ pub fn write_all_impl(
706692 current_view.id
707693 };
708694
709− Some((doc.id(), target_view))
710− })
711− .collect();
712−
713− for (doc_id, target_view) in saves {
714− let doc = doc_mut!(cx.editor, &doc_id);
695+ let fmt = if auto_format {
696+ doc.auto_format().map(|fmt| {
697+ let callback = make_format_callback(
698+ doc.id(),
699+ doc.version(),
700+ target_view,
701+ fmt,
702+ Some((None, force)),
703+ );
704+ jobs.add(Job::with_callback(callback).wait_before_exiting());
705+ })
706+ } else {
707+ None
708+ };
715709
716− if config.insert_final_newline {
717− insert_final_newline(doc, view_mut!(cx.editor, target_view));
718− }
710+ if fmt.is_none() {
711+ return Some(doc.id());
712+ }
719713
720− let fmt = if config.auto_format {
721− doc.auto_format().map(|fmt| {
722− let callback = make_format_callback(
723− doc_id,
724− doc.version(),
725− target_view,
726− fmt,
727− Some((None, force)),
728− );
729− jobs.add(Job::with_callback(callback).wait_before_exiting());
730− })
731− } else {
732714 None
733− };
715+ })
716+ .collect();
734717
735− if fmt.is_none() {
736− cx.editor.save::<PathBuf>(doc_id, None, force)?;
737− }
718+ // manually call save for the rest of docs that don't have a formatter
719+ for id in saves {
720+ cx.editor.save::<PathBuf>(id, None, force)?;
738721 }
739722
740723 if !errors.is_empty() && !force {
@@ -756,18 +739,6 @@ fn write_all(
756739 write_all_impl(cx, false, true)
757740}
758741
759−fn force_write_all(
760− cx: &mut compositor::Context,
761− _args: &[Cow<str>],
762− event: PromptEvent,
763−) -> anyhow::Result<()> {
764− if event != PromptEvent::Validate {
765− return Ok(());
766− }
767−
768− write_all_impl(cx, true, true)
769−}
770−
771742fn write_all_quit(
772743 cx: &mut compositor::Context,
773744 _args: &[Cow<str>],
@@ -921,27 +892,7 @@ fn yank_main_selection_to_clipboard(
921892 return Ok(());
922893 }
923894
924− yank_primary_selection_impl(cx.editor, '+');
925− Ok(())
926−}
927−
928−fn yank_joined(
929− cx: &mut compositor::Context,
930− args: &[Cow<str>],
931− event: PromptEvent,
932−) -> anyhow::Result<()> {
933− if event != PromptEvent::Validate {
934− return Ok(());
935− }
936−
937− ensure!(args.len() <= 1, ":yank-join takes at most 1 argument");
938−
939− let doc = doc!(cx.editor);
940− let default_sep = Cow::Borrowed(doc.line_ending.as_str());
941− let separator = args.first().unwrap_or(&default_sep);
942− let register = cx.editor.selected_register.unwrap_or('"');
943− yank_joined_impl(cx.editor, separator, register);
944− Ok(())
895+ yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard)
945896}
946897
947898fn yank_joined_to_clipboard(
@@ -956,8 +907,7 @@ fn yank_joined_to_clipboard(
956907 let doc = doc!(cx.editor);
957908 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
958909 let separator = args.first().unwrap_or(&default_sep);
959− yank_joined_impl(cx.editor, separator, '+');
960− Ok(())
910+ yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
961911}
962912
963913fn yank_main_selection_to_primary_clipboard(
@@ -969,8 +919,7 @@ fn yank_main_selection_to_primary_clipboard(
969919 return Ok(());
970920 }
971921
972− yank_primary_selection_impl(cx.editor, '*');
973− Ok(())
922+ yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection)
974923}
975924
976925fn yank_joined_to_primary_clipboard(
@@ -985,8 +934,7 @@ fn yank_joined_to_primary_clipboard(
985934 let doc = doc!(cx.editor);
986935 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
987936 let separator = args.first().unwrap_or(&default_sep);
988− yank_joined_impl(cx.editor, separator, '*');
989− Ok(())
937+ yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
990938}
991939
992940fn paste_clipboard_after(
@@ -998,8 +946,7 @@ fn paste_clipboard_after(
998946 return Ok(());
999947 }
1000948
1001− paste(cx.editor, '+', Paste::After, 1);
1002− Ok(())
949+ paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard, 1)
1003950}
1004951
1005952fn paste_clipboard_before(
@@ -1011,8 +958,7 @@ fn paste_clipboard_before(
1011958 return Ok(());
1012959 }
1013960
1014− paste(cx.editor, '+', Paste::Before, 1);
1015− Ok(())
961+ paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Clipboard, 1)
1016962}
1017963
1018964fn paste_primary_clipboard_after(
@@ -1024,8 +970,7 @@ fn paste_primary_clipboard_after(
1024970 return Ok(());
1025971 }
1026972
1027− paste(cx.editor, '*', Paste::After, 1);
1028− Ok(())
973+ paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection, 1)
1029974}
1030975
1031976fn paste_primary_clipboard_before(
@@ -1037,8 +982,30 @@ fn paste_primary_clipboard_before(
1037982 return Ok(());
1038983 }
1039984
1040− paste(cx.editor, '*', Paste::Before, 1);
1041− Ok(())
985+ paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Selection, 1)
986+}
987+
988+fn replace_selections_with_clipboard_impl(
989+ cx: &mut compositor::Context,
990+ clipboard_type: ClipboardType,
991+) -> anyhow::Result<()> {
992+ let scrolloff = cx.editor.config().scrolloff;
993+ let (view, doc) = current!(cx.editor);
994+
995+ match cx.editor.clipboard_provider.get_contents(clipboard_type) {
996+ Ok(contents) => {
997+ let selection = doc.selection(view.id);
998+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
999+ (range.from(), range.to(), Some(contents.as_str().into()))
1000+ });
1001+
1002+ doc.apply(&transaction, view.id);
1003+ doc.append_changes_to_history(view);
1004+ view.ensure_cursor_in_view(doc, scrolloff);
1005+ Ok(())
1006+ }
1007+ Err(e) => Err(e.context("Couldn't get system clipboard contents")),
1008+ }
10421009}
10431010
10441011fn replace_selections_with_clipboard(
@@ -1050,8 +1017,7 @@ fn replace_selections_with_clipboard(
10501017 return Ok(());
10511018 }
10521019
1053− replace_with_yanked_impl(cx.editor, '+', 1);
1054− Ok(())
1020+ replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
10551021}
10561022
10571023fn replace_selections_with_primary_clipboard(
@@ -1063,8 +1029,7 @@ fn replace_selections_with_primary_clipboard(
10631029 return Ok(());
10641030 }
10651031
1066− replace_with_yanked_impl(cx.editor, '*', 1);
1067− Ok(())
1032+ replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
10681033}
10691034
10701035fn show_clipboard_provider(
@@ -1077,7 +1042,7 @@ fn show_clipboard_provider(
10771042 }
10781043
10791044 cx.editor
1080− .set_status(cx.editor.registers.clipboard_provider_name().to_string());
1045+ .set_status(cx.editor.clipboard_provider.name().to_string());
10811046 Ok(())
10821047}
10831048
@@ -1097,11 +1062,14 @@ fn change_current_directory(
10971062 .as_ref(),
10981063 );
10991064
1100− helix_loader::set_current_working_dir(dir)?;
1065+ if let Err(e) = std::env::set_current_dir(dir) {
1066+ bail!("Couldn't change the current working directory: {}", e);
1067+ }
11011068
1069+ let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
11021070 cx.editor.set_status(format!(
11031071 "Current working directory is now {}",
1104− helix_loader::current_working_dir().display()
1072+ cwd.display()
11051073 ));
11061074 Ok(())
11071075}
@@ -1115,14 +1083,9 @@ fn show_current_directory(
11151083 return Ok(());
11161084 }
11171085
1118− let cwd = helix_loader::current_working_dir();
1119− let message = format!("Current working directory is {}", cwd.display());
1120−
1121− if cwd.exists() {
1122− cx.editor.set_status(message);
1123− } else {
1124− cx.editor.set_error(format!("{} (deleted)", message));
1125− }
1086+ let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
1087+ cx.editor
1088+ .set_status(format!("Current working directory is {}", cwd.display()));
11261089 Ok(())
11271090}
11281091
@@ -1282,17 +1245,12 @@ fn reload(
12821245 }
12831246
12841247 let scrolloff = cx.editor.config().scrolloff;
1248+ let redraw_handle = cx.editor.redraw_handle.clone();
12851249 let (view, doc) = current!(cx.editor);
1286− doc.reload(view, &cx.editor.diff_providers).map(|_| {
1287− view.ensure_cursor_in_view(doc, scrolloff);
1288− })?;
1289− if let Some(path) = doc.path() {
1290− cx.editor
1291− .language_servers
1292− .file_event_handler
1293− .file_changed(path.clone());
1294− }
1295− Ok(())
1250+ doc.reload(view, &cx.editor.diff_providers, redraw_handle)
1251+ .map(|_| {
1252+ view.ensure_cursor_in_view(doc, scrolloff);
1253+ })
12961254}
12971255
12981256fn reload_all(
@@ -1331,13 +1289,8 @@ fn reload_all(
13311289 // Ensure that the view is synced with the document's history.
13321290 view.sync_changes(doc);
13331291
1334− doc.reload(view, &cx.editor.diff_providers)?;
1335− if let Some(path) = doc.path() {
1336− cx.editor
1337− .language_servers
1338− .file_event_handler
1339− .file_changed(path.clone());
1340− }
1292+ let redraw_handle = cx.editor.redraw_handle.clone();
1293+ doc.reload(view, &cx.editor.diff_providers, redraw_handle)?;
13411294
13421295 for view_id in view_ids {
13431296 let view = view_mut!(cx.editor, view_id);
@@ -1386,8 +1339,9 @@ fn lsp_workspace_command(
13861339 .map(|options| (ls.id(), options))
13871340 })
13881341 else {
1389− cx.editor
1390− .set_status("No active language servers for this document support workspace commands");
1342+ cx.editor.set_status(
1343+ "No active language servers for this document support workspace commands",
1344+ );
13911345 return Ok(());
13921346 };
13931347
@@ -1450,6 +1404,7 @@ fn lsp_restart(
14501404 .language_config()
14511405 .context("LSP not defined for the current document")?;
14521406
1407+ let scope = config.scope.clone();
14531408 cx.editor.language_servers.restart(
14541409 config,
14551410 doc.path(),
@@ -1462,16 +1417,7 @@ fn lsp_restart(
14621417 .editor
14631418 .documents()
14641419 .filter_map(|doc| match doc.language_config() {
1465− Some(config)
1466− if config.language_servers.iter().any(|ls| {
1467− config
1468− .language_servers
1469− .iter()
1470− .any(|restarted_ls| restarted_ls.name == ls.name)
1471− }) =>
1472− {
1473− Some(doc.id())
1474− }
1420+ Some(config) if config.scope.eq(&scope) => Some(doc.id()),
14751421 _ => None,
14761422 })
14771423 .collect();
@@ -1543,84 +1489,6 @@ fn tree_sitter_scopes(
15431489 Ok(())
15441490}
15451491
1546−fn tree_sitter_highlight_name(
1547− cx: &mut compositor::Context,
1548− _args: &[Cow<str>],
1549− event: PromptEvent,
1550−) -> anyhow::Result<()> {
1551− fn find_highlight_at_cursor(
1552− cx: &mut compositor::Context<'_>,
1553− ) -> Option<helix_core::syntax::Highlight> {
1554− use helix_core::syntax::HighlightEvent;
1555−
1556− let (view, doc) = current!(cx.editor);
1557− let syntax = doc.syntax()?;
1558− let text = doc.text().slice(..);
1559− let cursor = doc.selection(view.id).primary().cursor(text);
1560− let byte = text.char_to_byte(cursor);
1561− let node = syntax
1562− .tree()
1563− .root_node()
1564− .descendant_for_byte_range(byte, byte)?;
1565− // Query the same range as the one used in syntax highlighting.
1566− let range = {
1567− // Calculate viewport byte ranges:
1568− let row = text.char_to_line(view.offset.anchor.min(text.len_chars()));
1569− // Saturating subs to make it inclusive zero indexing.
1570− let last_line = text.len_lines().saturating_sub(1);
1571− let height = view.inner_area(doc).height;
1572− let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
1573− let start = text.line_to_byte(row.min(last_line));
1574− let end = text.line_to_byte(last_visible_line + 1);
1575−
1576− start..end
1577− };
1578−
1579− let mut highlight = None;
1580−
1581− for event in syntax.highlight_iter(text, Some(range), None) {
1582− match event.unwrap() {
1583− HighlightEvent::Source { start, end }
1584− if start == node.start_byte() && end == node.end_byte() =>
1585− {
1586− return highlight;
1587− }
1588− HighlightEvent::HighlightStart(hl) => {
1589− highlight = Some(hl);
1590− }
1591− _ => (),
1592− }
1593− }
1594−
1595− None
1596− }
1597−
1598− if event != PromptEvent::Validate {
1599− return Ok(());
1600− }
1601−
1602− let Some(highlight) = find_highlight_at_cursor(cx) else {
1603− return Ok(());
1604− };
1605−
1606− let content = cx.editor.theme.scope(highlight.0).to_string();
1607−
1608− let callback = async move {
1609− let call: job::Callback = Callback::EditorCompositor(Box::new(
1610− move |editor: &mut Editor, compositor: &mut Compositor| {
1611− let content = ui::Markdown::new(content, editor.syn_loader.clone());
1612− let popup = Popup::new("hover", content).auto_close(true);
1613− compositor.replace_or_push("hover", popup);
1614− },
1615− ));
1616− Ok(call)
1617− };
1618−
1619− cx.jobs.callback(callback);
1620−
1621− Ok(())
1622−}
1623−
16241492fn vsplit(
16251493 cx: &mut compositor::Context,
16261494 args: &[Cow<str>],
@@ -1630,8 +1498,10 @@ fn vsplit(
16301498 return Ok(());
16311499 }
16321500
1501+ let id = view!(cx.editor).doc;
1502+
16331503 if args.is_empty() {
1634− split(cx.editor, Action::VerticalSplit);
1504+ cx.editor.switch(id, Action::VerticalSplit);
16351505 } else {
16361506 for arg in args {
16371507 cx.editor
@@ -1651,8 +1521,10 @@ fn hsplit(
16511521 return Ok(());
16521522 }
16531523
1524+ let id = view!(cx.editor).doc;
1525+
16541526 if args.is_empty() {
1655− split(cx.editor, Action::HorizontalSplit);
1527+ cx.editor.switch(id, Action::HorizontalSplit);
16561528 } else {
16571529 for arg in args {
16581530 cx.editor
@@ -1767,7 +1639,7 @@ fn tutor(
17671639 let path = helix_loader::runtime_file(Path::new("tutor"));
17681640 cx.editor.open(&path, Action::Replace)?;
17691641 // Unset path to prevent accidentally saving to the original tutor file.
1770− doc_mut!(cx.editor).set_path(None);
1642+ doc_mut!(cx.editor).set_path(None)?;
17711643 Ok(())
17721644}
17731645
@@ -1885,7 +1757,7 @@ fn set_option(
18851757
18861758 *value = if value.is_string() {
18871759 // JSON strings require quotes, so we can't .parse() directly
1888− Value::String(arg.to_string())
1760+ serde_json::Value::String(arg.to_string())
18891761 } else {
18901762 arg.parse().map_err(field_error)?
18911763 };
@@ -1910,8 +1782,8 @@ fn toggle_option(
19101782 return Ok(());
19111783 }
19121784
1913− if args.is_empty() {
1914− anyhow::bail!("Bad arguments. Usage: `:toggle key [values]?`");
1785+ if args.len() != 1 {
1786+ anyhow::bail!("Bad arguments. Usage: `:toggle key`");
19151787 }
19161788 let key = &args[0].to_lowercase();
19171789
@@ -1921,58 +1793,22 @@ fn toggle_option(
19211793 let pointer = format!("/{}", key.replace('.', "/"));
19221794 let value = config.pointer_mut(&pointer).ok_or_else(key_error)?;
19231795
1924− *value = match value {
1925− Value::Bool(ref value) => {
1926− ensure!(
1927− args.len() == 1,
1928− "Bad arguments. For boolean configurations use: `:toggle key`"
1929− );
1930− Value::Bool(!value)
1931− }
1932− Value::String(ref value) => {
1933− ensure!(
1934− args.len() > 2,
1935− "Bad arguments. For string configurations use: `:toggle key val1 val2 ...`",
1936− );
1937−
1938− Value::String(
1939− args[1..]
1940− .iter()
1941− .skip_while(|e| *e != value)
1942− .nth(1)
1943− .unwrap_or_else(|| &args[1])
1944− .to_string(),
1945− )
1946− }
1947− Value::Number(ref value) => {
1948− ensure!(
1949− args.len() > 2,
1950− "Bad arguments. For number configurations use: `:toggle key val1 val2 ...`",
1951− );
1952−
1953− Value::Number(
1954− args[1..]
1955− .iter()
1956− .skip_while(|&e| value.to_string() != *e.to_string())
1957− .nth(1)
1958− .unwrap_or_else(|| &args[1])
1959− .parse()?,
1960− )
1961− }
1962− Value::Null | Value::Object(_) | Value::Array(_) => {
1963− anyhow::bail!("Configuration {key} does not support toggle yet")
1964− }
1796+ let Value::Bool(old_value) = *value else {
1797+ anyhow::bail!("Key `{}` is not toggle-able", key)
19651798 };
19661799
1967− let status = format!("'{key}' is now set to {value}");
1968− let config = serde_json::from_value(config)
1969− .map_err(|err| anyhow::anyhow!("Cannot parse `{:?}`, {}", &args, err))?;
1800+ let new_value = !old_value;
1801+ *value = Value::Bool(new_value);
1802+ // This unwrap should never fail because we only replace one boolean value
1803+ // with another, maintaining a valid json config
1804+ let config = serde_json::from_value(config).unwrap();
19701805
19711806 cx.editor
19721807 .config_events
19731808 .0
19741809 .send(ConfigEvent::Update(config))?;
1975− cx.editor.set_status(status);
1810+ cx.editor
1811+ .set_status(format!("Option `{}` is now set to `{}`", key, new_value));
19761812 Ok(())
19771813}
19781814
@@ -2375,721 +2211,592 @@ fn clear_register(
23752211 format!("Invalid register {}", args[0])
23762212 );
23772213 let register = args[0].chars().next().unwrap_or_default();
2378− if cx.editor.registers.remove(register) {
2379− cx.editor
2380− .set_status(format!("Register {} cleared", register));
2381− } else {
2382− cx.editor
2383− .set_error(format!("Register {} not found", register));
2214+ match cx.editor.registers.remove(register) {
2215+ Some(_) => cx
2216+ .editor
2217+ .set_status(format!("Register {} cleared", register)),
2218+ None => cx
2219+ .editor
2220+ .set_error(format!("Register {} not found", register)),
23842221 }
23852222 Ok(())
23862223}
23872224
2388−fn redraw(
2389− cx: &mut compositor::Context,
2390− _args: &[Cow<str>],
2391− event: PromptEvent,
2392−) -> anyhow::Result<()> {
2393− if event != PromptEvent::Validate {
2394− return Ok(());
2395− }
2396−
2397− let callback = Box::pin(async move {
2398− let call: job::Callback =
2399− job::Callback::EditorCompositor(Box::new(|_editor, compositor| {
2400− compositor.need_full_redraw();
2401− }));
2402−
2403− Ok(call)
2404− });
2405−
2406− cx.jobs.callback(callback);
2407−
2408− Ok(())
2409−}
2410−
2411−fn move_buffer(
2412− cx: &mut compositor::Context,
2413− args: &[Cow<str>],
2414− event: PromptEvent,
2415−) -> anyhow::Result<()> {
2416− if event != PromptEvent::Validate {
2417− return Ok(());
2418− }
2419−
2420− ensure!(args.len() == 1, format!(":move takes one argument"));
2421− let doc = doc!(cx.editor);
2422−
2423− let new_path = get_canonicalized_path(&PathBuf::from(args.first().unwrap().to_string()));
2424− let old_path = doc
2425− .path()
2426− .ok_or_else(|| anyhow!("Scratch buffer cannot be moved. Use :write instead"))?
2427− .clone();
2428− let old_path_as_url = doc.url().unwrap();
2429− let new_path_as_url = Url::from_file_path(&new_path).unwrap();
2430−
2431− let edits: Vec<(
2432− helix_lsp::Result<helix_lsp::lsp::WorkspaceEdit>,
2433− OffsetEncoding,
2434− String,
2435− )> = doc
2436− .language_servers()
2437− .map(|lsp| {
2438− (
2439− lsp.prepare_file_rename(&old_path_as_url, &new_path_as_url),
2440− lsp.offset_encoding(),
2441− lsp.name().to_owned(),
2442− )
2443− })
2444− .filter(|(f, _, _)| f.is_some())
2445− .map(|(f, encoding, name)| (helix_lsp::block_on(f.unwrap()), encoding, name))
2446− .collect();
2447−
2448− for (lsp_reply, encoding, name) in edits {
2449− match lsp_reply {
2450− Ok(edit) => {
2451− if let Err(e) = apply_workspace_edit(cx.editor, encoding, &edit) {
2452− log::error!(
2453− ":move command failed to apply edits from lsp {}: {:?}",
2454− name,
2455− e
2456− );
2457− };
2458− }
2459− Err(e) => {
2460− log::error!("LSP {} failed to treat willRename request: {:?}", name, e);
2461− }
2462− };
2463− }
2464−
2465− let doc = doc_mut!(cx.editor);
2466−
2467− doc.set_path(Some(new_path.as_path()));
2468− if let Err(e) = std::fs::rename(&old_path, &new_path) {
2469− doc.set_path(Some(old_path.as_path()));
2470− bail!("Could not move file: {}", e);
2471− };
2472−
2473− doc.language_servers().for_each(|lsp| {
2474− lsp.did_file_rename(&old_path_as_url, &new_path_as_url);
2475− });
2476−
2477− cx.editor
2478− .language_servers
2479− .file_event_handler
2480− .file_changed(new_path);
2481−
2482− Ok(())
2483−}
2484−
24852225pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
2486− TypableCommand {
2487− name: "quit",
2488− aliases: &["q"],
2489− doc: "Close the current view.",
2490− fun: quit,
2491− signature: CommandSignature::none(),
2492− },
2493− TypableCommand {
2494− name: "quit!",
2495− aliases: &["q!"],
2496− doc: "Force close the current view, ignoring unsaved changes.",
2497− fun: force_quit,
2498− signature: CommandSignature::none(),
2499− },
2500− TypableCommand {
2501− name: "open",
2502− aliases: &["o"],
2503− doc: "Open a file from disk into the current view.",
2504− fun: open,
2505− signature: CommandSignature::all(completers::filename),
2506− },
2507− TypableCommand {
2508− name: "buffer-close",
2509− aliases: &["bc", "bclose"],
2510− doc: "Close the current buffer.",
2511− fun: buffer_close,
2512− signature: CommandSignature::all(completers::buffer),
2513− },
2514− TypableCommand {
2515− name: "buffer-close!",
2516− aliases: &["bc!", "bclose!"],
2517− doc: "Close the current buffer forcefully, ignoring unsaved changes.",
2518− fun: force_buffer_close,
2519− signature: CommandSignature::all(completers::buffer)
2520− },
2521− TypableCommand {
2522− name: "buffer-close-others",
2523− aliases: &["bco", "bcloseother"],
2524− doc: "Close all buffers but the currently focused one.",
2525− fun: buffer_close_others,
2526− signature: CommandSignature::none(),
2527− },
2528− TypableCommand {
2529− name: "buffer-close-others!",
2530− aliases: &["bco!", "bcloseother!"],
2531− doc: "Force close all buffers but the currently focused one.",
2532− fun: force_buffer_close_others,
2533− signature: CommandSignature::none(),
2534− },
2535− TypableCommand {
2536− name: "buffer-close-all",
2537− aliases: &["bca", "bcloseall"],
2538− doc: "Close all buffers without quitting.",
2539− fun: buffer_close_all,
2540− signature: CommandSignature::none(),
2541− },
2542− TypableCommand {
2543− name: "buffer-close-all!",
2544− aliases: &["bca!", "bcloseall!"],
2545− doc: "Force close all buffers ignoring unsaved changes without quitting.",
2546− fun: force_buffer_close_all,
2547− signature: CommandSignature::none(),
2548− },
2549− TypableCommand {
2550− name: "buffer-next",
2551− aliases: &["bn", "bnext"],
2552− doc: "Goto next buffer.",
2553− fun: buffer_next,
2554− signature: CommandSignature::none(),
2555− },
2556− TypableCommand {
2557− name: "buffer-previous",
2558− aliases: &["bp", "bprev"],
2559− doc: "Goto previous buffer.",
2560− fun: buffer_previous,
2561− signature: CommandSignature::none(),
2562− },
2563− TypableCommand {
2564− name: "write",
2565− aliases: &["w"],
2566− doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
2567− fun: write,
2568− signature: CommandSignature::positional(&[completers::filename]),
2569− },
2570− TypableCommand {
2571− name: "write!",
2572− aliases: &["w!"],
2573− doc: "Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt)",
2574− fun: force_write,
2575− signature: CommandSignature::positional(&[completers::filename]),
2576− },
2577− TypableCommand {
2578− name: "write-buffer-close",
2579− aliases: &["wbc"],
2580− doc: "Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt)",
2581− fun: write_buffer_close,
2582− signature: CommandSignature::positional(&[completers::filename]),
2583− },
2584− TypableCommand {
2585− name: "write-buffer-close!",
2586− aliases: &["wbc!"],
2587− doc: "Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt)",
2588− fun: force_write_buffer_close,
2589− signature: CommandSignature::positional(&[completers::filename]),
2590− },
2591− TypableCommand {
2592− name: "new",
2593− aliases: &["n"],
2594− doc: "Create a new scratch buffer.",
2595− fun: new_file,
2596− signature: CommandSignature::none(),
2597− },
2598− TypableCommand {
2599− name: "format",
2600− aliases: &["fmt"],
2601− doc: "Format the file using the LSP formatter.",
2602− fun: format,
2603− signature: CommandSignature::none(),
2604− },
2605− TypableCommand {
2606− name: "indent-style",
2607− aliases: &[],
2608− doc: "Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.)",
2609− fun: set_indent_style,
2610− signature: CommandSignature::none(),
2611− },
2612− TypableCommand {
2613− name: "line-ending",
2614− aliases: &[],
2615− #[cfg(not(feature = "unicode-lines"))]
2616− doc: "Set the document's default line ending. Options: crlf, lf.",
2617− #[cfg(feature = "unicode-lines")]
2618− doc: "Set the document's default line ending. Options: crlf, lf, cr, ff, nel.",
2619− fun: set_line_ending,
2620− signature: CommandSignature::none(),
2621− },
2622− TypableCommand {
2623− name: "earlier",
2624− aliases: &["ear"],
2625− doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
2626− fun: earlier,
2627− signature: CommandSignature::none(),
2628− },
2629− TypableCommand {
2630− name: "later",
2631− aliases: &["lat"],
2632− doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
2633− fun: later,
2634− signature: CommandSignature::none(),
2635− },
2636− TypableCommand {
2637− name: "write-quit",
2638− aliases: &["wq", "x"],
2639− doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
2640− fun: write_quit,
2641− signature: CommandSignature::positional(&[completers::filename]),
2642− },
2643− TypableCommand {
2644− name: "write-quit!",
2645− aliases: &["wq!", "x!"],
2646− doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
2647− fun: force_write_quit,
2648− signature: CommandSignature::positional(&[completers::filename]),
2649− },
2650− TypableCommand {
2651− name: "write-all",
2652− aliases: &["wa"],
2653− doc: "Write changes from all buffers to disk.",
2654− fun: write_all,
2655− signature: CommandSignature::none(),
2656− },
2657− TypableCommand {
2658− name: "write-all!",
2659− aliases: &["wa!"],
2660− doc: "Forcefully write changes from all buffers to disk creating necessary subdirectories.",
2661− fun: force_write_all,
2662− signature: CommandSignature::none(),
2663− },
2664− TypableCommand {
2665− name: "write-quit-all",
2666− aliases: &["wqa", "xa"],
2667− doc: "Write changes from all buffers to disk and close all views.",
2668− fun: write_all_quit,
2669− signature: CommandSignature::none(),
2670− },
2671− TypableCommand {
2672− name: "write-quit-all!",
2673− aliases: &["wqa!", "xa!"],
2674− doc: "Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes).",
2675− fun: force_write_all_quit,
2676− signature: CommandSignature::none(),
2677− },
2678− TypableCommand {
2679− name: "quit-all",
2680− aliases: &["qa"],
2681− doc: "Close all views.",
2682− fun: quit_all,
2683− signature: CommandSignature::none(),
2684− },
2685− TypableCommand {
2686− name: "quit-all!",
2687− aliases: &["qa!"],
2688− doc: "Force close all views ignoring unsaved changes.",
2689− fun: force_quit_all,
2690− signature: CommandSignature::none(),
2691− },
2692− TypableCommand {
2693− name: "cquit",
2694− aliases: &["cq"],
2695− doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).",
2696− fun: cquit,
2697− signature: CommandSignature::none(),
2698− },
2699− TypableCommand {
2700− name: "cquit!",
2701− aliases: &["cq!"],
2702− doc: "Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).",
2703− fun: force_cquit,
2704− signature: CommandSignature::none(),
2705− },
2706− TypableCommand {
2707− name: "theme",
2708− aliases: &[],
2709− doc: "Change the editor theme (show current theme if no name specified).",
2710− fun: theme,
2711− signature: CommandSignature::positional(&[completers::theme]),
2712− },
2713− TypableCommand {
2714− name: "yank-join",
2715− aliases: &[],
2716− doc: "Yank joined selections. A separator can be provided as first argument. Default value is newline.",
2717− fun: yank_joined,
2718− signature: CommandSignature::none(),
2719− },
2720− TypableCommand {
2721− name: "clipboard-yank",
2722− aliases: &[],
2723− doc: "Yank main selection into system clipboard.",
2724− fun: yank_main_selection_to_clipboard,
2725− signature: CommandSignature::none(),
2726− },
2727− TypableCommand {
2728− name: "clipboard-yank-join",
2729− aliases: &[],
2730− doc: "Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2731− fun: yank_joined_to_clipboard,
2732− signature: CommandSignature::none(),
2733− },
2734− TypableCommand {
2735− name: "primary-clipboard-yank",
2736− aliases: &[],
2737− doc: "Yank main selection into system primary clipboard.",
2738− fun: yank_main_selection_to_primary_clipboard,
2739− signature: CommandSignature::none(),
2740− },
2741− TypableCommand {
2742− name: "primary-clipboard-yank-join",
2743− aliases: &[],
2744− doc: "Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2745− fun: yank_joined_to_primary_clipboard,
2746− signature: CommandSignature::none(),
2747− },
2748− TypableCommand {
2749− name: "clipboard-paste-after",
2750− aliases: &[],
2751− doc: "Paste system clipboard after selections.",
2752− fun: paste_clipboard_after,
2753− signature: CommandSignature::none(),
2754− },
2755− TypableCommand {
2756− name: "clipboard-paste-before",
2757− aliases: &[],
2758− doc: "Paste system clipboard before selections.",
2759− fun: paste_clipboard_before,
2760− signature: CommandSignature::none(),
2761− },
2762− TypableCommand {
2763− name: "clipboard-paste-replace",
2764− aliases: &[],
2765− doc: "Replace selections with content of system clipboard.",
2766− fun: replace_selections_with_clipboard,
2767− signature: CommandSignature::none(),
2768− },
2769− TypableCommand {
2770− name: "primary-clipboard-paste-after",
2771− aliases: &[],
2772− doc: "Paste primary clipboard after selections.",
2773− fun: paste_primary_clipboard_after,
2774− signature: CommandSignature::none(),
2775− },
2776− TypableCommand {
2777− name: "primary-clipboard-paste-before",
2778− aliases: &[],
2779− doc: "Paste primary clipboard before selections.",
2780− fun: paste_primary_clipboard_before,
2781− signature: CommandSignature::none(),
2782− },
2783− TypableCommand {
2784− name: "primary-clipboard-paste-replace",
2785− aliases: &[],
2786− doc: "Replace selections with content of system primary clipboard.",
2787− fun: replace_selections_with_primary_clipboard,
2788− signature: CommandSignature::none(),
2789− },
2790− TypableCommand {
2791− name: "show-clipboard-provider",
2792− aliases: &[],
2793− doc: "Show clipboard provider name in status bar.",
2794− fun: show_clipboard_provider,
2795− signature: CommandSignature::none(),
2796− },
2797− TypableCommand {
2798− name: "change-current-directory",
2799− aliases: &["cd"],
2800− doc: "Change the current working directory.",
2801− fun: change_current_directory,
2802− signature: CommandSignature::positional(&[completers::directory]),
2803− },
2804− TypableCommand {
2805− name: "show-directory",
2806− aliases: &["pwd"],
2807− doc: "Show the current working directory.",
2808− fun: show_current_directory,
2809− signature: CommandSignature::none(),
2810− },
2811− TypableCommand {
2812− name: "encoding",
2813− aliases: &[],
2814− doc: "Set encoding. Based on `https://encoding.spec.whatwg.org`.",
2815− fun: set_encoding,
2816− signature: CommandSignature::none(),
2817− },
2818− TypableCommand {
2819− name: "character-info",
2820− aliases: &["char"],
2821− doc: "Get info about the character under the primary cursor.",
2822− fun: get_character_info,
2823− signature: CommandSignature::none(),
2824− },
2825− TypableCommand {
2826− name: "reload",
2827− aliases: &["rl"],
2828− doc: "Discard changes and reload from the source file.",
2829− fun: reload,
2830− signature: CommandSignature::none(),
2831− },
2832− TypableCommand {
2833− name: "reload-all",
2834− aliases: &["rla"],
2835− doc: "Discard changes and reload all documents from the source files.",
2836− fun: reload_all,
2837− signature: CommandSignature::none(),
2838− },
2839− TypableCommand {
2840− name: "update",
2841− aliases: &["u"],
2842− doc: "Write changes only if the file has been modified.",
2843− fun: update,
2844− signature: CommandSignature::none(),
2845− },
2846− TypableCommand {
2847− name: "lsp-workspace-command",
2848− aliases: &[],
2849− doc: "Open workspace command picker",
2850− fun: lsp_workspace_command,
2851− signature: CommandSignature::positional(&[completers::lsp_workspace_command]),
2852− },
2853− TypableCommand {
2854− name: "lsp-restart",
2855− aliases: &[],
2856− doc: "Restarts the language servers used by the current doc",
2857− fun: lsp_restart,
2858− signature: CommandSignature::none(),
2859− },
2860− TypableCommand {
2861− name: "lsp-stop",
2862− aliases: &[],
2863− doc: "Stops the language servers that are used by the current doc",
2864− fun: lsp_stop,
2865− signature: CommandSignature::none(),
2866− },
2867− TypableCommand {
2868− name: "tree-sitter-scopes",
2869− aliases: &[],
2870− doc: "Display tree sitter scopes, primarily for theming and development.",
2871− fun: tree_sitter_scopes,
2872− signature: CommandSignature::none(),
2873− },
2874− TypableCommand {
2875− name: "tree-sitter-highlight-name",
2876− aliases: &[],
2877− doc: "Display name of tree-sitter highlight scope under the cursor.",
2878− fun: tree_sitter_highlight_name,
2879− signature: CommandSignature::none(),
2880− },
2881− TypableCommand {
2882− name: "debug-start",
2883− aliases: &["dbg"],
2884− doc: "Start a debug session from a given template with given parameters.",
2885− fun: debug_start,
2886− signature: CommandSignature::none(),
2887− },
2888− TypableCommand {
2889− name: "debug-remote",
2890− aliases: &["dbg-tcp"],
2891− doc: "Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.",
2892− fun: debug_remote,
2893− signature: CommandSignature::none(),
2894− },
2895− TypableCommand {
2896− name: "debug-eval",
2897− aliases: &[],
2898− doc: "Evaluate expression in current debug context.",
2899− fun: debug_eval,
2900− signature: CommandSignature::none(),
2901− },
2902− TypableCommand {
2903− name: "vsplit",
2904− aliases: &["vs"],
2905− doc: "Open the file in a vertical split.",
2906− fun: vsplit,
2907− signature: CommandSignature::all(completers::filename)
2908− },
2909− TypableCommand {
2910− name: "vsplit-new",
2911− aliases: &["vnew"],
2912− doc: "Open a scratch buffer in a vertical split.",
2913− fun: vsplit_new,
2914− signature: CommandSignature::none(),
2915− },
2916− TypableCommand {
2917− name: "hsplit",
2918− aliases: &["hs", "sp"],
2919− doc: "Open the file in a horizontal split.",
2920− fun: hsplit,
2921− signature: CommandSignature::all(completers::filename)
2922− },
2923− TypableCommand {
2924− name: "hsplit-new",
2925− aliases: &["hnew"],
2926− doc: "Open a scratch buffer in a horizontal split.",
2927− fun: hsplit_new,
2928− signature: CommandSignature::none(),
2929− },
2930− TypableCommand {
2931− name: "tutor",
2932− aliases: &[],
2933− doc: "Open the tutorial.",
2934− fun: tutor,
2935− signature: CommandSignature::none(),
2936− },
2937− TypableCommand {
2938− name: "goto",
2939− aliases: &["g"],
2940− doc: "Goto line number.",
2941− fun: goto_line_number,
2942− signature: CommandSignature::none(),
2943− },
2944− TypableCommand {
2945− name: "set-language",
2946− aliases: &["lang"],
2947− doc: "Set the language of current buffer (show current language if no value specified).",
2948− fun: language,
2949− signature: CommandSignature::positional(&[completers::language]),
2950− },
2951− TypableCommand {
2952− name: "set-option",
2953− aliases: &["set"],
2954− doc: "Set a config option at runtime.\nFor example to disable smart case search, use `:set search.smart-case false`.",
2955− fun: set_option,
2956− // TODO: Add support for completion of the options value(s), when appropriate.
2957− signature: CommandSignature::positional(&[completers::setting]),
2958− },
2959− TypableCommand {
2960− name: "toggle-option",
2961− aliases: &["toggle"],
2962− doc: "Toggle a boolean config option at runtime.\nFor example to toggle smart case search, use `:toggle search.smart-case`.",
2963− fun: toggle_option,
2964− signature: CommandSignature::positional(&[completers::setting]),
2965− },
2966− TypableCommand {
2967− name: "get-option",
2968− aliases: &["get"],
2969− doc: "Get the current value of a config option.",
2970− fun: get_option,
2971− signature: CommandSignature::positional(&[completers::setting]),
2972− },
2973− TypableCommand {
2974− name: "sort",
2975− aliases: &[],
2976− doc: "Sort ranges in selection.",
2977− fun: sort,
2978− signature: CommandSignature::none(),
2979− },
2980− TypableCommand {
2981− name: "rsort",
2982− aliases: &[],
2983− doc: "Sort ranges in selection in reverse order.",
2984− fun: sort_reverse,
2985− signature: CommandSignature::none(),
2986− },
2987− TypableCommand {
2988− name: "reflow",
2989− aliases: &[],
2990− doc: "Hard-wrap the current selection of lines to a given width.",
2991− fun: reflow,
2992− signature: CommandSignature::none(),
2993− },
2994− TypableCommand {
2995− name: "tree-sitter-subtree",
2996− aliases: &["ts-subtree"],
2997− doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
2998− fun: tree_sitter_subtree,
2999− signature: CommandSignature::none(),
3000− },
3001− TypableCommand {
3002− name: "config-reload",
3003− aliases: &[],
3004− doc: "Refresh user config.",
3005− fun: refresh_config,
3006− signature: CommandSignature::none(),
3007− },
3008− TypableCommand {
3009− name: "config-open",
3010− aliases: &[],
3011− doc: "Open the user config.toml file.",
3012− fun: open_config,
3013− signature: CommandSignature::none(),
3014− },
3015− TypableCommand {
3016− name: "config-open-workspace",
3017− aliases: &[],
3018− doc: "Open the workspace config.toml file.",
3019− fun: open_workspace_config,
3020− signature: CommandSignature::none(),
3021− },
3022− TypableCommand {
3023− name: "log-open",
3024− aliases: &[],
3025− doc: "Open the helix log file.",
3026− fun: open_log,
3027− signature: CommandSignature::none(),
3028− },
3029− TypableCommand {
3030− name: "insert-output",
3031− aliases: &[],
3032− doc: "Run shell command, inserting output before each selection.",
3033− fun: insert_output,
3034− signature: CommandSignature::none(),
3035− },
3036− TypableCommand {
3037− name: "append-output",
3038− aliases: &[],
3039− doc: "Run shell command, appending output after each selection.",
3040− fun: append_output,
3041− signature: CommandSignature::none(),
3042− },
3043− TypableCommand {
3044− name: "pipe",
3045− aliases: &[],
3046− doc: "Pipe each selection to the shell command.",
3047− fun: pipe,
3048− signature: CommandSignature::none(),
3049− },
3050− TypableCommand {
3051− name: "pipe-to",
3052− aliases: &[],
3053− doc: "Pipe each selection to the shell command, ignoring output.",
3054− fun: pipe_to,
3055− signature: CommandSignature::none(),
3056− },
3057− TypableCommand {
3058− name: "run-shell-command",
3059− aliases: &["sh"],
3060− doc: "Run a shell command",
3061− fun: run_shell_command,
3062− signature: CommandSignature::all(completers::filename)
3063− },
3064− TypableCommand {
3065− name: "reset-diff-change",
3066− aliases: &["diffget", "diffg"],
3067− doc: "Reset the diff change at the cursor position.",
3068− fun: reset_diff_change,
3069− signature: CommandSignature::none(),
3070− },
3071− TypableCommand {
3072− name: "clear-register",
3073− aliases: &[],
3074− doc: "Clear given register. If no argument is provided, clear all registers.",
3075− fun: clear_register,
3076− signature: CommandSignature::none(),
3077− },
3078− TypableCommand {
3079− name: "redraw",
3080− aliases: &[],
3081− doc: "Clear and re-render the whole UI",
3082− fun: redraw,
3083− signature: CommandSignature::none(),
3084− },
3085− TypableCommand {
3086− name: "move",
3087− aliases: &[],
3088− doc: "Move the current buffer and its corresponding file to a different path",
3089− fun: move_buffer,
3090− signature: CommandSignature::positional(&[completers::filename]),
3091− },
3092−];
2226+ TypableCommand {
2227+ name: "quit",
2228+ aliases: &["q"],
2229+ doc: "Close the current view.",
2230+ fun: quit,
2231+ signature: CommandSignature::none(),
2232+ },
2233+ TypableCommand {
2234+ name: "quit!",
2235+ aliases: &["q!"],
2236+ doc: "Force close the current view, ignoring unsaved changes.",
2237+ fun: force_quit,
2238+ signature: CommandSignature::none(),
2239+ },
2240+ TypableCommand {
2241+ name: "open",
2242+ aliases: &["o"],
2243+ doc: "Open a file from disk into the current view.",
2244+ fun: open,
2245+ signature: CommandSignature::all(completers::filename),
2246+ },
2247+ TypableCommand {
2248+ name: "buffer-close",
2249+ aliases: &["bc", "bclose"],
2250+ doc: "Close the current buffer.",
2251+ fun: buffer_close,
2252+ signature: CommandSignature::all(completers::buffer),
2253+ },
2254+ TypableCommand {
2255+ name: "buffer-close!",
2256+ aliases: &["bc!", "bclose!"],
2257+ doc: "Close the current buffer forcefully, ignoring unsaved changes.",
2258+ fun: force_buffer_close,
2259+ signature: CommandSignature::all(completers::buffer)
2260+ },
2261+ TypableCommand {
2262+ name: "buffer-close-others",
2263+ aliases: &["bco", "bcloseother"],
2264+ doc: "Close all buffers but the currently focused one.",
2265+ fun: buffer_close_others,
2266+ signature: CommandSignature::none(),
2267+ },
2268+ TypableCommand {
2269+ name: "buffer-close-others!",
2270+ aliases: &["bco!", "bcloseother!"],
2271+ doc: "Force close all buffers but the currently focused one.",
2272+ fun: force_buffer_close_others,
2273+ signature: CommandSignature::none(),
2274+ },
2275+ TypableCommand {
2276+ name: "buffer-close-all",
2277+ aliases: &["bca", "bcloseall"],
2278+ doc: "Close all buffers without quitting.",
2279+ fun: buffer_close_all,
2280+ signature: CommandSignature::none(),
2281+ },
2282+ TypableCommand {
2283+ name: "buffer-close-all!",
2284+ aliases: &["bca!", "bcloseall!"],
2285+ doc: "Force close all buffers ignoring unsaved changes without quitting.",
2286+ fun: force_buffer_close_all,
2287+ signature: CommandSignature::none(),
2288+ },
2289+ TypableCommand {
2290+ name: "buffer-next",
2291+ aliases: &["bn", "bnext"],
2292+ doc: "Goto next buffer.",
2293+ fun: buffer_next,
2294+ signature: CommandSignature::none(),
2295+ },
2296+ TypableCommand {
2297+ name: "buffer-previous",
2298+ aliases: &["bp", "bprev"],
2299+ doc: "Goto previous buffer.",
2300+ fun: buffer_previous,
2301+ signature: CommandSignature::none(),
2302+ },
2303+ TypableCommand {
2304+ name: "write",
2305+ aliases: &["w"],
2306+ doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
2307+ fun: write,
2308+ signature: CommandSignature::positional(&[completers::filename]),
2309+ },
2310+ TypableCommand {
2311+ name: "write!",
2312+ aliases: &["w!"],
2313+ doc: "Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt)",
2314+ fun: force_write,
2315+ signature: CommandSignature::positional(&[completers::filename]),
2316+ },
2317+ TypableCommand {
2318+ name: "write-buffer-close",
2319+ aliases: &["wbc"],
2320+ doc: "Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt)",
2321+ fun: write_buffer_close,
2322+ signature: CommandSignature::positional(&[completers::filename]),
2323+ },
2324+ TypableCommand {
2325+ name: "write-buffer-close!",
2326+ aliases: &["wbc!"],
2327+ doc: "Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt)",
2328+ fun: force_write_buffer_close,
2329+ signature: CommandSignature::positional(&[completers::filename]),
2330+ },
2331+ TypableCommand {
2332+ name: "new",
2333+ aliases: &["n"],
2334+ doc: "Create a new scratch buffer.",
2335+ fun: new_file,
2336+ // TODO: This seems to complete with a filename, but doesn't use that filename to
2337+ // set the path of the newly created buffer.
2338+ signature: CommandSignature::positional(&[completers::filename]),
2339+ },
2340+ TypableCommand {
2341+ name: "format",
2342+ aliases: &["fmt"],
2343+ doc: "Format the file using the LSP formatter.",
2344+ fun: format,
2345+ signature: CommandSignature::none(),
2346+ },
2347+ TypableCommand {
2348+ name: "indent-style",
2349+ aliases: &[],
2350+ doc: "Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)",
2351+ fun: set_indent_style,
2352+ signature: CommandSignature::none(),
2353+ },
2354+ TypableCommand {
2355+ name: "line-ending",
2356+ aliases: &[],
2357+ #[cfg(not(feature = "unicode-lines"))]
2358+ doc: "Set the document's default line ending. Options: crlf, lf.",
2359+ #[cfg(feature = "unicode-lines")]
2360+ doc: "Set the document's default line ending. Options: crlf, lf, cr, ff, nel.",
2361+ fun: set_line_ending,
2362+ signature: CommandSignature::none(),
2363+ },
2364+ TypableCommand {
2365+ name: "earlier",
2366+ aliases: &["ear"],
2367+ doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
2368+ fun: earlier,
2369+ signature: CommandSignature::none(),
2370+ },
2371+ TypableCommand {
2372+ name: "later",
2373+ aliases: &["lat"],
2374+ doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
2375+ fun: later,
2376+ signature: CommandSignature::none(),
2377+ },
2378+ TypableCommand {
2379+ name: "write-quit",
2380+ aliases: &["wq", "x"],
2381+ doc: "Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt)",
2382+ fun: write_quit,
2383+ signature: CommandSignature::positional(&[completers::filename]),
2384+ },
2385+ TypableCommand {
2386+ name: "write-quit!",
2387+ aliases: &["wq!", "x!"],
2388+ doc: "Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
2389+ fun: force_write_quit,
2390+ signature: CommandSignature::positional(&[completers::filename]),
2391+ },
2392+ TypableCommand {
2393+ name: "write-all",
2394+ aliases: &["wa"],
2395+ doc: "Write changes from all buffers to disk.",
2396+ fun: write_all,
2397+ signature: CommandSignature::none(),
2398+ },
2399+ TypableCommand {
2400+ name: "write-quit-all",
2401+ aliases: &["wqa", "xa"],
2402+ doc: "Write changes from all buffers to disk and close all views.",
2403+ fun: write_all_quit,
2404+ signature: CommandSignature::none(),
2405+ },
2406+ TypableCommand {
2407+ name: "write-quit-all!",
2408+ aliases: &["wqa!", "xa!"],
2409+ doc: "Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes).",
2410+ fun: force_write_all_quit,
2411+ signature: CommandSignature::none(),
2412+ },
2413+ TypableCommand {
2414+ name: "quit-all",
2415+ aliases: &["qa"],
2416+ doc: "Close all views.",
2417+ fun: quit_all,
2418+ signature: CommandSignature::none(),
2419+ },
2420+ TypableCommand {
2421+ name: "quit-all!",
2422+ aliases: &["qa!"],
2423+ doc: "Force close all views ignoring unsaved changes.",
2424+ fun: force_quit_all,
2425+ signature: CommandSignature::none(),
2426+ },
2427+ TypableCommand {
2428+ name: "cquit",
2429+ aliases: &["cq"],
2430+ doc: "Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2).",
2431+ fun: cquit,
2432+ signature: CommandSignature::none(),
2433+ },
2434+ TypableCommand {
2435+ name: "cquit!",
2436+ aliases: &["cq!"],
2437+ doc: "Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).",
2438+ fun: force_cquit,
2439+ signature: CommandSignature::none(),
2440+ },
2441+ TypableCommand {
2442+ name: "theme",
2443+ aliases: &[],
2444+ doc: "Change the editor theme (show current theme if no name specified).",
2445+ fun: theme,
2446+ signature: CommandSignature::positional(&[completers::theme]),
2447+ },
2448+ TypableCommand {
2449+ name: "clipboard-yank",
2450+ aliases: &[],
2451+ doc: "Yank main selection into system clipboard.",
2452+ fun: yank_main_selection_to_clipboard,
2453+ signature: CommandSignature::none(),
2454+ },
2455+ TypableCommand {
2456+ name: "clipboard-yank-join",
2457+ aliases: &[],
2458+ doc: "Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2459+ fun: yank_joined_to_clipboard,
2460+ signature: CommandSignature::none(),
2461+ },
2462+ TypableCommand {
2463+ name: "primary-clipboard-yank",
2464+ aliases: &[],
2465+ doc: "Yank main selection into system primary clipboard.",
2466+ fun: yank_main_selection_to_primary_clipboard,
2467+ signature: CommandSignature::none(),
2468+ },
2469+ TypableCommand {
2470+ name: "primary-clipboard-yank-join",
2471+ aliases: &[],
2472+ doc: "Yank joined selections into system primary clipboard. A separator can be provided as first argument. Default value is newline.", // FIXME: current UI can't display long doc.
2473+ fun: yank_joined_to_primary_clipboard,
2474+ signature: CommandSignature::none(),
2475+ },
2476+ TypableCommand {
2477+ name: "clipboard-paste-after",
2478+ aliases: &[],
2479+ doc: "Paste system clipboard after selections.",
2480+ fun: paste_clipboard_after,
2481+ signature: CommandSignature::none(),
2482+ },
2483+ TypableCommand {
2484+ name: "clipboard-paste-before",
2485+ aliases: &[],
2486+ doc: "Paste system clipboard before selections.",
2487+ fun: paste_clipboard_before,
2488+ signature: CommandSignature::none(),
2489+ },
2490+ TypableCommand {
2491+ name: "clipboard-paste-replace",
2492+ aliases: &[],
2493+ doc: "Replace selections with content of system clipboard.",
2494+ fun: replace_selections_with_clipboard,
2495+ signature: CommandSignature::none(),
2496+ },
2497+ TypableCommand {
2498+ name: "primary-clipboard-paste-after",
2499+ aliases: &[],
2500+ doc: "Paste primary clipboard after selections.",
2501+ fun: paste_primary_clipboard_after,
2502+ signature: CommandSignature::none(),
2503+ },
2504+ TypableCommand {
2505+ name: "primary-clipboard-paste-before",
2506+ aliases: &[],
2507+ doc: "Paste primary clipboard before selections.",
2508+ fun: paste_primary_clipboard_before,
2509+ signature: CommandSignature::none(),
2510+ },
2511+ TypableCommand {
2512+ name: "primary-clipboard-paste-replace",
2513+ aliases: &[],
2514+ doc: "Replace selections with content of system primary clipboard.",
2515+ fun: replace_selections_with_primary_clipboard,
2516+ signature: CommandSignature::none(),
2517+ },
2518+ TypableCommand {
2519+ name: "show-clipboard-provider",
2520+ aliases: &[],
2521+ doc: "Show clipboard provider name in status bar.",
2522+ fun: show_clipboard_provider,
2523+ signature: CommandSignature::none(),
2524+ },
2525+ TypableCommand {
2526+ name: "change-current-directory",
2527+ aliases: &["cd"],
2528+ doc: "Change the current working directory.",
2529+ fun: change_current_directory,
2530+ signature: CommandSignature::positional(&[completers::directory]),
2531+ },
2532+ TypableCommand {
2533+ name: "show-directory",
2534+ aliases: &["pwd"],
2535+ doc: "Show the current working directory.",
2536+ fun: show_current_directory,
2537+ signature: CommandSignature::none(),
2538+ },
2539+ TypableCommand {
2540+ name: "encoding",
2541+ aliases: &[],
2542+ doc: "Set encoding. Based on `https://encoding.spec.whatwg.org`.",
2543+ fun: set_encoding,
2544+ signature: CommandSignature::none(),
2545+ },
2546+ TypableCommand {
2547+ name: "character-info",
2548+ aliases: &["char"],
2549+ doc: "Get info about the character under the primary cursor.",
2550+ fun: get_character_info,
2551+ signature: CommandSignature::none(),
2552+ },
2553+ TypableCommand {
2554+ name: "reload",
2555+ aliases: &[],
2556+ doc: "Discard changes and reload from the source file.",
2557+ fun: reload,
2558+ signature: CommandSignature::none(),
2559+ },
2560+ TypableCommand {
2561+ name: "reload-all",
2562+ aliases: &[],
2563+ doc: "Discard changes and reload all documents from the source files.",
2564+ fun: reload_all,
2565+ signature: CommandSignature::none(),
2566+ },
2567+ TypableCommand {
2568+ name: "update",
2569+ aliases: &["u"],
2570+ doc: "Write changes only if the file has been modified.",
2571+ fun: update,
2572+ signature: CommandSignature::none(),
2573+ },
2574+ TypableCommand {
2575+ name: "lsp-workspace-command",
2576+ aliases: &[],
2577+ doc: "Open workspace command picker",
2578+ fun: lsp_workspace_command,
2579+ signature: CommandSignature::positional(&[completers::lsp_workspace_command]),
2580+ },
2581+ TypableCommand {
2582+ name: "lsp-restart",
2583+ aliases: &[],
2584+ doc: "Restarts the language servers used by the current doc",
2585+ fun: lsp_restart,
2586+ signature: CommandSignature::none(),
2587+ },
2588+ TypableCommand {
2589+ name: "lsp-stop",
2590+ aliases: &[],
2591+ doc: "Stops the language servers that are used by the current doc",
2592+ fun: lsp_stop,
2593+ signature: CommandSignature::none(),
2594+ },
2595+ TypableCommand {
2596+ name: "tree-sitter-scopes",
2597+ aliases: &[],
2598+ doc: "Display tree sitter scopes, primarily for theming and development.",
2599+ fun: tree_sitter_scopes,
2600+ signature: CommandSignature::none(),
2601+ },
2602+ TypableCommand {
2603+ name: "debug-start",
2604+ aliases: &["dbg"],
2605+ doc: "Start a debug session from a given template with given parameters.",
2606+ fun: debug_start,
2607+ signature: CommandSignature::none(),
2608+ },
2609+ TypableCommand {
2610+ name: "debug-remote",
2611+ aliases: &["dbg-tcp"],
2612+ doc: "Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters.",
2613+ fun: debug_remote,
2614+ signature: CommandSignature::none(),
2615+ },
2616+ TypableCommand {
2617+ name: "debug-eval",
2618+ aliases: &[],
2619+ doc: "Evaluate expression in current debug context.",
2620+ fun: debug_eval,
2621+ signature: CommandSignature::none(),
2622+ },
2623+ TypableCommand {
2624+ name: "vsplit",
2625+ aliases: &["vs"],
2626+ doc: "Open the file in a vertical split.",
2627+ fun: vsplit,
2628+ signature: CommandSignature::all(completers::filename)
2629+ },
2630+ TypableCommand {
2631+ name: "vsplit-new",
2632+ aliases: &["vnew"],
2633+ doc: "Open a scratch buffer in a vertical split.",
2634+ fun: vsplit_new,
2635+ signature: CommandSignature::none(),
2636+ },
2637+ TypableCommand {
2638+ name: "hsplit",
2639+ aliases: &["hs", "sp"],
2640+ doc: "Open the file in a horizontal split.",
2641+ fun: hsplit,
2642+ signature: CommandSignature::all(completers::filename)
2643+ },
2644+ TypableCommand {
2645+ name: "hsplit-new",
2646+ aliases: &["hnew"],
2647+ doc: "Open a scratch buffer in a horizontal split.",
2648+ fun: hsplit_new,
2649+ signature: CommandSignature::none(),
2650+ },
2651+ TypableCommand {
2652+ name: "tutor",
2653+ aliases: &[],
2654+ doc: "Open the tutorial.",
2655+ fun: tutor,
2656+ signature: CommandSignature::none(),
2657+ },
2658+ TypableCommand {
2659+ name: "goto",
2660+ aliases: &["g"],
2661+ doc: "Goto line number.",
2662+ fun: goto_line_number,
2663+ signature: CommandSignature::none(),
2664+ },
2665+ TypableCommand {
2666+ name: "set-language",
2667+ aliases: &["lang"],
2668+ doc: "Set the language of current buffer (show current language if no value specified).",
2669+ fun: language,
2670+ signature: CommandSignature::positional(&[completers::language]),
2671+ },
2672+ TypableCommand {
2673+ name: "set-option",
2674+ aliases: &["set"],
2675+ doc: "Set a config option at runtime.\nFor example to disable smart case search, use `:set search.smart-case false`.",
2676+ fun: set_option,
2677+ // TODO: Add support for completion of the options value(s), when appropriate.
2678+ signature: CommandSignature::positional(&[completers::setting]),
2679+ },
2680+ TypableCommand {
2681+ name: "toggle-option",
2682+ aliases: &["toggle"],
2683+ doc: "Toggle a boolean config option at runtime.\nFor example to toggle smart case search, use `:toggle search.smart-case`.",
2684+ fun: toggle_option,
2685+ signature: CommandSignature::positional(&[completers::setting]),
2686+ },
2687+ TypableCommand {
2688+ name: "get-option",
2689+ aliases: &["get"],
2690+ doc: "Get the current value of a config option.",
2691+ fun: get_option,
2692+ signature: CommandSignature::positional(&[completers::setting]),
2693+ },
2694+ TypableCommand {
2695+ name: "sort",
2696+ aliases: &[],
2697+ doc: "Sort ranges in selection.",
2698+ fun: sort,
2699+ signature: CommandSignature::none(),
2700+ },
2701+ TypableCommand {
2702+ name: "rsort",
2703+ aliases: &[],
2704+ doc: "Sort ranges in selection in reverse order.",
2705+ fun: sort_reverse,
2706+ signature: CommandSignature::none(),
2707+ },
2708+ TypableCommand {
2709+ name: "reflow",
2710+ aliases: &[],
2711+ doc: "Hard-wrap the current selection of lines to a given width.",
2712+ fun: reflow,
2713+ signature: CommandSignature::none(),
2714+ },
2715+ TypableCommand {
2716+ name: "tree-sitter-subtree",
2717+ aliases: &["ts-subtree"],
2718+ doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
2719+ fun: tree_sitter_subtree,
2720+ signature: CommandSignature::none(),
2721+ },
2722+ TypableCommand {
2723+ name: "config-reload",
2724+ aliases: &[],
2725+ doc: "Refresh user config.",
2726+ fun: refresh_config,
2727+ signature: CommandSignature::none(),
2728+ },
2729+ TypableCommand {
2730+ name: "config-open",
2731+ aliases: &[],
2732+ doc: "Open the user config.toml file.",
2733+ fun: open_config,
2734+ signature: CommandSignature::none(),
2735+ },
2736+ TypableCommand {
2737+ name: "config-open-workspace",
2738+ aliases: &[],
2739+ doc: "Open the workspace config.toml file.",
2740+ fun: open_workspace_config,
2741+ signature: CommandSignature::none(),
2742+ },
2743+ TypableCommand {
2744+ name: "log-open",
2745+ aliases: &[],
2746+ doc: "Open the helix log file.",
2747+ fun: open_log,
2748+ signature: CommandSignature::none(),
2749+ },
2750+ TypableCommand {
2751+ name: "insert-output",
2752+ aliases: &[],
2753+ doc: "Run shell command, inserting output before each selection.",
2754+ fun: insert_output,
2755+ signature: CommandSignature::none(),
2756+ },
2757+ TypableCommand {
2758+ name: "append-output",
2759+ aliases: &[],
2760+ doc: "Run shell command, appending output after each selection.",
2761+ fun: append_output,
2762+ signature: CommandSignature::none(),
2763+ },
2764+ TypableCommand {
2765+ name: "pipe",
2766+ aliases: &[],
2767+ doc: "Pipe each selection to the shell command.",
2768+ fun: pipe,
2769+ signature: CommandSignature::none(),
2770+ },
2771+ TypableCommand {
2772+ name: "pipe-to",
2773+ aliases: &[],
2774+ doc: "Pipe each selection to the shell command, ignoring output.",
2775+ fun: pipe_to,
2776+ signature: CommandSignature::none(),
2777+ },
2778+ TypableCommand {
2779+ name: "run-shell-command",
2780+ aliases: &["sh"],
2781+ doc: "Run a shell command",
2782+ fun: run_shell_command,
2783+ signature: CommandSignature::all(completers::filename)
2784+ },
2785+ TypableCommand {
2786+ name: "reset-diff-change",
2787+ aliases: &["diffget", "diffg"],
2788+ doc: "Reset the diff change at the cursor position.",
2789+ fun: reset_diff_change,
2790+ signature: CommandSignature::none(),
2791+ },
2792+ TypableCommand {
2793+ name: "clear-register",
2794+ aliases: &[],
2795+ doc: "Clear given register. If no argument is provided, clear all registers.",
2796+ fun: clear_register,
2797+ signature: CommandSignature::none(),
2798+ },
2799+ ];
30932800
30942801pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
30952802 Lazy::new(|| {
@@ -3108,18 +2815,28 @@ pub(super) fn command_mode(cx: &mut Context) {
31082815 ":".into(),
31092816 Some(':'),
31102817 |editor: &Editor, input: &str| {
2818+ static FUZZY_MATCHER: Lazy<fuzzy_matcher::skim::SkimMatcherV2> =
2819+ Lazy::new(fuzzy_matcher::skim::SkimMatcherV2::default);
2820+
31112821 let shellwords = Shellwords::from(input);
31122822 let words = shellwords.words();
31132823
31142824 if words.is_empty() || (words.len() == 1 && !shellwords.ends_with_whitespace()) {
3115− fuzzy_match(
3116− input,
3117− TYPABLE_COMMAND_LIST.iter().map(|command| command.name),
3118− false,
3119− )
3120− .into_iter()
3121− .map(|(name, _)| (0.., name.into()))
3122− .collect()
2825+ // If the command has not been finished yet, complete commands.
2826+ let mut matches: Vec<_> = typed::TYPABLE_COMMAND_LIST
2827+ .iter()
2828+ .filter_map(|command| {
2829+ FUZZY_MATCHER
2830+ .fuzzy_match(command.name, input)
2831+ .map(|score| (command.name, score))
2832+ })
2833+ .collect();
2834+
2835+ matches.sort_unstable_by_key(|(_file, score)| std::cmp::Reverse(*score));
2836+ matches
2837+ .into_iter()
2838+ .map(|(name, _)| (0.., name.into()))
2839+ .collect()
31232840 } else {
31242841 // Otherwise, use the command's completer and the last shellword
31252842 // as completion input.
helix-term/src/compositor.rs−12
@@ -16,7 +16,6 @@ pub enum EventResult {
1616}
1717
1818use crate::job::Jobs;
19−use crate::ui::picker;
2019use helix_view::Editor;
2120
2221pub use helix_view::input::Event;
@@ -80,7 +79,6 @@ pub struct Compositor {
8079 area: Rect,
8180
8281 pub(crate) last_picker: Option<Box<dyn Component>>,
83− pub(crate) full_redraw: bool,
8482}
8583
8684impl Compositor {
@@ -89,7 +87,6 @@ impl Compositor {
8987 layers: Vec::new(),
9088 area,
9189 last_picker: None,
92− full_redraw: false,
9390 }
9491 }
9592
@@ -103,11 +100,6 @@ impl Compositor {
103100
104101 /// Add a layer to be rendered in front of all existing layers.
105102 pub fn push(&mut self, mut layer: Box<dyn Component>) {
106− // immediately clear last_picker field to avoid excessive memory
107− // consumption for picker with many items
108− if layer.id() == Some(picker::ID) {
109− self.last_picker = None;
110− }
111103 let size = self.size();
112104 // trigger required_size on init
113105 layer.required_size((size.width, size.height));
@@ -208,10 +200,6 @@ impl Compositor {
208200 .find(|component| component.id() == Some(id))
209201 .and_then(|component| component.as_any_mut().downcast_mut())
210202 }
211−
212− pub fn need_full_redraw(&mut self) {
213− self.full_redraw = true;
214− }
215203}
216204
217205// View casting, taken straight from Cursive
helix-term/src/config.rs+8−8
@@ -1,5 +1,5 @@
11use crate::keymap;
2−use crate::keymap::{merge_keys, KeyTrie};
2+use crate::keymap::{merge_keys, Keymap};
33use helix_loader::merge_toml_values;
44use helix_view::document::Mode;
55use serde::Deserialize;
@@ -12,7 +12,7 @@ use toml::de::Error as TomlError;
1212#[derive(Debug, Clone, PartialEq)]
1313pub struct Config {
1414 pub theme: Option<String>,
15− pub keys: HashMap<Mode, KeyTrie>,
15+ pub keys: HashMap<Mode, Keymap>,
1616 pub editor: helix_view::editor::Config,
1717}
1818
@@ -20,7 +20,7 @@ pub struct Config {
2020#[serde(deny_unknown_fields)]
2121pub struct ConfigRaw {
2222 pub theme: Option<String>,
23− pub keys: Option<HashMap<Mode, KeyTrie>>,
23+ pub keys: Option<HashMap<Mode, Keymap>>,
2424 pub editor: Option<toml::Value>,
2525}
2626
@@ -109,7 +109,6 @@ impl Config {
109109 )?,
110110 }
111111 }
112−
113112 // these are just two io errors return the one for the global config
114113 (Err(err), Err(_)) => return Err(err),
115114 };
@@ -139,6 +138,7 @@ mod tests {
139138 #[test]
140139 fn parsing_keymaps_config_file() {
141140 use crate::keymap;
141+ use crate::keymap::Keymap;
142142 use helix_core::hashmap;
143143 use helix_view::document::Mode;
144144
@@ -155,13 +155,13 @@ mod tests {
155155 merge_keys(
156156 &mut keys,
157157 hashmap! {
158− Mode::Insert => keymap!({ "Insert mode"
158+ Mode::Insert => Keymap::new(keymap!({ "Insert mode"
159159 "y" => move_line_down,
160160 "S-C-a" => delete_selection,
161− }),
162− Mode::Normal => keymap!({ "Normal mode"
161+ })),
162+ Mode::Normal => Keymap::new(keymap!({ "Normal mode"
163163 "A-F12" => move_next_word_end,
164− }),
164+ })),
165165 },
166166 );
167167
helix-term/src/health.rs+16−44
@@ -181,8 +181,8 @@ pub fn languages_all() -> std::io::Result<()> {
181181 .language
182182 .sort_unstable_by_key(|l| l.language_id.clone());
183183
184− let check_binary = |cmd: Option<&str>| match cmd {
185− Some(cmd) => match which::which(cmd) {
184+ let check_binary = |cmd: Option<String>| match cmd {
185+ Some(cmd) => match which::which(&cmd) {
186186 Ok(_) => column(&format!("✓ {}", cmd), Color::Green),
187187 Err(_) => column(&format!("✘ {}", cmd), Color::Red),
188188 },
@@ -192,15 +192,17 @@ pub fn languages_all() -> std::io::Result<()> {
192192 for lang in &syn_loader_conf.language {
193193 column(&lang.language_id, Color::Reset);
194194
195− let mut cmds = lang.language_servers.iter().filter_map(|ls| {
195+ // TODO multiple language servers (check binary for each supported language server, not just the first)
196+
197+ let lsp = lang.language_servers.first().and_then(|ls| {
196198 syn_loader_conf
197199 .language_server
198200 .get(&ls.name)
199− .map(|config| config.command.as_str())
201+ .map(|config| config.command.clone())
200202 });
201− check_binary(cmds.next());
203+ check_binary(lsp);
202204
203− let dap = lang.debugger.as_ref().map(|dap| dap.command.as_str());
205+ let dap = lang.debugger.as_ref().map(|dap| dap.command.to_string());
204206 check_binary(dap);
205207
206208 for ts_feat in TsFeature::all() {
@@ -211,12 +213,6 @@ pub fn languages_all() -> std::io::Result<()> {
211213 }
212214
213215 writeln!(stdout)?;
214−
215− for cmd in cmds {
216− column("", Color::Reset);
217− check_binary(Some(cmd));
218− writeln!(stdout)?;
219− }
220216 }
221217
222218 Ok(())
@@ -272,12 +268,15 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
272268 }
273269 };
274270
275− probe_protocols(
271+ // TODO multiple language servers
272+ probe_protocol(
276273 "language server",
277− lang.language_servers
278− .iter()
279− .filter_map(|ls| syn_loader_conf.language_server.get(&ls.name))
280− .map(|config| config.command.as_str()),
274+ lang.language_servers.first().and_then(|ls| {
275+ syn_loader_conf
276+ .language_server
277+ .get(&ls.name)
278+ .map(|config| config.command.clone())
279+ }),
281280 )?;
282281
283282 probe_protocol(
@@ -292,33 +291,6 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
292291 Ok(())
293292}
294293
295−/// Display diagnostics about multiple LSPs and DAPs.
296−fn probe_protocols<'a, I: Iterator<Item = &'a str> + 'a>(
297− protocol_name: &str,
298− server_cmds: I,
299−) -> std::io::Result<()> {
300− let stdout = std::io::stdout();
301− let mut stdout = stdout.lock();
302− let mut server_cmds = server_cmds.peekable();
303−
304− write!(stdout, "Configured {}s:", protocol_name)?;
305− if server_cmds.peek().is_none() {
306− writeln!(stdout, "{}", " None".yellow())?;
307− return Ok(());
308− }
309− writeln!(stdout)?;
310−
311− for cmd in server_cmds {
312− let (path, icon) = match which::which(cmd) {
313− Ok(path) => (path.display().to_string().green(), "✓".green()),
314− Err(_) => (format!("'{}' not found in $PATH", cmd).red(), "✘".red()),
315− };
316− writeln!(stdout, " {} {}: {}", icon, cmd, path)?;
317− }
318−
319− Ok(())
320−}
321−
322294/// Display diagnostics about LSP and DAP.
323295fn probe_protocol(protocol_name: &str, server_cmd: Option<String>) -> std::io::Result<()> {
324296 let stdout = std::io::stdout();
helix-term/src/keymap.rs+145−105
@@ -18,7 +18,7 @@ use std::{
1818pub use default::default;
1919use macros::key;
2020
21−#[derive(Debug, Clone, Default)]
21+#[derive(Debug, Clone)]
2222pub struct KeyTrieNode {
2323 /// A label for keys coming under this node, like "Goto mode"
2424 name: String,
@@ -52,6 +52,10 @@ impl KeyTrieNode {
5252 }
5353 }
5454
55+ pub fn name(&self) -> &str {
56+ &self.name
57+ }
58+
5559 /// Merge another Node in. Leaves and subnodes from the other node replace
5660 /// corresponding keyevent in self, except when both other and self have
5761 /// subnodes for same key. In that case the merge is recursive.
@@ -73,40 +77,49 @@ impl KeyTrieNode {
7377 }
7478
7579 pub fn infobox(&self) -> Info {
76− let mut body: Vec<(BTreeSet<KeyEvent>, &str)> = Vec::with_capacity(self.len());
80+ let mut body: Vec<(&str, BTreeSet<KeyEvent>)> = Vec::with_capacity(self.len());
7781 for (&key, trie) in self.iter() {
7882 let desc = match trie {
79− KeyTrie::MappableCommand(cmd) => {
83+ KeyTrie::Leaf(cmd) => {
8084 if cmd.name() == "no_op" {
8185 continue;
8286 }
8387 cmd.doc()
8488 }
85− KeyTrie::Node(n) => &n.name,
89+ KeyTrie::Node(n) => n.name(),
8690 KeyTrie::Sequence(_) => "[Multiple commands]",
8791 };
88− match body.iter().position(|(_, d)| d == &desc) {
92+ match body.iter().position(|(d, _)| d == &desc) {
8993 Some(pos) => {
90− body[pos].0.insert(key);
94+ body[pos].1.insert(key);
9195 }
92− None => body.push((BTreeSet::from([key]), desc)),
96+ None => body.push((desc, BTreeSet::from([key]))),
9397 }
9498 }
95− body.sort_unstable_by_key(|(keys, _)| {
99+ body.sort_unstable_by_key(|(_, keys)| {
96100 self.order
97101 .iter()
98102 .position(|&k| k == *keys.iter().next().unwrap())
99103 .unwrap()
100104 });
105+ let prefix = format!("{} ", self.name());
106+ if body.iter().all(|(desc, _)| desc.starts_with(&prefix)) {
107+ body = body
108+ .into_iter()
109+ .map(|(desc, keys)| (desc.strip_prefix(&prefix).unwrap(), keys))
110+ .collect();
111+ }
112+ Info::from_keymap(self.name(), body)
113+ }
114+ /// Get a reference to the key trie node's order.
115+ pub fn order(&self) -> &[KeyEvent] {
116+ self.order.as_slice()
117+ }
118+}
101119
102− let body: Vec<_> = body
103− .into_iter()
104− .map(|(events, desc)| {
105− let events = events.iter().map(ToString::to_string).collect::<Vec<_>>();
106− (events.join(", "), desc)
107− })
108− .collect();
109− Info::new(&self.name, &body)
120+impl Default for KeyTrieNode {
121+ fn default() -> Self {
122+ Self::new("", HashMap::new(), Vec::new())
110123 }
111124}
112125
@@ -132,7 +145,7 @@ impl DerefMut for KeyTrieNode {
132145
133146#[derive(Debug, Clone, PartialEq)]
134147pub enum KeyTrie {
135− MappableCommand(MappableCommand),
148+ Leaf(MappableCommand),
136149 Sequence(Vec<MappableCommand>),
137150 Node(KeyTrieNode),
138151}
@@ -161,7 +174,7 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
161174 {
162175 command
163176 .parse::<MappableCommand>()
164− .map(KeyTrie::MappableCommand)
177+ .map(KeyTrie::Leaf)
165178 .map_err(E::custom)
166179 }
167180
@@ -195,43 +208,17 @@ impl<'de> serde::de::Visitor<'de> for KeyTrieVisitor {
195208}
196209
197210impl KeyTrie {
198− pub fn reverse_map(&self) -> ReverseKeymap {
199− // recursively visit all nodes in keymap
200− fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec<KeyEvent>) {
201− match node {
202− KeyTrie::MappableCommand(cmd) => {
203− let name = cmd.name();
204− if name != "no_op" {
205− cmd_map.entry(name.into()).or_default().push(keys.clone())
206− }
207− }
208− KeyTrie::Node(next) => {
209− for (key, trie) in &next.map {
210− keys.push(*key);
211− map_node(cmd_map, trie, keys);
212− keys.pop();
213− }
214− }
215− KeyTrie::Sequence(_) => {}
216− };
217− }
218−
219− let mut res = HashMap::new();
220− map_node(&mut res, self, &mut Vec::new());
221− res
222− }
223−
224211 pub fn node(&self) -> Option<&KeyTrieNode> {
225212 match *self {
226213 KeyTrie::Node(ref node) => Some(node),
227− KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
214+ KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
228215 }
229216 }
230217
231218 pub fn node_mut(&mut self) -> Option<&mut KeyTrieNode> {
232219 match *self {
233220 KeyTrie::Node(ref mut node) => Some(node),
234− KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
221+ KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
235222 }
236223 }
237224
@@ -248,7 +235,7 @@ impl KeyTrie {
248235 trie = match trie {
249236 KeyTrie::Node(map) => map.get(key),
250237 // leaf encountered while keys left to process
251− KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
238+ KeyTrie::Leaf(_) | KeyTrie::Sequence(_) => None,
252239 }?
253240 }
254241 Some(trie)
@@ -269,11 +256,75 @@ pub enum KeymapResult {
269256 Cancelled(Vec<KeyEvent>),
270257}
271258
259+#[derive(Debug, Clone, PartialEq, Deserialize)]
260+#[serde(transparent)]
261+pub struct Keymap {
262+ /// Always a Node
263+ root: KeyTrie,
264+}
265+
272266/// A map of command names to keybinds that will execute the command.
273267pub type ReverseKeymap = HashMap<String, Vec<Vec<KeyEvent>>>;
274268
269+impl Keymap {
270+ pub fn new(root: KeyTrie) -> Self {
271+ Keymap { root }
272+ }
273+
274+ pub fn reverse_map(&self) -> ReverseKeymap {
275+ // recursively visit all nodes in keymap
276+ fn map_node(cmd_map: &mut ReverseKeymap, node: &KeyTrie, keys: &mut Vec<KeyEvent>) {
277+ match node {
278+ KeyTrie::Leaf(cmd) => match cmd {
279+ MappableCommand::Typable { name, .. } => {
280+ cmd_map.entry(name.into()).or_default().push(keys.clone())
281+ }
282+ MappableCommand::Static { name, .. } => cmd_map
283+ .entry(name.to_string())
284+ .or_default()
285+ .push(keys.clone()),
286+ },
287+ KeyTrie::Node(next) => {
288+ for (key, trie) in &next.map {
289+ keys.push(*key);
290+ map_node(cmd_map, trie, keys);
291+ keys.pop();
292+ }
293+ }
294+ KeyTrie::Sequence(_) => {}
295+ };
296+ }
297+
298+ let mut res = HashMap::new();
299+ map_node(&mut res, &self.root, &mut Vec::new());
300+ res
301+ }
302+
303+ pub fn root(&self) -> &KeyTrie {
304+ &self.root
305+ }
306+
307+ pub fn merge(&mut self, other: Self) {
308+ self.root.merge_nodes(other.root);
309+ }
310+}
311+
312+impl Deref for Keymap {
313+ type Target = KeyTrieNode;
314+
315+ fn deref(&self) -> &Self::Target {
316+ self.root.node().unwrap()
317+ }
318+}
319+
320+impl Default for Keymap {
321+ fn default() -> Self {
322+ Self::new(KeyTrie::Node(KeyTrieNode::default()))
323+ }
324+}
325+
275326pub struct Keymaps {
276− pub map: Box<dyn DynAccess<HashMap<Mode, KeyTrie>>>,
327+ pub map: Box<dyn DynAccess<HashMap<Mode, Keymap>>>,
277328 /// Stores pending keys waiting for the next key. This is relative to a
278329 /// sticky node if one is in use.
279330 state: Vec<KeyEvent>,
@@ -282,7 +333,7 @@ pub struct Keymaps {
282333}
283334
284335impl Keymaps {
285− pub fn new(map: Box<dyn DynAccess<HashMap<Mode, KeyTrie>>>) -> Self {
336+ pub fn new(map: Box<dyn DynAccess<HashMap<Mode, Keymap>>>) -> Self {
286337 Self {
287338 map,
288339 state: Vec::new(),
@@ -290,7 +341,7 @@ impl Keymaps {
290341 }
291342 }
292343
293− pub fn map(&self) -> DynGuard<HashMap<Mode, KeyTrie>> {
344+ pub fn map(&self) -> DynGuard<HashMap<Mode, Keymap>> {
294345 self.map.load()
295346 }
296347
@@ -322,11 +373,11 @@ impl Keymaps {
322373 let first = self.state.get(0).unwrap_or(&key);
323374 let trie_node = match self.sticky {
324375 Some(ref trie) => Cow::Owned(KeyTrie::Node(trie.clone())),
325− None => Cow::Borrowed(keymap),
376+ None => Cow::Borrowed(&keymap.root),
326377 };
327378
328379 let trie = match trie_node.search(&[*first]) {
329− Some(KeyTrie::MappableCommand(ref cmd)) => {
380+ Some(KeyTrie::Leaf(ref cmd)) => {
330381 return KeymapResult::Matched(cmd.clone());
331382 }
332383 Some(KeyTrie::Sequence(ref cmds)) => {
@@ -345,7 +396,7 @@ impl Keymaps {
345396 }
346397 KeymapResult::Pending(map.clone())
347398 }
348− Some(KeyTrie::MappableCommand(cmd)) => {
399+ Some(KeyTrie::Leaf(cmd)) => {
349400 self.state.clear();
350401 KeymapResult::Matched(cmd.clone())
351402 }
@@ -365,13 +416,9 @@ impl Default for Keymaps {
365416}
366417
367418/// Merge default config keys with user overwritten keys for custom user config.
368−pub fn merge_keys(dst: &mut HashMap<Mode, KeyTrie>, mut delta: HashMap<Mode, KeyTrie>) {
419+pub fn merge_keys(dst: &mut HashMap<Mode, Keymap>, mut delta: HashMap<Mode, Keymap>) {
369420 for (mode, keys) in dst {
370− keys.merge_nodes(
371− delta
372− .remove(mode)
373− .unwrap_or_else(|| KeyTrie::Node(KeyTrieNode::default())),
374− )
421+ keys.merge(delta.remove(mode).unwrap_or_default())
375422 }
376423}
377424
@@ -400,15 +447,17 @@ mod tests {
400447 #[test]
401448 fn merge_partial_keys() {
402449 let keymap = hashmap! {
403− Mode::Normal => keymap!({ "Normal mode"
404− "i" => normal_mode,
405− "无" => insert_mode,
406− "z" => jump_backward,
407− "g" => { "Merge into goto mode"
408− "$" => goto_line_end,
409− "g" => delete_char_forward,
410− },
411− })
450+ Mode::Normal => Keymap::new(
451+ keymap!({ "Normal mode"
452+ "i" => normal_mode,
453+ "无" => insert_mode,
454+ "z" => jump_backward,
455+ "g" => { "Merge into goto mode"
456+ "$" => goto_line_end,
457+ "g" => delete_char_forward,
458+ },
459+ })
460+ )
412461 };
413462 let mut merged_keyamp = default();
414463 merge_keys(&mut merged_keyamp, keymap.clone());
@@ -435,52 +484,40 @@ mod tests {
435484 let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
436485 // Assumes that `g` is a node in default keymap
437486 assert_eq!(
438− keymap.search(&[key!('g'), key!('$')]).unwrap(),
439− &KeyTrie::MappableCommand(MappableCommand::goto_line_end),
487+ keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
488+ &KeyTrie::Leaf(MappableCommand::goto_line_end),
440489 "Leaf should be present in merged subnode"
441490 );
442491 // Assumes that `gg` is in default keymap
443492 assert_eq!(
444− keymap.search(&[key!('g'), key!('g')]).unwrap(),
445− &KeyTrie::MappableCommand(MappableCommand::delete_char_forward),
493+ keymap.root().search(&[key!('g'), key!('g')]).unwrap(),
494+ &KeyTrie::Leaf(MappableCommand::delete_char_forward),
446495 "Leaf should replace old leaf in merged subnode"
447496 );
448497 // Assumes that `ge` is in default keymap
449498 assert_eq!(
450− keymap.search(&[key!('g'), key!('e')]).unwrap(),
451− &KeyTrie::MappableCommand(MappableCommand::goto_last_line),
499+ keymap.root().search(&[key!('g'), key!('e')]).unwrap(),
500+ &KeyTrie::Leaf(MappableCommand::goto_last_line),
452501 "Old leaves in subnode should be present in merged node"
453502 );
454503
455− assert!(
456− merged_keyamp
457− .get(&Mode::Normal)
458− .and_then(|key_trie| key_trie.node())
459− .unwrap()
460− .len()
461− > 1
462− );
463− assert!(
464− merged_keyamp
465− .get(&Mode::Insert)
466− .and_then(|key_trie| key_trie.node())
467− .unwrap()
468− .len()
469− > 0
470− );
504+ assert!(merged_keyamp.get(&Mode::Normal).unwrap().len() > 1);
505+ assert!(merged_keyamp.get(&Mode::Insert).unwrap().len() > 0);
471506 }
472507
473508 #[test]
474509 fn order_should_be_set() {
475510 let keymap = hashmap! {
476− Mode::Normal => keymap!({ "Normal mode"
477− "space" => { ""
478− "s" => { ""
479− "v" => vsplit,
480− "c" => hsplit,
511+ Mode::Normal => Keymap::new(
512+ keymap!({ "Normal mode"
513+ "space" => { ""
514+ "s" => { ""
515+ "v" => vsplit,
516+ "c" => hsplit,
517+ },
481518 },
482− },
483− })
519+ })
520+ )
484521 };
485522 let mut merged_keyamp = default();
486523 merge_keys(&mut merged_keyamp, keymap.clone());
@@ -488,19 +525,22 @@ mod tests {
488525 let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
489526 // Make sure mapping works
490527 assert_eq!(
491− keymap.search(&[key!(' '), key!('s'), key!('v')]).unwrap(),
492− &KeyTrie::MappableCommand(MappableCommand::vsplit),
528+ keymap
529+ .root()
530+ .search(&[key!(' '), key!('s'), key!('v')])
531+ .unwrap(),
532+ &KeyTrie::Leaf(MappableCommand::vsplit),
493533 "Leaf should be present in merged subnode"
494534 );
495535 // Make sure an order was set during merge
496− let node = keymap.search(&[crate::key!(' ')]).unwrap();
497− assert!(!node.node().unwrap().order.as_slice().is_empty())
536+ let node = keymap.root().search(&[crate::key!(' ')]).unwrap();
537+ assert!(!node.node().unwrap().order().is_empty())
498538 }
499539
500540 #[test]
501541 fn aliased_modes_are_same_in_default_keymap() {
502542 let keymaps = Keymaps::default().map();
503− let root = keymaps.get(&Mode::Normal).unwrap();
543+ let root = keymaps.get(&Mode::Normal).unwrap().root();
504544 assert_eq!(
505545 root.search(&[key!(' '), key!('w')]).unwrap(),
506546 root.search(&["C-w".parse::<KeyEvent>().unwrap()]).unwrap(),
@@ -523,7 +563,7 @@ mod tests {
523563 },
524564 "j" | "k" => move_line_down,
525565 });
526− let keymap = normal_mode;
566+ let keymap = Keymap::new(normal_mode);
527567 let mut reverse_map = keymap.reverse_map();
528568
529569 // sort keybindings in order to have consistent tests
@@ -571,7 +611,7 @@ mod tests {
571611 modifiers: KeyModifiers::NONE,
572612 };
573613
574− let expectation = KeyTrie::Node(KeyTrieNode::new(
614+ let expectation = Keymap::new(KeyTrie::Node(KeyTrieNode::new(
575615 "",
576616 hashmap! {
577617 key => KeyTrie::Sequence(vec!{
@@ -588,7 +628,7 @@ mod tests {
588628 })
589629 },
590630 vec![key],
591− ));
631+ )));
592632
593633 assert_eq!(toml::from_str(keys), Ok(expectation));
594634 }
helix-term/src/keymap/default.rs+7−14
@@ -1,10 +1,10 @@
11use std::collections::HashMap;
22
33use super::macros::keymap;
4−use super::{KeyTrie, Mode};
4+use super::{Keymap, Mode};
55use helix_core::hashmap;
66
7−pub fn default() -> HashMap<Mode, KeyTrie> {
7+pub fn default() -> HashMap<Mode, Keymap> {
88 let normal = keymap!({ "Normal mode"
99 "h" | "left" => move_char_left,
1010 "j" | "down" => move_visual_line_down,
@@ -79,7 +79,6 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
7979
8080 "s" => select_regex,
8181 "A-s" => split_selection_on_newline,
82− "A-minus" => merge_selections,
8382 "A-_" => merge_consecutive_selections,
8483 "S" => split_selection,
8584 ";" => collapse_selection,
@@ -88,8 +87,6 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
8887 "A-i" | "A-down" => shrink_selection,
8988 "A-p" | "A-left" => select_prev_sibling,
9089 "A-n" | "A-right" => select_next_sibling,
91− "A-e" => move_parent_node_end,
92− "A-b" => move_parent_node_start,
9390
9491 "%" => select_all,
9592 "x" => extend_line_below,
@@ -267,7 +264,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
267264 "C-v" | "v" => vsplit_new,
268265 },
269266 },
270− "y" => yank_to_clipboard,
267+ "y" => yank_joined_to_clipboard,
271268 "Y" => yank_main_selection_to_clipboard,
272269 "p" => paste_clipboard_after,
273270 "P" => paste_clipboard_before,
@@ -338,9 +335,6 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
338335 "B" => extend_prev_long_word_start,
339336 "E" => extend_next_long_word_end,
340337
341− "A-e" => extend_parent_node_end,
342− "A-b" => extend_parent_node_start,
343−
344338 "n" => extend_search_next,
345339 "N" => extend_search_prev,
346340
@@ -373,8 +367,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
373367 "C-h" | "backspace" | "S-backspace" => delete_char_backward,
374368 "C-d" | "del" => delete_char_forward,
375369 "C-j" | "ret" => insert_newline,
376− "tab" => smart_tab,
377− "S-tab" => insert_tab,
370+ "tab" => insert_tab,
378371
379372 "up" => move_visual_line_up,
380373 "down" => move_visual_line_down,
@@ -386,8 +379,8 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
386379 "end" => goto_line_end_newline,
387380 });
388381 hashmap!(
389− Mode::Normal => normal,
390− Mode::Select => select,
391− Mode::Insert => insert,
382+ Mode::Normal => Keymap::new(normal),
383+ Mode::Select => Keymap::new(select),
384+ Mode::Insert => Keymap::new(insert),
392385 )
393386}
helix-term/src/keymap/macros.rs+4−3
@@ -62,11 +62,12 @@ macro_rules! alt {
6262 };
6363}
6464
65−/// Macro for defining a `KeyTrie`. Example:
65+/// Macro for defining the root of a `Keymap` object. Example:
6666///
6767/// ```
6868/// # use helix_core::hashmap;
6969/// # use helix_term::keymap;
70+/// # use helix_term::keymap::Keymap;
7071/// let normal_mode = keymap!({ "Normal mode"
7172/// "i" => insert_mode,
7273/// "g" => { "Goto"
@@ -75,12 +76,12 @@ macro_rules! alt {
7576/// },
7677/// "j" | "down" => move_line_down,
7778/// });
78−/// let keymap = normal_mode;
79+/// let keymap = Keymap::new(normal_mode);
7980/// ```
8081#[macro_export]
8182macro_rules! keymap {
8283 (@trie $cmd:ident) => {
83− $crate::keymap::KeyTrie::MappableCommand($crate::commands::MappableCommand::$cmd)
84+ $crate::keymap::KeyTrie::Leaf($crate::commands::MappableCommand::$cmd)
8485 };
8586
8687 (@trie
helix-term/src/main.rs+18−22
@@ -4,8 +4,9 @@ use helix_loader::VERSION_AND_GIT_HASH;
44use helix_term::application::Application;
55use helix_term::args::Args;
66use helix_term::config::{Config, ConfigLoadError};
7+use std::path::PathBuf;
78
8−fn setup_logging(verbosity: u64) -> Result<()> {
9+fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
910 let mut base_config = fern::Dispatch::new();
1011
1112 base_config = match verbosity {
@@ -26,7 +27,7 @@ fn setup_logging(verbosity: u64) -> Result<()> {
2627 message
2728 ))
2829 })
29− .chain(fern::log_file(helix_loader::log_file())?);
30+ .chain(fern::log_file(logpath)?);
3031
3132 base_config.chain(file_config).apply()?;
3233
@@ -40,6 +41,12 @@ fn main() -> Result<()> {
4041
4142#[tokio::main]
4243async fn main_impl() -> Result<i32> {
44+ let logpath = helix_loader::log_file();
45+ let parent = logpath.parent().unwrap();
46+ if !parent.exists() {
47+ std::fs::create_dir_all(parent).ok();
48+ }
49+
4350 let help = format!(
4451 "\
4552{} {}
@@ -61,25 +68,20 @@ FLAGS:
6168 -g, --grammar {{fetch|build}} Fetches or builds tree-sitter grammars listed in languages.toml
6269 -c, --config <file> Specifies a file to use for configuration
6370 -v Increases logging verbosity each use for up to 3 times
64− --log <file> Specifies a file to use for logging
71+ --log Specifies a file to use for logging
6572 (default file: {})
6673 -V, --version Prints version information
6774 --vsplit Splits all given files vertically into different windows
6875 --hsplit Splits all given files horizontally into different windows
69− -w, --working-dir <path> Specify an initial working directory
70− +N Open the first given file at line number N
7176",
7277 env!("CARGO_PKG_NAME"),
7378 VERSION_AND_GIT_HASH,
7479 env!("CARGO_PKG_AUTHORS"),
7580 env!("CARGO_PKG_DESCRIPTION"),
76− helix_loader::default_log_file().display(),
81+ logpath.display(),
7782 );
7883
79− let mut args = Args::parse_args().context("could not parse arguments")?;
80−
81− helix_loader::initialize_config_file(args.config_file.clone());
82− helix_loader::initialize_log_file(args.log_file.clone());
84+ let args = Args::parse_args().context("could not parse arguments")?;
8385
8486 // Help has a higher priority and should be handled separately.
8587 if args.display_help {
@@ -114,21 +116,15 @@ FLAGS:
114116 return Ok(0);
115117 }
116118
117− setup_logging(args.verbosity).context("failed to initialize logging")?;
119+ let logpath = args.log_file.as_ref().cloned().unwrap_or(logpath);
120+ setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
118121
119− // Before setting the working directory, resolve all the paths in args.files
120− for (path, _) in args.files.iter_mut() {
121− *path = helix_core::path::get_canonicalized_path(path);
122+ let config_dir = helix_loader::config_dir();
123+ if !config_dir.exists() {
124+ std::fs::create_dir_all(&config_dir).ok();
122125 }
123126
124− // NOTE: Set the working directory early so the correct configuration is loaded. Be aware that
125− // Application::new() depends on this logic so it must be updated if this changes.
126− if let Some(path) = &args.working_directory {
127− helix_loader::set_current_working_dir(path)?;
128− } else if let Some((path, _)) = args.files.first().filter(|p| p.0.is_dir()) {
129− // If the first file is a directory, it will be the working directory unless -w was specified
130− helix_loader::set_current_working_dir(path)?;
131− }
127+ helix_loader::initialize_config_file(args.config_file.clone());
132128
133129 let config = match Config::load_default() {
134130 Ok(config) => config,
helix-term/src/ui/completion.rs+32−60
@@ -2,7 +2,6 @@ use crate::compositor::{Component, Context, Event, EventResult};
22use helix_view::{
33 document::SavePoint,
44 editor::CompleteAction,
5− graphics::Margin,
65 theme::{Modifier, Style},
76 ViewId,
87};
@@ -111,7 +110,6 @@ impl Completion {
111110 start_offset: usize,
112111 trigger_offset: usize,
113112 ) -> Self {
114− let preview_completion_insert = editor.config().preview_completion_insert;
115113 let replace_mode = editor.config().completion_replace;
116114 // Sort completion items according to their preselect status (given by the LSP server)
117115 items.sort_by_key(|item| !item.item.preselect.unwrap_or(false));
@@ -121,7 +119,7 @@ impl Completion {
121119 fn item_to_transaction(
122120 doc: &Document,
123121 view_id: ViewId,
124− item: &lsp::CompletionItem,
122+ item: &CompletionItem,
125123 offset_encoding: OffsetEncoding,
126124 trigger_offset: usize,
127125 include_placeholder: bool,
@@ -132,7 +130,7 @@ impl Completion {
132130 let text = doc.text().slice(..);
133131 let primary_cursor = selection.primary().cursor(text);
134132
135− let (edit_offset, new_text) = if let Some(edit) = &item.text_edit {
133+ let (edit_offset, new_text) = if let Some(edit) = &item.item.text_edit {
136134 let edit = match edit {
137135 lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
138136 lsp::CompletionTextEdit::InsertAndReplace(item) => {
@@ -145,9 +143,7 @@ impl Completion {
145143 }
146144 };
147145
148− let Some(range) =
149− util::lsp_range_to_range(doc.text(), edit.range, offset_encoding)
150− else {
146+ let Some(range) = util::lsp_range_to_range(doc.text(), edit.range, offset_encoding) else{
151147 return Transaction::new(doc.text());
152148 };
153149
@@ -157,9 +153,10 @@ impl Completion {
157153 (Some((start_offset, end_offset)), edit.new_text)
158154 } else {
159155 let new_text = item
156+ .item
160157 .insert_text
161158 .clone()
162− .unwrap_or_else(|| item.label.clone());
159+ .unwrap_or_else(|| item.item.label.clone());
163160 // check that we are still at the correct savepoint
164161 // we can still generate a transaction regardless but if the
165162 // document changed (and not just the selection) then we will
@@ -168,9 +165,9 @@ impl Completion {
168165 (None, new_text)
169166 };
170167
171− if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET))
168+ if matches!(item.item.kind, Some(lsp::CompletionItemKind::SNIPPET))
172169 || matches!(
173− item.insert_text_format,
170+ item.item.insert_text_format,
174171 Some(lsp::InsertTextFormat::SNIPPET)
175172 )
176173 {
@@ -215,26 +212,9 @@ impl Completion {
215212
216213 let (view, doc) = current!(editor);
217214
218− macro_rules! language_server {
219− ($item:expr) => {
220− match editor
221− .language_servers
222− .get_by_id($item.language_server_id)
223− {
224− Some(ls) => ls,
225− None => {
226− editor.set_error("completions are outdated");
227− // TODO close the completion menu somehow,
228− // currently there is no trivial way to access the EditorView to close the completion menu
229− return;
230− }
231− }
232− };
233− }
234−
235215 match event {
236216 PromptEvent::Abort => {}
237− PromptEvent::Update if preview_completion_insert => {
217+ PromptEvent::Update => {
238218 // Update creates "ghost" transactions which are not sent to the
239219 // lsp server to avoid messing up re-requesting completions. Once a
240220 // completion has been selected (with tab, c-n or c-p) it's always accepted whenever anything
@@ -256,18 +236,23 @@ impl Completion {
256236 // always present here
257237 let item = item.unwrap();
258238
239+ let offset_encoding = editor
240+ .language_servers
241+ .get_by_id(item.language_server_id)
242+ .expect("language server disappeared between completion request and application")
243+ .offset_encoding();
244+
259245 let transaction = item_to_transaction(
260246 doc,
261247 view.id,
262− &item.item,
263− language_server!(item).offset_encoding(),
248+ item,
249+ offset_encoding,
264250 trigger_offset,
265251 true,
266252 replace_mode,
267253 );
268254 doc.apply_temporary(&transaction, view.id);
269255 }
270− PromptEvent::Update => {}
271256 PromptEvent::Validate => {
272257 if let Some(CompleteAction::Selected { savepoint }) =
273258 editor.last_completion.take()
@@ -277,8 +262,11 @@ impl Completion {
277262 // always present here
278263 let mut item = item.unwrap().clone();
279264
280− let language_server = language_server!(item);
281− let offset_encoding = language_server.offset_encoding();
265+ let offset_encoding = editor
266+ .language_servers
267+ .get_by_id(item.language_server_id)
268+ .expect("language server disappeared between completion request and application")
269+ .offset_encoding();
282270
283271 let language_server = editor
284272 .language_servers
@@ -295,12 +283,10 @@ impl Completion {
295283 };
296284 // if more text was entered, remove it
297285 doc.restore(view, &savepoint, true);
298− // save an undo checkpoint before the completion
299− doc.append_changes_to_history(view);
300286 let transaction = item_to_transaction(
301287 doc,
302288 view.id,
303− &item.item,
289+ &item,
304290 offset_encoding,
305291 trigger_offset,
306292 false,
@@ -327,18 +313,9 @@ impl Completion {
327313 }
328314 };
329315 });
330−
331− let margin = if editor.menu_border() {
332− Margin::vertical(1)
333− } else {
334− Margin::none()
335− };
336−
337316 let popup = Popup::new(Self::ID, menu)
338317 .with_scrollbar(false)
339− .ignore_escape_key(true)
340− .margin(margin);
341−
318+ .ignore_escape_key(true);
342319 let mut completion = Self {
343320 popup,
344321 start_offset,
@@ -424,18 +401,19 @@ impl Completion {
424401 Some(item) if !item.resolved => item.clone(),
425402 _ => return false,
426403 };
427−
428− let Some(language_server) = cx
404+ let language_server = match cx
429405 .editor
430− .language_server_by_id(current_item.language_server_id)
431− else {
432− return false;
406+ .language_servers
407+ .get_by_id(current_item.language_server_id)
408+ {
409+ Some(language_server) => language_server,
410+ None => return false,
433411 };
434412
435413 // This method should not block the compositor so we handle the response asynchronously.
436− let Some(future) = language_server.resolve_completion_item(current_item.item.clone())
437− else {
438− return false;
414+ let future = match language_server.resolve_completion_item(current_item.item.clone()) {
415+ Some(future) => future,
416+ None => return false,
439417 };
440418
441419 cx.callback(
@@ -579,12 +557,6 @@ impl Component for Completion {
579557 // clear area
580558 let background = cx.editor.theme.get("ui.popup");
581559 surface.clear_with(doc_area, background);
582−
583− if cx.editor.popup_border() {
584− use tui::widgets::{Block, Borders, Widget};
585− Widget::render(Block::default().borders(Borders::ALL), doc_area, surface);
586− }
587−
588560 markdown_doc.render(doc_area, surface, cx);
589561 }
590562}
helix-term/src/ui/document.rs+24−60
@@ -97,8 +97,7 @@ pub fn render_document(
9797 doc: &Document,
9898 offset: ViewPosition,
9999 doc_annotations: &TextAnnotations,
100− syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
101− overlay_highlight_iter: impl Iterator<Item = HighlightEvent>,
100+ highlight_iter: impl Iterator<Item = HighlightEvent>,
102101 theme: &Theme,
103102 line_decoration: &mut [Box<dyn LineDecoration + '_>],
104103 translated_positions: &mut [TranslatedPosition],
@@ -110,8 +109,7 @@ pub fn render_document(
110109 offset,
111110 &doc.text_format(viewport.width, Some(theme)),
112111 doc_annotations,
113− syntax_highlight_iter,
114− overlay_highlight_iter,
112+ highlight_iter,
115113 theme,
116114 line_decoration,
117115 translated_positions,
@@ -159,8 +157,7 @@ pub fn render_text<'t>(
159157 offset: ViewPosition,
160158 text_fmt: &TextFormat,
161159 text_annotations: &TextAnnotations,
162− syntax_highlight_iter: impl Iterator<Item = HighlightEvent>,
163− overlay_highlight_iter: impl Iterator<Item = HighlightEvent>,
160+ highlight_iter: impl Iterator<Item = HighlightEvent>,
164161 theme: &Theme,
165162 line_decorations: &mut [Box<dyn LineDecoration + '_>],
166163 translated_positions: &mut [TranslatedPosition],
@@ -181,16 +178,10 @@ pub fn render_text<'t>(
181178
182179 let (mut formatter, mut first_visible_char_idx) =
183180 DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, offset.anchor);
184− let mut syntax_styles = StyleIter {
181+ let mut styles = StyleIter {
185182 text_style: renderer.text_style,
186183 active_highlights: Vec::with_capacity(64),
187− highlight_iter: syntax_highlight_iter,
188− theme,
189− };
190− let mut overlay_styles = StyleIter {
191− text_style: Style::default(),
192− active_highlights: Vec::with_capacity(64),
193− highlight_iter: overlay_highlight_iter,
184+ highlight_iter,
194185 theme,
195186 };
196187
@@ -202,10 +193,7 @@ pub fn render_text<'t>(
202193 };
203194 let mut is_in_indent_area = true;
204195 let mut last_line_indent_level = 0;
205− let mut syntax_style_span = syntax_styles
206− .next()
207− .unwrap_or_else(|| (Style::default(), usize::MAX));
208− let mut overlay_style_span = overlay_styles
196+ let mut style_span = styles
209197 .next()
210198 .unwrap_or_else(|| (Style::default(), usize::MAX));
211199
@@ -233,16 +221,9 @@ pub fn render_text<'t>(
233221
234222 // skip any graphemes on visual lines before the block start
235223 if pos.row < row_off {
236− if char_pos >= syntax_style_span.1 {
237− syntax_style_span = if let Some(syntax_style_span) = syntax_styles.next() {
238− syntax_style_span
239− } else {
240− break;
241− }
242− }
243− if char_pos >= overlay_style_span.1 {
244− overlay_style_span = if let Some(overlay_style_span) = overlay_styles.next() {
245− overlay_style_span
224+ if char_pos >= style_span.1 {
225+ style_span = if let Some(style_span) = styles.next() {
226+ style_span
246227 } else {
247228 break;
248229 }
@@ -279,15 +260,8 @@ pub fn render_text<'t>(
279260 }
280261
281262 // acquire the correct grapheme style
282− if char_pos >= syntax_style_span.1 {
283− syntax_style_span = syntax_styles
284− .next()
285− .unwrap_or((Style::default(), usize::MAX));
286− }
287− if char_pos >= overlay_style_span.1 {
288− overlay_style_span = overlay_styles
289− .next()
290− .unwrap_or((Style::default(), usize::MAX));
263+ if char_pos >= style_span.1 {
264+ style_span = styles.next().unwrap_or((Style::default(), usize::MAX));
291265 }
292266 char_pos += grapheme.doc_chars();
293267
@@ -301,25 +275,22 @@ pub fn render_text<'t>(
301275 pos,
302276 );
303277
304− let (syntax_style, overlay_style) =
305− if let GraphemeSource::VirtualText { highlight } = grapheme.source {
306− let mut style = renderer.text_style;
307− if let Some(highlight) = highlight {
308− style = style.patch(theme.highlight(highlight.0))
309− }
310− (style, Style::default())
278+ let grapheme_style = if let GraphemeSource::VirtualText { highlight } = grapheme.source {
279+ let style = renderer.text_style;
280+ if let Some(highlight) = highlight {
281+ style.patch(theme.highlight(highlight.0))
311282 } else {
312− (syntax_style_span.0, overlay_style_span.0)
313− };
283+ style
284+ }
285+ } else {
286+ style_span.0
287+ };
314288
315− let is_virtual = grapheme.is_virtual();
289+ let virt = grapheme.is_virtual();
316290 renderer.draw_grapheme(
317291 grapheme.grapheme,
318− GraphemeStyle {
319− syntax_style,
320− overlay_style,
321− },
322− is_virtual,
292+ grapheme_style,
293+ virt,
323294 &mut last_line_indent_level,
324295 &mut is_in_indent_area,
325296 pos,
@@ -351,11 +322,6 @@ pub struct TextRenderer<'a> {
351322 pub viewport: Rect,
352323}
353324
354−pub struct GraphemeStyle {
355− syntax_style: Style,
356− overlay_style: Style,
357−}
358−
359325impl<'a> TextRenderer<'a> {
360326 pub fn new(
361327 surface: &'a mut Surface,
@@ -429,7 +395,7 @@ impl<'a> TextRenderer<'a> {
429395 pub fn draw_grapheme(
430396 &mut self,
431397 grapheme: Grapheme,
432− grapheme_style: GraphemeStyle,
398+ mut style: Style,
433399 is_virtual: bool,
434400 last_indent_level: &mut usize,
435401 is_in_indent_area: &mut bool,
@@ -439,11 +405,9 @@ impl<'a> TextRenderer<'a> {
439405 let is_whitespace = grapheme.is_whitespace();
440406
441407 // TODO is it correct to apply the whitespace style to all unicode white spaces?
442− let mut style = grapheme_style.syntax_style;
443408 if is_whitespace {
444409 style = style.patch(self.whitespace_style);
445410 }
446− style = style.patch(grapheme_style.overlay_style);
447411
448412 let width = grapheme.width();
449413 let space = if is_virtual { " " } else { &self.space };
helix-term/src/ui/editor.rs+57−110
@@ -43,8 +43,6 @@ pub struct EditorView {
4343 pub(crate) last_insert: (commands::MappableCommand, Vec<InsertEvent>),
4444 pub(crate) completion: Option<Completion>,
4545 spinners: ProgressSpinners,
46− /// Tracks if the terminal window is focused by reaction to terminal focus events
47− terminal_focused: bool,
4846}
4947
5048#[derive(Debug, Clone)]
@@ -73,7 +71,6 @@ impl EditorView {
7371 last_insert: (commands::MappableCommand::normal_mode, Vec::new()),
7472 completion: None,
7573 spinners: ProgressSpinners::default(),
76− terminal_focused: true,
7774 }
7875 }
7976
@@ -124,20 +121,16 @@ impl EditorView {
124121 line_decorations.push(Box::new(line_decoration));
125122 }
126123
127− let syntax_highlights =
124+ let mut highlights =
128125 Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
129−
130− let mut overlay_highlights =
131− Self::empty_highlight_iter(doc, view.offset.anchor, inner.height);
132− let overlay_syntax_highlights = Self::overlay_syntax_highlights(
126+ let overlay_highlights = Self::overlay_syntax_highlights(
133127 doc,
134128 view.offset.anchor,
135129 inner.height,
136130 &text_annotations,
137131 );
138− if !overlay_syntax_highlights.is_empty() {
139− overlay_highlights =
140− Box::new(syntax::merge(overlay_highlights, overlay_syntax_highlights));
132+ if !overlay_highlights.is_empty() {
133+ highlights = Box::new(syntax::merge(highlights, overlay_highlights));
141134 }
142135
143136 for diagnostic in Self::doc_diagnostics_highlights(doc, theme) {
@@ -146,41 +139,39 @@ impl EditorView {
146139 if diagnostic.is_empty() {
147140 continue;
148141 }
149− overlay_highlights = Box::new(syntax::merge(overlay_highlights, diagnostic));
142+ highlights = Box::new(syntax::merge(highlights, diagnostic));
150143 }
151144
152− if is_focused {
145+ let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
153146 let highlights = syntax::merge(
154− overlay_highlights,
147+ highlights,
155148 Self::doc_selection_highlights(
156149 editor.mode(),
157150 doc,
158151 view,
159152 theme,
160153 &config.cursor_shape,
161− self.terminal_focused,
162154 ),
163155 );
164156 let focused_view_elements = Self::highlight_focused_view_elements(view, doc, theme);
165157 if focused_view_elements.is_empty() {
166− overlay_highlights = Box::new(highlights)
158+ Box::new(highlights)
167159 } else {
168− overlay_highlights = Box::new(syntax::merge(highlights, focused_view_elements))
160+ Box::new(syntax::merge(highlights, focused_view_elements))
169161 }
170− }
162+ } else {
163+ Box::new(highlights)
164+ };
171165
172− let gutter_overflow = view.gutter_offset(doc) == 0;
173− if !gutter_overflow {
174− Self::render_gutter(
175− editor,
176− doc,
177− view,
178− view.area,
179− theme,
180− is_focused & self.terminal_focused,
181− &mut line_decorations,
182− );
183− }
166+ Self::render_gutter(
167+ editor,
168+ doc,
169+ view,
170+ view.area,
171+ theme,
172+ is_focused,
173+ &mut line_decorations,
174+ );
184175
185176 if is_focused {
186177 let cursor = doc
@@ -200,8 +191,7 @@ impl EditorView {
200191 doc,
201192 view.offset,
202193 &text_annotations,
203− syntax_highlights,
204− overlay_highlights,
194+ highlights,
205195 theme,
206196 &mut line_decorations,
207197 &mut translated_positions,
@@ -261,39 +251,27 @@ impl EditorView {
261251 .for_each(|area| surface.set_style(area, ruler_theme))
262252 }
263253
264− fn viewport_byte_range(
265− text: helix_core::RopeSlice,
266− row: usize,
267− height: u16,
268− ) -> std::ops::Range<usize> {
269− // Calculate viewport byte ranges:
270− // Saturating subs to make it inclusive zero indexing.
271− let last_line = text.len_lines().saturating_sub(1);
272− let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
273− let start = text.line_to_byte(row.min(last_line));
274− let end = text.line_to_byte(last_visible_line + 1);
275−
276− start..end
277− }
278−
279− pub fn empty_highlight_iter(
254+ pub fn overlay_syntax_highlights(
280255 doc: &Document,
281256 anchor: usize,
282257 height: u16,
283− ) -> Box<dyn Iterator<Item = HighlightEvent>> {
258+ text_annotations: &TextAnnotations,
259+ ) -> Vec<(usize, std::ops::Range<usize>)> {
284260 let text = doc.text().slice(..);
285261 let row = text.char_to_line(anchor.min(text.len_chars()));
286262
287− // Calculate viewport byte ranges:
288− // Saturating subs to make it inclusive zero indexing.
289− let range = Self::viewport_byte_range(text, row, height);
290− Box::new(
291− [HighlightEvent::Source {
292− start: text.byte_to_char(range.start),
293− end: text.byte_to_char(range.end),
294− }]
295− .into_iter(),
296− )
263+ let range = {
264+ // Calculate viewport byte ranges:
265+ // Saturating subs to make it inclusive zero indexing.
266+ let last_line = text.len_lines().saturating_sub(1);
267+ let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
268+ let start = text.line_to_byte(row.min(last_line));
269+ let end = text.line_to_byte(last_visible_line + 1);
270+
271+ start..end
272+ };
273+
274+ text_annotations.collect_overlay_highlights(range)
297275 }
298276
299277 /// Get syntax highlights for a document in a view represented by the first line
@@ -308,7 +286,16 @@ impl EditorView {
308286 let text = doc.text().slice(..);
309287 let row = text.char_to_line(anchor.min(text.len_chars()));
310288
311− let range = Self::viewport_byte_range(text, row, height);
289+ let range = {
290+ // Calculate viewport byte ranges:
291+ // Saturating subs to make it inclusive zero indexing.
292+ let last_line = text.len_lines().saturating_sub(1);
293+ let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
294+ let start = text.line_to_byte(row.min(last_line));
295+ let end = text.line_to_byte(last_visible_line + 1);
296+
297+ start..end
298+ };
312299
313300 match doc.syntax() {
314301 Some(syntax) => {
@@ -341,20 +328,6 @@ impl EditorView {
341328 }
342329 }
343330
344− pub fn overlay_syntax_highlights(
345− doc: &Document,
346− anchor: usize,
347− height: u16,
348− text_annotations: &TextAnnotations,
349− ) -> Vec<(usize, std::ops::Range<usize>)> {
350− let text = doc.text().slice(..);
351− let row = text.char_to_line(anchor.min(text.len_chars()));
352−
353− let range = Self::viewport_byte_range(text, row, height);
354−
355− text_annotations.collect_overlay_highlights(range)
356− }
357−
358331 /// Get highlight spans for document diagnostics
359332 pub fn doc_diagnostics_highlights(
360333 doc: &Document,
@@ -386,7 +359,7 @@ impl EditorView {
386359 let mut warning_vec = Vec::new();
387360 let mut error_vec = Vec::new();
388361
389− for diagnostic in doc.shown_diagnostics() {
362+ for diagnostic in doc.diagnostics() {
390363 // Separate diagnostics into different Vecs by severity.
391364 let (vec, scope) = match diagnostic.severity {
392365 Some(Severity::Info) => (&mut info_vec, info),
@@ -421,7 +394,6 @@ impl EditorView {
421394 view: &View,
422395 theme: &Theme,
423396 cursor_shape_config: &CursorShapeConfig,
424− is_terminal_focused: bool,
425397 ) -> Vec<(usize, std::ops::Range<usize>)> {
426398 let text = doc.text().slice(..);
427399 let selection = doc.selection(view.id);
@@ -469,7 +441,7 @@ impl EditorView {
469441
470442 // Special-case: cursor at end of the rope.
471443 if range.head == range.anchor && range.head == text.len_chars() {
472− if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
444+ if !selection_is_primary || cursor_is_block {
473445 // Bar and underline cursors are drawn by the terminal
474446 // BUG: If the editor area loses focus while having a bar or
475447 // underline cursor (eg. when a regex prompt has focus) then
@@ -492,17 +464,13 @@ impl EditorView {
492464 cursor_start
493465 };
494466 spans.push((selection_scope, range.anchor..selection_end));
495− // add block cursors
496− // skip primary cursor if terminal is unfocused - crossterm cursor is used in that case
497− if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
467+ if !selection_is_primary || cursor_is_block {
498468 spans.push((cursor_scope, cursor_start..range.head));
499469 }
500470 } else {
501471 // Reverse case.
502472 let cursor_end = next_grapheme_boundary(text, range.head);
503− // add block cursors
504− // skip primary cursor if terminal is unfocused - crossterm cursor is used in that case
505− if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
473+ if !selection_is_primary || cursor_is_block {
506474 spans.push((cursor_scope, range.head..cursor_end));
507475 }
508476 // non block cursors look like they exclude the cursor
@@ -533,9 +501,7 @@ impl EditorView {
533501 use helix_core::match_brackets;
534502 let pos = doc.selection(view.id).primary().cursor(text);
535503
536− if let Some(pos) =
537− match_brackets::find_matching_bracket(syntax, doc.text().slice(..), pos)
538− {
504+ if let Some(pos) = match_brackets::find_matching_bracket(syntax, doc.text(), pos) {
539505 // ensure col is on screen
540506 if let Some(highlight) = theme.find_scope_index_exact("ui.cursor.match") {
541507 return vec![(highlight, pos..pos + 1)];
@@ -941,9 +907,8 @@ impl EditorView {
941907 let text = doc.text().slice(..);
942908 let cursor = doc.selection(view.id).primary().cursor(text);
943909
944− let shift_position = |pos: usize| -> usize {
945− (pos + cursor).saturating_sub(trigger_offset)
946− };
910+ let shift_position =
911+ |pos: usize| -> usize { pos + cursor - trigger_offset };
947912
948913 let tx = Transaction::change(
949914 doc.text(),
@@ -978,8 +943,6 @@ impl EditorView {
978943 self.handle_keymap_event(mode, cxt, event);
979944 if self.keymaps.pending().is_empty() {
980945 cxt.editor.count = None
981− } else {
982− cxt.editor.selected_register = cxt.register.take();
983946 }
984947 }
985948 }
@@ -1099,7 +1062,6 @@ impl EditorView {
10991062 let editor = &mut cxt.editor;
11001063
11011064 if let Some((pos, view_id)) = pos_and_view(editor, row, column, true) {
1102− let prev_view_id = view!(editor).id;
11031065 let doc = doc_mut!(editor, &view!(editor, view_id).doc);
11041066
11051067 if modifiers == KeyModifiers::ALT {
@@ -1109,10 +1071,6 @@ impl EditorView {
11091071 doc.set_selection(view_id, Selection::point(pos));
11101072 }
11111073
1112− if view_id != prev_view_id {
1113− self.clear_completion(editor);
1114− }
1115−
11161074 editor.focus(view_id);
11171075 editor.ensure_cursor_in_view(view_id);
11181076
@@ -1406,17 +1364,13 @@ impl Component for EditorView {
14061364
14071365 Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
14081366 Event::IdleTimeout => self.handle_idle_timeout(&mut cx),
1409− Event::FocusGained => {
1410− self.terminal_focused = true;
1411− EventResult::Consumed(None)
1412− }
1367+ Event::FocusGained => EventResult::Ignored(None),
14131368 Event::FocusLost => {
14141369 if context.editor.config().auto_save {
14151370 if let Err(e) = commands::typed::write_all_impl(context, false, false) {
14161371 context.editor.set_error(format!("{}", e));
14171372 }
14181373 }
1419− self.terminal_focused = false;
14201374 EventResult::Consumed(None)
14211375 }
14221376 }
@@ -1526,15 +1480,8 @@ impl Component for EditorView {
15261480
15271481 fn cursor(&self, _area: Rect, editor: &Editor) -> (Option<Position>, CursorKind) {
15281482 match editor.cursor() {
1529− // all block cursors are drawn manually
1530− (pos, CursorKind::Block) => {
1531− if self.terminal_focused {
1532− (pos, CursorKind::Hidden)
1533− } else {
1534− // use crossterm cursor when terminal loses focus
1535− (pos, CursorKind::Underline)
1536− }
1537− }
1483+ // All block cursors are drawn manually
1484+ (pos, CursorKind::Block) => (pos, CursorKind::Hidden),
15381485 cursor => cursor,
15391486 }
15401487 }
helix-term/src/ui/fuzzy_match.rsnew file+239
@@ -0,0 +1,239 @@
1+use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
2+use fuzzy_matcher::FuzzyMatcher;
3+
4+#[cfg(test)]
5+mod test;
6+
7+struct QueryAtom {
8+ kind: QueryAtomKind,
9+ atom: String,
10+ ignore_case: bool,
11+ inverse: bool,
12+}
13+impl QueryAtom {
14+ fn new(atom: &str) -> Option<QueryAtom> {
15+ let mut atom = atom.to_string();
16+ let inverse = atom.starts_with('!');
17+ if inverse {
18+ atom.remove(0);
19+ }
20+
21+ let mut kind = match atom.chars().next() {
22+ Some('^') => QueryAtomKind::Prefix,
23+ Some('\'') => QueryAtomKind::Substring,
24+ _ if inverse => QueryAtomKind::Substring,
25+ _ => QueryAtomKind::Fuzzy,
26+ };
27+
28+ if atom.starts_with(['^', '\'']) {
29+ atom.remove(0);
30+ }
31+
32+ if atom.is_empty() {
33+ return None;
34+ }
35+
36+ if atom.ends_with('$') && !atom.ends_with("\\$") {
37+ atom.pop();
38+ kind = if kind == QueryAtomKind::Prefix {
39+ QueryAtomKind::Exact
40+ } else {
41+ QueryAtomKind::Postfix
42+ }
43+ }
44+
45+ Some(QueryAtom {
46+ kind,
47+ atom: atom.replace('\\', ""),
48+ // not ideal but fuzzy_matches only knows ascii uppercase so more consistent
49+ // to behave the same
50+ ignore_case: kind != QueryAtomKind::Fuzzy
51+ && atom.chars().all(|c| c.is_ascii_lowercase()),
52+ inverse,
53+ })
54+ }
55+
56+ fn indices(&self, matcher: &Matcher, item: &str, indices: &mut Vec<usize>) -> bool {
57+ // for inverse there are no indices to return
58+ // just return whether we matched
59+ if self.inverse {
60+ return self.matches(matcher, item);
61+ }
62+ let buf;
63+ let item = if self.ignore_case {
64+ buf = item.to_ascii_lowercase();
65+ &buf
66+ } else {
67+ item
68+ };
69+ let off = match self.kind {
70+ QueryAtomKind::Fuzzy => {
71+ if let Some((_, fuzzy_indices)) = matcher.fuzzy_indices(item, &self.atom) {
72+ indices.extend_from_slice(&fuzzy_indices);
73+ return true;
74+ } else {
75+ return false;
76+ }
77+ }
78+ QueryAtomKind::Substring => {
79+ if let Some(off) = item.find(&self.atom) {
80+ off
81+ } else {
82+ return false;
83+ }
84+ }
85+ QueryAtomKind::Prefix if item.starts_with(&self.atom) => 0,
86+ QueryAtomKind::Postfix if item.ends_with(&self.atom) => item.len() - self.atom.len(),
87+ QueryAtomKind::Exact if item == self.atom => 0,
88+ _ => return false,
89+ };
90+
91+ indices.extend(off..(off + self.atom.len()));
92+ true
93+ }
94+
95+ fn matches(&self, matcher: &Matcher, item: &str) -> bool {
96+ let buf;
97+ let item = if self.ignore_case {
98+ buf = item.to_ascii_lowercase();
99+ &buf
100+ } else {
101+ item
102+ };
103+ let mut res = match self.kind {
104+ QueryAtomKind::Fuzzy => matcher.fuzzy_match(item, &self.atom).is_some(),
105+ QueryAtomKind::Substring => item.contains(&self.atom),
106+ QueryAtomKind::Prefix => item.starts_with(&self.atom),
107+ QueryAtomKind::Postfix => item.ends_with(&self.atom),
108+ QueryAtomKind::Exact => item == self.atom,
109+ };
110+ if self.inverse {
111+ res = !res;
112+ }
113+ res
114+ }
115+}
116+
117+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
118+enum QueryAtomKind {
119+ /// Item is a fuzzy match of this behaviour
120+ ///
121+ /// Usage: `foo`
122+ Fuzzy,
123+ /// Item contains query atom as a continuous substring
124+ ///
125+ /// Usage `'foo`
126+ Substring,
127+ /// Item starts with query atom
128+ ///
129+ /// Usage: `^foo`
130+ Prefix,
131+ /// Item ends with query atom
132+ ///
133+ /// Usage: `foo$`
134+ Postfix,
135+ /// Item is equal to query atom
136+ ///
137+ /// Usage `^foo$`
138+ Exact,
139+}
140+
141+#[derive(Default)]
142+pub struct FuzzyQuery {
143+ first_fuzzy_atom: Option<String>,
144+ query_atoms: Vec<QueryAtom>,
145+}
146+
147+fn query_atoms(query: &str) -> impl Iterator<Item = &str> + '_ {
148+ let mut saw_backslash = false;
149+ query.split(move |c| {
150+ saw_backslash = match c {
151+ ' ' if !saw_backslash => return true,
152+ '\\' => true,
153+ _ => false,
154+ };
155+ false
156+ })
157+}
158+
159+impl FuzzyQuery {
160+ pub fn refine(&self, query: &str, old_query: &str) -> (FuzzyQuery, bool) {
161+ // TODO: we could be a lot smarter about this
162+ let new_query = Self::new(query);
163+ let mut is_refinement = query.starts_with(old_query);
164+
165+ // if the last atom is an inverse atom adding more text to it
166+ // will actually increase the number of matches and we can not refine
167+ // the matches.
168+ if is_refinement && !self.query_atoms.is_empty() {
169+ let last_idx = self.query_atoms.len() - 1;
170+ if self.query_atoms[last_idx].inverse
171+ && self.query_atoms[last_idx].atom != new_query.query_atoms[last_idx].atom
172+ {
173+ is_refinement = false;
174+ }
175+ }
176+
177+ (new_query, is_refinement)
178+ }
179+
180+ pub fn new(query: &str) -> FuzzyQuery {
181+ let mut first_fuzzy_query = None;
182+ let query_atoms = query_atoms(query)
183+ .filter_map(|atom| {
184+ let atom = QueryAtom::new(atom)?;
185+ if atom.kind == QueryAtomKind::Fuzzy && first_fuzzy_query.is_none() {
186+ first_fuzzy_query = Some(atom.atom);
187+ None
188+ } else {
189+ Some(atom)
190+ }
191+ })
192+ .collect();
193+ FuzzyQuery {
194+ first_fuzzy_atom: first_fuzzy_query,
195+ query_atoms,
196+ }
197+ }
198+
199+ pub fn fuzzy_match(&self, item: &str, matcher: &Matcher) -> Option<i64> {
200+ // use the rank of the first fuzzzy query for the rank, because merging ranks is not really possible
201+ // this behaviour matches fzf and skim
202+ let score = self
203+ .first_fuzzy_atom
204+ .as_ref()
205+ .map_or(Some(0), |atom| matcher.fuzzy_match(item, atom))?;
206+ if self
207+ .query_atoms
208+ .iter()
209+ .any(|atom| !atom.matches(matcher, item))
210+ {
211+ return None;
212+ }
213+ Some(score)
214+ }
215+
216+ pub fn fuzzy_indices(&self, item: &str, matcher: &Matcher) -> Option<(i64, Vec<usize>)> {
217+ let (score, mut indices) = self.first_fuzzy_atom.as_ref().map_or_else(
218+ || Some((0, Vec::new())),
219+ |atom| matcher.fuzzy_indices(item, atom),
220+ )?;
221+
222+ // fast path for the common case of just a single atom
223+ if self.query_atoms.is_empty() {
224+ return Some((score, indices));
225+ }
226+
227+ for atom in &self.query_atoms {
228+ if !atom.indices(matcher, item, &mut indices) {
229+ return None;
230+ }
231+ }
232+
233+ // deadup and remove duplicate matches
234+ indices.sort_unstable();
235+ indices.dedup();
236+
237+ Some((score, indices))
238+ }
239+}
helix-term/src/ui/fuzzy_match/test.rsnew file+47
@@ -0,0 +1,47 @@
1+use crate::ui::fuzzy_match::FuzzyQuery;
2+use crate::ui::fuzzy_match::Matcher;
3+
4+fn run_test<'a>(query: &str, items: &'a [&'a str]) -> Vec<String> {
5+ let query = FuzzyQuery::new(query);
6+ let matcher = Matcher::default();
7+ items
8+ .iter()
9+ .filter_map(|item| {
10+ let (_, indices) = query.fuzzy_indices(item, &matcher)?;
11+ let matched_string = indices
12+ .iter()
13+ .map(|&pos| item.chars().nth(pos).unwrap())
14+ .collect();
15+ Some(matched_string)
16+ })
17+ .collect()
18+}
19+
20+#[test]
21+fn match_single_value() {
22+ let matches = run_test("foo", &["foobar", "foo", "bar"]);
23+ assert_eq!(matches, &["foo", "foo"])
24+}
25+
26+#[test]
27+fn match_multiple_values() {
28+ let matches = run_test(
29+ "foo bar",
30+ &["foo bar", "foo bar", "bar foo", "bar", "foo"],
31+ );
32+ assert_eq!(matches, &["foobar", "foobar", "barfoo"])
33+}
34+
35+#[test]
36+fn space_escape() {
37+ let matches = run_test(r"foo\ bar", &["bar foo", "foo bar", "foobar"]);
38+ assert_eq!(matches, &["foo bar"])
39+}
40+
41+#[test]
42+fn trim() {
43+ let matches = run_test(r" foo bar ", &["bar foo", "foo bar", "foobar"]);
44+ assert_eq!(matches, &["barfoo", "foobar", "foobar"]);
45+ let matches = run_test(r" foo bar\ ", &["bar foo", "foo bar", "foobar"]);
46+ assert_eq!(matches, &["bar foo"])
47+}
helix-term/src/ui/lsp.rs+3−5
@@ -62,7 +62,7 @@ impl Component for SignatureHelp {
6262 });
6363
6464 let sig_text = crate::ui::markdown::highlighted_code_block(
65− &self.signature,
65+ self.signature.clone(),
6666 &self.language,
6767 Some(&cx.editor.theme),
6868 Arc::clone(&self.config_loader),
@@ -92,9 +92,7 @@ impl Component for SignatureHelp {
9292 Some(doc) => Markdown::new(doc.clone(), Arc::clone(&self.config_loader)),
9393 };
9494 let sig_doc = sig_doc.parse(Some(&cx.editor.theme));
95− let sig_doc_area = area
96− .clip_top(sig_text_area.height + 2)
97− .clip_bottom(u16::from(cx.editor.popup_border()));
95+ let sig_doc_area = area.clip_top(sig_text_area.height + 2);
9896 let sig_doc_para = Paragraph::new(sig_doc)
9997 .wrap(Wrap { trim: false })
10098 .scroll((cx.scroll.unwrap_or_default() as u16, 0));
@@ -111,7 +109,7 @@ impl Component for SignatureHelp {
111109 let max_text_width = (viewport.0 - PADDING).min(120);
112110
113111 let signature_text = crate::ui::markdown::highlighted_code_block(
114− &self.signature,
112+ self.signature.clone(),
115113 &self.language,
116114 None,
117115 Arc::clone(&self.config_loader),
helix-term/src/ui/markdown.rs+12−19
@@ -10,15 +10,14 @@ use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag};
1010
1111use helix_core::{
1212 syntax::{self, HighlightEvent, InjectionLanguageMarker, Syntax},
13− RopeSlice,
13+ Rope,
1414};
1515use helix_view::{
1616 graphics::{Margin, Rect, Style},
17− theme::Modifier,
1817 Theme,
1918};
2019
21−fn styled_multiline_text<'a>(text: &str, style: Style) -> Text<'a> {
20+fn styled_multiline_text<'a>(text: String, style: Style) -> Text<'a> {
2221 let spans: Vec<_> = text
2322 .lines()
2423 .map(|line| Span::styled(line.to_string(), style))
@@ -28,7 +27,7 @@ fn styled_multiline_text<'a>(text: &str, style: Style) -> Text<'a> {
2827}
2928
3029pub fn highlighted_code_block<'a>(
31− text: &str,
30+ text: String,
3231 language: &str,
3332 theme: Option<&Theme>,
3433 config_loader: Arc<syntax::Loader>,
@@ -46,13 +45,13 @@ pub fn highlighted_code_block<'a>(
4645 None => return styled_multiline_text(text, code_style),
4746 };
4847
49− let ropeslice = RopeSlice::from(text);
48+ let rope = Rope::from(text.as_ref());
5049 let syntax = config_loader
5150 .language_configuration_for_injection_string(&InjectionLanguageMarker::Name(
5251 language.into(),
5352 ))
5453 .and_then(|config| config.highlight_config(theme.scopes()))
55− .and_then(|config| Syntax::new(ropeslice, config, Arc::clone(&config_loader)));
54+ .and_then(|config| Syntax::new(&rope, config, Arc::clone(&config_loader)));
5655
5756 let syntax = match syntax {
5857 Some(s) => s,
@@ -60,7 +59,7 @@ pub fn highlighted_code_block<'a>(
6059 };
6160
6261 let highlight_iter = syntax
63− .highlight_iter(ropeslice, None, None)
62+ .highlight_iter(rope.slice(..), None, None)
6463 .map(|e| e.unwrap());
6564 let highlight_iter: Box<dyn Iterator<Item = HighlightEvent>> =
6665 if let Some(spans) = additional_highlight_spans {
@@ -184,9 +183,7 @@ impl Markdown {
184183 // Transform text in `<code>` blocks into `Event::Code`
185184 let mut in_code = false;
186185 let parser = parser.filter_map(|event| match event {
187− Event::Html(tag)
188− if tag.starts_with("<code") && matches!(tag.chars().nth(5), Some(' ' | '>')) =>
189− {
186+ Event::Html(tag) if *tag == *"<code>" => {
190187 in_code = true;
191188 None
192189 }
@@ -270,7 +267,7 @@ impl Markdown {
270267 CodeBlockKind::Indented => "",
271268 };
272269 let tui_text = highlighted_code_block(
273− &text,
270+ text.to_string(),
274271 language,
275272 theme,
276273 Arc::clone(&self.config_loader),
@@ -278,21 +275,17 @@ impl Markdown {
278275 );
279276 lines.extend(tui_text.lines.into_iter());
280277 } else {
281− let style = match tags.last() {
282− Some(Tag::Heading(level, ..)) => match level {
278+ let style = if let Some(Tag::Heading(level, ..)) = tags.last() {
279+ match level {
283280 HeadingLevel::H1 => heading_styles[0],
284281 HeadingLevel::H2 => heading_styles[1],
285282 HeadingLevel::H3 => heading_styles[2],
286283 HeadingLevel::H4 => heading_styles[3],
287284 HeadingLevel::H5 => heading_styles[4],
288285 HeadingLevel::H6 => heading_styles[5],
289− },
290− Some(Tag::Emphasis) => text_style.add_modifier(Modifier::ITALIC),
291− Some(Tag::Strong) => text_style.add_modifier(Modifier::BOLD),
292− Some(Tag::Strikethrough) => {
293− text_style.add_modifier(Modifier::CROSSED_OUT)
294286 }
295− _ => text_style,
287+ } else {
288+ text_style
296289 };
297290 spans.push(Span::styled(text, style));
298291 }
helix-term/src/ui/menu.rs+40−73
@@ -1,29 +1,22 @@
1−use std::{borrow::Cow, cmp::Reverse, path::PathBuf};
1+use std::{borrow::Cow, path::PathBuf};
22
33use crate::{
44 compositor::{Callback, Component, Compositor, Context, Event, EventResult},
55 ctrl, key, shift,
66};
7−use helix_core::fuzzy::MATCHER;
8−use nucleo::pattern::{Atom, AtomKind, CaseMatching};
9−use nucleo::{Config, Utf32Str};
10−use tui::{
11− buffer::Buffer as Surface,
12− widgets::{Block, Borders, Table, Widget},
13−};
7+use tui::{buffer::Buffer as Surface, widgets::Table};
148
159pub use tui::widgets::{Cell, Row};
1610
17−use helix_view::{
18− editor::SmartTabConfig,
19− graphics::{Margin, Rect},
20− Editor,
21−};
11+use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
12+use fuzzy_matcher::FuzzyMatcher;
13+
14+use helix_view::{graphics::Rect, Editor};
2215use tui::layout::Constraint;
2316
24−pub trait Item: Sync + Send + 'static {
17+pub trait Item {
2518 /// Additional editor state that is used for label calculation.
26− type Data: Sync + Send + 'static;
19+ type Data;
2720
2821 fn format(&self, data: &Self::Data) -> Row;
2922
@@ -58,8 +51,9 @@ pub struct Menu<T: Item> {
5851
5952 cursor: Option<usize>,
6053
54+ matcher: Box<Matcher>,
6155 /// (index, score)
62− matches: Vec<(u32, u32)>,
56+ matches: Vec<(usize, i64)>,
6357
6458 widths: Vec<Constraint>,
6559
@@ -81,10 +75,11 @@ impl<T: Item> Menu<T> {
8175 editor_data: <T as Item>::Data,
8276 callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
8377 ) -> Self {
84− let matches = (0..options.len() as u32).map(|i| (i, 0)).collect();
78+ let matches = (0..options.len()).map(|i| (i, 0)).collect();
8579 Self {
8680 options,
8781 editor_data,
82+ matcher: Box::new(Matcher::default().ignore_case()),
8883 matches,
8984 cursor: None,
9085 widths: Vec::new(),
@@ -99,19 +94,20 @@ impl<T: Item> Menu<T> {
9994 pub fn score(&mut self, pattern: &str) {
10095 // reuse the matches allocation
10196 self.matches.clear();
102− let mut matcher = MATCHER.lock();
103− matcher.config = Config::DEFAULT;
104− let pattern = Atom::new(pattern, CaseMatching::Ignore, AtomKind::Fuzzy, false);
105− let mut buf = Vec::new();
106− let matches = self.options.iter().enumerate().filter_map(|(i, option)| {
107− let text = option.filter_text(&self.editor_data);
108− pattern
109− .score(Utf32Str::new(&text, &mut buf), &mut matcher)
110− .map(|score| (i as u32, score as u32))
111− });
112− self.matches.extend(matches);
113− self.matches
114− .sort_unstable_by_key(|&(i, score)| (Reverse(score), i));
97+ self.matches.extend(
98+ self.options
99+ .iter()
100+ .enumerate()
101+ .filter_map(|(index, option)| {
102+ let text = option.filter_text(&self.editor_data);
103+ // TODO: using fuzzy_indices could give us the char idx for match highlighting
104+ self.matcher
105+ .fuzzy_match(&text, pattern)
106+ .map(|score| (index, score))
107+ }),
108+ );
109+ // Order of equal elements needs to be preserved as LSP preselected items come in order of high to low priority
110+ self.matches.sort_by_key(|(_, score)| -score);
115111
116112 // reset cursor position
117113 self.cursor = None;
@@ -205,7 +201,7 @@ impl<T: Item> Menu<T> {
205201 self.cursor.and_then(|cursor| {
206202 self.matches
207203 .get(cursor)
208− .map(|(index, _score)| &self.options[*index as usize])
204+ .map(|(index, _score)| &self.options[*index])
209205 })
210206 }
211207
@@ -213,7 +209,7 @@ impl<T: Item> Menu<T> {
213209 self.cursor.and_then(|cursor| {
214210 self.matches
215211 .get(cursor)
216− .map(|(index, _score)| &mut self.options[*index as usize])
212+ .map(|(index, _score)| &mut self.options[*index])
217213 })
218214 }
219215
@@ -251,21 +247,6 @@ impl<T: Item + 'static> Component for Menu<T> {
251247 compositor.pop();
252248 }));
253249
254− // Ignore tab key when supertab is turned on in order not to interfere
255− // with it. (Is there a better way to do this?)
256− if (event == key!(Tab) || event == shift!(Tab))
257− && cx.editor.config().auto_completion
258− && matches!(
259− cx.editor.config().smart_tab,
260− Some(SmartTabConfig {
261− enable: true,
262− supersede_menu: true,
263− })
264− )
265− {
266− return EventResult::Ignored(None);
267− }
268−
269250 match event {
270251 // esc or ctrl-c aborts the completion and closes the menu
271252 key!(Esc) | ctrl!('c') => {
@@ -329,15 +310,6 @@ impl<T: Item + 'static> Component for Menu<T> {
329310 let selected = theme.get("ui.menu.selected");
330311 surface.clear_with(area, style);
331312
332− let render_borders = cx.editor.menu_border();
333−
334− let area = if render_borders {
335− Widget::render(Block::default().borders(Borders::ALL), area, surface);
336− area.inner(&Margin::vertical(1))
337− } else {
338− area
339− };
340−
341313 let scroll = self.scroll;
342314
343315 let options: Vec<_> = self
@@ -345,7 +317,7 @@ impl<T: Item + 'static> Component for Menu<T> {
345317 .iter()
346318 .map(|(index, _score)| {
347319 // (index, self.options.get(*index).unwrap()) // get_unchecked
348− &self.options[*index as usize] // get_unchecked
320+ &self.options[*index] // get_unchecked
349321 })
350322 .collect();
351323
@@ -378,19 +350,15 @@ impl<T: Item + 'static> Component for Menu<T> {
378350 false,
379351 );
380352
381− let render_borders = cx.editor.menu_border();
382−
383− if !render_borders {
384− if let Some(cursor) = self.cursor {
385− let offset_from_top = cursor - scroll;
386− let left = &mut surface[(area.left(), area.y + offset_from_top as u16)];
387− left.set_style(selected);
388− let right = &mut surface[(
389− area.right().saturating_sub(1),
390− area.y + offset_from_top as u16,
391− )];
392− right.set_style(selected);
393− }
353+ if let Some(cursor) = self.cursor {
354+ let offset_from_top = cursor - scroll;
355+ let left = &mut surface[(area.left(), area.y + offset_from_top as u16)];
356+ left.set_style(selected);
357+ let right = &mut surface[(
358+ area.right().saturating_sub(1),
359+ area.y + offset_from_top as u16,
360+ )];
361+ right.set_style(selected);
394362 }
395363
396364 let fits = len <= win_height;
@@ -405,13 +373,12 @@ impl<T: Item + 'static> Component for Menu<T> {
405373 for i in 0..win_height {
406374 cell = &mut surface[(area.right() - 1, area.top() + i as u16)];
407375
408− let half_block = if render_borders { "▌" } else { "▐" };
376+ cell.set_symbol("▐"); // right half block
409377
410378 if scroll_line <= i && i < scroll_line + scroll_height {
411379 // Draw scroll thumb
412− cell.set_symbol(half_block);
413380 cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
414− } else if !render_borders {
381+ } else {
415382 // Draw scroll track
416383 cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
417384 }
helix-term/src/ui/mod.rs+164−101
@@ -1,12 +1,13 @@
11mod completion;
22mod document;
33pub(crate) mod editor;
4+mod fuzzy_match;
45mod info;
56pub mod lsp;
67mod markdown;
78pub mod menu;
89pub mod overlay;
9−pub mod picker;
10+mod picker;
1011pub mod popup;
1112mod prompt;
1213mod spinner;
@@ -20,7 +21,7 @@ pub use completion::{Completion, CompletionItem};
2021pub use editor::EditorView;
2122pub use markdown::Markdown;
2223pub use menu::Menu;
23−pub use picker::{DynamicPicker, FileLocation, Picker};
24+pub use picker::{DynamicPicker, FileLocation, FilePicker, Picker};
2425pub use popup::Popup;
2526pub use prompt::{Prompt, PromptEvent};
2627pub use spinner::{ProgressSpinners, Spinner};
@@ -63,7 +64,7 @@ pub fn regex_prompt(
6364 prompt: std::borrow::Cow<'static, str>,
6465 history_register: Option<char>,
6566 completion_fn: impl FnMut(&Editor, &str) -> Vec<prompt::Completion> + 'static,
66− fun: impl Fn(&mut crate::compositor::Context, Regex, PromptEvent) + 'static,
67+ fun: impl Fn(&mut Editor, Regex, PromptEvent) + 'static,
6768) {
6869 let (view, doc) = current!(cx.editor);
6970 let doc_id = view.doc;
@@ -110,7 +111,7 @@ pub fn regex_prompt(
110111 view.jumps.push((doc_id, snapshot.clone()));
111112 }
112113
113− fun(cx, regex, event);
114+ fun(cx.editor, regex, event);
114115
115116 let (view, doc) = current!(cx.editor);
116117 view.ensure_cursor_in_view(doc, config.scrolloff);
@@ -141,21 +142,23 @@ pub fn regex_prompt(
141142 };
142143
143144 cx.jobs.callback(callback);
145+ } else {
146+ // Update
147+ // TODO: mark command line as error
144148 }
145149 }
146150 }
147151 }
148152 }
149153 },
150− )
151− .with_language("regex", std::sync::Arc::clone(&cx.editor.syn_loader));
154+ );
152155 // Calculate initial completion
153156 prompt.recalculate_completion(cx.editor);
154157 // prompt
155158 cx.push_layer(Box::new(prompt));
156159}
157160
158−pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker<PathBuf> {
161+pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> FilePicker<PathBuf> {
159162 use ignore::{types::TypesBuilder, WalkBuilder};
160163 use std::time::Instant;
161164
@@ -173,13 +176,9 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
173176 .git_ignore(config.file_picker.git_ignore)
174177 .git_global(config.file_picker.git_global)
175178 .git_exclude(config.file_picker.git_exclude)
176− .sort_by_file_name(|name1, name2| name1.cmp(name2))
177179 .max_depth(config.file_picker.max_depth)
178180 .filter_entry(move |entry| filter_picker_entry(entry, &absolute_root, dedup_symlinks));
179181
180− walk_builder.add_custom_ignore_filename(helix_loader::config_dir().join("ignore"));
181− walk_builder.add_custom_ignore_filename(".helix/ignore");
182−
183182 // We want to exclude files that the editor can't handle yet
184183 let mut type_builder = TypesBuilder::new();
185184 type_builder
@@ -193,60 +192,59 @@ pub fn file_picker(root: PathBuf, config: &helix_view::editor::Config) -> Picker
193192 .build()
194193 .expect("failed to build excluded_types");
195194 walk_builder.types(excluded_types);
196− let mut files = walk_builder.build().filter_map(|entry| {
195+
196+ // We want files along with their modification date for sorting
197+ let files = walk_builder.build().filter_map(|entry| {
197198 let entry = entry.ok()?;
198− if !entry.file_type()?.is_file() {
199− return None;
199+ // This is faster than entry.path().is_dir() since it uses cached fs::Metadata fetched by ignore/walkdir
200+ if entry.file_type()?.is_file() {
201+ Some(entry.into_path())
202+ } else {
203+ None
200204 }
201− Some(entry.into_path())
202205 });
206+
207+ // Cap the number of files if we aren't in a git project, preventing
208+ // hangs when using the picker in your home directory
209+ let mut files: Vec<PathBuf> = if root.join(".git").exists() {
210+ files.collect()
211+ } else {
212+ // const MAX: usize = 8192;
213+ const MAX: usize = 100_000;
214+ files.take(MAX).collect()
215+ };
216+ files.sort();
217+
203218 log::debug!("file_picker init {:?}", Instant::now().duration_since(now));
204219
205− let picker = Picker::new(Vec::new(), root, move |cx, path: &PathBuf, action| {
206− if let Err(e) = cx.editor.open(path, action) {
207− let err = if let Some(err) = e.source() {
208− format!("{}", err)
209− } else {
210− format!("unable to open \"{}\"", path.display())
211− };
212− cx.editor.set_error(err);
213− }
214− })
215− .with_preview(|_editor, path| Some((path.clone().into(), None)));
216− let injector = picker.injector();
217− let timeout = std::time::Instant::now() + std::time::Duration::from_millis(30);
218−
219− let mut hit_timeout = false;
220− for file in &mut files {
221− if injector.push(file).is_err() {
222− break;
223− }
224− if std::time::Instant::now() >= timeout {
225− hit_timeout = true;
226− break;
227− }
228− }
229− if hit_timeout {
230− std::thread::spawn(move || {
231− for file in files {
232− if injector.push(file).is_err() {
233− break;
234− }
220+ FilePicker::new(
221+ files,
222+ root,
223+ move |cx, path: &PathBuf, action| {
224+ if let Err(e) = cx.editor.open(path, action) {
225+ let err = if let Some(err) = e.source() {
226+ format!("{}", err)
227+ } else {
228+ format!("unable to open \"{}\"", path.display())
229+ };
230+ cx.editor.set_error(err);
235231 }
236− });
237− }
238− picker
232+ },
233+ |_editor, path| Some((path.clone().into(), None)),
234+ )
239235}
240236
241237pub mod completers {
242238 use crate::ui::prompt::Completion;
243− use helix_core::fuzzy::fuzzy_match;
239+ use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
240+ use fuzzy_matcher::FuzzyMatcher;
244241 use helix_core::syntax::LanguageServerFeature;
245242 use helix_view::document::SCRATCH_BUFFER_NAME;
246243 use helix_view::theme;
247244 use helix_view::{editor::Config, Editor};
248245 use once_cell::sync::Lazy;
249246 use std::borrow::Cow;
247+ use std::cmp::Reverse;
250248
251249 pub type Completer = fn(&Editor, &str) -> Vec<Completion>;
252250
@@ -255,16 +253,31 @@ pub mod completers {
255253 }
256254
257255 pub fn buffer(editor: &Editor, input: &str) -> Vec<Completion> {
258− let names = editor.documents.values().map(|doc| {
259− doc.relative_path()
260− .map(|p| p.display().to_string().into())
261− .unwrap_or_else(|| Cow::from(SCRATCH_BUFFER_NAME))
262− });
263−
264− fuzzy_match(input, names, true)
256+ let mut names: Vec<_> = editor
257+ .documents
258+ .values()
259+ .map(|doc| {
260+ let name = doc
261+ .relative_path()
262+ .map(|p| p.display().to_string())
263+ .unwrap_or_else(|| String::from(SCRATCH_BUFFER_NAME));
264+ ((0..), Cow::from(name))
265+ })
266+ .collect();
267+
268+ let matcher = Matcher::default();
269+
270+ let mut matches: Vec<_> = names
265271 .into_iter()
266− .map(|(name, _)| ((0..), name))
267− .collect()
272+ .filter_map(|(_range, name)| {
273+ matcher.fuzzy_match(&name, input).map(|score| (name, score))
274+ })
275+ .collect();
276+
277+ matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
278+ names = matches.into_iter().map(|(name, _)| ((0..), name)).collect();
279+
280+ names
268281 }
269282
270283 pub fn theme(_editor: &Editor, input: &str) -> Vec<Completion> {
@@ -277,10 +290,26 @@ pub mod completers {
277290 names.sort();
278291 names.dedup();
279292
280− fuzzy_match(input, names, false)
293+ let mut names: Vec<_> = names
281294 .into_iter()
282− .map(|(name, _)| ((0..), name.into()))
283− .collect()
295+ .map(|name| ((0..), Cow::from(name)))
296+ .collect();
297+
298+ let matcher = Matcher::default();
299+
300+ let mut matches: Vec<_> = names
301+ .into_iter()
302+ .filter_map(|(_range, name)| {
303+ matcher.fuzzy_match(&name, input).map(|score| (name, score))
304+ })
305+ .collect();
306+
307+ matches.sort_unstable_by(|(name1, score1), (name2, score2)| {
308+ (Reverse(*score1), name1).cmp(&(Reverse(*score2), name2))
309+ });
310+ names = matches.into_iter().map(|(name, _)| ((0..), name)).collect();
311+
312+ names
284313 }
285314
286315 /// Recursive function to get all keys from this value and add them to vec
@@ -307,22 +336,22 @@ pub mod completers {
307336 keys
308337 });
309338
310− fuzzy_match(input, &*KEYS, false)
339+ let matcher = Matcher::default();
340+
341+ let mut matches: Vec<_> = KEYS
342+ .iter()
343+ .filter_map(|name| matcher.fuzzy_match(name, input).map(|score| (name, score)))
344+ .collect();
345+
346+ matches.sort_unstable_by_key(|(_file, score)| Reverse(*score));
347+ matches
311348 .into_iter()
312349 .map(|(name, _)| ((0..), name.into()))
313350 .collect()
314351 }
315352
316353 pub fn filename(editor: &Editor, input: &str) -> Vec<Completion> {
317− filename_with_git_ignore(editor, input, true)
318− }
319−
320− pub fn filename_with_git_ignore(
321− editor: &Editor,
322− input: &str,
323− git_ignore: bool,
324− ) -> Vec<Completion> {
325− filename_impl(editor, input, git_ignore, |entry| {
354+ filename_impl(editor, input, |entry| {
326355 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
327356
328357 if is_dir {
@@ -334,6 +363,8 @@ pub mod completers {
334363 }
335364
336365 pub fn language(editor: &Editor, input: &str) -> Vec<Completion> {
366+ let matcher = Matcher::default();
367+
337368 let text: String = "text".into();
338369
339370 let language_ids = editor
@@ -342,13 +373,27 @@ pub mod completers {
342373 .map(|config| &config.language_id)
343374 .chain(std::iter::once(&text));
344375
345− fuzzy_match(input, language_ids, false)
376+ let mut matches: Vec<_> = language_ids
377+ .filter_map(|language_id| {
378+ matcher
379+ .fuzzy_match(language_id, input)
380+ .map(|score| (language_id, score))
381+ })
382+ .collect();
383+
384+ matches.sort_unstable_by(|(language1, score1), (language2, score2)| {
385+ (Reverse(*score1), language1).cmp(&(Reverse(*score2), language2))
386+ });
387+
388+ matches
346389 .into_iter()
347− .map(|(name, _)| ((0..), name.to_owned().into()))
390+ .map(|(language, _score)| ((0..), language.clone().into()))
348391 .collect()
349392 }
350393
351394 pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
395+ let matcher = Matcher::default();
396+
352397 let Some(options) = doc!(editor)
353398 .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
354399 .find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
@@ -356,22 +401,28 @@ pub mod completers {
356401 return vec![];
357402 };
358403
359− fuzzy_match(input, &options.commands, false)
404+ let mut matches: Vec<_> = options
405+ .commands
406+ .iter()
407+ .filter_map(|command| {
408+ matcher
409+ .fuzzy_match(command, input)
410+ .map(|score| (command, score))
411+ })
412+ .collect();
413+
414+ matches.sort_unstable_by(|(command1, score1), (command2, score2)| {
415+ (Reverse(*score1), command1).cmp(&(Reverse(*score2), command2))
416+ });
417+
418+ matches
360419 .into_iter()
361− .map(|(name, _)| ((0..), name.to_owned().into()))
420+ .map(|(command, _score)| ((0..), command.clone().into()))
362421 .collect()
363422 }
364423
365424 pub fn directory(editor: &Editor, input: &str) -> Vec<Completion> {
366− directory_with_git_ignore(editor, input, true)
367− }
368−
369− pub fn directory_with_git_ignore(
370− editor: &Editor,
371− input: &str,
372− git_ignore: bool,
373− ) -> Vec<Completion> {
374− filename_impl(editor, input, git_ignore, |entry| {
425+ filename_impl(editor, input, |entry| {
375426 let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir());
376427
377428 if is_dir {
@@ -394,12 +445,7 @@ pub mod completers {
394445 }
395446
396447 // TODO: we could return an iter/lazy thing so it can fetch as many as it needs.
397− fn filename_impl<F>(
398− _editor: &Editor,
399− input: &str,
400− git_ignore: bool,
401− filter_fn: F,
402− ) -> Vec<Completion>
448+ fn filename_impl<F>(_editor: &Editor, input: &str, filter_fn: F) -> Vec<Completion>
403449 where
404450 F: Fn(&ignore::DirEntry) -> FileMatch,
405451 {
@@ -430,7 +476,7 @@ pub mod completers {
430476 match path.parent() {
431477 Some(path) if !path.as_os_str().is_empty() => path.to_path_buf(),
432478 // Path::new("h")'s parent is Some("")...
433− _ => helix_loader::current_working_dir(),
479+ _ => std::env::current_dir().expect("couldn't determine current directory"),
434480 }
435481 };
436482
@@ -439,10 +485,9 @@ pub mod completers {
439485
440486 let end = input.len()..;
441487
442− let files = WalkBuilder::new(&dir)
488+ let mut files: Vec<_> = WalkBuilder::new(&dir)
443489 .hidden(false)
444490 .follow_links(false) // We're scanning over depth 1
445− .git_ignore(git_ignore)
446491 .max_depth(Some(1))
447492 .build()
448493 .filter_map(|file| {
@@ -471,25 +516,43 @@ pub mod completers {
471516 path.push("");
472517 }
473518
474− let path = path.into_os_string().into_string().ok()?;
475− Some(Cow::from(path))
519+ let path = path.to_str()?.to_owned();
520+ Some((end.clone(), Cow::from(path)))
476521 })
477522 }) // TODO: unwrap or skip
478− .filter(|path| !path.is_empty());
523+ .filter(|(_, path)| !path.is_empty()) // TODO
524+ .collect();
479525
480526 // if empty, return a list of dirs and files in current dir
481527 if let Some(file_name) = file_name {
528+ let matcher = Matcher::default();
529+
530+ // inefficient, but we need to calculate the scores, filter out None, then sort.
531+ let mut matches: Vec<_> = files
532+ .into_iter()
533+ .filter_map(|(_range, file)| {
534+ matcher
535+ .fuzzy_match(&file, &file_name)
536+ .map(|score| (file, score))
537+ })
538+ .collect();
539+
482540 let range = (input.len().saturating_sub(file_name.len()))..;
483− fuzzy_match(&file_name, files, true)
541+
542+ matches.sort_unstable_by(|(file1, score1), (file2, score2)| {
543+ (Reverse(*score1), file1).cmp(&(Reverse(*score2), file2))
544+ });
545+
546+ files = matches
484547 .into_iter()
485− .map(|(name, _)| (range.clone(), name))
486− .collect()
548+ .map(|(file, _)| (range.clone(), file))
549+ .collect();
487550
488551 // TODO: complete to longest common match
489552 } else {
490− let mut files: Vec<_> = files.map(|file| (end.clone(), file)).collect();
491553 files.sort_unstable_by(|(_, path1), (_, path2)| path1.cmp(path2));
492− files
493554 }
555+
556+ files
494557 }
495558}
helix-term/src/ui/picker.rs+577−572

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/tests/integration.rs−1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-tui/Cargo.toml+15−15

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-tui/src/terminal.rs+14

This diff is too big to show here.

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

This diff is too big to show here.

helix-vcs/Cargo.toml+10−12

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-view/Cargo.toml+18−22

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-view/src/lib.rs−1

This diff is too big to show here.

helix-view/src/register.rsdeleted−333

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

languages.toml+295−720

This diff is too big to show here.

logo.svg+1−1

This diff is too big to show here.

runtime/queries/_javascript/highlights.scmdeleted−36

This diff is too big to show here.

runtime/queries/_javascript/locals.scmdeleted−14

This diff is too big to show here.

runtime/queries/_javascript/tags.scmdeleted−88

This diff is too big to show here.

runtime/queries/_jsx/highlights.scmdeleted−43

This diff is too big to show here.

runtime/queries/_jsx/indents.scmdeleted−6

This diff is too big to show here.

runtime/queries/_typescript/highlights.scmdeleted−142

This diff is too big to show here.

runtime/queries/_typescript/indents.scmdeleted−5

This diff is too big to show here.

runtime/queries/_typescript/locals.scmdeleted−33

This diff is too big to show here.

runtime/queries/_typescript/tags.scmdeleted−23

This diff is too big to show here.

runtime/queries/_typescript/textobjects.scmdeleted−6

This diff is too big to show here.

runtime/queries/agda/highlights.scmdeleted−124

This diff is too big to show here.

runtime/queries/bash/indents.scm−1

This diff is too big to show here.

runtime/queries/bash/textobjects.scmdeleted−9

This diff is too big to show here.

runtime/queries/blueprint/highlights.scmdeleted−61

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/cairo/indents.scmdeleted−1

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/cairo/locals.scmdeleted−1

This diff is too big to show here.

runtime/queries/cairo/textobjects.scmdeleted−1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/dbml/highlights.scmdeleted−44

This diff is too big to show here.

runtime/queries/ecma/README.mddeleted−14

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/forth/highlights.scmdeleted−7

This diff is too big to show here.

runtime/queries/fortran/injections.scmdeleted−2

This diff is too big to show here.

runtime/queries/fsharp/highlights.scmdeleted−196

This diff is too big to show here.

runtime/queries/fsharp/locals.scmdeleted−25

This diff is too big to show here.

runtime/queries/gas/highlights.scmdeleted−21

This diff is too big to show here.

runtime/queries/gas/injections.scmdeleted−2

This diff is too big to show here.

runtime/queries/gas/textobjects.scmdeleted−2

This diff is too big to show here.

runtime/queries/gemini/highlights.scmdeleted−26

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/gn/highlights.scmdeleted−42

This diff is too big to show here.

runtime/queries/gn/injections.scmdeleted−15

This diff is too big to show here.

runtime/queries/gn/locals.scmdeleted−16

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/haskell/injections.scm−8

This diff is too big to show here.

runtime/queries/janet/highlights.scmdeleted−1

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/java/indents.scmdeleted−35

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/jinja/highlights.scmdeleted−7

This diff is too big to show here.

runtime/queries/jinja/injections.scmdeleted−4

This diff is too big to show here.

runtime/queries/json5/highlights.scmdeleted−11

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/jsx/tags.scmdeleted−3

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/kdl/indents.scmdeleted−3

This diff is too big to show here.

runtime/queries/kdl/textobjects.scmdeleted−27

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/kotlin/locals.scmdeleted−15

This diff is too big to show here.

runtime/queries/log/highlights.scmdeleted−10

This diff is too big to show here.

runtime/queries/lpf/highlights.scmdeleted−19

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/matlab/context.scmdeleted−41

This diff is too big to show here.

runtime/queries/matlab/folds.scmdeleted−11

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/matlab/indents.scmdeleted−23

This diff is too big to show here.

runtime/queries/matlab/injections.scmdeleted−2

This diff is too big to show here.

runtime/queries/matlab/locals.scmdeleted−19

This diff is too big to show here.

runtime/queries/matlab/textobjects.scmdeleted−9

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/nickel/injections.scmdeleted−3

This diff is too big to show here.

runtime/queries/nix/injections.scm−14

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/nunjucks/highlights.scmdeleted−1

This diff is too big to show here.

runtime/queries/nunjucks/injections.scmdeleted−1

This diff is too big to show here.

runtime/queries/ocaml/highlights.scm−2

This diff is too big to show here.

runtime/queries/perl/fold.scmdeleted−19

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/pod/highlights.scmdeleted−61

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/purescript/textobjects.scmdeleted−13

This diff is too big to show here.

runtime/queries/python/highlights.scm−6

This diff is too big to show here.

runtime/queries/python/indents.scm−9

This diff is too big to show here.

runtime/queries/racket/indents.scmdeleted−1

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/robot/highlights.scm+13−51

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/rust/highlights.scm−7

This diff is too big to show here.

runtime/queries/rust/indents.scm+2−69

This diff is too big to show here.

runtime/queries/rust/injections.scm−6

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/scala/locals.scmdeleted−29

This diff is too big to show here.

runtime/queries/scheme/highlights.scm−5

This diff is too big to show here.

runtime/queries/scheme/indents.scmdeleted−43

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/slint/injections.scmdeleted−2

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/smali/folds.scmdeleted−12

This diff is too big to show here.

runtime/queries/smali/highlights.scmdeleted−216

This diff is too big to show here.

runtime/queries/smali/indents.scmdeleted−31

This diff is too big to show here.

runtime/queries/smali/injections.scmdeleted−2

This diff is too big to show here.

runtime/queries/smali/locals.scmdeleted−42

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/strace/highlights.scmdeleted−8

This diff is too big to show here.

runtime/queries/svelte/indents.scmdeleted−18

This diff is too big to show here.

runtime/queries/svelte/injections.scm−10

This diff is too big to show here.

runtime/queries/t32/highlights.scmdeleted−227

This diff is too big to show here.

runtime/queries/t32/injections.scmdeleted−2

This diff is too big to show here.

runtime/queries/templ/highlights.scmdeleted−99

This diff is too big to show here.

runtime/queries/templ/injections.scmdeleted−4

This diff is too big to show here.

runtime/queries/todotxt/highlights.scmdeleted−6

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/tsx/locals.scmdeleted−3

This diff is too big to show here.

runtime/queries/tsx/tags.scmdeleted−3

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/typst/highlights.scmdeleted−77

This diff is too big to show here.

runtime/queries/typst/injections.scmdeleted−6

This diff is too big to show here.

runtime/queries/unison/highlights.scmdeleted−87

This diff is too big to show here.

runtime/queries/unison/injections.scmdeleted−1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/webc/highlights.scmdeleted−1

This diff is too big to show here.

runtime/queries/webc/injections.scmdeleted−1

This diff is too big to show here.

runtime/queries/wren/highlights.scmdeleted−82

This diff is too big to show here.

runtime/queries/wren/indents.scmdeleted−29

This diff is too big to show here.

runtime/queries/wren/injections.scmdeleted−3

This diff is too big to show here.

runtime/queries/wren/locals.scmdeleted−21

This diff is too big to show here.

runtime/queries/wren/textobjects.scmdeleted−16

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/yaml/injections.scm−52

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/README.md−8

This diff is too big to show here.

runtime/themes/autumn.toml+65−67

This diff is too big to show here.

runtime/themes/autumn_night.toml+8−9

This diff is too big to show here.

runtime/themes/catppuccin_frappe.toml−2

This diff is too big to show here.

runtime/themes/catppuccin_latte.toml−2

This diff is too big to show here.

runtime/themes/catppuccin_macchiato.toml−2

This diff is too big to show here.

runtime/themes/catppuccin_mocha.toml−11

This diff is too big to show here.

runtime/themes/cyan_light.tomldeleted−154

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/darcula.toml+4−10

This diff is too big to show here.

runtime/themes/dark_plus.toml+1−2

This diff is too big to show here.

runtime/themes/dracula.toml−2

This diff is too big to show here.

runtime/themes/everblush.toml−1

This diff is too big to show here.

runtime/themes/everforest_dark.toml−4

This diff is too big to show here.

runtime/themes/ferra.toml+2−1

This diff is too big to show here.

runtime/themes/gruber-darker.tomldeleted−114

This diff is too big to show here.

runtime/themes/gruvbox.toml−1

This diff is too big to show here.

runtime/themes/gruvbox_dark_hard.toml+2

This diff is too big to show here.

runtime/themes/gruvbox_dark_soft.tomldeleted−7

This diff is too big to show here.

runtime/themes/gruvbox_light.toml+2

This diff is too big to show here.

runtime/themes/horizon-dark.tomldeleted−80

This diff is too big to show here.

runtime/themes/jellybeans.toml+1−1

This diff is too big to show here.

runtime/themes/kaolin-dark.tomldeleted−108

This diff is too big to show here.

runtime/themes/kaolin-light.tomldeleted−60

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/licenses/everforest.LICENSEdeleted−21

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi.LICENSEdeleted−5

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi_deuteranopia.LICENSEdeleted−5

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi_tinted.LICENSEdeleted−5

This diff is too big to show here.

runtime/themes/licenses/modus_vivendi_tritanopia.LICENSEdeleted−5

This diff is too big to show here.

runtime/themes/licenses/poimandres.LICENSEdeleted−21

This diff is too big to show here.

runtime/themes/licenses/serika-syntax.LICENSEdeleted−21

This diff is too big to show here.

runtime/themes/licenses/sonokai.LICENSEdeleted−21

This diff is too big to show here.

runtime/themes/licenses/starlight.LICENSEdeleted−24

This diff is too big to show here.

runtime/themes/material_darker.tomldeleted−20

This diff is too big to show here.

runtime/themes/material_deep_ocean.tomldeleted−123

This diff is too big to show here.

runtime/themes/material_oceanic.tomldeleted−21

This diff is too big to show here.

runtime/themes/material_palenight.tomldeleted−19

This diff is too big to show here.

runtime/themes/merionette.tomldeleted−98

This diff is too big to show here.

runtime/themes/modus_operandi.tomldeleted−194

This diff is too big to show here.

runtime/themes/modus_operandi_deuteranopia.tomldeleted−46

This diff is too big to show here.

runtime/themes/modus_operandi_tinted.tomldeleted−45

This diff is too big to show here.

runtime/themes/modus_operandi_tritanopia.tomldeleted−73

This diff is too big to show here.

runtime/themes/modus_vivendi.tomldeleted−242

This diff is too big to show here.

runtime/themes/modus_vivendi_deuteranopia.tomldeleted−158

This diff is too big to show here.

runtime/themes/modus_vivendi_tinted.tomldeleted−144

This diff is too big to show here.

runtime/themes/modus_vivendi_tritanopia.tomldeleted−186

This diff is too big to show here.

runtime/themes/molokai.toml+1−1

This diff is too big to show here.

runtime/themes/monokai.toml−5

This diff is too big to show here.

runtime/themes/naysayer.tomldeleted−91

This diff is too big to show here.

runtime/themes/new_moon.tomldeleted−124

This diff is too big to show here.

runtime/themes/nightfox.toml+2−2

This diff is too big to show here.

runtime/themes/noctis.toml+26−26

This diff is too big to show here.

runtime/themes/nord-night.tomldeleted−23

This diff is too big to show here.

runtime/themes/nord.toml+105−162

This diff is too big to show here.

runtime/themes/onedark.toml−1

This diff is too big to show here.

runtime/themes/onedarker.toml−1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/poimandres.tomldeleted−113

This diff is too big to show here.

runtime/themes/poimandres_storm.tomldeleted−19

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/rose_pine.toml+29−31

This diff is too big to show here.

runtime/themes/spacebones_light.toml−1

This diff is too big to show here.

runtime/themes/starlight.tomldeleted−113

This diff is too big to show here.

runtime/themes/vim_dark_high_contrast.tomldeleted−72

This diff is too big to show here.

runtime/themes/yellowed.tomldeleted−135

This diff is too big to show here.

runtime/themes/zed_onedark.tomldeleted−110

This diff is too big to show here.

runtime/themes/zed_onelight.tomldeleted−55

This diff is too big to show here.

runtime/themes/zenburn.toml+1−1

This diff is too big to show here.

runtime/tutor+3−157

This diff is too big to show here.

rust-toolchain.toml+2−2

This diff is too big to show here.

xtask/Cargo.toml+6−12

This diff is too big to show here.