on master help sign in
640 files changed+49587−15661
since splitsnapshots
unifiedsplit
.cargo/config.toml+1−1 .envrc+1 .github/ISSUE_TEMPLATE/bug_report.yaml+14−3 .github/workflows/build.yml+30−73 .github/workflows/cachix.yml+3−3 .github/workflows/gh-pages.yml+5−5 .github/workflows/languages.toml+1−1 .github/workflows/msrv-rust-toolchain.toml−3 .github/workflows/release.yml+59−55 .ignore−3 CHANGELOG.md+937−1 Cargo.lock+1529−377 Cargo.toml+24 README.md+21−85 VERSION−1 base16_theme.toml+1 book/book.toml+6−1 book/custom.css+231 book/src/SUMMARY.md+6−5 book/src/commands.md+1−1 book/src/configuration.md+164−43 book/src/from-vim.md+3−3 book/src/generated/lang-support.md+84−26 book/src/generated/typable-cmd.md+22−6 book/src/guides/README.md+1−1 book/src/guides/adding_languages.md+45−35 book/src/guides/indent.md+320−44 book/src/guides/injection.md+57 book/src/guides/textobject.md+7−9 book/src/install.md+244−91 book/src/keymap.md+91−71 book/src/lang-support.md+2−2 book/src/languages.md+118−38 book/src/remapping.md+35−10 book/src/themes.md+130−93 book/src/usage.md+131−92 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−342 book/theme/favicon.png book/theme/favicon.svg+1−22 book/theme/highlight.css−56 book/theme/highlight.js−6 book/theme/index.hbs+87−51 contrib/Helix.appdata.xml+108 contrib/Helix.desktop+3−1 contrib/completion/hx.bash+1−1 contrib/helix.png docs/CONTRIBUTING.md+21−5 docs/releases.md+14−6 docs/vision.md+1−1 flake.lock+39−140 flake.nix+166−145 grammars.nix+24−21 helix-core/Cargo.toml+24−16 helix-core/src/auto_pairs.rs+1−1 helix-core/src/comment.rs+34−25 helix-core/src/diagnostic.rs+7−1 helix-core/src/diff.rs+202−45 helix-core/src/doc_formatter.rs+384 helix-core/src/doc_formatter/test.rs+182 helix-core/src/fuzzy.rs+43 helix-core/src/graphemes.rs+182−1 helix-core/src/history.rs+34−6 helix-core/src/increment/date_time.rs+75−246 helix-core/src/increment/integer.rs+235 helix-core/src/increment/mod.rs+7−5 helix-core/src/increment/number.rs−507 helix-core/src/indent.rs+702−200 helix-core/src/lib.rs+15−52 helix-core/src/line_ending.rs+9−2 helix-core/src/match_brackets.rs+232−29 helix-core/src/movement.rs+400−39 helix-core/src/path.rs+27−8 helix-core/src/position.rs+452−1 helix-core/src/register.rs−83 helix-core/src/rope_reader.rs+37 helix-core/src/selection.rs+205−79 helix-core/src/shellwords.rs+187−162 helix-core/src/state.rs−17 helix-core/src/surround.rs+145−122 helix-core/src/syntax.rs+634−258 helix-core/src/test.rs+139−18 helix-core/src/text_annotations.rs+271 helix-core/src/textobject.rs+17−5 helix-core/src/transaction.rs+322−56 helix-core/src/wrap.rs+4−2 helix-core/tests/data/indent/cpp.cpp+48 helix-core/tests/data/indent/indent.rs−1 helix-core/tests/data/indent/languages.toml+14−1 helix-core/tests/indent.rs+187−24 helix-dap/Cargo.toml+11−9 helix-dap/src/client.rs+54−15 helix-dap/src/lib.rs+3−3 helix-dap/src/transport.rs+28−18 helix-dap/src/types.rs+23−3 helix-event/Cargo.toml+16 helix-event/src/lib.rs+8 helix-event/src/redraw.rs+49 helix-loader/Cargo.toml+17−13 helix-loader/build.rs+71 helix-loader/src/config.rs+36−32 helix-loader/src/grammar.rs+137−57 helix-loader/src/lib.rs+175−58 helix-lsp/Cargo.toml+17−12 helix-lsp/src/client.rs+693−120 helix-lsp/src/file_event.rs+193 helix-lsp/src/jsonrpc.rs+43−2 helix-lsp/src/lib.rs+589−109 helix-lsp/src/snippet.rs+1010 helix-lsp/src/transport.rs+81−20 helix-parsec/Cargo.toml+14 helix-parsec/src/lib.rs+574 helix-term/Cargo.toml+37−29 helix-term/build.rs−21 helix-term/src/application.rs+530−269 helix-term/src/args.rs+29 helix-term/src/commands.rs+1889−1074 helix-term/src/commands/dap.rs+115−65 helix-term/src/commands/lsp.rs+873−372 helix-term/src/commands/typed.rs+1645−701 helix-term/src/compositor.rs+26−67 helix-term/src/config.rs+110−30 helix-term/src/health.rs+75−25 helix-term/src/job.rs+5−2 helix-term/src/keymap.rs+159−167 helix-term/src/keymap/default.rs+41−21 helix-term/src/keymap/macros.rs+3−4 helix-term/src/lib.rs+25 helix-term/src/main.rs+39−33 helix-term/src/ui/completion.rs+361−215 helix-term/src/ui/document.rs+520 helix-term/src/ui/editor.rs+419−436 helix-term/src/ui/fuzzy_match.rs−74 helix-term/src/ui/fuzzy_match/test.rs−47 helix-term/src/ui/lsp.rs+9−4 helix-term/src/ui/markdown.rs+24−18 helix-term/src/ui/menu.rs+115−71 helix-term/src/ui/mod.rs+141−163 helix-term/src/ui/overlay.rs+5−1 helix-term/src/ui/picker.rs+769−444 helix-term/src/ui/popup.rs+93−13 helix-term/src/ui/prompt.rs+84−59 helix-term/src/ui/statusline.rs+146−16 helix-term/src/ui/text.rs+1−1 helix-term/tests/integration.rs+3−5 helix-term/tests/test/auto_indent.rs+3−9 helix-term/tests/test/auto_pairs.rs+5−11 helix-term/tests/test/commands.rs+415−104 helix-term/tests/test/commands/movement.rs+452 helix-term/tests/test/commands/write.rs+566 helix-term/tests/test/helpers.rs+70−40 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+355−43 helix-term/tests/test/splits.rs+69−6 helix-term/tests/test/write.rs−287 helix-tui/Cargo.toml+17−15 helix-tui/src/backend/crossterm.rs+175−46 helix-tui/src/backend/mod.rs+5−1 helix-tui/src/backend/test.rs+17 helix-tui/src/buffer.rs+52−10 helix-tui/src/lib.rs+9−3 helix-tui/src/terminal.rs+26−14 helix-tui/src/text.rs+28 helix-tui/src/widgets/block.rs+2−7 helix-tui/src/widgets/mod.rs+2−2 helix-tui/src/widgets/table.rs+49−69 helix-vcs/Cargo.toml+32 helix-vcs/src/diff.rs+292 helix-vcs/src/diff/line_cache.rs+138 helix-vcs/src/diff/worker.rs+204 helix-vcs/src/diff/worker/test.rs+145 helix-vcs/src/git.rs+139 helix-vcs/src/git/test.rs+121 helix-vcs/src/lib.rs+78 helix-view/Cargo.toml+29−19 helix-view/src/base64.rs+1−1 helix-view/src/clipboard.rs+30−10 helix-view/src/document.rs+895−180 helix-view/src/editor.rs+640−132 helix-view/src/graphics.rs+3−5 helix-view/src/gutter.rs+333−90 helix-view/src/handlers/dap.rs+64−4 helix-view/src/info.rs+5−25 helix-view/src/input.rs+209−5 helix-view/src/keyboard.rs+195−12 helix-view/src/lib.rs+16−21 helix-view/src/macros.rs+1−1 helix-view/src/register.rs+333 helix-view/src/theme.rs+156−97 helix-view/src/tree.rs+58−47 helix-view/src/view.rs+676−173 languages.toml+1342−300 logo.svg+1 logo_dark.svg+115 logo_light.svg+115 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+12 runtime/queries/bash/textobjects.scm+9 runtime/queries/bibtex/highlights.scm+47 runtime/queries/bibtex/locals.scm runtime/queries/bibtex/tags.scm runtime/queries/bicep/highlights.scm+73 runtime/queries/blueprint/highlights.scm+61 runtime/queries/c/highlights.scm+1 runtime/queries/c/indents.scm+19−1 runtime/queries/cairo/highlights.scm+1−94 runtime/queries/cairo/indents.scm+1 runtime/queries/cairo/injections.scm+2−4 runtime/queries/cairo/locals.scm+1 runtime/queries/cairo/textobjects.scm+1 runtime/queries/capnp/folds.scm+14 runtime/queries/capnp/highlights.scm+141 runtime/queries/capnp/indents.scm+19 runtime/queries/capnp/injections.scm+2 runtime/queries/capnp/locals.scm+96 runtime/queries/cmake/textobjects.scm+4−1 runtime/queries/comment/highlights.scm+20−4 runtime/queries/common-lisp/highlights.scm+1 runtime/queries/common-lisp/indents.scm+1 runtime/queries/common-lisp/injections.scm+1 runtime/queries/cpp/highlights.scm+1−1 runtime/queries/cpp/injections.scm+3 runtime/queries/crystal/highlights.scm+1 runtime/queries/crystal/injections.scm+1 runtime/queries/crystal/locals.scm+1 runtime/queries/crystal/textobjects.scm+1 runtime/queries/css/highlights.scm+68−47 runtime/queries/dart/highlights.scm+3−1 runtime/queries/dbml/highlights.scm+44 runtime/queries/dhall/highlights.scm+110 runtime/queries/dhall/injections.scm+2 runtime/queries/dhall/textobjects.scm+23 runtime/queries/dockerfile/highlights.scm+4−1 runtime/queries/dtd/highlights.scm+39 runtime/queries/dtd/injections.scm+2 runtime/queries/ecma/README.md+14 runtime/queries/ecma/highlights.scm+49−21 runtime/queries/ecma/indents.scm+6 runtime/queries/ecma/locals.scm+17−3 runtime/queries/edoc/highlights.scm+2 runtime/queries/edoc/injections.scm+2−2 runtime/queries/elixir/indents.scm+13 runtime/queries/elm/textobjects.scm+63 runtime/queries/env/highlights.scm+1−19 runtime/queries/erlang/highlights.scm+45−15 runtime/queries/erlang/injections.scm+7−2 runtime/queries/erlang/locals.scm+30 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/gdscript/highlights.scm+86−29 runtime/queries/gdscript/indents.scm+35 runtime/queries/gdscript/injections.scm+2 runtime/queries/gemini/highlights.scm+26 runtime/queries/git-commit/highlights.scm+5−1 runtime/queries/git-commit/textobjects.scm+2 runtime/queries/git-rebase/highlights.scm+33−8 runtime/queries/git-rebase/injections.scm+5−4 runtime/queries/gleam/highlights.scm+19−5 runtime/queries/gn/highlights.scm+42 runtime/queries/gn/injections.scm+15 runtime/queries/gn/locals.scm+16 runtime/queries/go/highlights.scm+71−23 runtime/queries/go/indents.scm+19−5 runtime/queries/go/locals.scm+11−3 runtime/queries/godot-resource/highlights.scm+2−1 runtime/queries/godot-resource/injections.scm+18 runtime/queries/hare/highlights.scm+4−1 runtime/queries/haskell-persistent/folds.scm+3 runtime/queries/haskell-persistent/highlights.scm+37 runtime/queries/haskell-persistent/locals.scm+1 runtime/queries/haskell/highlights.scm+24−11 runtime/queries/haskell/injections.scm+12 runtime/queries/haskell/textobjects.scm+13 runtime/queries/heex/injections.scm+5−1 runtime/queries/hosts/highlights.scm+3 runtime/queries/html/highlights.scm+1 runtime/queries/hurl/highlights.scm+127 runtime/queries/hurl/indents.scm+11 runtime/queries/hurl/injections.scm+14 runtime/queries/janet/highlights.scm+1 runtime/queries/java/highlights.scm+98−4 runtime/queries/java/indents.scm+35 runtime/queries/java/injections.scm+1−1 runtime/queries/java/textobjects.scm+35 runtime/queries/javascript/highlights.scm+2−37 runtime/queries/javascript/indents.scm+3−1 runtime/queries/javascript/injections.scm+3−1 runtime/queries/javascript/locals.scm+3−1 runtime/queries/javascript/tags.scm+2−87 runtime/queries/javascript/textobjects.scm+3−1 runtime/queries/jinja/highlights.scm+7 runtime/queries/jinja/injections.scm+4 runtime/queries/json5/highlights.scm+11 runtime/queries/jsx/highlights.scm+2−36 runtime/queries/jsx/indents.scm+2−8 runtime/queries/jsx/injections.scm+3−1 runtime/queries/jsx/locals.scm+3−1 runtime/queries/jsx/tags.scm+3 runtime/queries/jsx/textobjects.scm+3−1 runtime/queries/julia/indents.scm+16 runtime/queries/julia/injections.scm+6 runtime/queries/julia/textobjects.scm+46 runtime/queries/just/folds.scm+4 runtime/queries/just/highlights.scm+33 runtime/queries/just/indents.scm+3 runtime/queries/just/injections.scm+16 runtime/queries/just/locals.scm+10 runtime/queries/just/textobjects.scm+48 runtime/queries/kdl/highlights.scm+4−4 runtime/queries/kdl/indents.scm+3 runtime/queries/kdl/textobjects.scm+27 runtime/queries/kotlin/highlights.scm+4−1 runtime/queries/kotlin/locals.scm+15 runtime/queries/latex/injections.scm+6−2 runtime/queries/log/highlights.scm+10 runtime/queries/lpf/highlights.scm+19 runtime/queries/make/highlights.scm+6−1 runtime/queries/make/injections.scm+3 runtime/queries/markdoc/highlights.scm+16 runtime/queries/markdoc/injections.scm+2 runtime/queries/markdown.inline/injections.scm+6−1 runtime/queries/markdown/highlights.scm+10−1 runtime/queries/markdown/injections.scm+8−1 runtime/queries/matlab/context.scm+41 runtime/queries/matlab/folds.scm+11 runtime/queries/matlab/highlights.scm+128 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/mermaid/highlights.scm+187 runtime/queries/meson/highlights.scm+46−46 runtime/queries/meson/indents.scm+4−3 runtime/queries/msbuild/highlights.scm+1 runtime/queries/msbuild/indents.scm+1 runtime/queries/msbuild/injections.scm+1 runtime/queries/nasm/highlights.scm+126 runtime/queries/nasm/injections.scm+2 runtime/queries/nasm/textobjects.scm+15 runtime/queries/nickel/highlights.scm+2−2 runtime/queries/nickel/indents.scm+1−1 runtime/queries/nickel/injections.scm+3 runtime/queries/nim/highlights.scm+315 runtime/queries/nim/indents.scm+54 runtime/queries/nim/textobjects.scm+19 runtime/queries/nix/highlights.scm+6−1 runtime/queries/nix/injections.scm+140−1 runtime/queries/nu/folds.scm−4 runtime/queries/nu/highlights.scm+276−44 runtime/queries/nu/injections.scm+1−1 runtime/queries/nu/locals.scm−13 runtime/queries/nunjucks/highlights.scm+1 runtime/queries/nunjucks/injections.scm+1 runtime/queries/ocaml/highlights.scm+2 runtime/queries/odin/highlights.scm+18−133 runtime/queries/odin/indents.scm+16 runtime/queries/odin/injections.scm+2 runtime/queries/opencl/highlights.scm+150 runtime/queries/opencl/indents.scm+1 runtime/queries/opencl/injections.scm+1 runtime/queries/opencl/textobjects.scm+1 runtime/queries/passwd/highlights.scm+7 runtime/queries/pem/highlights.scm+7 runtime/queries/perl/fold.scm+19 runtime/queries/perl/highlights.scm+72−155 runtime/queries/perl/indents.scm+25−11 runtime/queries/perl/injections.scm+3−1 runtime/queries/perl/textobjects.scm+10−13 runtime/queries/php/highlights.scm+229−40 runtime/queries/php/injections.scm+19 runtime/queries/po/highlights.scm+15 runtime/queries/po/textobjects.scm+6 runtime/queries/pod/highlights.scm+61 runtime/queries/ponylang/highlights.scm+176 runtime/queries/ponylang/indents.scm+32 runtime/queries/ponylang/locals.scm+37 runtime/queries/ponylang/textobjects.scm+64 runtime/queries/prisma/highlights.scm+2 runtime/queries/prql/highlights.scm+143 runtime/queries/prql/injections.scm+8 runtime/queries/purescript/highlights.scm+164−1 runtime/queries/purescript/injections.scm+2−1 runtime/queries/purescript/locals.scm+4−1 runtime/queries/purescript/textobjects.scm+13 runtime/queries/python/highlights.scm+7−1 runtime/queries/python/indents.scm+22 runtime/queries/qml/highlights.scm+90 runtime/queries/qml/indents.scm+6 runtime/queries/qml/injections.scm+16 runtime/queries/racket/highlights.scm+1 runtime/queries/racket/indents.scm+1 runtime/queries/racket/injections.scm+1 runtime/queries/regex/highlights.scm+1−1 runtime/queries/rego/highlights.scm+68 runtime/queries/rego/injections.scm+2 runtime/queries/robot/highlights.scm+59 runtime/queries/ron/highlights.scm+43−1 runtime/queries/ron/indents.scm+12−1 runtime/queries/ron/injections.scm+2−1 runtime/queries/rst/highlights.scm+38 runtime/queries/ruby/highlights.scm+82−38 runtime/queries/ruby/injections.scm+6 runtime/queries/rust/highlights.scm+29−27 runtime/queries/rust/indents.scm+71−3 runtime/queries/rust/injections.scm+10−4 runtime/queries/rust/locals.scm+14−1 runtime/queries/rust/textobjects.scm+5−2 runtime/queries/sage/highlights.scm+1 runtime/queries/sage/injections.scm+2 runtime/queries/sage/textobjects.scm+1 runtime/queries/scala/highlights.scm+102−30 runtime/queries/scala/locals.scm+29 runtime/queries/scheme/highlights.scm+32−28 runtime/queries/scheme/indents.scm+43 runtime/queries/scss/highlights.scm+1−1 runtime/queries/scss/injections.scm+1−1 runtime/queries/slint/highlights.scm+118−72 runtime/queries/slint/indents.scm+6−5 runtime/queries/slint/injections.scm+2 runtime/queries/slint/locals.scm+1−1 runtime/queries/smali/folds.scm+12 runtime/queries/smali/highlights.scm+216 runtime/queries/smali/indents.scm+31 runtime/queries/smali/injections.scm+2 runtime/queries/smali/locals.scm+42 runtime/queries/smithy/highlights.scm+102 runtime/queries/sql/highlights.scm+345−119 runtime/queries/strace/highlights.scm+8 runtime/queries/svelte/indents.scm+18 runtime/queries/svelte/injections.scm+10 runtime/queries/sway/highlights.scm+336 runtime/queries/sway/indents.scm+71 runtime/queries/sway/injections.scm+2 runtime/queries/sway/locals.scm+17 runtime/queries/sway/textobjects.scm+52 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/tfvars/folds.scm+1 runtime/queries/tfvars/highlights.scm+1 runtime/queries/tfvars/indents.scm+1 runtime/queries/tfvars/injections.scm+1 runtime/queries/todotxt/highlights.scm+6 runtime/queries/toml/highlights.scm+15−2 runtime/queries/tsx/highlights.scm+3−1 runtime/queries/tsx/indents.scm+3−1 runtime/queries/tsx/injections.scm+3−1 runtime/queries/tsx/locals.scm+3 runtime/queries/tsx/tags.scm+3 runtime/queries/tsx/textobjects.scm+3−1 runtime/queries/typescript/highlights.scm+2−63 runtime/queries/typescript/indents.scm+2−6 runtime/queries/typescript/injections.scm+3−1 runtime/queries/typescript/locals.scm+3−2 runtime/queries/typescript/tags.scm+2−22 runtime/queries/typescript/textobjects.scm+2−7 runtime/queries/typst/highlights.scm+77 runtime/queries/typst/injections.scm+6 runtime/queries/unison/highlights.scm+87 runtime/queries/unison/injections.scm+1 runtime/queries/uxntal/highlights.scm+15 runtime/queries/v/highlights.scm+158−67 runtime/queries/v/indents.scm+16 runtime/queries/v/injections.scm+6 runtime/queries/v/textobjects.scm+27 runtime/queries/vhdl/highlights.scm+338 runtime/queries/vue/injections.scm+27−3 runtime/queries/webc/highlights.scm+1 runtime/queries/webc/injections.scm+1 runtime/queries/wren/highlights.scm+82 runtime/queries/wren/indents.scm+29 runtime/queries/wren/injections.scm+3 runtime/queries/wren/locals.scm+21 runtime/queries/wren/textobjects.scm+16 runtime/queries/yaml/indents.scm+35−1 runtime/queries/yaml/injections.scm+52 runtime/queries/yuck/highlights.scm+107 runtime/queries/yuck/injections.scm+2 runtime/queries/zig/highlights.scm+5−2 runtime/queries/zig/indents.scm−1 runtime/queries/zig/textobjects.scm+3−3 runtime/themes/README.md+8 runtime/themes/acme.toml+13−4 runtime/themes/adwaita-dark.toml+172 runtime/themes/amberwood.toml+138 runtime/themes/autumn.toml+74−64 runtime/themes/autumn_night.toml+14−72 runtime/themes/ayu_dark.toml+52−43 runtime/themes/ayu_evolve.toml+37 runtime/themes/ayu_light.toml+62−60 runtime/themes/ayu_mirage.toml+60−52 runtime/themes/base16_default_dark.toml+1 runtime/themes/base16_default_light.toml+1 runtime/themes/base16_terminal.toml+1 runtime/themes/base16_transparent.toml+31−23 runtime/themes/bogster.toml+5−1 runtime/themes/bogster_light.toml+5−1 runtime/themes/boo_berry.toml+7−1 runtime/themes/catppuccin_frappe.toml+3−105 runtime/themes/catppuccin_latte.toml+3−106 runtime/themes/catppuccin_macchiato.toml+3−105 runtime/themes/catppuccin_mocha.toml+36−17 runtime/themes/cyan_light.toml+154 runtime/themes/darcula-solid.toml+16 runtime/themes/darcula.toml+17−5 runtime/themes/dark_high_contrast.toml+31−23 runtime/themes/dark_plus.toml+11−3 runtime/themes/doom_acario_dark.toml+6−5 runtime/themes/dracula.toml+128−64 runtime/themes/dracula_at_night.toml+3 runtime/themes/emacs.toml+12−6 runtime/themes/everblush.toml+115 runtime/themes/everforest_dark.toml+79−52 runtime/themes/everforest_light.toml+79−53 runtime/themes/ferra.toml+83 runtime/themes/flatwhite.toml+13−4 runtime/themes/fleet_dark.toml+166 runtime/themes/fleetish.toml−114 runtime/themes/github_dark.toml+229 runtime/themes/github_dark_colorblind.toml+151 runtime/themes/github_dark_dimmed.toml+151 runtime/themes/github_dark_high_contrast.toml+151 runtime/themes/github_dark_tritanopia.toml+151 runtime/themes/github_light.toml+229 runtime/themes/github_light_colorblind.toml+151 runtime/themes/github_light_high_contrast.toml+151 runtime/themes/github_light_tritanopia.toml+151 runtime/themes/gruber-darker.toml+114 runtime/themes/gruvbox.toml+12−5 runtime/themes/gruvbox_dark_hard.toml+2−91 runtime/themes/gruvbox_dark_soft.toml+7 runtime/themes/gruvbox_light.toml+1−72 runtime/themes/heisenberg.toml+4−4 runtime/themes/hex_lavender.toml+31 runtime/themes/hex_steel.toml+25−15 runtime/themes/hex_toxic.toml+5−5 runtime/themes/horizon-dark.toml+80 runtime/themes/ingrid.toml+6 runtime/themes/jellybeans.toml+138 runtime/themes/kanagawa.toml+56−29 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/meliora.toml+8−3 runtime/themes/mellow.toml+143 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+48 runtime/themes/monokai.toml+12−3 runtime/themes/monokai_aqua.toml+17 runtime/themes/monokai_pro.toml+8−1 runtime/themes/monokai_pro_machine.toml+5−1 runtime/themes/monokai_pro_octagon.toml+5−1 runtime/themes/monokai_pro_ristretto.toml+5−1 runtime/themes/monokai_pro_spectrum.toml+7−3 runtime/themes/naysayer.toml+91 runtime/themes/new_moon.toml+124 runtime/themes/night_owl.toml+6−1 runtime/themes/nightfox.toml+188 runtime/themes/noctis.toml+41−33 runtime/themes/noctis_bordo.toml+6−1 runtime/themes/nord-night.toml+23 runtime/themes/nord.toml+163−96 runtime/themes/nord_light.toml+8−3 runtime/themes/onedark.toml+9−1 runtime/themes/onedarker.toml+9−1 runtime/themes/onelight.toml+150−84 runtime/themes/papercolor-dark.toml+67−110 runtime/themes/papercolor-light.toml+195−98 runtime/themes/penumbra+.toml+5−1 runtime/themes/poimandres.toml+113 runtime/themes/poimandres_storm.toml+19 runtime/themes/pop-dark.toml+25−17 runtime/themes/rasmus.toml+6−1 runtime/themes/rose_pine.toml+182−74 runtime/themes/rose_pine_dawn.toml+27−78 runtime/themes/rose_pine_moon.toml+27−85 runtime/themes/serika-dark.toml+9−3 runtime/themes/serika-light.toml+10−5 runtime/themes/snazzy.toml+65−19 runtime/themes/solarized_dark.toml+8−2 runtime/themes/solarized_light.toml+8−1 runtime/themes/sonokai.toml+10−3 runtime/themes/spacebones_light.toml+6−1 runtime/themes/starlight.toml+113 runtime/themes/tokyonight.toml+6−1 runtime/themes/varua.toml+10−2 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+63 runtime/tutor+336−68 rust-toolchain.toml+2−2 theme.toml+5−1 xtask/Cargo.toml+13−7 xtask/src/docgen.rs+6−5 xtask/src/helpers.rs+2−2 xtask/src/themelint.rs+4−2
.cargo/config.toml+1−1
@@ -1,3 +1,3 @@
11[alias]
22xtask = "run --package xtask --"
3−integration-test = "test --features integration --workspace --test integration"
3+integration-test = "test --features integration --profile integration --workspace --test integration"
.envrc+1
@@ -1,5 +1,6 @@
11watch_file shell.nix
22watch_file flake.lock
3+watch_file rust-toolchain.toml
34
45# try to use flakes, if it fails use normal nix (ie. shell.nix)
56use flake || use nix
.github/ISSUE_TEMPLATE/bug_report.yaml+14−3
@@ -32,7 +32,7 @@ body:
3232 id: helix-log
3333 attributes:
3434 label: Helix log
35− description: See `hx -h` for log file path
35+ description: See `hx -h` for log file path. If you can reproduce the issue run `RUST_BACKTRACE=1 hx -vv` to generate a more detailed log file.
3636 value: |
3737 <details><summary>~/.cache/helix/helix.log</summary>
3838
@@ -55,13 +55,24 @@ body:
5555 placeholder: wezterm 20220101-133340-7edc5b5a
5656 validations:
5757 required: true
58+ - type: input
59+ id: installation-method
60+ attributes:
61+ label: Installation Method
62+ description: >
63+ How you installed Helix - from a package manager like Homebrew or the
64+ AUR, built from source, downloaded a binary from the releases page, etc.
65+ placeholder: "source / brew / nixpkgs / flake / releases page"
66+ validations:
67+ required: true
5868 - type: input
5969 id: helix-version
6070 attributes:
6171 label: Helix Version
6272 description: >
6373 Helix version (`hx -V` if using a release, `git describe` if building
64− from master)
65− placeholder: "helix 0.6.0 (c0dbd6dc)"
74+ from master).
75+ **Make sure that you are using the [latest helix release](https://github.com/helix-editor/helix/releases) or a newer master build**
76+ placeholder: "helix 22.12 (5eaa6d97)"
6677 validations:
6778 required: true
.github/workflows/build.yml+30−73
@@ -4,36 +4,29 @@ on:
44 push:
55 branches:
66 - master
7+ merge_group:
78 schedule:
89 - cron: '00 01 * * *'
910
1011jobs:
1112 check:
12− name: Check
13+ name: Check (msrv)
1314 runs-on: ubuntu-latest
14− strategy:
15− matrix:
16− rust: [stable, msrv]
1715 steps:
1816 - name: Checkout sources
19− uses: actions/checkout@v3
20−
21− - name: Use MSRV rust toolchain
22− if: matrix.rust == 'msrv'
23− run: cp .github/workflows/msrv-rust-toolchain.toml rust-toolchain.toml
24−
17+ uses: actions/checkout@v4
2518 - name: Install stable toolchain
2619 uses: helix-editor/rust-toolchain@v1
2720 with:
2821 profile: minimal
2922 override: true
3023
31− - uses: Swatinem/rust-cache@v1
24+ - uses: Swatinem/rust-cache@v2
25+ with:
26+ shared-key: "build"
3227
3328 - name: Run cargo check
34− uses: actions-rs/cargo@v1
35− with:
36− command: check
29+ run: cargo check
3730
3831 test:
3932 name: Test Suite
@@ -43,15 +36,14 @@ jobs:
4336 HELIX_LOG_LEVEL: info
4437 steps:
4538 - name: Checkout sources
46− uses: actions/checkout@v3
39+ uses: actions/checkout@v4
4740
4841 - name: Install stable toolchain
49− uses: helix-editor/rust-toolchain@v1
50− with:
51− profile: minimal
52− override: true
42+ uses: dtolnay/rust-toolchain@1.70
5343
54− - uses: Swatinem/rust-cache@v1
44+ - uses: Swatinem/rust-cache@v2
45+ with:
46+ shared-key: "build"
5547
5648 - name: Cache test tree-sitter grammar
5749 uses: actions/cache@v3
@@ -61,15 +53,10 @@ jobs:
6153 restore-keys: ${{ runner.os }}-stable-v${{ env.CACHE_VERSION }}-tree-sitter-grammars-
6254
6355 - name: Run cargo test
64− uses: actions-rs/cargo@v1
65− with:
66− command: test
67− args: --workspace
56+ run: cargo test --workspace
6857
6958 - name: Run cargo integration-test
70− uses: actions-rs/cargo@v1
71− with:
72− command: integration-test
59+ run: cargo integration-test
7360
7461 strategy:
7562 matrix:
@@ -80,34 +67,25 @@ jobs:
8067 runs-on: ubuntu-latest
8168 steps:
8269 - name: Checkout sources
83− uses: actions/checkout@v3
70+ uses: actions/checkout@v4
8471
8572 - name: Install stable toolchain
86− uses: helix-editor/rust-toolchain@v1
73+ uses: dtolnay/rust-toolchain@1.70
8774 with:
88− profile: minimal
89− override: true
9075 components: rustfmt, clippy
9176
92− - uses: Swatinem/rust-cache@v1
77+ - uses: Swatinem/rust-cache@v2
78+ with:
79+ shared-key: "build"
9380
9481 - name: Run cargo fmt
95− uses: actions-rs/cargo@v1
96− with:
97− command: fmt
98− args: --all -- --check
82+ run: cargo fmt --all --check
9983
10084 - name: Run cargo clippy
101− uses: actions-rs/cargo@v1
102− with:
103− command: clippy
104− args: --workspace --all-targets -- -D warnings
85+ run: cargo clippy --workspace --all-targets -- -D warnings
10586
10687 - name: Run cargo doc
107− uses: actions-rs/cargo@v1
108− with:
109− command: doc
110− args: --no-deps --workspace --document-private-items
88+ run: cargo doc --no-deps --workspace --document-private-items
11189 env:
11290 RUSTDOCFLAGS: -D warnings
11391
@@ -116,21 +94,20 @@ jobs:
11694 runs-on: ubuntu-latest
11795 steps:
11896 - name: Checkout sources
119− uses: actions/checkout@v3
97+ uses: actions/checkout@v4
12098
12199 - name: Install stable toolchain
122− uses: helix-editor/rust-toolchain@v1
100+ uses: dtolnay/rust-toolchain@1.70
101+
102+ - uses: Swatinem/rust-cache@v2
123103 with:
124− profile: minimal
125− override: true
104+ shared-key: "build"
126105
127− - uses: Swatinem/rust-cache@v1
106+ - name: Validate queries
107+ run: cargo xtask query-check
128108
129109 - name: Generate docs
130− uses: actions-rs/cargo@v1
131− with:
132− command: xtask
133− args: docgen
110+ run: cargo xtask docgen
134111
135112 - name: Check uncommitted documentation changes
136113 run: |
@@ -139,23 +116,3 @@ jobs:
139116 || (echo "Run 'cargo xtask docgen', commit the changes and push again" \
140117 && exit 1)
141118
142− queries:
143− name: Tree-sitter queries
144− runs-on: ubuntu-latest
145− steps:
146− - name: Checkout sources
147− uses: actions/checkout@v3
148−
149− - name: Install stable toolchain
150− uses: helix-editor/rust-toolchain@v1
151− with:
152− profile: minimal
153− override: true
154−
155− - uses: Swatinem/rust-cache@v1
156−
157− - name: Generate docs
158− uses: actions-rs/cargo@v1
159− with:
160− command: xtask
161− args: query-check
.github/workflows/cachix.yml+3−3
@@ -11,13 +11,13 @@ jobs:
1111 runs-on: ubuntu-latest
1212 steps:
1313 - name: Checkout sources
14− uses: actions/checkout@v3
14+ uses: actions/checkout@v4
1515
1616 - name: Install nix
17− uses: cachix/install-nix-action@v18
17+ uses: cachix/install-nix-action@v24
1818
1919 - name: Authenticate with Cachix
20− uses: cachix/cachix-action@v12
20+ uses: cachix/cachix-action@v13
2121 with:
2222 name: helix
2323 authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
.github/workflows/gh-pages.yml+5−5
@@ -11,7 +11,7 @@ jobs:
1111 deploy:
1212 runs-on: ubuntu-latest
1313 steps:
14− - uses: actions/checkout@v3
14+ - uses: actions/checkout@v4
1515
1616 - name: Setup mdBook
1717 uses: peaceiris/actions-mdbook@v1
@@ -26,16 +26,16 @@ jobs:
2626 OUTDIR=$(basename ${{ github.ref }})
2727 echo "OUTDIR=$OUTDIR" >> $GITHUB_ENV
2828
29− - name: Deploy
29+ - name: Deploy stable
3030 uses: peaceiris/actions-gh-pages@v3
31+ if: startswith(github.ref, 'refs/tags/')
3132 with:
3233 github_token: ${{ secrets.GITHUB_TOKEN }}
3334 publish_dir: ./book/book
34− destination_dir: ./${{ env.OUTDIR }}
3535
36− - name: Deploy stable
36+ - name: Deploy
3737 uses: peaceiris/actions-gh-pages@v3
38− if: startswith(github.ref, 'refs/tags/')
3938 with:
4039 github_token: ${{ secrets.GITHUB_TOKEN }}
4140 publish_dir: ./book/book
41+ destination_dir: ./${{ env.OUTDIR }}
.github/workflows/languages.toml+1−1
@@ -11,7 +11,7 @@ indent = { tab-width = 4, unit = " " }
1111
1212[[grammar]]
1313name = "rust"
14−source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "a360da0a29a19c281d08295a35ecd0544d2da211" }
14+source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "0431a2c60828731f27491ee9fdefe25e250ce9c9" }
1515
1616[[language]]
1717name = "nix"
.github/workflows/msrv-rust-toolchain.tomldeleted−3
@@ -1,3 +0,0 @@
1−[toolchain]
2−channel = "1.61.0"
3−components = ["rustfmt", "rust-src"]
.github/workflows/release.yml+59−55
@@ -23,26 +23,20 @@ jobs:
2323 runs-on: ubuntu-latest
2424 steps:
2525 - name: Checkout sources
26− uses: actions/checkout@v3
26+ uses: actions/checkout@v4
2727
2828 - name: Install stable toolchain
29− uses: helix-editor/rust-toolchain@v1
30− with:
31− profile: minimal
32− override: true
29+ uses: dtolnay/rust-toolchain@stable
3330
34− - uses: Swatinem/rust-cache@v1
31+ - uses: Swatinem/rust-cache@v2
3532
3633 - name: Fetch tree-sitter grammars
37− uses: actions-rs/cargo@v1
38− with:
39− command: run
40− args: --package=helix-loader --bin=hx-loader
34+ run: cargo run --package=helix-loader --bin=hx-loader
4135
4236 - name: Bundle grammars
4337 run: tar cJf grammars.tar.xz -C runtime/grammars/sources .
4438
45− - uses: actions/upload-artifact@v3
39+ - uses: actions/upload-artifact@v4
4640 with:
4741 name: grammars
4842 path: grammars.tar.xz
@@ -50,6 +44,16 @@ jobs:
5044 dist:
5145 name: Dist
5246 needs: [fetch-grammars]
47+ env:
48+ # For some builds, we use cross to test on 32-bit and big-endian
49+ # systems.
50+ CARGO: cargo
51+ # When CARGO is set to CROSS, this is set to `--target matrix.target`.
52+ TARGET_FLAGS:
53+ # When CARGO is set to CROSS, TARGET_DIR includes matrix.target.
54+ TARGET_DIR: ./target
55+ # Emit backtraces on panics.
56+ RUST_BACKTRACE: 1
5357 runs-on: ${{ matrix.os }}
5458 strategy:
5559 fail-fast: false # don't fail other jobs if one fails
@@ -57,30 +61,31 @@ jobs:
5761 build: [x86_64-linux, x86_64-macos, x86_64-windows] #, x86_64-win-gnu, win32-msvc
5862 include:
5963 - build: x86_64-linux
60− os: ubuntu-20.04
64+ os: ubuntu-latest
6165 rust: stable
6266 target: x86_64-unknown-linux-gnu
6367 cross: false
6468 - build: aarch64-linux
65− os: ubuntu-20.04
69+ os: ubuntu-latest
6670 rust: stable
6771 target: aarch64-unknown-linux-gnu
6872 cross: true
69− - build: riscv64-linux
70− os: ubuntu-20.04
71− rust: stable
72− target: riscv64gc-unknown-linux-gnu
73− cross: true
73+ # - build: riscv64-linux
74+ # os: ubuntu-latest
75+ # rust: stable
76+ # target: riscv64gc-unknown-linux-gnu
77+ # cross: true
7478 - build: x86_64-macos
7579 os: macos-latest
7680 rust: stable
7781 target: x86_64-apple-darwin
7882 cross: false
7983 - build: x86_64-windows
80− os: windows-2019
84+ os: windows-latest
8185 rust: stable
8286 target: x86_64-pc-windows-msvc
8387 cross: false
88+ # 23.03: build issues
8489 - build: aarch64-macos
8590 os: macos-latest
8691 rust: stable
@@ -98,10 +103,10 @@ jobs:
98103
99104 steps:
100105 - name: Checkout sources
101− uses: actions/checkout@v3
106+ uses: actions/checkout@v4
102107
103108 - name: Download grammars
104− uses: actions/download-artifact@v3
109+ uses: actions/download-artifact@v4
105110
106111 - name: Move grammars under runtime
107112 if: "!startsWith(matrix.os, 'windows')"
@@ -109,13 +114,17 @@ jobs:
109114 mkdir -p runtime/grammars/sources
110115 tar xJf grammars/grammars.tar.xz -C runtime/grammars/sources
111116
117+ # The rust-toolchain action ignores rust-toolchain.toml files.
118+ # Removing this before building with cargo ensures that the rust-toolchain
119+ # is considered the same between installation and usage.
120+ - name: Remove the rust-toolchain.toml file
121+ run: rm rust-toolchain.toml
122+
112123 - name: Install ${{ matrix.rust }} toolchain
113− uses: actions-rs/toolchain@v1
124+ uses: dtolnay/rust-toolchain@master
114125 with:
115− profile: minimal
116126 toolchain: ${{ matrix.rust }}
117127 target: ${{ matrix.target }}
118− override: true
119128
120129 # Install a pre-release version of Cross
121130 # TODO: We need to pre-install Cross because we need cross-rs/cross#591 to
@@ -123,15 +132,20 @@ jobs:
123132 # 0.3.0, which includes cross-rs/cross#591, is released.
124133 - name: Install Cross
125134 if: "matrix.cross"
126− run: cargo install cross --git https://github.com/cross-rs/cross.git --rev 47df5c76e7cba682823a0b6aa6d95c17b31ba63a
135+ run: |
136+ cargo install cross --git https://github.com/cross-rs/cross.git --rev 47df5c76e7cba682823a0b6aa6d95c17b31ba63a
137+ echo "CARGO=cross" >> $GITHUB_ENV
138+ # echo "TARGET_FLAGS=--target ${{ matrix.target }}" >> $GITHUB_ENV
139+ # echo "TARGET_DIR=./target/${{ matrix.target }}" >> $GITHUB_ENV
140+
141+ - name: Show command used for Cargo
142+ run: |
143+ echo "cargo command is: ${{ env.CARGO }}"
144+ echo "target flag is: ${{ env.TARGET_FLAGS }}"
127145
128146 - name: Run cargo test
129− uses: actions-rs/cargo@v1
130147 if: "!matrix.skip_tests"
131− with:
132− use-cross: ${{ matrix.cross }}
133− command: test
134− args: --release --locked --target ${{ matrix.target }} --workspace
148+ run: ${{ env.CARGO }} test --release --locked --target ${{ matrix.target }} --workspace
135149
136150 - name: Set profile.release.strip = true
137151 shell: bash
@@ -142,16 +156,16 @@ jobs:
142156 EOF
143157
144158 - name: Build release binary
145− uses: actions-rs/cargo@v1
146− with:
147− use-cross: ${{ matrix.cross }}
148− command: build
149− args: --release --locked --target ${{ matrix.target }}
159+ run: ${{ env.CARGO }} build --release --locked --target ${{ matrix.target }}
150160
151161 - name: Build AppImage
152162 shell: bash
153− if: matrix.build == 'aarch64-linux' || matrix.build == 'x86_64-linux'
163+ if: matrix.build == 'x86_64-linux'
154164 run: |
165+ # Required as of 22.x https://github.com/AppImage/AppImageKit/wiki/FUSE
166+ sudo add-apt-repository universe
167+ sudo apt install libfuse2
168+
155169 mkdir dist
156170
157171 name=dev
@@ -206,7 +220,7 @@ jobs:
206220 fi
207221 cp -r runtime dist
208222
209− - uses: actions/upload-artifact@v3
223+ - uses: actions/upload-artifact@v4
210224 with:
211225 name: bins-${{ matrix.build }}
212226 path: dist
@@ -217,19 +231,9 @@ jobs:
217231 runs-on: ubuntu-latest
218232 steps:
219233 - name: Checkout sources
220− uses: actions/checkout@v3
234+ uses: actions/checkout@v4
221235
222− - uses: actions/download-artifact@v3
223−
224− - name: Calculate tag name
225− run: |
226− name=dev
227− if [[ $GITHUB_REF == refs/tags/* ]]; then
228− name=${GITHUB_REF:10}
229− fi
230− echo ::set-output name=val::$name
231− echo TAG=$name >> $GITHUB_ENV
232− id: tagname
236+ - uses: actions/download-artifact@v4
233237
234238 - name: Build archive
235239 shell: bash
@@ -250,8 +254,8 @@ jobs:
250254 if [[ $platform =~ "windows" ]]; then
251255 exe=".exe"
252256 fi
253− pkgname=helix-$TAG-$platform
254− mkdir $pkgname
257+ pkgname=helix-$GITHUB_REF_NAME-$platform
258+ mkdir -p $pkgname
255259 cp $source/LICENSE $source/README.md $pkgname
256260 mkdir $pkgname/contrib
257261 cp -r $source/contrib/completion $pkgname/contrib
@@ -259,7 +263,7 @@ jobs:
259263 mv bins-$platform/hx$exe $pkgname
260264 chmod +x $pkgname/hx$exe
261265
262− if [[ "$platform" = "aarch64-linux" || "$platform" = "x86_64-linux" ]]; then
266+ if [[ "$platform" = "x86_64-linux" ]]; then
263267 mv bins-$platform/helix-*.AppImage* dist/
264268 fi
265269
@@ -270,7 +274,7 @@ jobs:
270274 fi
271275 done
272276
273− tar cJf dist/helix-$TAG-source.tar.xz -C $source .
277+ tar cJf dist/helix-$GITHUB_REF_NAME-source.tar.xz -C $source .
274278 mv dist $source/
275279
276280 - name: Upload binaries to release
@@ -280,11 +284,11 @@ jobs:
280284 repo_token: ${{ secrets.GITHUB_TOKEN }}
281285 file: dist/*
282286 file_glob: true
283− tag: ${{ steps.tagname.outputs.val }}
287+ tag: ${{ github.ref_name }}
284288 overwrite: true
285289
286290 - name: Upload binaries as artifact
287− uses: actions/upload-artifact@v3
291+ uses: actions/upload-artifact@v4
288292 if: env.preview == 'true'
289293 with:
290294 name: release
.ignore−3
@@ -1,5 +1,2 @@
11# Things that we don't want ripgrep to search that we do want in git
22# https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#automatic-filtering
3−
4−# Minified JS vendored from mdbook
5−book/theme/highlight.js
CHANGELOG.md+937−1
@@ -1,3 +1,939 @@
1+# 23.10 (2023-10-24)
2+
3+A big shout out to all the contributors! We had 118 contributors in this release.
4+
5+Breaking changes:
6+
7+- Support multiple language servers per language ([#2507](https://github.com/helix-editor/helix/pull/2507))
8+ - This is a breaking change to language configuration
9+
10+Features:
11+
12+- Support multiple language servers per language ([#2507](https://github.com/helix-editor/helix/pull/2507), [#7082](https://github.com/helix-editor/helix/pull/7082), [#7286](https://github.com/helix-editor/helix/pull/7286), [#8374](https://github.com/helix-editor/helix/pull/8374))
13+- Add a statusline element for the selected register ([#7222](https://github.com/helix-editor/helix/pull/7222))
14+- Add `%`, `#`, `.`, `*` and `+` special registers ([#6985](https://github.com/helix-editor/helix/pull/6985))
15+- Add initial support for LSP DidChangeWatchedFiles notifications ([#7665](https://github.com/helix-editor/helix/pull/7665))
16+- Search buffer contents in `global_search` ([#5652](https://github.com/helix-editor/helix/pull/5652))
17+- Add a "smart tab" command that intelligently jumps the cursor on tab ([#4443](https://github.com/helix-editor/helix/pull/4443))
18+- Add a statusline element for whether a file is read-only ([#7222](https://github.com/helix-editor/helix/pull/7222), [#7875](https://github.com/helix-editor/helix/pull/7875))
19+- Syntax highlight regex prompts ([#7738](https://github.com/helix-editor/helix/pull/7738))
20+- Allow defining alignment in indent queries ([#5355](https://github.com/helix-editor/helix/pull/5355))
21+- Show visual feedback in `surround_replace` ([#7588](https://github.com/helix-editor/helix/pull/7588))
22+- Switch to Nucleo for fuzzy matching ([#7814](https://github.com/helix-editor/helix/pull/7814), [#8148](https://github.com/helix-editor/helix/pull/8148), [#8192](https://github.com/helix-editor/helix/pull/8192), [#8194](https://github.com/helix-editor/helix/pull/8194))
23+- Insert a trailing newline on write ([#8157](https://github.com/helix-editor/helix/pull/8157))
24+- Add a `-w`/`--working-dir` CLI flag for specifying a working directory on startup ([#8223](https://github.com/helix-editor/helix/pull/8223), [#8498](https://github.com/helix-editor/helix/pull/8498), [#8520](https://github.com/helix-editor/helix/pull/8520))
25+- Accept a `+N` CLI argument to set the first file's line number ([#8521](https://github.com/helix-editor/helix/pull/8521))
26+- Accept Helix-specific ignore files in `.helix/ignore` and `~/.config/helix/ignore` ([#8099](https://github.com/helix-editor/helix/pull/8099))
27+
28+Commands:
29+
30+- `merge_selections` (`A-minus`) - merge all selections into one selection that covers all ranges ([#7053](https://github.com/helix-editor/helix/pull/7053))
31+- `move_prev_long_word_end` and `extend_prev_long_word_end` - move/extend to the end of the previous WORD ([#6905](https://github.com/helix-editor/helix/pull/6905))
32+- `reverse_selection_contents` - swaps the values of each selection so they are reversed ([#7329](https://github.com/helix-editor/helix/pull/7329))
33+- Add `:rl` and `:rla` aliases for `:reload` and `:reload-all` ([#7158](https://github.com/helix-editor/helix/pull/7158))
34+- `yank_joined` - join the selections and yank to the selected register ([#7195](https://github.com/helix-editor/helix/pull/7195))
35+- `:write-all!` (`:wa!`) - forcibly write all buffers to disk and create any necessary subdirectories ([#7577](https://github.com/helix-editor/helix/pull/7577))
36+- `:redraw` - clear re-render the UI ([#6949](https://github.com/helix-editor/helix/pull/6949))
37+- `:tree-sitter-highlight-name` - show the theme scope name of the highlight under the cursor ([#8170](https://github.com/helix-editor/helix/pull/8170))
38+
39+Usability improvements:
40+
41+- Allow cycling option values at runtime ([#4411](https://github.com/helix-editor/helix/pull/4411), [#7240](https://github.com/helix-editor/helix/pull/7240), [#7877](https://github.com/helix-editor/helix/pull/7877))
42+- Exit gracefully on termination signals ([#7236](https://github.com/helix-editor/helix/pull/7236))
43+- Add plaintext matching fallback to tree-sitter pair matching ([#4288](https://github.com/helix-editor/helix/pull/4288))
44+- Persist register selection in pending keymaps ([0e08349](https://github.com/helix-editor/helix/commit/0e08349))
45+- Propagate the count and register to command palette commands ([b394997](https://github.com/helix-editor/helix/commit/b394997))
46+- Auto indent on `insert_at_line_start` ([#5837](https://github.com/helix-editor/helix/pull/5837))
47+- Add a config option to control whether LSP completions are automatically inserted on preview ([#7189](https://github.com/helix-editor/helix/pull/7189))
48+- Add a config option for default line endings ([#5621](https://github.com/helix-editor/helix/pull/5621), [#7357](https://github.com/helix-editor/helix/pull/7357))
49+- Allow ANSI colors in themes ([#5119](https://github.com/helix-editor/helix/pull/5119))
50+- Match pairs that don't form a standalone tree-sitter node ([#7242](https://github.com/helix-editor/helix/pull/7242))
51+- Allow indent sizes of up to 16 columns ([#7429](https://github.com/helix-editor/helix/pull/7429))
52+- Improve performance of mapping positions through changes ([#7408](https://github.com/helix-editor/helix/pull/7408), [8d39a81](https://github.com/helix-editor/helix/commit/8d39a81), [#7471](https://github.com/helix-editor/helix/pull/7471))
53+- Mark buffers created from stdin as modified ([#7431](https://github.com/helix-editor/helix/pull/7431))
54+- Forcibly shut down uninitialized language servers ([#7449](https://github.com/helix-editor/helix/pull/7449))
55+- Add filename completer for shell prompts ([#7569](https://github.com/helix-editor/helix/pull/7569))
56+- Allow binding F13-F24 ([#7672](https://github.com/helix-editor/helix/pull/7672))
57+- Resolve LSP code actions ([#7677](https://github.com/helix-editor/helix/pull/7677), [#8421](https://github.com/helix-editor/helix/pull/8421))
58+- Save an undo checkpoint before accepting completions ([#7747](https://github.com/helix-editor/helix/pull/7747))
59+- Include gitignored files in debugger completions ([#7936](https://github.com/helix-editor/helix/pull/7936))
60+- Make editor remember the last search register ([#5244](https://github.com/helix-editor/helix/pull/5244))
61+- Open directories with `goto_file` ([#7909](https://github.com/helix-editor/helix/pull/7909))
62+- Use relative path to open buffer in `goto_file` (`gf`) ([#7965](https://github.com/helix-editor/helix/pull/7965))
63+- Support `default` color in themes ([#8083](https://github.com/helix-editor/helix/pull/8083), [#8114](https://github.com/helix-editor/helix/pull/8114))
64+- Toggle between relative and absolute line numbers when the terminal loses focus ([#7955](https://github.com/helix-editor/helix/pull/7955))
65+- Lower default idle-timeout to 250ms ([060e73a](https://github.com/helix-editor/helix/commit/060e73a))
66+- Allow theming diff gutters separately from other diff colors ([#8343](https://github.com/helix-editor/helix/pull/8343))
67+- Style bold/italic/strikethrough in markdown doc popups ([#8385](https://github.com/helix-editor/helix/pull/8385))
68+- Maintain the cursor position and view when splitting with `:hsplit`/`:vsplit` ([#8109](https://github.com/helix-editor/helix/pull/8109))
69+- Accept `-` in macros outside of `<`/`>` ([#8475](https://github.com/helix-editor/helix/pull/8475))
70+- Show all language servers for each language in `--health` ([#7315](https://github.com/helix-editor/helix/pull/7315))
71+- Don't break on hyphens in `:reflow` ([#8569](https://github.com/helix-editor/helix/pull/8569))
72+
73+Fixes:
74+
75+- Update diagnostics correctly on language server exit ([#7111](https://github.com/helix-editor/helix/pull/7111))
76+- Fix off-by-one in `select_references_to_symbol_under_cursor` ([#7132](https://github.com/helix-editor/helix/pull/7132))
77+- Extend selection with repeat-last-motion only if the original motion extended the selection ([#7159](https://github.com/helix-editor/helix/pull/7159))
78+- Fix undefined behavior in the diff gutter ([#7227](https://github.com/helix-editor/helix/pull/7227))
79+- Check that tab width is non-zero ([#7178](https://github.com/helix-editor/helix/pull/7178))
80+- Fix styles being overwritten in table rows with multiple cells ([#7281](https://github.com/helix-editor/helix/pull/7281))
81+- Add file for `--log` CLI arg in help text ([#7307](https://github.com/helix-editor/helix/pull/7307))
82+- Fix underflow when repeating a completion that has a negative shift position ([#7322](https://github.com/helix-editor/helix/pull/7322))
83+- Prefer longer matches in `select_next_sibling` and `select_prev_sibling` ([#7332](https://github.com/helix-editor/helix/pull/7332))
84+- Preview scratch buffers in the jumplist picker ([#7331](https://github.com/helix-editor/helix/pull/7331))
85+- Fix chunking by bytes in tree-sitter parsing ([#7417](https://github.com/helix-editor/helix/pull/7417))
86+- Discard LSP publishDiagnostic from uninitialized servers ([#7467](https://github.com/helix-editor/helix/pull/7467))
87+- Use negotiated position encoding for LSP workspace edits ([#7469](https://github.com/helix-editor/helix/pull/7469))
88+- Fix error message for unknown gutter types in config ([#7534](https://github.com/helix-editor/helix/pull/7534))
89+- Fix `:log-open` when `--log` CLI arg is specified ([#7573](https://github.com/helix-editor/helix/pull/7573), [#7585](https://github.com/helix-editor/helix/pull/7585))
90+- Fix debouncing of LSP messages to fix the last message sticking around ([#7538](https://github.com/helix-editor/helix/pull/7538), [#8023](https://github.com/helix-editor/helix/pull/8023))
91+- Fix crash when the current working directory is deleted ([#7185](https://github.com/helix-editor/helix/pull/7185))
92+- Fix piping to Helix on macOS ([#5468](https://github.com/helix-editor/helix/pull/5468))
93+- Fix crash when parsing overlapping injections ([#7621](https://github.com/helix-editor/helix/pull/7621))
94+- Clear the statusline when the prompt is visible ([#7646](https://github.com/helix-editor/helix/pull/7646))
95+- Fix range formatting error message typo ([#7823](https://github.com/helix-editor/helix/pull/7823))
96+- Skip rendering gutters when gutter width exceeds view width ([#7821](https://github.com/helix-editor/helix/pull/7821))
97+- Center the picker preview using visual lines ([#7837](https://github.com/helix-editor/helix/pull/7837))
98+- Align view correctly for background buffers opened with `A-ret` ([#7691](https://github.com/helix-editor/helix/pull/7691))
99+- Fix cursor resetting to block when quitting via a keybind ([#7931](https://github.com/helix-editor/helix/pull/7931))
100+- Remove path completions for the `:new` command ([#8010](https://github.com/helix-editor/helix/pull/8010))
101+- Use binary path resolved by `which` for formatter commands ([#8064](https://github.com/helix-editor/helix/pull/8064))
102+- Handle crossterm's `hidden` modifier ([#8120](https://github.com/helix-editor/helix/pull/8120))
103+- Clear completion when switching between windows with the mouse ([#8118](https://github.com/helix-editor/helix/pull/8118))
104+- Eagerly remove the last picker (`<space>'`) when the picker has many items ([#8127](https://github.com/helix-editor/helix/pull/8127))
105+- Fix find commands for buffers with non-LF line-endings ([#8111](https://github.com/helix-editor/helix/pull/8111))
106+- Detect the tmux clipboard provider on macOS ([#8182](https://github.com/helix-editor/helix/pull/8182))
107+- Fix syntax highlighting in dynamic picker preview pane ([#8206](https://github.com/helix-editor/helix/pull/8206))
108+- Recognize HTML code tags with attributes as code in markdown previews ([#8397](https://github.com/helix-editor/helix/pull/8397))
109+- Fix multicursor snippet placeholder directions ([#8423](https://github.com/helix-editor/helix/pull/8423))
110+- Only show diagnostic highlights when diagnostics are enabled for a language server ([#8551](https://github.com/helix-editor/helix/pull/8551))
111+
112+Themes:
113+
114+- Improve the selection color in `ferra` ([#7138](https://github.com/helix-editor/helix/pull/7138))
115+- Add `variable.other.member` theming to `spacebones_light` ([#7125](https://github.com/helix-editor/helix/pull/7125))
116+- Update `autumn` and theme the soft-wrap indicator ([#7229](https://github.com/helix-editor/helix/pull/7229))
117+- Add `gruvbox_dark_soft` ([#7139](https://github.com/helix-editor/helix/pull/7139))
118+- Add `merionette` ([#7186](https://github.com/helix-editor/helix/pull/7186))
119+- Add `zed_onedark` and `zed_onelight` ([#7250](https://github.com/helix-editor/helix/pull/7250))
120+- Use light-gray for `onedarker` inlay hint theming ([#7433](https://github.com/helix-editor/helix/pull/7433))
121+- Update the Nord theme to follow the style guidelines ([#7490](https://github.com/helix-editor/helix/pull/7490))
122+- Tune `dark_plus` inlay hint colors ([#7611](https://github.com/helix-editor/helix/pull/7611))
123+- Add `naysayer` ([#7570](https://github.com/helix-editor/helix/pull/7570))
124+- Add `kaolin-dark`, `kaolin-light` and `kaolin-valley-dark` ([#7151](https://github.com/helix-editor/helix/pull/7151))
125+- Fix selection highlighting in gruvbox variants ([#7717](https://github.com/helix-editor/helix/pull/7717))
126+- Add soft-wrap indicator to `gruvbox` ([#7736](https://github.com/helix-editor/helix/pull/7736))
127+- Add missing palette definitions in `everforest_dark` ([#7739](https://github.com/helix-editor/helix/pull/7739))
128+- Increase diagnostics clarity in `pop-dark` ([#7702](https://github.com/helix-editor/helix/pull/7702))
129+- Add `vim_dark_high_contrast` ([#7785](https://github.com/helix-editor/helix/pull/7785))
130+- Add `new_moon` ([#7834](https://github.com/helix-editor/helix/pull/7834))
131+- Add `yellowed` ([#7849](https://github.com/helix-editor/helix/pull/7849))
132+- Improve comment readability for `autumn` ([#7939](https://github.com/helix-editor/helix/pull/7939))
133+- Distinguish active bufferline buffer in `monokai` ([#7983](https://github.com/helix-editor/helix/pull/7983))
134+- Update ruler colors in `nord` ([#7995](https://github.com/helix-editor/helix/pull/7995))
135+- Update Catppuccin themes ([#8102](https://github.com/helix-editor/helix/pull/8102))
136+- Add text focus scope and diagnostics undercurls for `nord` ([#8165](https://github.com/helix-editor/helix/pull/8165))
137+- Add material theme collection ([#8211](https://github.com/helix-editor/helix/pull/8211))
138+- Improve indent line color in `dracula` ([#8266](https://github.com/helix-editor/helix/pull/8266))
139+- Clean up and refactor `papercolor` to use inheritance ([#8276](https://github.com/helix-editor/helix/pull/8276))
140+- Fix `zenburn` inlay hint color ([#8278](https://github.com/helix-editor/helix/pull/8278)a)
141+- Fix picker crash when previewing an invalid range ([e9d0bd7](https://github.com/helix-editor/helix/commit/e9d0bd7))
142+- Correctly center items in the picker preview ([13d4463](https://github.com/helix-editor/helix/commit/13d4463))
143+- Add `cyan_light` ([#8293](https://github.com/helix-editor/helix/pull/8293), [#8587](https://github.com/helix-editor/helix/pull/8587))
144+- Theme HTML tags in `onedark` ([#8409](https://github.com/helix-editor/helix/pull/8409))
145+- Refine `darcula` and `darcula-solid` themes ([#8412](https://github.com/helix-editor/helix/pull/8412))
146+- Improve `nord` highlights ([#8414](https://github.com/helix-editor/helix/pull/8414))
147+- Add `nord-night` ([#8549](https://github.com/helix-editor/helix/pull/8549))
148+
149+New languages:
150+
151+- Blueprint ([#7213](https://github.com/helix-editor/helix/pull/7213), [#8161](https://github.com/helix-editor/helix/pull/8161))
152+- Forth ([#7256](https://github.com/helix-editor/helix/pull/7256), [#7334](https://github.com/helix-editor/helix/pull/7334))
153+- t32 ([#7140](https://github.com/helix-editor/helix/pull/7140), [#7811](https://github.com/helix-editor/helix/pull/7811))
154+- WebC ([#7290](https://github.com/helix-editor/helix/pull/7290))
155+- Persistent DSL for Haskell ([#7261](https://github.com/helix-editor/helix/pull/7261))
156+- F# ([#7619](https://github.com/helix-editor/helix/pull/7619), [#8024](https://github.com/helix-editor/helix/pull/8024))
157+- Wren ([#7765](https://github.com/helix-editor/helix/pull/7765), [#7819](https://github.com/helix-editor/helix/pull/7819))
158+- Unison ([#7724](https://github.com/helix-editor/helix/pull/7724))
159+- Todo.txt ([#7835](https://github.com/helix-editor/helix/pull/7835))
160+- Jinja and Handlebars ([#7233](https://github.com/helix-editor/helix/pull/7233))
161+- Pod ([#7907](https://github.com/helix-editor/helix/pull/7907))
162+- Strace ([#7928](https://github.com/helix-editor/helix/pull/7928))
163+- Gemini ([#8070](https://github.com/helix-editor/helix/pull/8070))
164+- GNU Assembler (GAS) ([#8291](https://github.com/helix-editor/helix/pull/8291))
165+- JSON5 ([#8473](https://github.com/helix-editor/helix/pull/8473))
166+- TEMPL ([#8540](https://github.com/helix-editor/helix/pull/8540))
167+
168+Updated languages and queries:
169+
170+- Add one to the ruler numbers for git-commit ([#7072](https://github.com/helix-editor/helix/pull/7072))
171+- Recognize XAML files as XML ([#7083](https://github.com/helix-editor/helix/pull/7083))
172+- Recognize `Cargo.lock` as TOML ([#7095](https://github.com/helix-editor/helix/pull/7095))
173+- Use Rust grammar for Cairo ([c6d1430](https://github.com/helix-editor/helix/commit/c6d1430))
174+- Update tree-sitter-nickel ([#7059](https://github.com/helix-editor/helix/pull/7059), [#7551](https://github.com/helix-editor/helix/pull/7551))
175+- Tune auto-pair characters for Nickel ([#7059](https://github.com/helix-editor/helix/pull/7059))
176+- Recognize `Vagrantfile` as Ruby ([#7112](https://github.com/helix-editor/helix/pull/7112))
177+- Recognize hidden justfiles as Just ([#7088](https://github.com/helix-editor/helix/pull/7088))
178+- Update Java and TypeScript highlight queries ([#7145](https://github.com/helix-editor/helix/pull/7145))
179+- Recognize `.zimrc` as Bash ([#7146](https://github.com/helix-editor/helix/pull/7146))
180+- Recognize `.gir` as XML ([#7152](https://github.com/helix-editor/helix/pull/7152))
181+- Update tree-sitter-scala ([#7147](https://github.com/helix-editor/helix/pull/7147))
182+- Recognize make file-type as Makefile ([#7212](https://github.com/helix-editor/helix/pull/7212))
183+- Update tree-sitter-verilog ([#7262](https://github.com/helix-editor/helix/pull/7262))
184+- Update tree-sitter-cpp ([#7285](https://github.com/helix-editor/helix/pull/7285))
185+- Support core mode for delve debugger ([#7300](https://github.com/helix-editor/helix/pull/7300))
186+- Add Fortran comment injections ([#7305](https://github.com/helix-editor/helix/pull/7305))
187+- Switch Vue language server to `vue-language-server` ([#7312](https://github.com/helix-editor/helix/pull/7312))
188+- Update tree-sitter-sql ([#7387](https://github.com/helix-editor/helix/pull/7387), [#8464](https://github.com/helix-editor/helix/pull/8464))
189+- Replace the MATLAB tre-sitter grammar ([#7388](https://github.com/helix-editor/helix/pull/7388), [#7442](https://github.com/helix-editor/helix/pull/7442), [#7491](https://github.com/helix-editor/helix/pull/7491), [#7493](https://github.com/helix-editor/helix/pull/7493), [#7511](https://github.com/helix-editor/helix/pull/7511), [#7532](https://github.com/helix-editor/helix/pull/7532), [#8040](https://github.com/helix-editor/helix/pull/8040))
190+- Highlight TOML table headers ([#7441](https://github.com/helix-editor/helix/pull/7441))
191+- Recognize `cppm` file-type as C++ ([#7492](https://github.com/helix-editor/helix/pull/7492))
192+- Refactor ecma language queries into private and public queries ([#7207](https://github.com/helix-editor/helix/pull/7207))
193+- Update tree-sitter-dart ([#7576](https://github.com/helix-editor/helix/pull/7576))
194+- Add shebang for nushell files ([#7606](https://github.com/helix-editor/helix/pull/7606))
195+- Recognize systemd files as INI ([#7592](https://github.com/helix-editor/helix/pull/7592))
196+- Update TypeScript, TSX and Svelte grammars ([#6874](https://github.com/helix-editor/helix/pull/6874))
197+- Enable inlay hints in the Svelte language server ([#7622](https://github.com/helix-editor/helix/pull/7622))
198+- Recognize `Brewfile`s as Ruby ([#7629](https://github.com/helix-editor/helix/pull/7629))
199+- Add more file-types for R ([#7633](https://github.com/helix-editor/helix/pull/7633))
200+- Switch tree-sitter-perl to official upstream parser ([#7644](https://github.com/helix-editor/helix/pull/7644), [#7947](https://github.com/helix-editor/helix/pull/7947))
201+- Fix predicate typo in comment highlights ([#7732](https://github.com/helix-editor/helix/pull/7732))
202+- Update tree-sitter-prql ([#7771](https://github.com/helix-editor/helix/pull/7771))
203+- Recognize `.gitf` as JSON ([#7781](https://github.com/helix-editor/helix/pull/7781))
204+- Switch V language server to `v-analyzer` ([#7760](https://github.com/helix-editor/helix/pull/7760))
205+- Add protobuf language servers ([#7796](https://github.com/helix-editor/helix/pull/7796))
206+- Update tree-sitter-zig ([#7803](https://github.com/helix-editor/helix/pull/7803))
207+- Update tree-sitter-hare ([#7784](https://github.com/helix-editor/helix/pull/7784))
208+- Add Java indent queries ([#7844](https://github.com/helix-editor/helix/pull/7844))
209+- Update tree-sitter-scheme ([979933b](https://github.com/helix-editor/helix/commit/979933b))
210+- Recognize `scm` as Scheme instead of TSQ ([5707151](https://github.com/helix-editor/helix/commit/5707151))
211+- Update tree-sitter-git-commit ([#7831](https://github.com/helix-editor/helix/pull/7831))
212+- Update JavaScript, TypeScript and TSX grammars ([#7852](https://github.com/helix-editor/helix/pull/7852))
213+- Update tree-sitter-nu ([#7873](https://github.com/helix-editor/helix/pull/7873))
214+- Fix YAML indentation ([#6768](https://github.com/helix-editor/helix/pull/6768))
215+- Add `csharp-ls`, Pyright, Pylyzer and add roots for Python ([#7897](https://github.com/helix-editor/helix/pull/7897), [#8032](https://github.com/helix-editor/helix/pull/8032))
216+- Update tree-sitter-slint ([#7893](https://github.com/helix-editor/helix/pull/7893))
217+- Recognize more ZSH file-types as Bash ([#7930](https://github.com/helix-editor/helix/pull/7930))
218+- Recognize `star` extension as Starlark ([#7922](https://github.com/helix-editor/helix/pull/7922))
219+- Fix inline HTML tag highlighting in markdown ([#7960](https://github.com/helix-editor/helix/pull/7960))
220+- Update tree-sitter-robot ([#7970](https://github.com/helix-editor/helix/pull/7970))
221+- Highlight Dart 3 `sealed` and `base` keywords ([#7974](https://github.com/helix-editor/helix/pull/7974))
222+- Add configuration for `ltex-ls` to the default `languages.toml` ([#7838](https://github.com/helix-editor/helix/pull/7838))
223+- Update tree-sitter-strace ([#8087](https://github.com/helix-editor/helix/pull/8087))
224+- Update tree-sitter-gleam, enable auto-format ([#8085](https://github.com/helix-editor/helix/pull/8085))
225+- Update tree-sitter-esdl ([#8222](https://github.com/helix-editor/helix/pull/8222))
226+- Expand ignore file-types ([#8220](https://github.com/helix-editor/helix/pull/8220))
227+- Recognize feed related formats as XML ([#8232](https://github.com/helix-editor/helix/pull/8232))
228+- Improve YAML injections ([#8217](https://github.com/helix-editor/helix/pull/8217))
229+- Add shebangs for TypeScript, Julia, Java and OCaml ([95e994a](https://github.com/helix-editor/helix/commit/95e994a))
230+- Highlight abbreviations in Scheme ([ef23847](https://github.com/helix-editor/helix/commit/ef23847))
231+- Remove backtic auto-pair in OCaml ([#8260](https://github.com/helix-editor/helix/pull/8260))
232+- Recognize `flake.lock` as JSON ([#8304](https://github.com/helix-editor/helix/pull/8304))
233+- Add Python test script injection for Nix ([b4494e1](https://github.com/helix-editor/helix/commit/b4494e1))
234+- Fix Nix comment injection precedence ([37e48f4](https://github.com/helix-editor/helix/commit/37e48f4))
235+- Recognize editorconfig files as INI ([#8308](https://github.com/helix-editor/helix/pull/8308))
236+- Recognize `.babelrc` as JSON ([#8309](https://github.com/helix-editor/helix/pull/8309))
237+- Switch Purescript to its own tree-sitter parser ([#8306](https://github.com/helix-editor/helix/pull/8306), [#8338](https://github.com/helix-editor/helix/pull/8338), [#8527](https://github.com/helix-editor/helix/pull/8527))
238+- Update Unison highlights ([#8315](https://github.com/helix-editor/helix/pull/8315))
239+- Recognize `.webmanifest` as JSON ([#8342](https://github.com/helix-editor/helix/pull/8342))
240+- Recognize polkit policy files as XML ([#8369](https://github.com/helix-editor/helix/pull/8369))
241+- Recognize polkit rules files as JavaScript ([#8370](https://github.com/helix-editor/helix/pull/8370))
242+- Update Go highlight queries ([#8399](https://github.com/helix-editor/helix/pull/8399))
243+- Add shebangs for Makefiles ([#8410](https://github.com/helix-editor/helix/pull/8410))
244+- Add file-type associations from VSCode ([#8388](https://github.com/helix-editor/helix/pull/8388))
245+- Add validation to JSON/CSS language server configs ([#8433](https://github.com/helix-editor/helix/pull/8433))
246+- Add a configuration for the tailwind language server ([#8442](https://github.com/helix-editor/helix/pull/8442))
247+- Add a configuration for the ansible language server ([#7973](https://github.com/helix-editor/helix/pull/7973))
248+- Add a configuration for the GraphQL language server ([#8492](https://github.com/helix-editor/helix/pull/8492))
249+- Indent while statements in Bash ([#8528](https://github.com/helix-editor/helix/pull/8528))
250+- Update tree-sitter-haskell and queries ([#8558](https://github.com/helix-editor/helix/pull/8558))
251+
252+Packaging:
253+
254+- Add an overlay to the Nix flake ([#7078](https://github.com/helix-editor/helix/pull/7078))
255+- Check for `git` before fetching or building grammars ([#7320](https://github.com/helix-editor/helix/pull/7320))
256+- Refactor Nix flake to use Crane ([#7763](https://github.com/helix-editor/helix/pull/7763))
257+- Remove the aarch64 appimage from the release CI ([#7832](https://github.com/helix-editor/helix/pull/7832))
258+- Add desktop and icon files to Nix flake output ([#7979](https://github.com/helix-editor/helix/pull/7979))
259+- Build flake packages with the latest stable Rust ([#8133](https://github.com/helix-editor/helix/pull/8133))
260+
261+# 23.05 (2023-05-18)
262+
263+23.05 is a smaller release focusing on fixes. There were 88 contributors in this release. Thank you all!
264+
265+Features:
266+
267+- Add a config option to exclude declaration from LSP references request ([#6886](https://github.com/helix-editor/helix/pull/6886))
268+- Enable injecting languages based on their file extension and shebang ([#3970](https://github.com/helix-editor/helix/pull/3970))
269+- Sort the buffer picker by most recent access ([#2980](https://github.com/helix-editor/helix/pull/2980))
270+- Perform syntax highlighting in the picker asynchronously ([#7028](https://github.com/helix-editor/helix/pull/7028))
271+
272+Commands:
273+
274+- `:update` is now aliased as `:u` ([#6835](https://github.com/helix-editor/helix/pull/6835))
275+- Add `extend_to_first_nonwhitespace` which acts the same as `goto_first_nonwhitespace` but always extends ([#6837](https://github.com/helix-editor/helix/pull/6837))
276+- Add `:clear-register` for clearing the given register or all registers ([#5695](https://github.com/helix-editor/helix/pull/5695))
277+- Add `:write-buffer-close` and `:write-buffer-close!` ([#6947](https://github.com/helix-editor/helix/pull/6947))
278+
279+Fixes:
280+
281+- Normalize LSP workspace paths ([#6517](https://github.com/helix-editor/helix/pull/6517))
282+- Robustly handle invalid LSP ranges ([#6512](https://github.com/helix-editor/helix/pull/6512))
283+- Fix line number display for LSP goto pickers ([#6559](https://github.com/helix-editor/helix/pull/6559))
284+- Fix toggling of `soft-wrap.enable` option ([#6656](https://github.com/helix-editor/helix/pull/6656), [58e457a](https://github.com/helix-editor/helix/commit/58e457a), [#6742](https://github.com/helix-editor/helix/pull/6742))
285+- Handle `workspace/configuration` requests from stopped language servers ([#6693](https://github.com/helix-editor/helix/pull/6693))
286+- Fix possible crash from opening the jumplist picker ([#6672](https://github.com/helix-editor/helix/pull/6672))
287+- Fix theme preview returning to current theme on line and word deletions ([#6694](https://github.com/helix-editor/helix/pull/6694))
288+- Re-run crate build scripts on changes to revision and grammar repositories ([#6743](https://github.com/helix-editor/helix/pull/6743))
289+- Fix crash on opening from suspended state ([#6764](https://github.com/helix-editor/helix/pull/6764))
290+- Fix unwrap bug in DAP ([#6786](https://github.com/helix-editor/helix/pull/6786))
291+- Always build tree-sitter parsers with C++14 and C11 ([#6792](https://github.com/helix-editor/helix/pull/6792), [#6834](https://github.com/helix-editor/helix/pull/6834), [#6845](https://github.com/helix-editor/helix/pull/6845))
292+- Exit with a non-zero statuscode when tree-sitter parser builds fail ([#6795](https://github.com/helix-editor/helix/pull/6795))
293+- Flip symbol range in LSP goto commands ([#6794](https://github.com/helix-editor/helix/pull/6794))
294+- Fix runtime toggling of the `mouse` option ([#6675](https://github.com/helix-editor/helix/pull/6675))
295+- Fix panic in inlay hint computation when view anchor is out of bounds ([#6883](https://github.com/helix-editor/helix/pull/6883))
296+- Significantly improve performance of git discovery on slow file systems ([#6890](https://github.com/helix-editor/helix/pull/6890))
297+- Downgrade gix log level to info ([#6915](https://github.com/helix-editor/helix/pull/6915))
298+- Conserve BOM and properly support saving UTF16 files ([#6497](https://github.com/helix-editor/helix/pull/6497))
299+- Correctly handle completion re-request ([#6594](https://github.com/helix-editor/helix/pull/6594))
300+- Fix offset encoding in LSP `didChange` notifications ([#6921](https://github.com/helix-editor/helix/pull/6921))
301+- Change `gix` logging level to info ([#6915](https://github.com/helix-editor/helix/pull/6915))
302+- Improve error message when writes fail because parent directories do not exist ([#7014](https://github.com/helix-editor/helix/pull/7014))
303+- Replace DAP variables popup instead of pushing more popups ([#7034](https://github.com/helix-editor/helix/pull/7034))
304+- Disable tree-sitter for files after parsing for 500ms ([#7028](https://github.com/helix-editor/helix/pull/7028))
305+- Fix crash when deleting with multiple cursors ([#6024](https://github.com/helix-editor/helix/pull/6024))
306+- Fix selection sliding when deleting forwards in append mode ([#6024](https://github.com/helix-editor/helix/pull/6024))
307+- Fix completion on paths containing spaces ([#6779](https://github.com/helix-editor/helix/pull/6779))
308+
309+Themes:
310+
311+- Style inlay hints in `dracula` theme ([#6515](https://github.com/helix-editor/helix/pull/6515))
312+- Style inlay hints in `onedark` theme ([#6503](https://github.com/helix-editor/helix/pull/6503))
313+- Style inlay hints and the soft-wrap indicator in `varua` ([#6568](https://github.com/helix-editor/helix/pull/6568), [#6589](https://github.com/helix-editor/helix/pull/6589))
314+- Style inlay hints in `emacs` theme ([#6569](https://github.com/helix-editor/helix/pull/6569))
315+- Update `base16_transparent` and `dark_high_contrast` themes ([#6577](https://github.com/helix-editor/helix/pull/6577))
316+- Style inlay hints for `mellow` and `rasmus` themes ([#6583](https://github.com/helix-editor/helix/pull/6583))
317+- Dim pane divider for `base16_transparent` theme ([#6534](https://github.com/helix-editor/helix/pull/6534))
318+- Style inlay hints in `zenburn` theme ([#6593](https://github.com/helix-editor/helix/pull/6593))
319+- Style inlay hints in `boo_berry` theme ([#6625](https://github.com/helix-editor/helix/pull/6625))
320+- Add `ferra` theme ([#6619](https://github.com/helix-editor/helix/pull/6619), [#6776](https://github.com/helix-editor/helix/pull/6776))
321+- Style inlay hints in `nightfox` theme ([#6655](https://github.com/helix-editor/helix/pull/6655))
322+- Fix `ayu` theme family markup code block background ([#6538](https://github.com/helix-editor/helix/pull/6538))
323+- Improve whitespace and search match colors in `rose_pine` theme ([#6679](https://github.com/helix-editor/helix/pull/6679))
324+- Highlight selected items in `base16_transparent` theme ([#6716](https://github.com/helix-editor/helix/pull/6716))
325+- Adjust everforest to resemble original more closely ([#5866](https://github.com/helix-editor/helix/pull/5866))
326+- Refactor `dracula` theme ([#6552](https://github.com/helix-editor/helix/pull/6552), [#6767](https://github.com/helix-editor/helix/pull/6767), [#6855](https://github.com/helix-editor/helix/pull/6855), [#6987](https://github.com/helix-editor/helix/pull/6987))
327+- Style inlay hints in `darcula` theme ([#6732](https://github.com/helix-editor/helix/pull/6732))
328+- Style inlay hints in `kanagawa` theme ([#6773](https://github.com/helix-editor/helix/pull/6773))
329+- Improve `ayu_dark` theme ([#6622](https://github.com/helix-editor/helix/pull/6622))
330+- Refactor `noctis` theme multiple cursor highlighting ([96720e7](https://github.com/helix-editor/helix/commit/96720e7))
331+- Refactor `noctis` theme whitespace rendering and indent guides ([f2ccc03](https://github.com/helix-editor/helix/commit/f2ccc03))
332+- Add `amberwood` theme ([#6924](https://github.com/helix-editor/helix/pull/6924))
333+- Update `nightfox` theme ([#7061](https://github.com/helix-editor/helix/pull/7061))
334+
335+Language support:
336+
337+- R language server: use the `--no-echo` flag to silence output ([#6570](https://github.com/helix-editor/helix/pull/6570))
338+- Recognize CUDA files as C++ ([#6521](https://github.com/helix-editor/helix/pull/6521))
339+- Add support for Hurl ([#6450](https://github.com/helix-editor/helix/pull/6450))
340+- Add textobject queries for Julia ([#6588](https://github.com/helix-editor/helix/pull/6588))
341+- Update Ruby highlight queries ([#6587](https://github.com/helix-editor/helix/pull/6587))
342+- Add xsd to XML file-types ([#6631](https://github.com/helix-editor/helix/pull/6631))
343+- Support Robot Framework ([#6611](https://github.com/helix-editor/helix/pull/6611))
344+- Update Gleam tree-sitter parser ([#6641](https://github.com/helix-editor/helix/pull/6641))
345+- Update git-commit tree-sitter parser ([#6692](https://github.com/helix-editor/helix/pull/6692))
346+- Update Haskell tree-sitter parser ([#6317](https://github.com/helix-editor/helix/pull/6317))
347+- Add injection queries for Haskell quasiquotes ([#6474](https://github.com/helix-editor/helix/pull/6474))
348+- Highlight C/C++ escape sequences ([#6724](https://github.com/helix-editor/helix/pull/6724))
349+- Support Markdoc ([#6432](https://github.com/helix-editor/helix/pull/6432))
350+- Support OpenCL ([#6473](https://github.com/helix-editor/helix/pull/6473))
351+- Support DTD ([#6644](https://github.com/helix-editor/helix/pull/6644))
352+- Fix constant highlighting in Python queries ([#6751](https://github.com/helix-editor/helix/pull/6751))
353+- Support Just ([#6453](https://github.com/helix-editor/helix/pull/6453))
354+- Fix Go locals query for `var_spec` identifiers ([#6763](https://github.com/helix-editor/helix/pull/6763))
355+- Update Markdown tree-sitter parser ([#6785](https://github.com/helix-editor/helix/pull/6785))
356+- Fix Haskell workspace root for cabal projects ([#6828](https://github.com/helix-editor/helix/pull/6828))
357+- Avoid extra indentation in Go switches ([#6817](https://github.com/helix-editor/helix/pull/6817))
358+- Fix Go workspace roots ([#6884](https://github.com/helix-editor/helix/pull/6884))
359+- Set PerlNavigator as the default Perl language server ([#6860](https://github.com/helix-editor/helix/pull/6860))
360+- Highlight more sqlx macros in Rust ([#6793](https://github.com/helix-editor/helix/pull/6793))
361+- Switch Odin tree-sitter grammar ([#6766](https://github.com/helix-editor/helix/pull/6766))
362+- Recognize `poetry.lock` as TOML ([#6928](https://github.com/helix-editor/helix/pull/6928))
363+- Recognize Jupyter notebooks as JSON ([#6927](https://github.com/helix-editor/helix/pull/6927))
364+- Add language server configuration for Crystal ([#6948](https://github.com/helix-editor/helix/pull/6948))
365+- Add `build.gradle.kts` to Java and Scala roots ([#6970](https://github.com/helix-editor/helix/pull/6970))
366+- Recognize `sty` and `cls` files as latex ([#6986](https://github.com/helix-editor/helix/pull/6986))
367+- Update Dockerfile tree-sitter grammar ([#6895](https://github.com/helix-editor/helix/pull/6895))
368+- Add comment injections for Odin ([#7027](https://github.com/helix-editor/helix/pull/7027))
369+- Recognize `gml` as XML ([#7055](https://github.com/helix-editor/helix/pull/7055))
370+- Recognize `geojson` as JSON ([#7054](https://github.com/helix-editor/helix/pull/7054))
371+
372+Packaging:
373+
374+- Update the Nix flake dependencies, remove a deprecated option ([#6546](https://github.com/helix-editor/helix/pull/6546))
375+- Fix and re-enable aarch64-macos release binary builds ([#6504](https://github.com/helix-editor/helix/pull/6504))
376+- The git dependency on `tree-sitter` has been replaced with a regular crates.io dependency ([#6608](https://github.com/helix-editor/helix/pull/6608))
377+
378+# 23.03 (2023-03-31)
379+
380+23.03 brings some long-awaited and exciting features. Thank you to everyone involved! This release saw changes from 102 contributors.
381+
382+For the full log, check out the [git log](https://github.com/helix-editor/helix/compare/22.12..23.03).
383+Also check out the [release notes](https://helix-editor.com/news/release-23-03-highlights/) for more commentary on larger features.
384+
385+Breaking changes:
386+
387+- Select diagnostic range in `goto_*_diag` commands ([#4713](https://github.com/helix-editor/helix/pull/4713), [#5164](https://github.com/helix-editor/helix/pull/5164), [#6193](https://github.com/helix-editor/helix/pull/6193))
388+- Remove jump behavior from `increment`/`decrement` ([#4123](https://github.com/helix-editor/helix/pull/4123), [#5929](https://github.com/helix-editor/helix/pull/5929))
389+- Select change range in `goto_*_change` commands ([#5206](https://github.com/helix-editor/helix/pull/5206))
390+- Split file modification indicator from filename statusline elements ([#4731](https://github.com/helix-editor/helix/pull/4731), [#6036](https://github.com/helix-editor/helix/pull/6036))
391+- Jump to symbol ranges in LSP goto commands ([#5986](https://github.com/helix-editor/helix/pull/5986))
392+- Workspace detection now stops at the first `.helix/` directory (merging multiple `.helix/languages.toml` configurations is no longer supported) ([#5748](https://github.com/helix-editor/helix/pull/5748))
393+
394+Features:
395+
396+- Dynamic workspace symbol picker ([#5055](https://github.com/helix-editor/helix/pull/5055))
397+- Soft-wrap ([#5420](https://github.com/helix-editor/helix/pull/5420), [#5786](https://github.com/helix-editor/helix/pull/5786), [#5893](https://github.com/helix-editor/helix/pull/5893), [#6142](https://github.com/helix-editor/helix/pull/6142), [#6440](https://github.com/helix-editor/helix/pull/6440))
398+- Initial support for LSP snippet completions ([#5864](https://github.com/helix-editor/helix/pull/5864), [b1f7528](https://github.com/helix-editor/helix/commit/b1f7528), [#6263](https://github.com/helix-editor/helix/pull/6263), [bbf4800](https://github.com/helix-editor/helix/commit/bbf4800), [90348b8](https://github.com/helix-editor/helix/commit/90348b8), [f87299f](https://github.com/helix-editor/helix/commit/f87299f), [#6371](https://github.com/helix-editor/helix/pull/6371), [9fe3adc](https://github.com/helix-editor/helix/commit/9fe3adc))
399+- Add a statusline element for showing the current version control HEAD ([#5682](https://github.com/helix-editor/helix/pull/5682))
400+- Display LSP type hints ([#5420](https://github.com/helix-editor/helix/pull/5420), [#5934](https://github.com/helix-editor/helix/pull/5934), [#6312](https://github.com/helix-editor/helix/pull/6312))
401+- Enable the Kitty keyboard protocol on terminals with support ([#4939](https://github.com/helix-editor/helix/pull/4939), [#6170](https://github.com/helix-editor/helix/pull/6170), [#6194](https://github.com/helix-editor/helix/pull/6194), [#6438](https://github.com/helix-editor/helix/pull/6438))
402+- Add a statusline element for the basename of the current file ([#5318](https://github.com/helix-editor/helix/pull/5318))
403+- Add substring matching syntax for the picker ([#5658](https://github.com/helix-editor/helix/pull/5658))
404+- Support LSP `textDocument/prepareRename` ([#6103](https://github.com/helix-editor/helix/pull/6103))
405+- Allow multiple runtime directories with priorities ([#5411](https://github.com/helix-editor/helix/pull/5411))
406+- Allow configuring whether to insert or replace completions ([#5728](https://github.com/helix-editor/helix/pull/5728))
407+- Allow per-workspace config file `.helix/config.toml` ([#5748](https://github.com/helix-editor/helix/pull/5748))
408+- Add `workspace-lsp-roots` config option to support multiple LSP roots for use with monorepos ([#5748](https://github.com/helix-editor/helix/pull/5748))
409+
410+Commands:
411+
412+- `:pipe-to` which pipes selections into a shell command and ignores output ([#4931](https://github.com/helix-editor/helix/pull/4931))
413+- `merge_consecutive_selections` (`A-_`) combines all consecutive selections ([#5047](https://github.com/helix-editor/helix/pull/5047))
414+- `rotate_view_reverse` which focuses the previous view ([#5356](https://github.com/helix-editor/helix/pull/5356))
415+- `goto_declaration` (`gD`, requires LSP) which jumps to a symbol's declaration ([#5646](https://github.com/helix-editor/helix/pull/5646))
416+- `file_picker_in_current_buffer_directory` ([#4666](https://github.com/helix-editor/helix/pull/4666))
417+- `:character-info` which shows information about the character under the cursor ([#4000](https://github.com/helix-editor/helix/pull/4000))
418+- `:toggle-option` for toggling config options at runtime ([#4085](https://github.com/helix-editor/helix/pull/4085))
419+- `dap_restart` for restarting a debug session in DAP ([#5651](https://github.com/helix-editor/helix/pull/5651))
420+- `:lsp-stop` to stop the language server of the current buffer ([#5964](https://github.com/helix-editor/helix/pull/5964))
421+- `:reset-diff-change` for resetting a diff hunk to its original text ([#4974](https://github.com/helix-editor/helix/pull/4974))
422+- `:config-open-workspace` for opening the config file local to the current workspace ([#5748](https://github.com/helix-editor/helix/pull/5748))
423+
424+Usability improvements:
425+
426+- Remove empty detail section in completion menu when LSP doesn't send details ([#4902](https://github.com/helix-editor/helix/pull/4902))
427+- Pass client information on LSP initialization ([#4904](https://github.com/helix-editor/helix/pull/4904))
428+- Allow specifying environment variables for language servers in language config ([#4004](https://github.com/helix-editor/helix/pull/4004))
429+- Allow detached git worktrees to be recognized as root paths ([#5097](https://github.com/helix-editor/helix/pull/5097))
430+- Improve error message handling for theme loading failures ([#5073](https://github.com/helix-editor/helix/pull/5073))
431+- Print the names of binaries required for LSP/DAP in health-check ([#5195](https://github.com/helix-editor/helix/pull/5195))
432+- Improve sorting in the picker in cases of ties ([#5169](https://github.com/helix-editor/helix/pull/5169))
433+- Add theming for prompt suggestions ([#5104](https://github.com/helix-editor/helix/pull/5104))
434+- Open a file picker when using `:open` on directories ([#2707](https://github.com/helix-editor/helix/pull/2707), [#5278](https://github.com/helix-editor/helix/pull/5278))
435+- Reload language config with `:config-reload` ([#5239](https://github.com/helix-editor/helix/pull/5239), [#5381](https://github.com/helix-editor/helix/pull/5381), [#5431](https://github.com/helix-editor/helix/pull/5431))
436+- Improve indent queries for python when the tree is errored ([#5332](https://github.com/helix-editor/helix/pull/5332))
437+- Picker: Open files without closing the picker with `A-ret` ([#4435](https://github.com/helix-editor/helix/pull/4435))
438+- Allow theming cursors by primary/secondary and by mode ([#5130](https://github.com/helix-editor/helix/pull/5130))
439+- Allow configuration of the minimum width for the line-numbers gutter ([#4724](https://github.com/helix-editor/helix/pull/4724), [#5696](https://github.com/helix-editor/helix/pull/5696))
440+- Use filename completer for `:run-shell-command` command ([#5729](https://github.com/helix-editor/helix/pull/5729))
441+- Surround with line-endings with `ms<ret>` ([#4571](https://github.com/helix-editor/helix/pull/4571))
442+- Hide duplicate symlinks in file pickers ([#5658](https://github.com/helix-editor/helix/pull/5658))
443+- Tabulate buffer picker contents ([#5777](https://github.com/helix-editor/helix/pull/5777))
444+- Add an option to disable LSP ([#4425](https://github.com/helix-editor/helix/pull/4425))
445+- Short-circuit tree-sitter and word object motions ([#5851](https://github.com/helix-editor/helix/pull/5851))
446+- Add exit code to failed command message ([#5898](https://github.com/helix-editor/helix/pull/5898))
447+- Make `m` textobject look for pairs enclosing selections ([#3344](https://github.com/helix-editor/helix/pull/3344))
448+- Negotiate LSP position encoding ([#5894](https://github.com/helix-editor/helix/pull/5894), [a48d1a4](https://github.com/helix-editor/helix/commit/a48d1a4))
449+- Display deprecated LSP completions with strikethrough ([#5932](https://github.com/helix-editor/helix/pull/5932))
450+- Add JSONRPC request ID to failed LSP/DAP request log messages ([#6010](https://github.com/helix-editor/helix/pull/6010), [#6018](https://github.com/helix-editor/helix/pull/6018))
451+- Ignore case when filtering LSP completions ([#6008](https://github.com/helix-editor/helix/pull/6008))
452+- Show current language when no arguments are passed to `:set-language` ([#5895](https://github.com/helix-editor/helix/pull/5895))
453+- Refactor and rewrite all book documentation ([#5534](https://github.com/helix-editor/helix/pull/5534))
454+- Separate diagnostic picker message and code ([#6095](https://github.com/helix-editor/helix/pull/6095))
455+- Add a config option to bypass undercurl detection ([#6253](https://github.com/helix-editor/helix/pull/6253))
456+- Only complete appropriate arguments for typed commands ([#5966](https://github.com/helix-editor/helix/pull/5966))
457+- Discard outdated LSP diagnostics ([3c9d5d0](https://github.com/helix-editor/helix/commit/3c9d5d0))
458+- Discard outdated LSP workspace edits ([b6a4927](https://github.com/helix-editor/helix/commit/b6a4927))
459+- Run shell commands asynchronously ([#6373](https://github.com/helix-editor/helix/pull/6373))
460+- Show diagnostic codes in LSP diagnostic messages ([#6378](https://github.com/helix-editor/helix/pull/6378))
461+- Highlight the current line in a DAP debug session ([#5957](https://github.com/helix-editor/helix/pull/5957))
462+- Hide signature help if it overlaps with the completion menu ([#5523](https://github.com/helix-editor/helix/pull/5523), [7a69c40](https://github.com/helix-editor/helix/commit/7a69c40))
463+
464+Fixes:
465+
466+- Fix behavior of `auto-completion` flag for completion-on-trigger ([#5042](https://github.com/helix-editor/helix/pull/5042))
467+- Reset editor mode when changing buffers ([#5072](https://github.com/helix-editor/helix/pull/5072))
468+- Respect scrolloff settings in mouse movements ([#5255](https://github.com/helix-editor/helix/pull/5255))
469+- Avoid trailing `s` when only one file is opened ([#5189](https://github.com/helix-editor/helix/pull/5189))
470+- Fix erroneous indent between closers of auto-pairs ([#5330](https://github.com/helix-editor/helix/pull/5330))
471+- Expand `~` when parsing file paths in `:open` ([#5329](https://github.com/helix-editor/helix/pull/5329))
472+- Fix theme inheritance for default themes ([#5218](https://github.com/helix-editor/helix/pull/5218))
473+- Fix `extend_line` with a count when the current line(s) are selected ([#5288](https://github.com/helix-editor/helix/pull/5288))
474+- Prompt: Fix autocompletion for paths containing periods ([#5175](https://github.com/helix-editor/helix/pull/5175))
475+- Skip serializing JSONRPC params if params is null ([#5471](https://github.com/helix-editor/helix/pull/5471))
476+- Fix interaction with the `xclip` clipboard provider ([#5426](https://github.com/helix-editor/helix/pull/5426))
477+- Fix undo/redo execution from the command palette ([#5294](https://github.com/helix-editor/helix/pull/5294))
478+- Fix highlighting of non-block cursors ([#5575](https://github.com/helix-editor/helix/pull/5575))
479+- Fix panic when nooping in `join_selections` and `join_selections_space` ([#5423](https://github.com/helix-editor/helix/pull/5423))
480+- Fix selecting a changed file in global search ([#5639](https://github.com/helix-editor/helix/pull/5639))
481+- Fix initial syntax highlight layer sort order ([#5196](https://github.com/helix-editor/helix/pull/5196))
482+- Fix UTF-8 length handling for shellwords ([#5738](https://github.com/helix-editor/helix/pull/5738))
483+- Remove C-j and C-k bindings from the completion menu ([#5070](https://github.com/helix-editor/helix/pull/5070))
484+- Always commit to history when pasting ([#5790](https://github.com/helix-editor/helix/pull/5790))
485+- Properly handle LSP position encoding ([#5711](https://github.com/helix-editor/helix/pull/5711))
486+- Fix infinite loop in `copy_selection_on_prev_line` ([#5888](https://github.com/helix-editor/helix/pull/5888))
487+- Fix completion popup positioning ([#5842](https://github.com/helix-editor/helix/pull/5842))
488+- Fix a panic when uncommenting a line with only a comment token ([#5933](https://github.com/helix-editor/helix/pull/5933))
489+- Fix panic in `goto_window_center` at EOF ([#5987](https://github.com/helix-editor/helix/pull/5987))
490+- Ignore invalid file URIs sent by a language server ([#6000](https://github.com/helix-editor/helix/pull/6000))
491+- Decode LSP URIs for the workspace diagnostics picker ([#6016](https://github.com/helix-editor/helix/pull/6016))
492+- Fix incorrect usages of `tab_width` with `indent_width` ([#5918](https://github.com/helix-editor/helix/pull/5918))
493+- DAP: Send Disconnect if the Terminated event is received ([#5532](https://github.com/helix-editor/helix/pull/5532))
494+- DAP: Validate key and index exist when requesting variables ([#5628](https://github.com/helix-editor/helix/pull/5628))
495+- Check LSP renaming support before prompting for rename text ([#6257](https://github.com/helix-editor/helix/pull/6257))
496+- Fix indent guide rendering ([#6136](https://github.com/helix-editor/helix/pull/6136))
497+- Fix division by zero panic ([#6155](https://github.com/helix-editor/helix/pull/6155))
498+- Fix lacking space panic ([#6109](https://github.com/helix-editor/helix/pull/6109))
499+- Send error replies for malformed and unhandled LSP requests ([#6058](https://github.com/helix-editor/helix/pull/6058))
500+- Fix table column calculations for dynamic pickers ([#5920](https://github.com/helix-editor/helix/pull/5920))
501+- Skip adding jumplist entries for `:<n>` line number previews ([#5751](https://github.com/helix-editor/helix/pull/5751))
502+- Fix completion race conditions ([#6173](https://github.com/helix-editor/helix/pull/6173))
503+- Fix `shrink_selection` with multiple cursors ([#6093](https://github.com/helix-editor/helix/pull/6093))
504+- Fix indentation calculation for lines with mixed tabs/spaces ([#6278](https://github.com/helix-editor/helix/pull/6278))
505+- No-op `client/registerCapability` LSP requests ([#6258](https://github.com/helix-editor/helix/pull/6258))
506+- Send the STOP signal to all processes in the process group ([#3546](https://github.com/helix-editor/helix/pull/3546))
507+- Fix workspace edit client capabilities declaration ([7bf168d](https://github.com/helix-editor/helix/commit/7bf168d))
508+- Fix highlighting in picker results with multiple columns ([#6333](https://github.com/helix-editor/helix/pull/6333))
509+- Canonicalize paths before stripping the current dir as a prefix ([#6290](https://github.com/helix-editor/helix/pull/6290))
510+- Fix truncation behavior for long path names in the file picker ([#6410](https://github.com/helix-editor/helix/pull/6410), [67783dd](https://github.com/helix-editor/helix/commit/67783dd))
511+- Fix theme reloading behavior in `:config-reload` ([ab819d8](https://github.com/helix-editor/helix/commit/ab819d8))
512+
513+Themes:
514+
515+- Update `serika` ([#5038](https://github.com/helix-editor/helix/pull/5038), [#6344](https://github.com/helix-editor/helix/pull/6344))
516+- Update `flatwhite` ([#5036](https://github.com/helix-editor/helix/pull/5036), [#6323](https://github.com/helix-editor/helix/pull/6323))
517+- Update `autumn` ([#5051](https://github.com/helix-editor/helix/pull/5051), [#5397](https://github.com/helix-editor/helix/pull/5397), [#6280](https://github.com/helix-editor/helix/pull/6280), [#6316](https://github.com/helix-editor/helix/pull/6316))
518+- Update `acme` ([#5019](https://github.com/helix-editor/helix/pull/5019), [#5486](https://github.com/helix-editor/helix/pull/5486), [#5488](https://github.com/helix-editor/helix/pull/5488))
519+- Update `gruvbox` themes ([#5066](https://github.com/helix-editor/helix/pull/5066), [#5333](https://github.com/helix-editor/helix/pull/5333), [#5540](https://github.com/helix-editor/helix/pull/5540), [#6285](https://github.com/helix-editor/helix/pull/6285), [#6295](https://github.com/helix-editor/helix/pull/6295))
520+- Update `base16_transparent` ([#5105](https://github.com/helix-editor/helix/pull/5105))
521+- Update `dark_high_contrast` ([#5105](https://github.com/helix-editor/helix/pull/5105))
522+- Update `dracula` ([#5236](https://github.com/helix-editor/helix/pull/5236), [#5627](https://github.com/helix-editor/helix/pull/5627), [#6414](https://github.com/helix-editor/helix/pull/6414))
523+- Update `monokai_pro_spectrum` ([#5250](https://github.com/helix-editor/helix/pull/5250), [#5602](https://github.com/helix-editor/helix/pull/5602))
524+- Update `rose_pine` ([#5267](https://github.com/helix-editor/helix/pull/5267), [#5489](https://github.com/helix-editor/helix/pull/5489), [#6384](https://github.com/helix-editor/helix/pull/6384))
525+- Update `kanagawa` ([#5273](https://github.com/helix-editor/helix/pull/5273), [#5571](https://github.com/helix-editor/helix/pull/5571), [#6085](https://github.com/helix-editor/helix/pull/6085))
526+- Update `emacs` ([#5334](https://github.com/helix-editor/helix/pull/5334))
527+- Add `github` themes ([#5353](https://github.com/helix-editor/helix/pull/5353), [efeec12](https://github.com/helix-editor/helix/commit/efeec12))
528+ - Dark themes: `github_dark`, `github_dark_colorblind`, `github_dark_dimmed`, `github_dark_high_contrast`, `github_dark_tritanopia`
529+ - Light themes: `github_light`, `github_light_colorblind`, `github_light_dimmed`, `github_light_high_contrast`, `github_light_tritanopia`
530+- Update `solarized` variants ([#5445](https://github.com/helix-editor/helix/pull/5445), [#6327](https://github.com/helix-editor/helix/pull/6327))
531+- Update `catppuccin` variants ([#5404](https://github.com/helix-editor/helix/pull/5404), [#6107](https://github.com/helix-editor/helix/pull/6107), [#6269](https://github.com/helix-editor/helix/pull/6269), [#6464](https://github.com/helix-editor/helix/pull/6464))
532+- Use curly underlines in built-in themes ([#5419](https://github.com/helix-editor/helix/pull/5419))
533+- Update `zenburn` ([#5573](https://github.com/helix-editor/helix/pull/5573))
534+- Rewrite `snazzy` ([#3971](https://github.com/helix-editor/helix/pull/3971))
535+- Add `monokai_aqua` ([#5578](https://github.com/helix-editor/helix/pull/5578))
536+- Add `markup.strikethrough` to existing themes ([#5619](https://github.com/helix-editor/helix/pull/5619))
537+- Update `sonokai` ([#5440](https://github.com/helix-editor/helix/pull/5440))
538+- Update `onedark` ([#5755](https://github.com/helix-editor/helix/pull/5755))
539+- Add `ayu_evolve` ([#5638](https://github.com/helix-editor/helix/pull/5638), [#6028](https://github.com/helix-editor/helix/pull/6028), [#6225](https://github.com/helix-editor/helix/pull/6225))
540+- Add `jellybeans` ([#5719](https://github.com/helix-editor/helix/pull/5719))
541+- Update `fleet_dark` ([#5605](https://github.com/helix-editor/helix/pull/5605), [#6266](https://github.com/helix-editor/helix/pull/6266), [#6324](https://github.com/helix-editor/helix/pull/6324), [#6375](https://github.com/helix-editor/helix/pull/6375))
542+- Add `darcula-solid` ([#5778](https://github.com/helix-editor/helix/pull/5778))
543+- Remove text background from monokai themes ([#6009](https://github.com/helix-editor/helix/pull/6009))
544+- Update `pop_dark` ([#5992](https://github.com/helix-editor/helix/pull/5992), [#6208](https://github.com/helix-editor/helix/pull/6208), [#6227](https://github.com/helix-editor/helix/pull/6227), [#6292](https://github.com/helix-editor/helix/pull/6292))
545+- Add `everblush` ([#6086](https://github.com/helix-editor/helix/pull/6086))
546+- Add `adwaita-dark` ([#6042](https://github.com/helix-editor/helix/pull/6042), [#6342](https://github.com/helix-editor/helix/pull/6342))
547+- Update `papercolor` ([#6162](https://github.com/helix-editor/helix/pull/6162))
548+- Update `onelight` ([#6192](https://github.com/helix-editor/helix/pull/6192), [#6276](https://github.com/helix-editor/helix/pull/6276))
549+- Add `molokai` ([#6260](https://github.com/helix-editor/helix/pull/6260))
550+- Update `ayu` variants ([#6329](https://github.com/helix-editor/helix/pull/6329))
551+- Update `tokyonight` variants ([#6349](https://github.com/helix-editor/helix/pull/6349))
552+- Update `nord` variants ([#6376](https://github.com/helix-editor/helix/pull/6376))
553+
554+New languages:
555+
556+- BibTeX ([#5064](https://github.com/helix-editor/helix/pull/5064))
557+- Mermaid.js ([#5147](https://github.com/helix-editor/helix/pull/5147))
558+- Crystal ([#4993](https://github.com/helix-editor/helix/pull/4993), [#5205](https://github.com/helix-editor/helix/pull/5205))
559+- MATLAB/Octave ([#5192](https://github.com/helix-editor/helix/pull/5192))
560+- `tfvars` (uses HCL) ([#5396](https://github.com/helix-editor/helix/pull/5396))
561+- Ponylang ([#5416](https://github.com/helix-editor/helix/pull/5416))
562+- DHall ([1f6809c](https://github.com/helix-editor/helix/commit/1f6809c))
563+- Sagemath ([#5649](https://github.com/helix-editor/helix/pull/5649))
564+- MSBuild ([#5793](https://github.com/helix-editor/helix/pull/5793))
565+- pem ([#5797](https://github.com/helix-editor/helix/pull/5797))
566+- passwd ([#4959](https://github.com/helix-editor/helix/pull/4959))
567+- hosts ([#4950](https://github.com/helix-editor/helix/pull/4950), [#5914](https://github.com/helix-editor/helix/pull/5914))
568+- uxntal ([#6047](https://github.com/helix-editor/helix/pull/6047))
569+- Yuck ([#6064](https://github.com/helix-editor/helix/pull/6064), [#6242](https://github.com/helix-editor/helix/pull/6242))
570+- GNU gettext PO ([#5996](https://github.com/helix-editor/helix/pull/5996))
571+- Sway ([#6023](https://github.com/helix-editor/helix/pull/6023))
572+- NASM ([#6068](https://github.com/helix-editor/helix/pull/6068))
573+- PRQL ([#6126](https://github.com/helix-editor/helix/pull/6126))
574+- reStructuredText ([#6180](https://github.com/helix-editor/helix/pull/6180))
575+- Smithy ([#6370](https://github.com/helix-editor/helix/pull/6370))
576+- VHDL ([#5826](https://github.com/helix-editor/helix/pull/5826))
577+- Rego (OpenPolicy Agent) ([#6415](https://github.com/helix-editor/helix/pull/6415))
578+- Nim ([#6123](https://github.com/helix-editor/helix/pull/6123))
579+
580+Updated languages and queries:
581+
582+- Use diff syntax for patch files ([#5085](https://github.com/helix-editor/helix/pull/5085))
583+- Add Haskell textobjects ([#5061](https://github.com/helix-editor/helix/pull/5061))
584+- Fix commonlisp configuration ([#5091](https://github.com/helix-editor/helix/pull/5091))
585+- Update Scheme ([bae890d](https://github.com/helix-editor/helix/commit/bae890d))
586+- Add indent queries for Bash ([#5149](https://github.com/helix-editor/helix/pull/5149))
587+- Recognize `c++` as a C++ extension ([#5183](https://github.com/helix-editor/helix/pull/5183))
588+- Enable HTTP server in `metals` (Scala) config ([#5551](https://github.com/helix-editor/helix/pull/5551))
589+- Change V-lang language server to `v ls` from `vls` ([#5677](https://github.com/helix-editor/helix/pull/5677))
590+- Inject comment grammar into Nix ([#5208](https://github.com/helix-editor/helix/pull/5208))
591+- Update Rust highlights ([#5238](https://github.com/helix-editor/helix/pull/5238), [#5349](https://github.com/helix-editor/helix/pull/5349))
592+- Fix HTML injection within Markdown ([#5265](https://github.com/helix-editor/helix/pull/5265))
593+- Fix comment token for godot ([#5276](https://github.com/helix-editor/helix/pull/5276))
594+- Expand injections for Vue ([#5268](https://github.com/helix-editor/helix/pull/5268))
595+- Add `.bash_aliases` as a Bash file-type ([#5347](https://github.com/helix-editor/helix/pull/5347))
596+- Fix comment token for sshclientconfig ([#5351](https://github.com/helix-editor/helix/pull/5351))
597+- Update Prisma ([#5417](https://github.com/helix-editor/helix/pull/5417))
598+- Update C++ ([#5457](https://github.com/helix-editor/helix/pull/5457))
599+- Add more file-types for Python ([#5593](https://github.com/helix-editor/helix/pull/5593))
600+- Update tree-sitter-scala ([#5576](https://github.com/helix-editor/helix/pull/5576))
601+- Add an injection regex for Lua ([#5606](https://github.com/helix-editor/helix/pull/5606))
602+- Add `build.gradle` to java roots configuration ([#5641](https://github.com/helix-editor/helix/pull/5641))
603+- Add Hub PR files to markdown file-types ([#5634](https://github.com/helix-editor/helix/pull/5634))
604+- Add an external formatter configuration for Cue ([#5679](https://github.com/helix-editor/helix/pull/5679))
605+- Add injections for builders and writers to Nix ([#5629](https://github.com/helix-editor/helix/pull/5629))
606+- Update tree-sitter-xml to fix whitespace parsing ([#5685](https://github.com/helix-editor/helix/pull/5685))
607+- Add `Justfile` to the make file-types configuration ([#5687](https://github.com/helix-editor/helix/pull/5687))
608+- Update tree-sitter-sql and highlight queries ([#5683](https://github.com/helix-editor/helix/pull/5683), [#5772](https://github.com/helix-editor/helix/pull/5772))
609+- Use the bash grammar and queries for env language ([#5720](https://github.com/helix-editor/helix/pull/5720))
610+- Add podspec files to ruby file-types ([#5811](https://github.com/helix-editor/helix/pull/5811))
611+- Recognize `.C` and `.H` file-types as C++ ([#5808](https://github.com/helix-editor/helix/pull/5808))
612+- Recognize plist and mobileconfig files as XML ([#5863](https://github.com/helix-editor/helix/pull/5863))
613+- Fix `select` indentation in Go ([#5713](https://github.com/helix-editor/helix/pull/5713))
614+- Check for external file modifications when writing ([#5805](https://github.com/helix-editor/helix/pull/5805))
615+- Recognize containerfiles as dockerfile syntax ([#5873](https://github.com/helix-editor/helix/pull/5873))
616+- Update godot grammar and queries ([#5944](https://github.com/helix-editor/helix/pull/5944), [#6186](https://github.com/helix-editor/helix/pull/6186))
617+- Improve DHall highlights ([#5959](https://github.com/helix-editor/helix/pull/5959))
618+- Recognize `.env.dist` and `source.env` as env language ([#6003](https://github.com/helix-editor/helix/pull/6003))
619+- Update tree-sitter-git-rebase ([#6030](https://github.com/helix-editor/helix/pull/6030), [#6094](https://github.com/helix-editor/helix/pull/6094))
620+- Improve SQL highlights ([#6041](https://github.com/helix-editor/helix/pull/6041))
621+- Improve markdown highlights and inject LaTeX ([#6100](https://github.com/helix-editor/helix/pull/6100))
622+- Add textobject queries for Elm ([#6084](https://github.com/helix-editor/helix/pull/6084))
623+- Recognize graphql schema file type ([#6159](https://github.com/helix-editor/helix/pull/6159))
624+- Improve highlighting in comments ([#6143](https://github.com/helix-editor/helix/pull/6143))
625+- Improve highlighting for JavaScript/TypeScript/ECMAScript languages ([#6205](https://github.com/helix-editor/helix/pull/6205))
626+- Improve PHP highlights ([#6203](https://github.com/helix-editor/helix/pull/6203), [#6250](https://github.com/helix-editor/helix/pull/6250), [#6299](https://github.com/helix-editor/helix/pull/6299))
627+- Improve Go highlights ([#6204](https://github.com/helix-editor/helix/pull/6204))
628+- Highlight unchecked sqlx functions as SQL in Rust ([#6256](https://github.com/helix-editor/helix/pull/6256))
629+- Improve Erlang highlights ([cdd6c8d](https://github.com/helix-editor/helix/commit/cdd6c8d))
630+- Improve Nix highlights ([fb4d703](https://github.com/helix-editor/helix/commit/fb4d703))
631+- Improve gdscript highlights ([#6311](https://github.com/helix-editor/helix/pull/6311))
632+- Improve Vlang highlights ([#6279](https://github.com/helix-editor/helix/pull/6279))
633+- Improve Makefile highlights ([#6339](https://github.com/helix-editor/helix/pull/6339))
634+- Remove auto-pair for `'` in OCaml ([#6381](https://github.com/helix-editor/helix/pull/6381))
635+- Fix indents in switch statements in ECMA languages ([#6369](https://github.com/helix-editor/helix/pull/6369))
636+- Recognize xlb and storyboard file-types as XML ([#6407](https://github.com/helix-editor/helix/pull/6407))
637+- Recognize cts and mts file-types as TypeScript ([#6424](https://github.com/helix-editor/helix/pull/6424))
638+- Recognize SVG file-type as XML ([#6431](https://github.com/helix-editor/helix/pull/6431))
639+- Add theme scopes for (un)checked list item markup scopes ([#6434](https://github.com/helix-editor/helix/pull/6434))
640+- Update git commit grammar and add the comment textobject ([#6439](https://github.com/helix-editor/helix/pull/6439), [#6493](https://github.com/helix-editor/helix/pull/6493))
641+- Recognize ARB file-type as JSON ([#6452](https://github.com/helix-editor/helix/pull/6452))
642+- Inject markdown into markdown strings in Julia ([#6489](https://github.com/helix-editor/helix/pull/6489))
643+
644+Packaging:
645+
646+- Fix Nix flake devShell for darwin hosts ([#5368](https://github.com/helix-editor/helix/pull/5368))
647+- Add Appstream metadata file to `contrib/` ([#5643](https://github.com/helix-editor/helix/pull/5643))
648+- Increase the MSRV to 1.65 ([#5570](https://github.com/helix-editor/helix/pull/5570), [#6185](https://github.com/helix-editor/helix/pull/6185))
649+- Expose the Nix flake's `wrapper` ([#5994](https://github.com/helix-editor/helix/pull/5994))
650+
651+# 22.12 (2022-12-06)
652+
653+This is a great big release filled with changes from a 99 contributors. A big _thank you_ to you all!
654+
655+As usual, the following is a summary of each of the changes since the last release.
656+For the full log, check out the [git log](https://github.com/helix-editor/helix/compare/22.08.1..22.12).
657+
658+Breaking changes:
659+
660+- Remove readline-like navigation bindings from the default insert mode keymap ([e12690e](https://github.com/helix-editor/helix/commit/e12690e), [#3811](https://github.com/helix-editor/helix/pull/3811), [#3827](https://github.com/helix-editor/helix/pull/3827), [#3915](https://github.com/helix-editor/helix/pull/3915), [#4088](https://github.com/helix-editor/helix/pull/4088))
661+- Rename `append_to_line` as `insert_at_line_end` and `prepend_to_line` as `insert_at_line_start` ([#3753](https://github.com/helix-editor/helix/pull/3753))
662+- Swap diagnostic picker and debug mode bindings in the space keymap ([#4229](https://github.com/helix-editor/helix/pull/4229))
663+- Select newly inserted text on paste or from shell commands ([#4458](https://github.com/helix-editor/helix/pull/4458), [#4608](https://github.com/helix-editor/helix/pull/4608), [#4619](https://github.com/helix-editor/helix/pull/4619), [#4824](https://github.com/helix-editor/helix/pull/4824))
664+- Select newly inserted surrounding characters on `ms<char>` ([#4752](https://github.com/helix-editor/helix/pull/4752))
665+- Exit select-mode after executing `replace_*` commands ([#4554](https://github.com/helix-editor/helix/pull/4554))
666+- Exit select-mode after executing surround commands ([#4858](https://github.com/helix-editor/helix/pull/4858))
667+- Change tree-sitter text-object keys ([#3782](https://github.com/helix-editor/helix/pull/3782))
668+- Rename `fleetish` theme to `fleet_dark` ([#4997](https://github.com/helix-editor/helix/pull/4997))
669+
670+Features:
671+
672+- Bufferline ([#2759](https://github.com/helix-editor/helix/pull/2759))
673+- Support underline styles and colors ([#4061](https://github.com/helix-editor/helix/pull/4061), [98c121c](https://github.com/helix-editor/helix/commit/98c121c))
674+- Inheritance for themes ([#3067](https://github.com/helix-editor/helix/pull/3067), [#4096](https://github.com/helix-editor/helix/pull/4096))
675+- Cursorcolumn ([#4084](https://github.com/helix-editor/helix/pull/4084))
676+- Overhauled system for writing files and quiting ([#2267](https://github.com/helix-editor/helix/pull/2267), [#4397](https://github.com/helix-editor/helix/pull/4397))
677+- Autosave when terminal loses focus ([#3178](https://github.com/helix-editor/helix/pull/3178))
678+- Use OSC52 as a fallback for the system clipboard ([#3220](https://github.com/helix-editor/helix/pull/3220))
679+- Show git diffs in the gutter ([#3890](https://github.com/helix-editor/helix/pull/3890), [#5012](https://github.com/helix-editor/helix/pull/5012), [#4995](https://github.com/helix-editor/helix/pull/4995))
680+- Add a logo ([dc1ec56](https://github.com/helix-editor/helix/commit/dc1ec56))
681+- Multi-cursor completion ([#4496](https://github.com/helix-editor/helix/pull/4496))
682+
683+Commands:
684+
685+- `file_picker_in_current_directory` (`<space>F`) ([#3701](https://github.com/helix-editor/helix/pull/3701))
686+- `:lsp-restart` to restart the current document's language server ([#3435](https://github.com/helix-editor/helix/pull/3435), [#3972](https://github.com/helix-editor/helix/pull/3972))
687+- `join_selections_space` (`A-j`) which joins selections and selects the joining whitespace ([#3549](https://github.com/helix-editor/helix/pull/3549))
688+- `:update` to write the current file if it is modified ([#4426](https://github.com/helix-editor/helix/pull/4426))
689+- `:lsp-workspace-command` for picking LSP commands to execute ([#3140](https://github.com/helix-editor/helix/pull/3140))
690+- `extend_prev_word_end` - the extend variant for `move_prev_word_end` ([7468fa2](https://github.com/helix-editor/helix/commit/7468fa2))
691+- `make_search_word_bounded` which adds regex word boundaries to the current search register value ([#4322](https://github.com/helix-editor/helix/pull/4322))
692+- `:reload-all` - `:reload` for all open buffers ([#4663](https://github.com/helix-editor/helix/pull/4663), [#4901](https://github.com/helix-editor/helix/pull/4901))
693+- `goto_next_change` (`]g`), `goto_prev_change` (`[g`), `goto_first_change` (`[G`), `goto_last_change` (`]G`) textobjects for jumping between VCS changes ([#4650](https://github.com/helix-editor/helix/pull/4650))
694+
695+Usability improvements and fixes:
696+
697+- Don't log 'LSP not defined' errors in the logfile ([1caba2d](https://github.com/helix-editor/helix/commit/1caba2d))
698+- Look for the external formatter program before invoking it ([#3670](https://github.com/helix-editor/helix/pull/3670))
699+- Don't send LSP didOpen events for documents without URLs ([44b4479](https://github.com/helix-editor/helix/commit/44b4479))
700+- Fix off-by-one in `extend_line_above` command ([#3689](https://github.com/helix-editor/helix/pull/3689))
701+- Use the original scroll offset when opening a split ([1acdfaa](https://github.com/helix-editor/helix/commit/1acdfaa))
702+- Handle auto-formatting failures and save the file anyway ([#3684](https://github.com/helix-editor/helix/pull/3684))
703+- Ensure the cursor is in view after `:reflow` ([#3733](https://github.com/helix-editor/helix/pull/3733))
704+- Add default rulers and reflow config for git commit messages ([#3738](https://github.com/helix-editor/helix/pull/3738))
705+- Improve grammar fetching and building output ([#3773](https://github.com/helix-editor/helix/pull/3773))
706+- Add a `text` language to language completion ([cc47d3f](https://github.com/helix-editor/helix/commit/cc47d3f))
707+- Improve error handling for `:set-language` ([e8add6f](https://github.com/helix-editor/helix/commit/e8add6f))
708+- Improve error handling for `:config-reload` ([#3668](https://github.com/helix-editor/helix/pull/3668))
709+- Improve error handling when passing improper ranges to syntax highlighting ([#3826](https://github.com/helix-editor/helix/pull/3826))
710+- Render `<code>` tags as raw markup in markdown ([#3425](https://github.com/helix-editor/helix/pull/3425))
711+- Remove border around the LSP code-actions popup ([#3444](https://github.com/helix-editor/helix/pull/3444))
712+- Canonicalize the path to the runtime directory ([#3794](https://github.com/helix-editor/helix/pull/3794))
713+- Add a `themelint` xtask for linting themes ([#3234](https://github.com/helix-editor/helix/pull/3234))
714+- Re-sort LSP diagnostics after applying transactions ([#3895](https://github.com/helix-editor/helix/pull/3895), [#4319](https://github.com/helix-editor/helix/pull/4319))
715+- Add a command-line flag to specify the log file ([#3807](https://github.com/helix-editor/helix/pull/3807))
716+- Track source and tag information in LSP diagnostics ([#3898](https://github.com/helix-editor/helix/pull/3898), [1df32c9](https://github.com/helix-editor/helix/commit/1df32c9))
717+- Fix theme returning to normal when exiting the `:theme` completion ([#3644](https://github.com/helix-editor/helix/pull/3644))
718+- Improve error messages for invalid commands in the keymap ([#3931](https://github.com/helix-editor/helix/pull/3931))
719+- Deduplicate regexs in `search_selection` command ([#3941](https://github.com/helix-editor/helix/pull/3941))
720+- Split the finding of LSP root and config roots ([#3929](https://github.com/helix-editor/helix/pull/3929))
721+- Ensure that the cursor is within view after auto-formatting ([#4047](https://github.com/helix-editor/helix/pull/4047))
722+- Add pseudo-pending to commands with on-next-key callbacks ([#4062](https://github.com/helix-editor/helix/pull/4062), [#4077](https://github.com/helix-editor/helix/pull/4077))
723+- Add live preview to `:goto` ([#2982](https://github.com/helix-editor/helix/pull/2982))
724+- Show regex compilation failure in a popup ([#3049](https://github.com/helix-editor/helix/pull/3049))
725+- Add 'cycled to end' and 'no more matches' for search ([#3176](https://github.com/helix-editor/helix/pull/3176), [#4101](https://github.com/helix-editor/helix/pull/4101))
726+- Add extending behavior to tree-sitter textobjects ([#3266](https://github.com/helix-editor/helix/pull/3266))
727+- Add `ui.gutter.selected` option for themes ([#3303](https://github.com/helix-editor/helix/pull/3303))
728+- Make statusline mode names configurable ([#3311](https://github.com/helix-editor/helix/pull/3311))
729+- Add a statusline element for total line count ([#3960](https://github.com/helix-editor/helix/pull/3960))
730+- Add extending behavior to `goto_window_*` commands ([#3985](https://github.com/helix-editor/helix/pull/3985))
731+- Fix a panic in signature help when the preview is too large ([#4030](https://github.com/helix-editor/helix/pull/4030))
732+- Add command names to the command palette ([#4071](https://github.com/helix-editor/helix/pull/4071), [#4223](https://github.com/helix-editor/helix/pull/4223), [#4495](https://github.com/helix-editor/helix/pull/4495))
733+- Find the LSP workspace root from the current document's path ([#3553](https://github.com/helix-editor/helix/pull/3553))
734+- Add an option to skip indent-guide levels ([#3819](https://github.com/helix-editor/helix/pull/3819), [2c36e33](https://github.com/helix-editor/helix/commit/2c36e33))
735+- Change focus to modified docs on quit ([#3872](https://github.com/helix-editor/helix/pull/3872))
736+- Respond to `USR1` signal by reloading config ([#3952](https://github.com/helix-editor/helix/pull/3952))
737+- Exit gracefully when the close operation fails ([#4081](https://github.com/helix-editor/helix/pull/4081))
738+- Fix goto/view center mismatch ([#4135](https://github.com/helix-editor/helix/pull/4135))
739+- Highlight the current file picker document on idle-timeout ([#3172](https://github.com/helix-editor/helix/pull/3172), [a85e386](https://github.com/helix-editor/helix/commit/a85e386))
740+- Apply transactions to jumplist selections ([#4186](https://github.com/helix-editor/helix/pull/4186), [#4227](https://github.com/helix-editor/helix/pull/4227), [#4733](https://github.com/helix-editor/helix/pull/4733), [#4865](https://github.com/helix-editor/helix/pull/4865), [#4912](https://github.com/helix-editor/helix/pull/4912), [#4965](https://github.com/helix-editor/helix/pull/4965), [#4981](https://github.com/helix-editor/helix/pull/4981))
741+- Use space as a separator for fuzzy matcher ([#3969](https://github.com/helix-editor/helix/pull/3969))
742+- Overlay all diagnostics with highest severity on top ([#4113](https://github.com/helix-editor/helix/pull/4113))
743+- Avoid re-parsing unmodified tree-sitter injections ([#4146](https://github.com/helix-editor/helix/pull/4146))
744+- Add extending captures for indentation, re-enable python indentation ([#3382](https://github.com/helix-editor/helix/pull/3382), [3e84434](https://github.com/helix-editor/helix/commit/3e84434))
745+- Only allow either `--vsplit` or `--hsplit` CLI flags at once ([#4202](https://github.com/helix-editor/helix/pull/4202))
746+- Fix append cursor location when selection anchor is at the end of the document ([#4147](https://github.com/helix-editor/helix/pull/4147))
747+- Improve selection yanking message ([#4275](https://github.com/helix-editor/helix/pull/4275))
748+- Log failures to load tree-sitter grammars as errors ([#4315](https://github.com/helix-editor/helix/pull/4315))
749+- Fix rendering of lines longer than 65,536 columns ([#4172](https://github.com/helix-editor/helix/pull/4172))
750+- Skip searching `.git` in `global_search` ([#4334](https://github.com/helix-editor/helix/pull/4334))
751+- Display tree-sitter scopes in a popup ([#4337](https://github.com/helix-editor/helix/pull/4337))
752+- Fix deleting a word from the end of the buffer ([#4328](https://github.com/helix-editor/helix/pull/4328))
753+- Pretty print the syntax tree in `:tree-sitter-subtree` ([#4295](https://github.com/helix-editor/helix/pull/4295), [#4606](https://github.com/helix-editor/helix/pull/4606))
754+- Allow specifying suffixes for file-type detection ([#2455](https://github.com/helix-editor/helix/pull/2455), [#4414](https://github.com/helix-editor/helix/pull/4414))
755+- Fix multi-byte auto-pairs ([#4024](https://github.com/helix-editor/helix/pull/4024))
756+- Improve sort scoring for LSP code-actions and completions ([#4134](https://github.com/helix-editor/helix/pull/4134))
757+- Fix the handling of quotes within shellwords ([#4098](https://github.com/helix-editor/helix/pull/4098))
758+- Fix `delete_word_backward` and `delete_word_forward` on newlines ([#4392](https://github.com/helix-editor/helix/pull/4392))
759+- Fix 'no entry found for key' crash on `:write-all` ([#4384](https://github.com/helix-editor/helix/pull/4384))
760+- Remove lowercase requirement for tree-sitter grammars ([#4346](https://github.com/helix-editor/helix/pull/4346))
761+- Resolve LSP completion items on idle-timeout ([#4406](https://github.com/helix-editor/helix/pull/4406), [#4797](https://github.com/helix-editor/helix/pull/4797))
762+- Render diagnostics in the file picker preview ([#4324](https://github.com/helix-editor/helix/pull/4324))
763+- Fix terminal freezing on `shell_insert_output` ([#4156](https://github.com/helix-editor/helix/pull/4156))
764+- Allow use of the count in the repeat operator (`.`) ([#4450](https://github.com/helix-editor/helix/pull/4450))
765+- Show the current theme name on `:theme` with no arguments ([#3740](https://github.com/helix-editor/helix/pull/3740))
766+- Fix rendering in very large terminals ([#4318](https://github.com/helix-editor/helix/pull/4318))
767+- Sort LSP preselected items to the top of the completion menu ([#4480](https://github.com/helix-editor/helix/pull/4480))
768+- Trim braces and quotes from paths in goto-file ([#4370](https://github.com/helix-editor/helix/pull/4370))
769+- Prevent automatic signature help outside of insert mode ([#4456](https://github.com/helix-editor/helix/pull/4456))
770+- Fix freezes with external programs that process stdin and stdout concurrently ([#4180](https://github.com/helix-editor/helix/pull/4180))
771+- Make `scroll` aware of tabs and wide characters ([#4519](https://github.com/helix-editor/helix/pull/4519))
772+- Correctly handle escaping in `command_mode` completion ([#4316](https://github.com/helix-editor/helix/pull/4316), [#4587](https://github.com/helix-editor/helix/pull/4587), [#4632](https://github.com/helix-editor/helix/pull/4632))
773+- Fix `delete_char_backward` for paired characters ([#4558](https://github.com/helix-editor/helix/pull/4558))
774+- Fix crash from two windows editing the same document ([#4570](https://github.com/helix-editor/helix/pull/4570))
775+- Fix pasting from the blackhole register ([#4497](https://github.com/helix-editor/helix/pull/4497))
776+- Support LSP insertReplace completion items ([1312682](https://github.com/helix-editor/helix/commit/1312682))
777+- Dynamically resize the line number gutter width ([#3469](https://github.com/helix-editor/helix/pull/3469))
778+- Fix crash for unknown completion item kinds ([#4658](https://github.com/helix-editor/helix/pull/4658))
779+- Re-enable `format_selections` for single selection ranges ([d4f5cab](https://github.com/helix-editor/helix/commit/d4f5cab))
780+- Limit the number of in-progress tree-sitter query matches ([#4707](https://github.com/helix-editor/helix/pull/4707), [#4830](https://github.com/helix-editor/helix/pull/4830))
781+- Use the special `#` register with `increment`/`decrement` to change by range number ([#4418](https://github.com/helix-editor/helix/pull/4418))
782+- Add a statusline element to show number of selected chars ([#4682](https://github.com/helix-editor/helix/pull/4682))
783+- Add a statusline element showing global LSP diagnostic warning and error counts ([#4569](https://github.com/helix-editor/helix/pull/4569))
784+- Add a scrollbar to popups ([#4449](https://github.com/helix-editor/helix/pull/4449))
785+- Prefer shorter matches in fuzzy matcher scoring ([#4698](https://github.com/helix-editor/helix/pull/4698))
786+- Use key-sequence format for command palette keybinds ([#4712](https://github.com/helix-editor/helix/pull/4712))
787+- Remove prefix filtering from autocompletion menu ([#4578](https://github.com/helix-editor/helix/pull/4578))
788+- Focus on the parent buffer when closing a split ([#4766](https://github.com/helix-editor/helix/pull/4766))
789+- Handle language server termination ([#4797](https://github.com/helix-editor/helix/pull/4797), [#4852](https://github.com/helix-editor/helix/pull/4852))
790+- Allow `r`/`t`/`f` to work on tab characters ([#4817](https://github.com/helix-editor/helix/pull/4817))
791+- Show a preview for scratch buffers in the buffer picker ([#3454](https://github.com/helix-editor/helix/pull/3454))
792+- Set a limit of entries in the jumplist ([#4750](https://github.com/helix-editor/helix/pull/4750))
793+- Re-use shell outputs when inserting or appending shell output ([#3465](https://github.com/helix-editor/helix/pull/3465))
794+- Check LSP server provider capabilities ([#3554](https://github.com/helix-editor/helix/pull/3554))
795+- Improve tree-sitter parsing performance on files with many language layers ([#4716](https://github.com/helix-editor/helix/pull/4716))
796+- Move indentation to the next line when using `<ret>` on a line with only whitespace ([#4854](https://github.com/helix-editor/helix/pull/4854))
797+- Remove selections for closed views from all documents ([#4888](https://github.com/helix-editor/helix/pull/4888))
798+- Improve performance of the `:reload` command ([#4457](https://github.com/helix-editor/helix/pull/4457))
799+- Properly handle media keys ([#4887](https://github.com/helix-editor/helix/pull/4887))
800+- Support LSP diagnostic data field ([#4935](https://github.com/helix-editor/helix/pull/4935))
801+- Handle C-i keycode as tab ([#4961](https://github.com/helix-editor/helix/pull/4961))
802+- Fix view alignment for jumplist picker jumps ([#3743](https://github.com/helix-editor/helix/pull/3743))
803+- Use OSC52 for tmux clipboard provider ([#5027](https://github.com/helix-editor/helix/pull/5027))
804+
805+Themes:
806+
807+- Add `varua` ([#3610](https://github.com/helix-editor/helix/pull/3610), [#4964](https://github.com/helix-editor/helix/pull/4964))
808+- Update `boo_berry` ([#3653](https://github.com/helix-editor/helix/pull/3653))
809+- Add `rasmus` ([#3728](https://github.com/helix-editor/helix/pull/3728))
810+- Add `papercolor_dark` ([#3742](https://github.com/helix-editor/helix/pull/3742))
811+- Update `monokai_pro_spectrum` ([#3814](https://github.com/helix-editor/helix/pull/3814))
812+- Update `nord` ([#3792](https://github.com/helix-editor/helix/pull/3792))
813+- Update `fleetish` ([#3844](https://github.com/helix-editor/helix/pull/3844), [#4487](https://github.com/helix-editor/helix/pull/4487), [#4813](https://github.com/helix-editor/helix/pull/4813))
814+- Update `flatwhite` ([#3843](https://github.com/helix-editor/helix/pull/3843))
815+- Add `darcula` ([#3739](https://github.com/helix-editor/helix/pull/3739))
816+- Update `papercolor` ([#3938](https://github.com/helix-editor/helix/pull/3938), [#4317](https://github.com/helix-editor/helix/pull/4317))
817+- Add bufferline colors to multiple themes ([#3881](https://github.com/helix-editor/helix/pull/3881))
818+- Add `gruvbox_dark_hard` ([#3948](https://github.com/helix-editor/helix/pull/3948))
819+- Add `onedarker` ([#3980](https://github.com/helix-editor/helix/pull/3980), [#4060](https://github.com/helix-editor/helix/pull/4060))
820+- Add `dark_high_contrast` ([#3312](https://github.com/helix-editor/helix/pull/3312))
821+- Update `bogster` ([#4121](https://github.com/helix-editor/helix/pull/4121), [#4264](https://github.com/helix-editor/helix/pull/4264))
822+- Update `sonokai` ([#4089](https://github.com/helix-editor/helix/pull/4089))
823+- Update `ayu_*` themes ([#4140](https://github.com/helix-editor/helix/pull/4140), [#4109](https://github.com/helix-editor/helix/pull/4109), [#4662](https://github.com/helix-editor/helix/pull/4662), [#4764](https://github.com/helix-editor/helix/pull/4764))
824+- Update `everforest` ([#3998](https://github.com/helix-editor/helix/pull/3998))
825+- Update `monokai_pro_octagon` ([#4247](https://github.com/helix-editor/helix/pull/4247))
826+- Add `heisenberg` ([#4209](https://github.com/helix-editor/helix/pull/4209))
827+- Add `bogster_light` ([#4265](https://github.com/helix-editor/helix/pull/4265))
828+- Update `pop-dark` ([#4323](https://github.com/helix-editor/helix/pull/4323))
829+- Update `rose_pine` ([#4221](https://github.com/helix-editor/helix/pull/4221))
830+- Add `kanagawa` ([#4300](https://github.com/helix-editor/helix/pull/4300))
831+- Add `hex_steel`, `hex_toxic` and `hex_lavendar` ([#4367](https://github.com/helix-editor/helix/pull/4367), [#4990](https://github.com/helix-editor/helix/pull/4990))
832+- Update `tokyonight` and `tokyonight_storm` ([#4415](https://github.com/helix-editor/helix/pull/4415))
833+- Update `gruvbox` ([#4626](https://github.com/helix-editor/helix/pull/4626))
834+- Update `dark_plus` ([#4661](https://github.com/helix-editor/helix/pull/4661), [#4678](https://github.com/helix-editor/helix/pull/4678))
835+- Add `zenburn` ([#4613](https://github.com/helix-editor/helix/pull/4613), [#4977](https://github.com/helix-editor/helix/pull/4977))
836+- Update `monokai_pro` ([#4789](https://github.com/helix-editor/helix/pull/4789))
837+- Add `mellow` ([#4770](https://github.com/helix-editor/helix/pull/4770))
838+- Add `nightfox` ([#4769](https://github.com/helix-editor/helix/pull/4769), [#4966](https://github.com/helix-editor/helix/pull/4966))
839+- Update `doom_acario_dark` ([#4979](https://github.com/helix-editor/helix/pull/4979))
840+- Update `autumn` ([#4996](https://github.com/helix-editor/helix/pull/4996))
841+- Update `acme` ([#4999](https://github.com/helix-editor/helix/pull/4999))
842+- Update `nord_light` ([#4999](https://github.com/helix-editor/helix/pull/4999))
843+- Update `serika_*` ([#5015](https://github.com/helix-editor/helix/pull/5015))
844+
845+LSP configurations:
846+
847+- Switch to `openscad-lsp` for OpenScad ([#3750](https://github.com/helix-editor/helix/pull/3750))
848+- Support Jsonnet ([#3748](https://github.com/helix-editor/helix/pull/3748))
849+- Support Markdown ([#3499](https://github.com/helix-editor/helix/pull/3499))
850+- Support Bass ([#3771](https://github.com/helix-editor/helix/pull/3771))
851+- Set roots configuration for Elixir and HEEx ([#3917](https://github.com/helix-editor/helix/pull/3917), [#3959](https://github.com/helix-editor/helix/pull/3959))
852+- Support Purescript ([#4242](https://github.com/helix-editor/helix/pull/4242))
853+- Set roots configuration for Julia ([#4361](https://github.com/helix-editor/helix/pull/4361))
854+- Support D ([#4372](https://github.com/helix-editor/helix/pull/4372))
855+- Increase default language server timeout for Julia ([#4575](https://github.com/helix-editor/helix/pull/4575))
856+- Use ElixirLS for HEEx ([#4679](https://github.com/helix-editor/helix/pull/4679))
857+- Support Bicep ([#4403](https://github.com/helix-editor/helix/pull/4403))
858+- Switch to `nil` for Nix ([433ccef](https://github.com/helix-editor/helix/commit/433ccef))
859+- Support QML ([#4842](https://github.com/helix-editor/helix/pull/4842))
860+- Enable auto-format for CSS ([#4987](https://github.com/helix-editor/helix/pull/4987))
861+- Support CommonLisp ([4176769](https://github.com/helix-editor/helix/commit/4176769))
862+
863+New languages:
864+
865+- SML ([#3692](https://github.com/helix-editor/helix/pull/3692))
866+- Jsonnet ([#3714](https://github.com/helix-editor/helix/pull/3714))
867+- Godot resource ([#3759](https://github.com/helix-editor/helix/pull/3759))
868+- Astro ([#3829](https://github.com/helix-editor/helix/pull/3829))
869+- SSH config ([#2455](https://github.com/helix-editor/helix/pull/2455), [#4538](https://github.com/helix-editor/helix/pull/4538))
870+- Bass ([#3771](https://github.com/helix-editor/helix/pull/3771))
871+- WAT (WebAssembly text format) ([#4040](https://github.com/helix-editor/helix/pull/4040), [#4542](https://github.com/helix-editor/helix/pull/4542))
872+- Purescript ([#4242](https://github.com/helix-editor/helix/pull/4242))
873+- D ([#4372](https://github.com/helix-editor/helix/pull/4372), [#4562](https://github.com/helix-editor/helix/pull/4562))
874+- VHS ([#4486](https://github.com/helix-editor/helix/pull/4486))
875+- KDL ([#4481](https://github.com/helix-editor/helix/pull/4481))
876+- XML ([#4518](https://github.com/helix-editor/helix/pull/4518))
877+- WIT ([#4525](https://github.com/helix-editor/helix/pull/4525))
878+- ENV ([#4536](https://github.com/helix-editor/helix/pull/4536))
879+- INI ([#4538](https://github.com/helix-editor/helix/pull/4538))
880+- Bicep ([#4403](https://github.com/helix-editor/helix/pull/4403), [#4751](https://github.com/helix-editor/helix/pull/4751))
881+- QML ([#4842](https://github.com/helix-editor/helix/pull/4842))
882+- CommonLisp ([4176769](https://github.com/helix-editor/helix/commit/4176769))
883+
884+Updated languages and queries:
885+
886+- Zig ([#3621](https://github.com/helix-editor/helix/pull/3621), [#4745](https://github.com/helix-editor/helix/pull/4745))
887+- Rust ([#3647](https://github.com/helix-editor/helix/pull/3647), [#3729](https://github.com/helix-editor/helix/pull/3729), [#3927](https://github.com/helix-editor/helix/pull/3927), [#4073](https://github.com/helix-editor/helix/pull/4073), [#4510](https://github.com/helix-editor/helix/pull/4510), [#4659](https://github.com/helix-editor/helix/pull/4659), [#4717](https://github.com/helix-editor/helix/pull/4717))
888+- Solidity ([20ed8c2](https://github.com/helix-editor/helix/commit/20ed8c2))
889+- Fish ([#3704](https://github.com/helix-editor/helix/pull/3704))
890+- Elixir ([#3645](https://github.com/helix-editor/helix/pull/3645), [#4333](https://github.com/helix-editor/helix/pull/4333), [#4821](https://github.com/helix-editor/helix/pull/4821))
891+- Diff ([#3708](https://github.com/helix-editor/helix/pull/3708))
892+- Nix ([665e27f](https://github.com/helix-editor/helix/commit/665e27f), [1fe3273](https://github.com/helix-editor/helix/commit/1fe3273))
893+- Markdown ([#3749](https://github.com/helix-editor/helix/pull/3749), [#4078](https://github.com/helix-editor/helix/pull/4078), [#4483](https://github.com/helix-editor/helix/pull/4483), [#4478](https://github.com/helix-editor/helix/pull/4478))
894+- GDScript ([#3760](https://github.com/helix-editor/helix/pull/3760))
895+- JSX and TSX ([#3853](https://github.com/helix-editor/helix/pull/3853), [#3973](https://github.com/helix-editor/helix/pull/3973))
896+- Ruby ([#3976](https://github.com/helix-editor/helix/pull/3976), [#4601](https://github.com/helix-editor/helix/pull/4601))
897+- R ([#4031](https://github.com/helix-editor/helix/pull/4031))
898+- WGSL ([#3996](https://github.com/helix-editor/helix/pull/3996), [#4079](https://github.com/helix-editor/helix/pull/4079))
899+- C# ([#4118](https://github.com/helix-editor/helix/pull/4118), [#4281](https://github.com/helix-editor/helix/pull/4281), [#4213](https://github.com/helix-editor/helix/pull/4213))
900+- Twig ([#4176](https://github.com/helix-editor/helix/pull/4176))
901+- Lua ([#3552](https://github.com/helix-editor/helix/pull/3552))
902+- C/C++ ([#4079](https://github.com/helix-editor/helix/pull/4079), [#4278](https://github.com/helix-editor/helix/pull/4278), [#4282](https://github.com/helix-editor/helix/pull/4282))
903+- Cairo ([17488f1](https://github.com/helix-editor/helix/commit/17488f1), [431f9c1](https://github.com/helix-editor/helix/commit/431f9c1), [09a6df1](https://github.com/helix-editor/helix/commit/09a6df1))
904+- Rescript ([#4356](https://github.com/helix-editor/helix/pull/4356))
905+- Zig ([#4409](https://github.com/helix-editor/helix/pull/4409))
906+- Scala ([#4353](https://github.com/helix-editor/helix/pull/4353), [#4697](https://github.com/helix-editor/helix/pull/4697), [#4701](https://github.com/helix-editor/helix/pull/4701))
907+- LaTeX ([#4528](https://github.com/helix-editor/helix/pull/4528), [#4922](https://github.com/helix-editor/helix/pull/4922))
908+- SQL ([#4529](https://github.com/helix-editor/helix/pull/4529))
909+- Python ([#4560](https://github.com/helix-editor/helix/pull/4560))
910+- Bash/Zsh ([#4582](https://github.com/helix-editor/helix/pull/4582))
911+- Nu ([#4583](https://github.com/helix-editor/helix/pull/4583))
912+- Julia ([#4588](https://github.com/helix-editor/helix/pull/4588))
913+- Typescript ([#4703](https://github.com/helix-editor/helix/pull/4703))
914+- Meson ([#4572](https://github.com/helix-editor/helix/pull/4572))
915+- Haskell ([#4800](https://github.com/helix-editor/helix/pull/4800))
916+- CMake ([#4809](https://github.com/helix-editor/helix/pull/4809))
917+- HTML ([#4829](https://github.com/helix-editor/helix/pull/4829), [#4881](https://github.com/helix-editor/helix/pull/4881))
918+- Java ([#4886](https://github.com/helix-editor/helix/pull/4886))
919+- Go ([#4906](https://github.com/helix-editor/helix/pull/4906), [#4969](https://github.com/helix-editor/helix/pull/4969), [#5010](https://github.com/helix-editor/helix/pull/5010))
920+- CSS ([#4882](https://github.com/helix-editor/helix/pull/4882))
921+- Racket ([#4915](https://github.com/helix-editor/helix/pull/4915))
922+- SCSS ([#5003](https://github.com/helix-editor/helix/pull/5003))
923+
924+Packaging:
925+
926+- Filter relevant source files in the Nix flake ([#3657](https://github.com/helix-editor/helix/pull/3657))
927+- Build a binary for `aarch64-linux` in the release CI ([038a91d](https://github.com/helix-editor/helix/commit/038a91d))
928+- Build an AppImage for `aarch64-linux` in the release CI ([b738031](https://github.com/helix-editor/helix/commit/b738031))
929+- Enable CI builds for `riscv64-linux` ([#3685](https://github.com/helix-editor/helix/pull/3685))
930+- Support preview releases in CI ([0090a2d](https://github.com/helix-editor/helix/commit/0090a2d))
931+- Strip binaries built in CI ([#3780](https://github.com/helix-editor/helix/pull/3780))
932+- Fix the development shell for the Nix Flake on `aarch64-darwin` ([#3810](https://github.com/helix-editor/helix/pull/3810))
933+- Raise the MSRV and create an MSRV policy ([#3896](https://github.com/helix-editor/helix/pull/3896), [#3913](https://github.com/helix-editor/helix/pull/3913), [#3961](https://github.com/helix-editor/helix/pull/3961))
934+- Fix Fish completions for `--config` and `--log` flags ([#3912](https://github.com/helix-editor/helix/pull/3912))
935+- Use builtin filenames option in Bash completion ([#4648](https://github.com/helix-editor/helix/pull/4648))
936+
1937# 22.08.1 (2022-09-01)
2938
3939This is a patch release that fixes a panic caused by closing splits or buffers. ([#3633](https://github.com/helix-editor/helix/pull/3633))
@@ -920,7 +1856,7 @@ to distinguish it in bug reports..
9201856
9211857- The `runtime/` directory is now properly detected on binary releases and
9221858 on cargo run. `~/.config/helix/runtime` can also be used.
923−- Registers can now be selected via " (for example `"ay`)
1859+- Registers can now be selected via " (for example, `"ay`)
9241860- Support for Nix files was added
9251861- Movement is now fully tested and matches Kakoune implementation
9261862- A per-file LSP symbol picker was added to space+s
Cargo.lock+1529−377
@@ -2,26 +2,55 @@
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+
14+[[package]]
15+name = "adler"
16+version = "1.0.2"
17+source = "registry+https://github.com/rust-lang/crates.io-index"
18+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19+
520[[package]]
621name = "ahash"
7−version = "0.7.6"
22+version = "0.8.6"
823source = "registry+https://github.com/rust-lang/crates.io-index"
9−checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
24+checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
1025dependencies = [
26+ "cfg-if",
1127 "getrandom",
1228 "once_cell",
1329 "version_check",
30+ "zerocopy",
1431]
1532
1633[[package]]
1734name = "aho-corasick"
18−version = "0.7.18"
35+version = "1.1.2"
1936source = "registry+https://github.com/rust-lang/crates.io-index"
20−checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
37+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
2138dependencies = [
2239 "memchr",
2340]
2441
42+[[package]]
43+name = "allocator-api2"
44+version = "0.2.14"
45+source = "registry+https://github.com/rust-lang/crates.io-index"
46+checksum = "c4f263788a35611fba42eb41ff811c5d0360c58b97402570312a350736e2542e"
47+
48+[[package]]
49+name = "android-tzdata"
50+version = "0.1.1"
51+source = "registry+https://github.com/rust-lang/crates.io-index"
52+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
53+
2554[[package]]
2655name = "android_system_properties"
2756version = "0.1.5"
@@ -33,15 +62,15 @@ dependencies = [
3362
3463[[package]]
3564name = "anyhow"
36−version = "1.0.66"
65+version = "1.0.76"
3766source = "registry+https://github.com/rust-lang/crates.io-index"
38−checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
67+checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355"
3968
4069[[package]]
4170name = "arc-swap"
42−version = "1.5.1"
71+version = "1.6.0"
4372source = "registry+https://github.com/rust-lang/crates.io-index"
44−checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164"
73+checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
4574
4675[[package]]
4776name = "autocfg"
@@ -49,40 +78,64 @@ version = "1.1.0"
4978source = "registry+https://github.com/rust-lang/crates.io-index"
5079checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
5180
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+
5296[[package]]
5397name = "bitflags"
5498version = "1.3.2"
5599source = "registry+https://github.com/rust-lang/crates.io-index"
56100checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
57101
102+[[package]]
103+name = "bitflags"
104+version = "2.4.1"
105+source = "registry+https://github.com/rust-lang/crates.io-index"
106+checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
107+
58108[[package]]
59109name = "bstr"
60−version = "0.2.17"
110+version = "1.8.0"
61111source = "registry+https://github.com/rust-lang/crates.io-index"
62−checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
112+checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
63113dependencies = [
64− "lazy_static",
65114 "memchr",
66115 "regex-automata",
116+ "serde",
67117]
68118
69119[[package]]
70−name = "bumpalo"
71−version = "3.11.0"
120+name = "btoi"
121+version = "0.4.3"
72122source = "registry+https://github.com/rust-lang/crates.io-index"
73−checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
123+checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad"
124+dependencies = [
125+ "num-traits",
126+]
74127
75128[[package]]
76−name = "bytecount"
77−version = "0.6.3"
129+name = "bumpalo"
130+version = "3.12.0"
78131source = "registry+https://github.com/rust-lang/crates.io-index"
79−checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
132+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
80133
81134[[package]]
82135name = "bytes"
83−version = "1.2.1"
136+version = "1.4.0"
84137source = "registry+https://github.com/rust-lang/crates.io-index"
85−checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
138+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
86139
87140[[package]]
88141name = "cassowary"
@@ -92,9 +145,12 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
92145
93146[[package]]
94147name = "cc"
95−version = "1.0.74"
148+version = "1.0.84"
96149source = "registry+https://github.com/rust-lang/crates.io-index"
97−checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574"
150+checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
151+dependencies = [
152+ "libc",
153+]
98154
99155[[package]]
100156name = "cfg-if"
@@ -115,25 +171,39 @@ dependencies = [
115171
116172[[package]]
117173name = "chrono"
118−version = "0.4.22"
174+version = "0.4.31"
119175source = "registry+https://github.com/rust-lang/crates.io-index"
120−checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
176+checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
121177dependencies = [
178+ "android-tzdata",
122179 "iana-time-zone",
123− "num-integer",
124180 "num-traits",
125− "winapi",
181+ "windows-targets 0.48.0",
126182]
127183
128184[[package]]
129185name = "clipboard-win"
130−version = "4.4.2"
186+version = "5.0.0"
131187source = "registry+https://github.com/rust-lang/crates.io-index"
132−checksum = "c4ab1b92798304eedc095b53942963240037c0516452cb11aeba709d420b2219"
188+checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
133189dependencies = [
134190 "error-code",
135− "str-buf",
136− "winapi",
191+]
192+
193+[[package]]
194+name = "clru"
195+version = "0.6.1"
196+source = "registry+https://github.com/rust-lang/crates.io-index"
197+checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807"
198+
199+[[package]]
200+name = "codespan-reporting"
201+version = "0.11.1"
202+source = "registry+https://github.com/rust-lang/crates.io-index"
203+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
204+dependencies = [
205+ "termcolor",
206+ "unicode-width",
137207]
138208
139209[[package]]
@@ -147,28 +217,77 @@ dependencies = [
147217
148218[[package]]
149219name = "core-foundation-sys"
220+version = "0.8.4"
221+source = "registry+https://github.com/rust-lang/crates.io-index"
222+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
223+
224+[[package]]
225+name = "cov-mark"
226+version = "1.1.0"
227+source = "registry+https://github.com/rust-lang/crates.io-index"
228+checksum = "9ffa3d3e0138386cd4361f63537765cac7ee40698028844635a54495a92f67f3"
229+
230+[[package]]
231+name = "crc32fast"
232+version = "1.3.2"
233+source = "registry+https://github.com/rust-lang/crates.io-index"
234+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
235+dependencies = [
236+ "cfg-if",
237+]
238+
239+[[package]]
240+name = "crossbeam-channel"
241+version = "0.5.8"
242+source = "registry+https://github.com/rust-lang/crates.io-index"
243+checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
244+dependencies = [
245+ "cfg-if",
246+ "crossbeam-utils",
247+]
248+
249+[[package]]
250+name = "crossbeam-deque"
150251version = "0.8.3"
151252source = "registry+https://github.com/rust-lang/crates.io-index"
152−checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
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+]
153272
154273[[package]]
155274name = "crossbeam-utils"
156−version = "0.8.11"
275+version = "0.8.16"
157276source = "registry+https://github.com/rust-lang/crates.io-index"
158−checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
277+checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
159278dependencies = [
160279 "cfg-if",
161− "once_cell",
162280]
163281
164282[[package]]
165283name = "crossterm"
166−version = "0.25.0"
284+version = "0.27.0"
167285source = "registry+https://github.com/rust-lang/crates.io-index"
168−checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
286+checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
169287dependencies = [
170− "bitflags",
288+ "bitflags 2.4.1",
171289 "crossterm_winapi",
290+ "filedescriptor",
172291 "futures-core",
173292 "libc",
174293 "mio",
@@ -180,217 +299,735 @@ dependencies = [
180299
181300[[package]]
182301name = "crossterm_winapi"
183−version = "0.9.0"
302+version = "0.9.1"
184303source = "registry+https://github.com/rust-lang/crates.io-index"
185−checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
304+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
186305dependencies = [
187306 "winapi",
188307]
189308
190309[[package]]
191−name = "dirs-next"
192−version = "2.0.0"
310+name = "cxx"
311+version = "1.0.94"
193312source = "registry+https://github.com/rust-lang/crates.io-index"
194−checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
313+checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
314+dependencies = [
315+ "cc",
316+ "cxxbridge-flags",
317+ "cxxbridge-macro",
318+ "link-cplusplus",
319+]
320+
321+[[package]]
322+name = "cxx-build"
323+version = "1.0.94"
324+source = "registry+https://github.com/rust-lang/crates.io-index"
325+checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
326+dependencies = [
327+ "cc",
328+ "codespan-reporting",
329+ "once_cell",
330+ "proc-macro2",
331+ "quote",
332+ "scratch",
333+ "syn 2.0.38",
334+]
335+
336+[[package]]
337+name = "cxxbridge-flags"
338+version = "1.0.94"
339+source = "registry+https://github.com/rust-lang/crates.io-index"
340+checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
341+
342+[[package]]
343+name = "cxxbridge-macro"
344+version = "1.0.94"
345+source = "registry+https://github.com/rust-lang/crates.io-index"
346+checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
347+dependencies = [
348+ "proc-macro2",
349+ "quote",
350+ "syn 2.0.38",
351+]
352+
353+[[package]]
354+name = "dunce"
355+version = "1.0.4"
356+source = "registry+https://github.com/rust-lang/crates.io-index"
357+checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
358+
359+[[package]]
360+name = "either"
361+version = "1.8.1"
362+source = "registry+https://github.com/rust-lang/crates.io-index"
363+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
364+
365+[[package]]
366+name = "encoding_rs"
367+version = "0.8.33"
368+source = "registry+https://github.com/rust-lang/crates.io-index"
369+checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
195370dependencies = [
196371 "cfg-if",
197− "dirs-sys-next",
198372]
199373
200374[[package]]
201−name = "dirs-sys-next"
202−version = "0.1.2"
375+name = "encoding_rs_io"
376+version = "0.1.7"
203377source = "registry+https://github.com/rust-lang/crates.io-index"
204−checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
378+checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83"
379+dependencies = [
380+ "encoding_rs",
381+]
382+
383+[[package]]
384+name = "equivalent"
385+version = "1.0.0"
386+source = "registry+https://github.com/rust-lang/crates.io-index"
387+checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
388+
389+[[package]]
390+name = "errno"
391+version = "0.3.8"
392+source = "registry+https://github.com/rust-lang/crates.io-index"
393+checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
394+dependencies = [
395+ "libc",
396+ "windows-sys 0.52.0",
397+]
398+
399+[[package]]
400+name = "error-code"
401+version = "3.0.0"
402+source = "registry+https://github.com/rust-lang/crates.io-index"
403+checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
404+
405+[[package]]
406+name = "etcetera"
407+version = "0.8.0"
408+source = "registry+https://github.com/rust-lang/crates.io-index"
409+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
410+dependencies = [
411+ "cfg-if",
412+ "home",
413+ "windows-sys 0.48.0",
414+]
415+
416+[[package]]
417+name = "faster-hex"
418+version = "0.9.0"
419+source = "registry+https://github.com/rust-lang/crates.io-index"
420+checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
421+dependencies = [
422+ "serde",
423+]
424+
425+[[package]]
426+name = "fastrand"
427+version = "2.0.0"
428+source = "registry+https://github.com/rust-lang/crates.io-index"
429+checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
430+
431+[[package]]
432+name = "fern"
433+version = "0.6.2"
434+source = "registry+https://github.com/rust-lang/crates.io-index"
435+checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee"
436+dependencies = [
437+ "log",
438+]
439+
440+[[package]]
441+name = "filedescriptor"
442+version = "0.8.2"
443+source = "registry+https://github.com/rust-lang/crates.io-index"
444+checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
205445dependencies = [
206446 "libc",
207− "redox_users",
447+ "thiserror",
208448 "winapi",
209449]
210450
211451[[package]]
212−name = "either"
213−version = "1.8.0"
452+name = "flate2"
453+version = "1.0.27"
454+source = "registry+https://github.com/rust-lang/crates.io-index"
455+checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
456+dependencies = [
457+ "crc32fast",
458+ "miniz_oxide",
459+]
460+
461+[[package]]
462+name = "form_urlencoded"
463+version = "1.2.1"
464+source = "registry+https://github.com/rust-lang/crates.io-index"
465+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
466+dependencies = [
467+ "percent-encoding",
468+]
469+
470+[[package]]
471+name = "futures-core"
472+version = "0.3.30"
473+source = "registry+https://github.com/rust-lang/crates.io-index"
474+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
475+
476+[[package]]
477+name = "futures-executor"
478+version = "0.3.29"
479+source = "registry+https://github.com/rust-lang/crates.io-index"
480+checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
481+dependencies = [
482+ "futures-core",
483+ "futures-task",
484+ "futures-util",
485+]
486+
487+[[package]]
488+name = "futures-task"
489+version = "0.3.30"
490+source = "registry+https://github.com/rust-lang/crates.io-index"
491+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
492+
493+[[package]]
494+name = "futures-util"
495+version = "0.3.30"
496+source = "registry+https://github.com/rust-lang/crates.io-index"
497+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
498+dependencies = [
499+ "futures-core",
500+ "futures-task",
501+ "pin-project-lite",
502+ "pin-utils",
503+ "slab",
504+]
505+
506+[[package]]
507+name = "getrandom"
508+version = "0.2.9"
509+source = "registry+https://github.com/rust-lang/crates.io-index"
510+checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
511+dependencies = [
512+ "cfg-if",
513+ "libc",
514+ "wasi",
515+]
516+
517+[[package]]
518+name = "gimli"
519+version = "0.27.3"
520+source = "registry+https://github.com/rust-lang/crates.io-index"
521+checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
522+
523+[[package]]
524+name = "gix"
525+version = "0.57.0"
526+source = "registry+https://github.com/rust-lang/crates.io-index"
527+checksum = "721c7497ab24b665ed5a1eb2b3526936aa60068e61ba260651f7e77c52feec69"
528+dependencies = [
529+ "gix-actor",
530+ "gix-commitgraph",
531+ "gix-config",
532+ "gix-date",
533+ "gix-diff",
534+ "gix-discover",
535+ "gix-features",
536+ "gix-fs",
537+ "gix-glob",
538+ "gix-hash",
539+ "gix-hashtable",
540+ "gix-lock",
541+ "gix-macros",
542+ "gix-object",
543+ "gix-odb",
544+ "gix-pack",
545+ "gix-path",
546+ "gix-ref",
547+ "gix-refspec",
548+ "gix-revision",
549+ "gix-revwalk",
550+ "gix-sec",
551+ "gix-tempfile",
552+ "gix-trace",
553+ "gix-traverse",
554+ "gix-url",
555+ "gix-utils",
556+ "gix-validate",
557+ "once_cell",
558+ "parking_lot",
559+ "smallvec",
560+ "thiserror",
561+ "unicode-normalization",
562+]
563+
564+[[package]]
565+name = "gix-actor"
566+version = "0.29.0"
567+source = "registry+https://github.com/rust-lang/crates.io-index"
568+checksum = "886014c4865b93ce268f1d3eddd4fd3261242c3f3ee61eb36009f913016a9059"
569+dependencies = [
570+ "bstr",
571+ "btoi",
572+ "gix-date",
573+ "itoa",
574+ "thiserror",
575+ "winnow 0.5.28",
576+]
577+
578+[[package]]
579+name = "gix-chunk"
580+version = "0.4.6"
581+source = "registry+https://github.com/rust-lang/crates.io-index"
582+checksum = "2ade37ef69870de0ed966b97c57a3a947a22ff3482a52c3b99b205f77bcb08fb"
583+dependencies = [
584+ "thiserror",
585+]
586+
587+[[package]]
588+name = "gix-commitgraph"
589+version = "0.23.0"
590+source = "registry+https://github.com/rust-lang/crates.io-index"
591+checksum = "e7559ea9cefee188cd88b05afcc8e3ef7a3cb4a5c647bccf06b981e591b02b77"
592+dependencies = [
593+ "bstr",
594+ "gix-chunk",
595+ "gix-features",
596+ "gix-hash",
597+ "memmap2",
598+ "thiserror",
599+]
600+
601+[[package]]
602+name = "gix-config"
603+version = "0.33.0"
604+source = "registry+https://github.com/rust-lang/crates.io-index"
605+checksum = "9c35dc7f9c00a42bbc9cfa1ca2ec0a78ad1b76ff0736d3d35dfd612962244467"
606+dependencies = [
607+ "bstr",
608+ "gix-config-value",
609+ "gix-features",
610+ "gix-glob",
611+ "gix-path",
612+ "gix-ref",
613+ "gix-sec",
614+ "memchr",
615+ "once_cell",
616+ "smallvec",
617+ "thiserror",
618+ "unicode-bom",
619+ "winnow 0.5.28",
620+]
621+
622+[[package]]
623+name = "gix-config-value"
624+version = "0.14.2"
625+source = "registry+https://github.com/rust-lang/crates.io-index"
626+checksum = "39f388cb2396aee82d6f460a2e7770659bdf8854e9e5478f7d2b1324a9698284"
627+dependencies = [
628+ "bitflags 2.4.1",
629+ "bstr",
630+ "gix-path",
631+ "libc",
632+ "thiserror",
633+]
634+
635+[[package]]
636+name = "gix-date"
637+version = "0.8.2"
214638source = "registry+https://github.com/rust-lang/crates.io-index"
215−checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
639+checksum = "9d85f4a01e0d05c3de585e28bae514d4baf01655e3fc3f14ce6f30bf62405345"
640+dependencies = [
641+ "bstr",
642+ "itoa",
643+ "thiserror",
644+ "time",
645+]
646+
647+[[package]]
648+name = "gix-diff"
649+version = "0.39.0"
650+source = "registry+https://github.com/rust-lang/crates.io-index"
651+checksum = "ac0e75f5afd2f6c47c800b6b0a000a08045739d0450d20482e8faa42543f62d1"
652+dependencies = [
653+ "bstr",
654+ "gix-hash",
655+ "gix-object",
656+ "thiserror",
657+]
658+
659+[[package]]
660+name = "gix-discover"
661+version = "0.28.0"
662+source = "registry+https://github.com/rust-lang/crates.io-index"
663+checksum = "f0b63ce2ad81632ac1a84ac370f85a5e580c3261580bd85ea17ff35d3a0bba18"
664+dependencies = [
665+ "bstr",
666+ "dunce",
667+ "gix-hash",
668+ "gix-path",
669+ "gix-ref",
670+ "gix-sec",
671+ "thiserror",
672+]
673+
674+[[package]]
675+name = "gix-features"
676+version = "0.37.0"
677+source = "registry+https://github.com/rust-lang/crates.io-index"
678+checksum = "befe7edea299a824504b5acc96d7a3a538125b38c42f3a8379f6912a29c90c81"
679+dependencies = [
680+ "crc32fast",
681+ "flate2",
682+ "gix-hash",
683+ "gix-trace",
684+ "libc",
685+ "once_cell",
686+ "prodash",
687+ "sha1_smol",
688+ "thiserror",
689+ "walkdir",
690+]
691+
692+[[package]]
693+name = "gix-fs"
694+version = "0.9.0"
695+source = "registry+https://github.com/rust-lang/crates.io-index"
696+checksum = "007017ce93b819ea52c0ec68306f7d72212a2d307c3d70f1548c7141c015d0a1"
697+dependencies = [
698+ "gix-features",
699+]
700+
701+[[package]]
702+name = "gix-glob"
703+version = "0.15.0"
704+source = "registry+https://github.com/rust-lang/crates.io-index"
705+checksum = "61fb116c2516d3a1170e010118f639944a6389872c875a008960cdab2a44ac72"
706+dependencies = [
707+ "bitflags 2.4.1",
708+ "bstr",
709+ "gix-features",
710+ "gix-path",
711+]
712+
713+[[package]]
714+name = "gix-hash"
715+version = "0.14.0"
716+source = "registry+https://github.com/rust-lang/crates.io-index"
717+checksum = "c52c3170a17b6031833cd2eb4ad7bc23f2755d06e6e70f78dec21d42e8fe1b30"
718+dependencies = [
719+ "faster-hex",
720+ "thiserror",
721+]
722+
723+[[package]]
724+name = "gix-hashtable"
725+version = "0.5.0"
726+source = "registry+https://github.com/rust-lang/crates.io-index"
727+checksum = "40a838e6366a5e5b84668b6997ce0981b833136468e8ba949f424c0ef2927eba"
728+dependencies = [
729+ "gix-hash",
730+ "hashbrown 0.14.3",
731+ "parking_lot",
732+]
733+
734+[[package]]
735+name = "gix-lock"
736+version = "12.0.0"
737+source = "registry+https://github.com/rust-lang/crates.io-index"
738+checksum = "6cf112ddee94223c119a8534dad027740dc3aba3365ac5edeef8a7f6660c74db"
739+dependencies = [
740+ "gix-tempfile",
741+ "gix-utils",
742+ "thiserror",
743+]
744+
745+[[package]]
746+name = "gix-macros"
747+version = "0.1.2"
748+source = "registry+https://github.com/rust-lang/crates.io-index"
749+checksum = "fc207b64189cf71bcb17fc841ab99a5d63d0845546b611e2703ce467f659323a"
750+dependencies = [
751+ "proc-macro2",
752+ "quote",
753+ "syn 2.0.38",
754+]
755+
756+[[package]]
757+name = "gix-object"
758+version = "0.40.0"
759+source = "registry+https://github.com/rust-lang/crates.io-index"
760+checksum = "e4c77e47ffba92127faf632a841fce23d19547e269bd8b88e68961a70eab4e93"
761+dependencies = [
762+ "bstr",
763+ "btoi",
764+ "gix-actor",
765+ "gix-date",
766+ "gix-features",
767+ "gix-hash",
768+ "gix-validate",
769+ "itoa",
770+ "smallvec",
771+ "thiserror",
772+ "winnow 0.5.28",
773+]
774+
775+[[package]]
776+name = "gix-odb"
777+version = "0.56.0"
778+source = "registry+https://github.com/rust-lang/crates.io-index"
779+checksum = "a3254f2005cc7553ea78e85e816a09150c6f7a64e6b7627b8d1fdc56721bea73"
780+dependencies = [
781+ "arc-swap",
782+ "gix-date",
783+ "gix-features",
784+ "gix-hash",
785+ "gix-object",
786+ "gix-pack",
787+ "gix-path",
788+ "gix-quote",
789+ "parking_lot",
790+ "tempfile",
791+ "thiserror",
792+]
793+
794+[[package]]
795+name = "gix-pack"
796+version = "0.46.0"
797+source = "registry+https://github.com/rust-lang/crates.io-index"
798+checksum = "02ebc8cd657eec207d82d8f876ca402361308ecd3c87a47935b0299506257b4f"
799+dependencies = [
800+ "clru",
801+ "gix-chunk",
802+ "gix-features",
803+ "gix-hash",
804+ "gix-hashtable",
805+ "gix-object",
806+ "gix-path",
807+ "gix-tempfile",
808+ "memmap2",
809+ "parking_lot",
810+ "smallvec",
811+ "thiserror",
812+]
216813
217814[[package]]
218−name = "encoding_rs"
219−version = "0.8.31"
815+name = "gix-path"
816+version = "0.10.2"
220817source = "registry+https://github.com/rust-lang/crates.io-index"
221−checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
818+checksum = "10931652a3da126990ac93bce1b1c600cc99d7c268d712b6360ed52174ce1b68"
222819dependencies = [
223− "cfg-if",
820+ "bstr",
821+ "gix-trace",
822+ "home",
823+ "once_cell",
824+ "thiserror",
224825]
225826
226827[[package]]
227−name = "encoding_rs_io"
228−version = "0.1.7"
828+name = "gix-quote"
829+version = "0.4.9"
229830source = "registry+https://github.com/rust-lang/crates.io-index"
230−checksum = "1cc3c5651fb62ab8aa3103998dade57efdd028544bd300516baa31840c252a83"
831+checksum = "6c43530cb94a7759807e6f8d180e17ac9c65673b891645c6c433831dc0cf4342"
231832dependencies = [
232− "encoding_rs",
833+ "bstr",
834+ "btoi",
835+ "thiserror",
233836]
234837
235838[[package]]
236−name = "error-code"
237−version = "2.3.1"
839+name = "gix-ref"
840+version = "0.40.0"
238841source = "registry+https://github.com/rust-lang/crates.io-index"
239−checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
842+checksum = "baa951b3b850d6d1be4f900768e49af20b76bf9505beac22af723d57249a2f1d"
240843dependencies = [
241− "libc",
242− "str-buf",
844+ "gix-actor",
845+ "gix-date",
846+ "gix-features",
847+ "gix-fs",
848+ "gix-hash",
849+ "gix-lock",
850+ "gix-object",
851+ "gix-path",
852+ "gix-tempfile",
853+ "gix-validate",
854+ "memmap2",
855+ "thiserror",
856+ "winnow 0.5.28",
243857]
244858
245859[[package]]
246−name = "etcetera"
247−version = "0.4.0"
860+name = "gix-refspec"
861+version = "0.21.0"
248862source = "registry+https://github.com/rust-lang/crates.io-index"
249−checksum = "d017fce18e4e9bfa75e1db51f49f4487bd3f8a7df509b24a46474a956ee962fd"
863+checksum = "88c61f849d58c06e3068a0b601cf10127d2a07cdad00a725ed66cf303f76f6b3"
250864dependencies = [
251− "cfg-if",
252− "dirs-next",
865+ "bstr",
866+ "gix-hash",
867+ "gix-revision",
868+ "gix-validate",
869+ "smallvec",
253870 "thiserror",
254871]
255872
256873[[package]]
257−name = "fastrand"
258−version = "1.8.0"
874+name = "gix-revision"
875+version = "0.25.0"
259876source = "registry+https://github.com/rust-lang/crates.io-index"
260−checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
877+checksum = "bcbbf91f4200c5c76802ef5f057b96f5a336827508881fe55a780be71a794d22"
261878dependencies = [
262− "instant",
879+ "bstr",
880+ "gix-date",
881+ "gix-hash",
882+ "gix-hashtable",
883+ "gix-object",
884+ "gix-revwalk",
885+ "gix-trace",
886+ "thiserror",
263887]
264888
265889[[package]]
266−name = "fern"
267−version = "0.6.1"
890+name = "gix-revwalk"
891+version = "0.11.0"
268892source = "registry+https://github.com/rust-lang/crates.io-index"
269−checksum = "3bdd7b0849075e79ee9a1836df22c717d1eba30451796fdc631b04565dd11e2a"
893+checksum = "33ab66354d83f70f2730391747d0c75f94ef3c3dd40ebde2e206db9faaf7a0a7"
270894dependencies = [
271− "log",
895+ "gix-commitgraph",
896+ "gix-date",
897+ "gix-hash",
898+ "gix-hashtable",
899+ "gix-object",
900+ "smallvec",
901+ "thiserror",
272902]
273903
274904[[package]]
275−name = "fnv"
276−version = "1.0.7"
905+name = "gix-sec"
906+version = "0.10.2"
277907source = "registry+https://github.com/rust-lang/crates.io-index"
278−checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
908+checksum = "a9963f38a42144253ed4d571882d7db5ef644c12e6726e4b75135597cc9d0e1a"
909+dependencies = [
910+ "bitflags 2.4.1",
911+ "gix-path",
912+ "libc",
913+ "windows 0.52.0",
914+]
279915
280916[[package]]
281−name = "form_urlencoded"
282−version = "1.1.0"
917+name = "gix-tempfile"
918+version = "12.0.0"
283919source = "registry+https://github.com/rust-lang/crates.io-index"
284−checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
920+checksum = "e76a494bd530e1a1309188ff971825a24f159c76c2db0bf71fa5dfb469a2c915"
285921dependencies = [
286− "percent-encoding",
922+ "gix-fs",
923+ "libc",
924+ "once_cell",
925+ "parking_lot",
926+ "tempfile",
287927]
288928
289929[[package]]
290−name = "futures-core"
291−version = "0.3.25"
930+name = "gix-trace"
931+version = "0.1.5"
292932source = "registry+https://github.com/rust-lang/crates.io-index"
293−checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
933+checksum = "bda62acb44dd86a40c7c3762a5403cfc1ac789ea559df54085cedf79864f809e"
294934
295935[[package]]
296−name = "futures-executor"
297−version = "0.3.25"
936+name = "gix-traverse"
937+version = "0.36.0"
298938source = "registry+https://github.com/rust-lang/crates.io-index"
299−checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
939+checksum = "8661fab39985c9214e56d81a63ceb5886ac948cec2fba76c39494d1e0e307ea8"
300940dependencies = [
301− "futures-core",
302− "futures-task",
303− "futures-util",
941+ "gix-commitgraph",
942+ "gix-date",
943+ "gix-hash",
944+ "gix-hashtable",
945+ "gix-object",
946+ "gix-revwalk",
947+ "smallvec",
948+ "thiserror",
304949]
305950
306951[[package]]
307−name = "futures-task"
308−version = "0.3.25"
309−source = "registry+https://github.com/rust-lang/crates.io-index"
310−checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
311−
312−[[package]]
313−name = "futures-util"
314−version = "0.3.25"
952+name = "gix-url"
953+version = "0.26.0"
315954source = "registry+https://github.com/rust-lang/crates.io-index"
316−checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
955+checksum = "10a0129c1e8b52736d7c5128300a4485dbc85863001371e2771ac1754bd89fd7"
317956dependencies = [
318− "futures-core",
319− "futures-task",
320− "pin-project-lite",
321− "pin-utils",
322− "slab",
957+ "bstr",
958+ "gix-features",
959+ "gix-path",
960+ "home",
961+ "thiserror",
962+ "url",
323963]
324964
325965[[package]]
326−name = "fuzzy-matcher"
327−version = "0.3.7"
966+name = "gix-utils"
967+version = "0.1.7"
328968source = "registry+https://github.com/rust-lang/crates.io-index"
329−checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
969+checksum = "ab9277a5e32e85d53f738096d872a4a9b76067ad471894ad31bd99c8fa2da1dc"
330970dependencies = [
331− "thread_local",
971+ "fastrand",
332972]
333973
334974[[package]]
335−name = "getrandom"
336−version = "0.2.7"
975+name = "gix-validate"
976+version = "0.8.2"
337977source = "registry+https://github.com/rust-lang/crates.io-index"
338−checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
978+checksum = "f805ebbdbaa4bfd98e2ee43e6d14099b8a4d9141f5d7b8202fea4d48e44263e7"
339979dependencies = [
340− "cfg-if",
341− "libc",
342− "wasi",
980+ "bstr",
981+ "thiserror",
343982]
344983
345984[[package]]
346985name = "globset"
347−version = "0.4.9"
986+version = "0.4.14"
348987source = "registry+https://github.com/rust-lang/crates.io-index"
349−checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
988+checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
350989dependencies = [
351990 "aho-corasick",
352991 "bstr",
353− "fnv",
354992 "log",
355− "regex",
993+ "regex-automata",
994+ "regex-syntax",
356995]
357996
358997[[package]]
359998name = "grep-matcher"
360−version = "0.1.5"
999+version = "0.1.7"
3611000source = "registry+https://github.com/rust-lang/crates.io-index"
362−checksum = "6d27563c33062cd33003b166ade2bb4fd82db1fd6a86db764dfdad132d46c1cc"
1001+checksum = "47a3141a10a43acfedc7c98a60a834d7ba00dfe7bec9071cbfc19b55b292ac02"
3631002dependencies = [
3641003 "memchr",
3651004]
3661005
3671006[[package]]
3681007name = "grep-regex"
369−version = "0.1.10"
1008+version = "0.1.12"
3701009source = "registry+https://github.com/rust-lang/crates.io-index"
371−checksum = "1345f8d33c89f2d5b081f2f2a41175adef9fd0bed2fea6a26c96c2deb027e58e"
1010+checksum = "f748bb135ca835da5cbc67ca0e6955f968db9c5df74ca4f56b18e1ddbc68230d"
3721011dependencies = [
373− "aho-corasick",
3741012 "bstr",
3751013 "grep-matcher",
3761014 "log",
377− "regex",
1015+ "regex-automata",
3781016 "regex-syntax",
379− "thread_local",
3801017]
3811018
3821019[[package]]
3831020name = "grep-searcher"
384−version = "0.1.10"
1021+version = "0.1.13"
3851022source = "registry+https://github.com/rust-lang/crates.io-index"
386−checksum = "48852bd08f9b4eb3040ecb6d2f4ade224afe880a9a0909c5563cc59fa67932cc"
1023+checksum = "ba536ae4f69bec62d8839584dd3153d3028ef31bb229f04e09fb5a9e5a193c54"
3871024dependencies = [
3881025 "bstr",
389− "bytecount",
3901026 "encoding_rs",
3911027 "encoding_rs_io",
3921028 "grep-matcher",
3931029 "log",
1030+ "memchr",
3941031 "memmap2",
3951032]
3961033
@@ -399,28 +1036,41 @@ name = "hashbrown"
3991036version = "0.12.3"
4001037source = "registry+https://github.com/rust-lang/crates.io-index"
4011038checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1039+
1040+[[package]]
1041+name = "hashbrown"
1042+version = "0.14.3"
1043+source = "registry+https://github.com/rust-lang/crates.io-index"
1044+checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
4021045dependencies = [
4031046 "ahash",
1047+ "allocator-api2",
4041048]
4051049
4061050[[package]]
4071051name = "helix-core"
408−version = "0.6.0"
1052+version = "23.10.0"
4091053dependencies = [
1054+ "ahash",
4101055 "arc-swap",
411− "bitflags",
1056+ "bitflags 2.4.1",
4121057 "chrono",
1058+ "dunce",
4131059 "encoding_rs",
4141060 "etcetera",
1061+ "hashbrown 0.14.3",
4151062 "helix-loader",
1063+ "imara-diff",
1064+ "indoc",
4161065 "log",
1066+ "nucleo",
4171067 "once_cell",
1068+ "parking_lot",
4181069 "quickcheck",
4191070 "regex",
4201071 "ropey",
4211072 "serde",
4221073 "serde_json",
423− "similar",
4241074 "slotmap",
4251075 "smallvec",
4261076 "smartstring",
@@ -434,7 +1084,7 @@ dependencies = [
4341084
4351085[[package]]
4361086name = "helix-dap"
437−version = "0.6.0"
1087+version = "23.10.0"
4381088dependencies = [
4391089 "anyhow",
4401090 "fern",
@@ -447,32 +1097,47 @@ dependencies = [
4471097 "which",
4481098]
4491099
1100+[[package]]
1101+name = "helix-event"
1102+version = "23.10.0"
1103+dependencies = [
1104+ "parking_lot",
1105+ "tokio",
1106+]
1107+
4501108[[package]]
4511109name = "helix-loader"
452−version = "0.6.0"
1110+version = "23.10.0"
4531111dependencies = [
4541112 "anyhow",
4551113 "cc",
1114+ "dunce",
4561115 "etcetera",
4571116 "libloading",
4581117 "log",
4591118 "once_cell",
4601119 "serde",
1120+ "tempfile",
4611121 "threadpool",
4621122 "toml",
4631123 "tree-sitter",
1124+ "which",
4641125]
4651126
4661127[[package]]
4671128name = "helix-lsp"
468−version = "0.6.0"
1129+version = "23.10.0"
4691130dependencies = [
4701131 "anyhow",
4711132 "futures-executor",
4721133 "futures-util",
1134+ "globset",
4731135 "helix-core",
1136+ "helix-loader",
1137+ "helix-parsec",
4741138 "log",
4751139 "lsp-types",
1140+ "parking_lot",
4761141 "serde",
4771142 "serde_json",
4781143 "thiserror",
@@ -481,9 +1146,13 @@ dependencies = [
4811146 "which",
4821147]
4831148
1149+[[package]]
1150+name = "helix-parsec"
1151+version = "23.10.0"
1152+
4841153[[package]]
4851154name = "helix-term"
486−version = "0.6.0"
1155+version = "23.10.0"
4871156dependencies = [
4881157 "anyhow",
4891158 "arc-swap",
@@ -492,19 +1161,23 @@ dependencies = [
4921161 "crossterm",
4931162 "fern",
4941163 "futures-util",
495− "fuzzy-matcher",
4961164 "grep-regex",
4971165 "grep-searcher",
4981166 "helix-core",
4991167 "helix-dap",
1168+ "helix-event",
5001169 "helix-loader",
5011170 "helix-lsp",
5021171 "helix-tui",
1172+ "helix-vcs",
5031173 "helix-view",
5041174 "ignore",
5051175 "indoc",
1176+ "libc",
5061177 "log",
1178+ "nucleo",
5071179 "once_cell",
1180+ "open",
5081181 "pulldown-cmark",
5091182 "serde",
5101183 "serde_json",
@@ -515,41 +1188,65 @@ dependencies = [
5151188 "tokio",
5161189 "tokio-stream",
5171190 "toml",
1191+ "url",
5181192 "which",
5191193]
5201194
5211195[[package]]
5221196name = "helix-tui"
523−version = "0.6.0"
1197+version = "23.10.0"
5241198dependencies = [
525− "bitflags",
1199+ "bitflags 2.4.1",
5261200 "cassowary",
5271201 "crossterm",
5281202 "helix-core",
5291203 "helix-view",
1204+ "log",
1205+ "once_cell",
5301206 "serde",
5311207 "termini",
5321208 "unicode-segmentation",
5331209]
5341210
1211+[[package]]
1212+name = "helix-vcs"
1213+version = "23.10.0"
1214+dependencies = [
1215+ "anyhow",
1216+ "arc-swap",
1217+ "gix",
1218+ "helix-core",
1219+ "helix-event",
1220+ "imara-diff",
1221+ "log",
1222+ "parking_lot",
1223+ "tempfile",
1224+ "tokio",
1225+]
1226+
5351227[[package]]
5361228name = "helix-view"
537−version = "0.6.0"
1229+version = "23.10.0"
5381230dependencies = [
5391231 "anyhow",
5401232 "arc-swap",
541− "bitflags",
1233+ "bitflags 2.4.1",
5421234 "chardetng",
5431235 "clipboard-win",
5441236 "crossterm",
5451237 "futures-util",
5461238 "helix-core",
5471239 "helix-dap",
1240+ "helix-event",
5481241 "helix-loader",
5491242 "helix-lsp",
5501243 "helix-tui",
1244+ "helix-vcs",
1245+ "libc",
5511246 "log",
5521247 "once_cell",
1248+ "parking_lot",
1249+ "rustix",
5531250 "serde",
5541251 "serde_json",
5551252 "slotmap",
@@ -562,32 +1259,51 @@ dependencies = [
5621259
5631260[[package]]
5641261name = "hermit-abi"
565−version = "0.1.19"
1262+version = "0.2.6"
5661263source = "registry+https://github.com/rust-lang/crates.io-index"
567−checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
1264+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
5681265dependencies = [
5691266 "libc",
5701267]
5711268
1269+[[package]]
1270+name = "home"
1271+version = "0.5.5"
1272+source = "registry+https://github.com/rust-lang/crates.io-index"
1273+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
1274+dependencies = [
1275+ "windows-sys 0.48.0",
1276+]
1277+
5721278[[package]]
5731279name = "iana-time-zone"
574−version = "0.1.47"
1280+version = "0.1.56"
5751281source = "registry+https://github.com/rust-lang/crates.io-index"
576−checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7"
1282+checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
5771283dependencies = [
5781284 "android_system_properties",
5791285 "core-foundation-sys",
1286+ "iana-time-zone-haiku",
5801287 "js-sys",
581− "once_cell",
5821288 "wasm-bindgen",
583− "winapi",
1289+ "windows 0.48.0",
1290+]
1291+
1292+[[package]]
1293+name = "iana-time-zone-haiku"
1294+version = "0.1.1"
1295+source = "registry+https://github.com/rust-lang/crates.io-index"
1296+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
1297+dependencies = [
1298+ "cxx",
1299+ "cxx-build",
5841300]
5851301
5861302[[package]]
5871303name = "idna"
588−version = "0.3.0"
1304+version = "0.5.0"
5891305source = "registry+https://github.com/rust-lang/crates.io-index"
590−checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
1306+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
5911307dependencies = [
5921308 "unicode-bidi",
5931309 "unicode-normalization",
@@ -595,79 +1311,116 @@ dependencies = [
5951311
5961312[[package]]
5971313name = "ignore"
598−version = "0.4.18"
1314+version = "0.4.21"
5991315source = "registry+https://github.com/rust-lang/crates.io-index"
600−checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
1316+checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060"
6011317dependencies = [
602− "crossbeam-utils",
1318+ "crossbeam-deque",
6031319 "globset",
604− "lazy_static",
6051320 "log",
6061321 "memchr",
607− "regex",
1322+ "regex-automata",
6081323 "same-file",
609− "thread_local",
6101324 "walkdir",
6111325 "winapi-util",
6121326]
6131327
1328+[[package]]
1329+name = "imara-diff"
1330+version = "0.1.5"
1331+source = "registry+https://github.com/rust-lang/crates.io-index"
1332+checksum = "e98c1d0ad70fc91b8b9654b1f33db55e59579d3b3de2bffdced0fdb810570cb8"
1333+dependencies = [
1334+ "ahash",
1335+ "hashbrown 0.12.3",
1336+]
1337+
1338+[[package]]
1339+name = "indexmap"
1340+version = "2.0.0"
1341+source = "registry+https://github.com/rust-lang/crates.io-index"
1342+checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
1343+dependencies = [
1344+ "equivalent",
1345+ "hashbrown 0.14.3",
1346+]
1347+
6141348[[package]]
6151349name = "indoc"
616−version = "1.0.7"
1350+version = "2.0.4"
6171351source = "registry+https://github.com/rust-lang/crates.io-index"
618−checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3"
1352+checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
6191353
6201354[[package]]
621−name = "instant"
622−version = "0.1.12"
1355+name = "is-docker"
1356+version = "0.2.0"
6231357source = "registry+https://github.com/rust-lang/crates.io-index"
624−checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
1358+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
6251359dependencies = [
626− "cfg-if",
1360+ "once_cell",
1361+]
1362+
1363+[[package]]
1364+name = "is-wsl"
1365+version = "0.4.0"
1366+source = "registry+https://github.com/rust-lang/crates.io-index"
1367+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
1368+dependencies = [
1369+ "is-docker",
1370+ "once_cell",
6271371]
6281372
6291373[[package]]
6301374name = "itoa"
631−version = "1.0.3"
1375+version = "1.0.6"
6321376source = "registry+https://github.com/rust-lang/crates.io-index"
633−checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
1377+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
6341378
6351379[[package]]
6361380name = "js-sys"
637−version = "0.3.59"
1381+version = "0.3.61"
6381382source = "registry+https://github.com/rust-lang/crates.io-index"
639−checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2"
1383+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
6401384dependencies = [
6411385 "wasm-bindgen",
6421386]
6431387
644−[[package]]
645−name = "lazy_static"
646−version = "1.4.0"
647−source = "registry+https://github.com/rust-lang/crates.io-index"
648−checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
649−
6501388[[package]]
6511389name = "libc"
652−version = "0.2.132"
1390+version = "0.2.151"
6531391source = "registry+https://github.com/rust-lang/crates.io-index"
654−checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
1392+checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
6551393
6561394[[package]]
6571395name = "libloading"
658−version = "0.7.4"
1396+version = "0.8.1"
6591397source = "registry+https://github.com/rust-lang/crates.io-index"
660−checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
1398+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
6611399dependencies = [
6621400 "cfg-if",
663− "winapi",
1401+ "windows-sys 0.48.0",
1402+]
1403+
1404+[[package]]
1405+name = "link-cplusplus"
1406+version = "1.0.8"
1407+source = "registry+https://github.com/rust-lang/crates.io-index"
1408+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
1409+dependencies = [
1410+ "cc",
6641411]
6651412
1413+[[package]]
1414+name = "linux-raw-sys"
1415+version = "0.4.11"
1416+source = "registry+https://github.com/rust-lang/crates.io-index"
1417+checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
1418+
6661419[[package]]
6671420name = "lock_api"
668−version = "0.4.8"
1421+version = "0.4.9"
6691422source = "registry+https://github.com/rust-lang/crates.io-index"
670−checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390"
1423+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
6711424dependencies = [
6721425 "autocfg",
6731426 "scopeguard",
@@ -675,20 +1428,17 @@ dependencies = [
6751428
6761429[[package]]
6771430name = "log"
678−version = "0.4.17"
1431+version = "0.4.20"
6791432source = "registry+https://github.com/rust-lang/crates.io-index"
680−checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
681−dependencies = [
682− "cfg-if",
683−]
1433+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
6841434
6851435[[package]]
6861436name = "lsp-types"
687−version = "0.93.2"
1437+version = "0.95.0"
6881438source = "registry+https://github.com/rust-lang/crates.io-index"
689−checksum = "9be6e9c7e2d18f651974370d7aff703f9513e0df6e464fd795660edc77e6ca51"
1439+checksum = "158c1911354ef73e8fe42da6b10c0484cb65c7f1007f28022e847706c1ab6984"
6901440dependencies = [
691− "bitflags",
1441+ "bitflags 1.3.2",
6921442 "serde",
6931443 "serde_json",
6941444 "serde_repr",
@@ -697,39 +1447,69 @@ dependencies = [
6971447
6981448[[package]]
6991449name = "memchr"
700−version = "2.5.0"
1450+version = "2.6.3"
7011451source = "registry+https://github.com/rust-lang/crates.io-index"
702−checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
1452+checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
7031453
7041454[[package]]
7051455name = "memmap2"
706−version = "0.5.7"
1456+version = "0.9.0"
7071457source = "registry+https://github.com/rust-lang/crates.io-index"
708−checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498"
1458+checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
7091459dependencies = [
7101460 "libc",
7111461]
7121462
1463+[[package]]
1464+name = "memoffset"
1465+version = "0.9.0"
1466+source = "registry+https://github.com/rust-lang/crates.io-index"
1467+checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
1468+dependencies = [
1469+ "autocfg",
1470+]
1471+
1472+[[package]]
1473+name = "miniz_oxide"
1474+version = "0.7.1"
1475+source = "registry+https://github.com/rust-lang/crates.io-index"
1476+checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
1477+dependencies = [
1478+ "adler",
1479+]
1480+
7131481[[package]]
7141482name = "mio"
715−version = "0.8.4"
1483+version = "0.8.9"
7161484source = "registry+https://github.com/rust-lang/crates.io-index"
717−checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
1485+checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
7181486dependencies = [
7191487 "libc",
7201488 "log",
7211489 "wasi",
722− "windows-sys",
1490+ "windows-sys 0.48.0",
7231491]
7241492
7251493[[package]]
726−name = "num-integer"
727−version = "0.1.45"
1494+name = "nucleo"
1495+version = "0.2.1"
7281496source = "registry+https://github.com/rust-lang/crates.io-index"
729−checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
1497+checksum = "ae5331f4bcce475cf28cb29c95366c3091af4b0aa7703f1a6bc858f29718fdf3"
7301498dependencies = [
731− "autocfg",
732− "num-traits",
1499+ "nucleo-matcher",
1500+ "parking_lot",
1501+ "rayon",
1502+]
1503+
1504+[[package]]
1505+name = "nucleo-matcher"
1506+version = "0.2.0"
1507+source = "registry+https://github.com/rust-lang/crates.io-index"
1508+checksum = "1b702b402fe286162d1f00b552a046ce74365d2ac473a2607ff36ba650f9bd57"
1509+dependencies = [
1510+ "cov-mark",
1511+ "memchr",
1512+ "unicode-segmentation",
7331513]
7341514
7351515[[package]]
@@ -743,19 +1523,48 @@ dependencies = [
7431523
7441524[[package]]
7451525name = "num_cpus"
746−version = "1.13.1"
1526+version = "1.15.0"
7471527source = "registry+https://github.com/rust-lang/crates.io-index"
748−checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
1528+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
7491529dependencies = [
7501530 "hermit-abi",
7511531 "libc",
7521532]
7531533
1534+[[package]]
1535+name = "num_threads"
1536+version = "0.1.6"
1537+source = "registry+https://github.com/rust-lang/crates.io-index"
1538+checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
1539+dependencies = [
1540+ "libc",
1541+]
1542+
1543+[[package]]
1544+name = "object"
1545+version = "0.31.1"
1546+source = "registry+https://github.com/rust-lang/crates.io-index"
1547+checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1"
1548+dependencies = [
1549+ "memchr",
1550+]
1551+
7541552[[package]]
7551553name = "once_cell"
756−version = "1.16.0"
1554+version = "1.19.0"
1555+source = "registry+https://github.com/rust-lang/crates.io-index"
1556+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
1557+
1558+[[package]]
1559+name = "open"
1560+version = "5.0.1"
7571561source = "registry+https://github.com/rust-lang/crates.io-index"
758−checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
1562+checksum = "90878fb664448b54c4e592455ad02831e23a3f7e157374a8b95654731aac7349"
1563+dependencies = [
1564+ "is-wsl",
1565+ "libc",
1566+ "pathdiff",
1567+]
7591568
7601569[[package]]
7611570name = "parking_lot"
@@ -769,28 +1578,34 @@ dependencies = [
7691578
7701579[[package]]
7711580name = "parking_lot_core"
772−version = "0.9.3"
1581+version = "0.9.7"
7731582source = "registry+https://github.com/rust-lang/crates.io-index"
774−checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
1583+checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
7751584dependencies = [
7761585 "cfg-if",
7771586 "libc",
778− "redox_syscall",
1587+ "redox_syscall 0.2.16",
7791588 "smallvec",
780− "windows-sys",
1589+ "windows-sys 0.45.0",
7811590]
7821591
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+
7831598[[package]]
7841599name = "percent-encoding"
785−version = "2.2.0"
1600+version = "2.3.1"
7861601source = "registry+https://github.com/rust-lang/crates.io-index"
787−checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
1602+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
7881603
7891604[[package]]
7901605name = "pin-project-lite"
791−version = "0.2.9"
1606+version = "0.2.12"
7921607source = "registry+https://github.com/rust-lang/crates.io-index"
793−checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
1608+checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05"
7941609
7951610[[package]]
7961611name = "pin-utils"
@@ -800,20 +1615,26 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
8001615
8011616[[package]]
8021617name = "proc-macro2"
803−version = "1.0.43"
1618+version = "1.0.69"
8041619source = "registry+https://github.com/rust-lang/crates.io-index"
805−checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
1620+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
8061621dependencies = [
8071622 "unicode-ident",
8081623]
8091624
1625+[[package]]
1626+name = "prodash"
1627+version = "28.0.0"
1628+source = "registry+https://github.com/rust-lang/crates.io-index"
1629+checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
1630+
8101631[[package]]
8111632name = "pulldown-cmark"
812−version = "0.9.2"
1633+version = "0.9.3"
8131634source = "registry+https://github.com/rust-lang/crates.io-index"
814−checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
1635+checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
8151636dependencies = [
816− "bitflags",
1637+ "bitflags 1.3.2",
8171638 "memchr",
8181639 "unicase",
8191640]
@@ -829,9 +1650,9 @@ dependencies = [
8291650
8301651[[package]]
8311652name = "quote"
832−version = "1.0.21"
1653+version = "1.0.29"
8331654source = "registry+https://github.com/rust-lang/crates.io-index"
834−checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
1655+checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
8351656dependencies = [
8361657 "proc-macro2",
8371658]
@@ -840,18 +1661,40 @@ dependencies = [
8401661name = "rand"
8411662version = "0.8.5"
8421663source = "registry+https://github.com/rust-lang/crates.io-index"
843−checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
1664+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
1665+dependencies = [
1666+ "rand_core",
1667+]
1668+
1669+[[package]]
1670+name = "rand_core"
1671+version = "0.6.4"
1672+source = "registry+https://github.com/rust-lang/crates.io-index"
1673+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
1674+dependencies = [
1675+ "getrandom",
1676+]
1677+
1678+[[package]]
1679+name = "rayon"
1680+version = "1.7.0"
1681+source = "registry+https://github.com/rust-lang/crates.io-index"
1682+checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
8441683dependencies = [
845− "rand_core",
1684+ "either",
1685+ "rayon-core",
8461686]
8471687
8481688[[package]]
849−name = "rand_core"
850−version = "0.6.3"
1689+name = "rayon-core"
1690+version = "1.11.0"
8511691source = "registry+https://github.com/rust-lang/crates.io-index"
852−checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
1692+checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
8531693dependencies = [
854− "getrandom",
1694+ "crossbeam-channel",
1695+ "crossbeam-deque",
1696+ "crossbeam-utils",
1697+ "num_cpus",
8551698]
8561699
8571700[[package]]
@@ -860,67 +1703,81 @@ version = "0.2.16"
8601703source = "registry+https://github.com/rust-lang/crates.io-index"
8611704checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
8621705dependencies = [
863− "bitflags",
1706+ "bitflags 1.3.2",
8641707]
8651708
8661709[[package]]
867−name = "redox_users"
868−version = "0.4.3"
1710+name = "redox_syscall"
1711+version = "0.4.1"
8691712source = "registry+https://github.com/rust-lang/crates.io-index"
870−checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
1713+checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
8711714dependencies = [
872− "getrandom",
873− "redox_syscall",
874− "thiserror",
1715+ "bitflags 1.3.2",
8751716]
8761717
8771718[[package]]
8781719name = "regex"
879−version = "1.7.0"
1720+version = "1.10.2"
8801721source = "registry+https://github.com/rust-lang/crates.io-index"
881−checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
1722+checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
8821723dependencies = [
8831724 "aho-corasick",
8841725 "memchr",
1726+ "regex-automata",
8851727 "regex-syntax",
8861728]
8871729
8881730[[package]]
8891731name = "regex-automata"
890−version = "0.1.10"
1732+version = "0.4.3"
8911733source = "registry+https://github.com/rust-lang/crates.io-index"
892−checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
1734+checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
1735+dependencies = [
1736+ "aho-corasick",
1737+ "memchr",
1738+ "regex-syntax",
1739+]
8931740
8941741[[package]]
8951742name = "regex-syntax"
896−version = "0.6.27"
1743+version = "0.8.2"
8971744source = "registry+https://github.com/rust-lang/crates.io-index"
898−checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
1745+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
8991746
9001747[[package]]
901−name = "remove_dir_all"
902−version = "0.5.3"
1748+name = "ropey"
1749+version = "1.6.1"
9031750source = "registry+https://github.com/rust-lang/crates.io-index"
904−checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
1751+checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5"
9051752dependencies = [
906− "winapi",
1753+ "smallvec",
1754+ "str_indices",
9071755]
9081756
9091757[[package]]
910−name = "ropey"
911−version = "1.5.0"
1758+name = "rustc-demangle"
1759+version = "0.1.23"
1760+source = "registry+https://github.com/rust-lang/crates.io-index"
1761+checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
1762+
1763+[[package]]
1764+name = "rustix"
1765+version = "0.38.28"
9121766source = "registry+https://github.com/rust-lang/crates.io-index"
913−checksum = "bbd22239fafefc42138ca5da064f3c17726a80d2379d817a3521240e78dd0064"
1767+checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
9141768dependencies = [
915− "smallvec",
916− "str_indices",
1769+ "bitflags 2.4.1",
1770+ "errno",
1771+ "libc",
1772+ "linux-raw-sys",
1773+ "windows-sys 0.52.0",
9171774]
9181775
9191776[[package]]
9201777name = "ryu"
921−version = "1.0.11"
1778+version = "1.0.13"
9221779source = "registry+https://github.com/rust-lang/crates.io-index"
923−checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
1780+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
9241781
9251782[[package]]
9261783name = "same-file"
@@ -937,31 +1794,37 @@ version = "1.1.0"
9371794source = "registry+https://github.com/rust-lang/crates.io-index"
9381795checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
9391796
1797+[[package]]
1798+name = "scratch"
1799+version = "1.0.5"
1800+source = "registry+https://github.com/rust-lang/crates.io-index"
1801+checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
1802+
9401803[[package]]
9411804name = "serde"
942−version = "1.0.147"
1805+version = "1.0.193"
9431806source = "registry+https://github.com/rust-lang/crates.io-index"
944−checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
1807+checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
9451808dependencies = [
9461809 "serde_derive",
9471810]
9481811
9491812[[package]]
9501813name = "serde_derive"
951−version = "1.0.147"
1814+version = "1.0.193"
9521815source = "registry+https://github.com/rust-lang/crates.io-index"
953−checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
1816+checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
9541817dependencies = [
9551818 "proc-macro2",
9561819 "quote",
957− "syn",
1820+ "syn 2.0.38",
9581821]
9591822
9601823[[package]]
9611824name = "serde_json"
962−version = "1.0.87"
1825+version = "1.0.108"
9631826source = "registry+https://github.com/rust-lang/crates.io-index"
964−checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
1827+checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
9651828dependencies = [
9661829 "itoa",
9671830 "ryu",
@@ -970,20 +1833,35 @@ dependencies = [
9701833
9711834[[package]]
9721835name = "serde_repr"
973−version = "0.1.9"
1836+version = "0.1.12"
9741837source = "registry+https://github.com/rust-lang/crates.io-index"
975−checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca"
1838+checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
9761839dependencies = [
9771840 "proc-macro2",
9781841 "quote",
979− "syn",
1842+ "syn 2.0.38",
1843+]
1844+
1845+[[package]]
1846+name = "serde_spanned"
1847+version = "0.6.3"
1848+source = "registry+https://github.com/rust-lang/crates.io-index"
1849+checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
1850+dependencies = [
1851+ "serde",
9801852]
9811853
1854+[[package]]
1855+name = "sha1_smol"
1856+version = "1.0.0"
1857+source = "registry+https://github.com/rust-lang/crates.io-index"
1858+checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
1859+
9821860[[package]]
9831861name = "signal-hook"
984−version = "0.3.14"
1862+version = "0.3.17"
9851863source = "registry+https://github.com/rust-lang/crates.io-index"
986−checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
1864+checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
9871865dependencies = [
9881866 "libc",
9891867 "signal-hook-registry",
@@ -1002,9 +1880,9 @@ dependencies = [
10021880
10031881[[package]]
10041882name = "signal-hook-registry"
1005−version = "1.4.0"
1883+version = "1.4.1"
10061884source = "registry+https://github.com/rust-lang/crates.io-index"
1007−checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
1885+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
10081886dependencies = [
10091887 "libc",
10101888]
@@ -1021,35 +1899,29 @@ dependencies = [
10211899 "tokio",
10221900]
10231901
1024−[[package]]
1025−name = "similar"
1026−version = "2.2.0"
1027−source = "registry+https://github.com/rust-lang/crates.io-index"
1028−checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803"
1029−
10301902[[package]]
10311903name = "slab"
1032−version = "0.4.7"
1904+version = "0.4.8"
10331905source = "registry+https://github.com/rust-lang/crates.io-index"
1034−checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
1906+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
10351907dependencies = [
10361908 "autocfg",
10371909]
10381910
10391911[[package]]
10401912name = "slotmap"
1041−version = "1.0.6"
1913+version = "1.0.7"
10421914source = "registry+https://github.com/rust-lang/crates.io-index"
1043−checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
1915+checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
10441916dependencies = [
10451917 "version_check",
10461918]
10471919
10481920[[package]]
10491921name = "smallvec"
1050−version = "1.10.0"
1922+version = "1.11.2"
10511923source = "registry+https://github.com/rust-lang/crates.io-index"
1052−checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
1924+checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
10531925
10541926[[package]]
10551927name = "smartstring"
@@ -1070,12 +1942,12 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
10701942
10711943[[package]]
10721944name = "socket2"
1073−version = "0.4.7"
1945+version = "0.5.5"
10741946source = "registry+https://github.com/rust-lang/crates.io-index"
1075−checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
1947+checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
10761948dependencies = [
10771949 "libc",
1078− "winapi",
1950+ "windows-sys 0.48.0",
10791951]
10801952
10811953[[package]]
@@ -1085,22 +1957,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
10851957checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
10861958
10871959[[package]]
1088−name = "str-buf"
1089−version = "1.0.6"
1960+name = "str_indices"
1961+version = "0.4.1"
10901962source = "registry+https://github.com/rust-lang/crates.io-index"
1091−checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
1963+checksum = "5f026164926842ec52deb1938fae44f83dfdb82d0a5b0270c5bd5935ab74d6dd"
10921964
10931965[[package]]
1094−name = "str_indices"
1095−version = "0.4.0"
1966+name = "syn"
1967+version = "1.0.109"
10961968source = "registry+https://github.com/rust-lang/crates.io-index"
1097−checksum = "9d9199fa80c817e074620be84374a520062ebac833f358d74b37060ce4a0f2c0"
1969+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
1970+dependencies = [
1971+ "proc-macro2",
1972+ "quote",
1973+ "unicode-ident",
1974+]
10981975
10991976[[package]]
11001977name = "syn"
1101−version = "1.0.99"
1978+version = "2.0.38"
11021979source = "registry+https://github.com/rust-lang/crates.io-index"
1103−checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
1980+checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
11041981dependencies = [
11051982 "proc-macro2",
11061983 "quote",
@@ -1109,25 +1986,33 @@ dependencies = [
11091986
11101987[[package]]
11111988name = "tempfile"
1112−version = "3.3.0"
1989+version = "3.8.1"
11131990source = "registry+https://github.com/rust-lang/crates.io-index"
1114−checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
1991+checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
11151992dependencies = [
11161993 "cfg-if",
11171994 "fastrand",
1118− "libc",
1119− "redox_syscall",
1120− "remove_dir_all",
1121− "winapi",
1995+ "redox_syscall 0.4.1",
1996+ "rustix",
1997+ "windows-sys 0.48.0",
1998+]
1999+
2000+[[package]]
2001+name = "termcolor"
2002+version = "1.2.0"
2003+source = "registry+https://github.com/rust-lang/crates.io-index"
2004+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
2005+dependencies = [
2006+ "winapi-util",
11222007]
11232008
11242009[[package]]
11252010name = "termini"
1126−version = "0.1.4"
2011+version = "1.0.0"
11272012source = "registry+https://github.com/rust-lang/crates.io-index"
1128−checksum = "8c0f7ecb9c2a380d2686a747e4fc574043712326e8d39fbd220ab3bd29768a12"
2013+checksum = "2ad441d87dd98bc5eeb31cf2fb7e4839968763006b478efb38668a3bf9da0d59"
11292014dependencies = [
1130− "dirs-next",
2015+ "home",
11312016]
11322017
11332018[[package]]
@@ -1143,40 +2028,60 @@ dependencies = [
11432028
11442029[[package]]
11452030name = "thiserror"
1146−version = "1.0.37"
2031+version = "1.0.52"
11472032source = "registry+https://github.com/rust-lang/crates.io-index"
1148−checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
2033+checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
11492034dependencies = [
11502035 "thiserror-impl",
11512036]
11522037
11532038[[package]]
11542039name = "thiserror-impl"
1155−version = "1.0.37"
2040+version = "1.0.52"
11562041source = "registry+https://github.com/rust-lang/crates.io-index"
1157−checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
2042+checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
11582043dependencies = [
11592044 "proc-macro2",
11602045 "quote",
1161− "syn",
2046+ "syn 2.0.38",
11622047]
11632048
11642049[[package]]
1165−name = "thread_local"
1166−version = "1.1.4"
2050+name = "threadpool"
2051+version = "1.8.1"
11672052source = "registry+https://github.com/rust-lang/crates.io-index"
1168−checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
2053+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
11692054dependencies = [
1170− "once_cell",
2055+ "num_cpus",
11712056]
11722057
11732058[[package]]
1174−name = "threadpool"
1175−version = "1.8.1"
2059+name = "time"
2060+version = "0.3.23"
11762061source = "registry+https://github.com/rust-lang/crates.io-index"
1177−checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
2062+checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446"
11782063dependencies = [
1179− "num_cpus",
2064+ "itoa",
2065+ "libc",
2066+ "num_threads",
2067+ "serde",
2068+ "time-core",
2069+ "time-macros",
2070+]
2071+
2072+[[package]]
2073+name = "time-core"
2074+version = "0.1.1"
2075+source = "registry+https://github.com/rust-lang/crates.io-index"
2076+checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
2077+
2078+[[package]]
2079+name = "time-macros"
2080+version = "0.2.10"
2081+source = "registry+https://github.com/rust-lang/crates.io-index"
2082+checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4"
2083+dependencies = [
2084+ "time-core",
11802085]
11812086
11822087[[package]]
@@ -1190,20 +2095,19 @@ dependencies = [
11902095
11912096[[package]]
11922097name = "tinyvec_macros"
1193−version = "0.1.0"
2098+version = "0.1.1"
11942099source = "registry+https://github.com/rust-lang/crates.io-index"
1195−checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
2100+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
11962101
11972102[[package]]
11982103name = "tokio"
1199−version = "1.21.2"
2104+version = "1.35.1"
12002105source = "registry+https://github.com/rust-lang/crates.io-index"
1201−checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
2106+checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
12022107dependencies = [
1203− "autocfg",
2108+ "backtrace",
12042109 "bytes",
12052110 "libc",
1206− "memchr",
12072111 "mio",
12082112 "num_cpus",
12092113 "parking_lot",
@@ -1211,25 +2115,25 @@ dependencies = [
12112115 "signal-hook-registry",
12122116 "socket2",
12132117 "tokio-macros",
1214− "winapi",
2118+ "windows-sys 0.48.0",
12152119]
12162120
12172121[[package]]
12182122name = "tokio-macros"
1219−version = "1.8.0"
2123+version = "2.2.0"
12202124source = "registry+https://github.com/rust-lang/crates.io-index"
1221−checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
2125+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
12222126dependencies = [
12232127 "proc-macro2",
12242128 "quote",
1225− "syn",
2129+ "syn 2.0.38",
12262130]
12272131
12282132[[package]]
12292133name = "tokio-stream"
1230−version = "0.1.11"
2134+version = "0.1.14"
12312135source = "registry+https://github.com/rust-lang/crates.io-index"
1232−checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce"
2136+checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
12332137dependencies = [
12342138 "futures-core",
12352139 "pin-project-lite",
@@ -1238,18 +2142,42 @@ dependencies = [
12382142
12392143[[package]]
12402144name = "toml"
1241−version = "0.5.9"
2145+version = "0.7.6"
12422146source = "registry+https://github.com/rust-lang/crates.io-index"
1243−checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
2147+checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
12442148dependencies = [
12452149 "serde",
2150+ "serde_spanned",
2151+ "toml_datetime",
2152+ "toml_edit",
12462153]
12472154
12482155[[package]]
1249−name = "tree-sitter"
1250−version = "0.20.9"
2156+name = "toml_datetime"
2157+version = "0.6.3"
2158+source = "registry+https://github.com/rust-lang/crates.io-index"
2159+checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
2160+dependencies = [
2161+ "serde",
2162+]
2163+
2164+[[package]]
2165+name = "toml_edit"
2166+version = "0.19.12"
12512167source = "registry+https://github.com/rust-lang/crates.io-index"
1252−checksum = "d4423c784fe11398ca91e505cdc71356b07b1a924fc8735cfab5333afe3e18bc"
2168+checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
2169+dependencies = [
2170+ "indexmap",
2171+ "serde",
2172+ "serde_spanned",
2173+ "toml_datetime",
2174+ "winnow 0.4.6",
2175+]
2176+
2177+[[package]]
2178+name = "tree-sitter"
2179+version = "0.20.10"
2180+source = "git+https://github.com/tree-sitter/tree-sitter?rev=ab09ae20d640711174b8da8a654f6b3dec93da1a#ab09ae20d640711174b8da8a654f6b3dec93da1a"
12532181dependencies = [
12542182 "cc",
12552183 "regex",
@@ -1266,9 +2194,15 @@ dependencies = [
12662194
12672195[[package]]
12682196name = "unicode-bidi"
1269−version = "0.3.8"
2197+version = "0.3.13"
2198+source = "registry+https://github.com/rust-lang/crates.io-index"
2199+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
2200+
2201+[[package]]
2202+name = "unicode-bom"
2203+version = "2.0.2"
12702204source = "registry+https://github.com/rust-lang/crates.io-index"
1271−checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
2205+checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552"
12722206
12732207[[package]]
12742208name = "unicode-general-category"
@@ -1278,46 +2212,42 @@ checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7"
12782212
12792213[[package]]
12802214name = "unicode-ident"
1281−version = "1.0.3"
2215+version = "1.0.8"
12822216source = "registry+https://github.com/rust-lang/crates.io-index"
1283−checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
2217+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
12842218
12852219[[package]]
12862220name = "unicode-linebreak"
1287−version = "0.1.4"
2221+version = "0.1.5"
12882222source = "registry+https://github.com/rust-lang/crates.io-index"
1289−checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
1290−dependencies = [
1291− "hashbrown",
1292− "regex",
1293−]
2223+checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
12942224
12952225[[package]]
12962226name = "unicode-normalization"
1297−version = "0.1.21"
2227+version = "0.1.22"
12982228source = "registry+https://github.com/rust-lang/crates.io-index"
1299−checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6"
2229+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
13002230dependencies = [
13012231 "tinyvec",
13022232]
13032233
13042234[[package]]
13052235name = "unicode-segmentation"
1306−version = "1.10.0"
2236+version = "1.10.1"
13072237source = "registry+https://github.com/rust-lang/crates.io-index"
1308−checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
2238+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
13092239
13102240[[package]]
13112241name = "unicode-width"
1312−version = "0.1.10"
2242+version = "0.1.11"
13132243source = "registry+https://github.com/rust-lang/crates.io-index"
1314−checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
2244+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
13152245
13162246[[package]]
13172247name = "url"
1318−version = "2.3.1"
2248+version = "2.5.0"
13192249source = "registry+https://github.com/rust-lang/crates.io-index"
1320−checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
2250+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
13212251dependencies = [
13222252 "form_urlencoded",
13232253 "idna",
@@ -1333,12 +2263,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
13332263
13342264[[package]]
13352265name = "walkdir"
1336−version = "2.3.2"
2266+version = "2.4.0"
13372267source = "registry+https://github.com/rust-lang/crates.io-index"
1338−checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
2268+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
13392269dependencies = [
13402270 "same-file",
1341− "winapi",
13422271 "winapi-util",
13432272]
13442273
@@ -1350,9 +2279,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
13502279
13512280[[package]]
13522281name = "wasm-bindgen"
1353−version = "0.2.82"
2282+version = "0.2.84"
13542283source = "registry+https://github.com/rust-lang/crates.io-index"
1355−checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
2284+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
13562285dependencies = [
13572286 "cfg-if",
13582287 "wasm-bindgen-macro",
@@ -1360,24 +2289,24 @@ dependencies = [
13602289
13612290[[package]]
13622291name = "wasm-bindgen-backend"
1363−version = "0.2.82"
2292+version = "0.2.84"
13642293source = "registry+https://github.com/rust-lang/crates.io-index"
1365−checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
2294+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
13662295dependencies = [
13672296 "bumpalo",
13682297 "log",
13692298 "once_cell",
13702299 "proc-macro2",
13712300 "quote",
1372− "syn",
2301+ "syn 1.0.109",
13732302 "wasm-bindgen-shared",
13742303]
13752304
13762305[[package]]
13772306name = "wasm-bindgen-macro"
1378−version = "0.2.82"
2307+version = "0.2.84"
13792308source = "registry+https://github.com/rust-lang/crates.io-index"
1380−checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
2309+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
13812310dependencies = [
13822311 "quote",
13832312 "wasm-bindgen-macro-support",
@@ -1385,32 +2314,34 @@ dependencies = [
13852314
13862315[[package]]
13872316name = "wasm-bindgen-macro-support"
1388−version = "0.2.82"
2317+version = "0.2.84"
13892318source = "registry+https://github.com/rust-lang/crates.io-index"
1390−checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
2319+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
13912320dependencies = [
13922321 "proc-macro2",
13932322 "quote",
1394− "syn",
2323+ "syn 1.0.109",
13952324 "wasm-bindgen-backend",
13962325 "wasm-bindgen-shared",
13972326]
13982327
13992328[[package]]
14002329name = "wasm-bindgen-shared"
1401−version = "0.2.82"
2330+version = "0.2.84"
14022331source = "registry+https://github.com/rust-lang/crates.io-index"
1403−checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
2332+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
14042333
14052334[[package]]
14062335name = "which"
1407−version = "4.3.0"
2336+version = "5.0.0"
14082337source = "registry+https://github.com/rust-lang/crates.io-index"
1409−checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
2338+checksum = "9bf3ea8596f3a0dd5980b46430f2058dfe2c36a27ccfbb1845d6fbfcd9ba6e14"
14102339dependencies = [
14112340 "either",
1412− "libc",
2341+ "home",
14132342 "once_cell",
2343+ "rustix",
2344+ "windows-sys 0.48.0",
14142345]
14152346
14162347[[package]]
@@ -1444,52 +2375,253 @@ version = "0.4.0"
14442375source = "registry+https://github.com/rust-lang/crates.io-index"
14452376checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
14462377
2378+[[package]]
2379+name = "windows"
2380+version = "0.48.0"
2381+source = "registry+https://github.com/rust-lang/crates.io-index"
2382+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
2383+dependencies = [
2384+ "windows-targets 0.48.0",
2385+]
2386+
2387+[[package]]
2388+name = "windows"
2389+version = "0.52.0"
2390+source = "registry+https://github.com/rust-lang/crates.io-index"
2391+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
2392+dependencies = [
2393+ "windows-core",
2394+ "windows-targets 0.52.0",
2395+]
2396+
2397+[[package]]
2398+name = "windows-core"
2399+version = "0.52.0"
2400+source = "registry+https://github.com/rust-lang/crates.io-index"
2401+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
2402+dependencies = [
2403+ "windows-targets 0.52.0",
2404+]
2405+
2406+[[package]]
2407+name = "windows-sys"
2408+version = "0.45.0"
2409+source = "registry+https://github.com/rust-lang/crates.io-index"
2410+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
2411+dependencies = [
2412+ "windows-targets 0.42.2",
2413+]
2414+
14472415[[package]]
14482416name = "windows-sys"
1449−version = "0.36.1"
2417+version = "0.48.0"
2418+source = "registry+https://github.com/rust-lang/crates.io-index"
2419+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
2420+dependencies = [
2421+ "windows-targets 0.48.0",
2422+]
2423+
2424+[[package]]
2425+name = "windows-sys"
2426+version = "0.52.0"
2427+source = "registry+https://github.com/rust-lang/crates.io-index"
2428+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
2429+dependencies = [
2430+ "windows-targets 0.52.0",
2431+]
2432+
2433+[[package]]
2434+name = "windows-targets"
2435+version = "0.42.2"
2436+source = "registry+https://github.com/rust-lang/crates.io-index"
2437+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
2438+dependencies = [
2439+ "windows_aarch64_gnullvm 0.42.2",
2440+ "windows_aarch64_msvc 0.42.2",
2441+ "windows_i686_gnu 0.42.2",
2442+ "windows_i686_msvc 0.42.2",
2443+ "windows_x86_64_gnu 0.42.2",
2444+ "windows_x86_64_gnullvm 0.42.2",
2445+ "windows_x86_64_msvc 0.42.2",
2446+]
2447+
2448+[[package]]
2449+name = "windows-targets"
2450+version = "0.48.0"
14502451source = "registry+https://github.com/rust-lang/crates.io-index"
1451−checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
2452+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
14522453dependencies = [
1453− "windows_aarch64_msvc",
1454− "windows_i686_gnu",
1455− "windows_i686_msvc",
1456− "windows_x86_64_gnu",
1457− "windows_x86_64_msvc",
2454+ "windows_aarch64_gnullvm 0.48.0",
2455+ "windows_aarch64_msvc 0.48.0",
2456+ "windows_i686_gnu 0.48.0",
2457+ "windows_i686_msvc 0.48.0",
2458+ "windows_x86_64_gnu 0.48.0",
2459+ "windows_x86_64_gnullvm 0.48.0",
2460+ "windows_x86_64_msvc 0.48.0",
14582461]
14592462
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+
2478+[[package]]
2479+name = "windows_aarch64_gnullvm"
2480+version = "0.42.2"
2481+source = "registry+https://github.com/rust-lang/crates.io-index"
2482+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
2483+
2484+[[package]]
2485+name = "windows_aarch64_gnullvm"
2486+version = "0.48.0"
2487+source = "registry+https://github.com/rust-lang/crates.io-index"
2488+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
2489+
2490+[[package]]
2491+name = "windows_aarch64_gnullvm"
2492+version = "0.52.0"
2493+source = "registry+https://github.com/rust-lang/crates.io-index"
2494+checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
2495+
2496+[[package]]
2497+name = "windows_aarch64_msvc"
2498+version = "0.42.2"
2499+source = "registry+https://github.com/rust-lang/crates.io-index"
2500+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
2501+
2502+[[package]]
2503+name = "windows_aarch64_msvc"
2504+version = "0.48.0"
2505+source = "registry+https://github.com/rust-lang/crates.io-index"
2506+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
2507+
14602508[[package]]
14612509name = "windows_aarch64_msvc"
1462−version = "0.36.1"
2510+version = "0.52.0"
2511+source = "registry+https://github.com/rust-lang/crates.io-index"
2512+checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
2513+
2514+[[package]]
2515+name = "windows_i686_gnu"
2516+version = "0.42.2"
2517+source = "registry+https://github.com/rust-lang/crates.io-index"
2518+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
2519+
2520+[[package]]
2521+name = "windows_i686_gnu"
2522+version = "0.48.0"
14632523source = "registry+https://github.com/rust-lang/crates.io-index"
1464−checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
2524+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
14652525
14662526[[package]]
14672527name = "windows_i686_gnu"
1468−version = "0.36.1"
2528+version = "0.52.0"
2529+source = "registry+https://github.com/rust-lang/crates.io-index"
2530+checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
2531+
2532+[[package]]
2533+name = "windows_i686_msvc"
2534+version = "0.42.2"
2535+source = "registry+https://github.com/rust-lang/crates.io-index"
2536+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
2537+
2538+[[package]]
2539+name = "windows_i686_msvc"
2540+version = "0.48.0"
14692541source = "registry+https://github.com/rust-lang/crates.io-index"
1470−checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
2542+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
14712543
14722544[[package]]
14732545name = "windows_i686_msvc"
1474−version = "0.36.1"
2546+version = "0.52.0"
2547+source = "registry+https://github.com/rust-lang/crates.io-index"
2548+checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
2549+
2550+[[package]]
2551+name = "windows_x86_64_gnu"
2552+version = "0.42.2"
2553+source = "registry+https://github.com/rust-lang/crates.io-index"
2554+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
2555+
2556+[[package]]
2557+name = "windows_x86_64_gnu"
2558+version = "0.48.0"
14752559source = "registry+https://github.com/rust-lang/crates.io-index"
1476−checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
2560+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
14772561
14782562[[package]]
14792563name = "windows_x86_64_gnu"
1480−version = "0.36.1"
2564+version = "0.52.0"
2565+source = "registry+https://github.com/rust-lang/crates.io-index"
2566+checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
2567+
2568+[[package]]
2569+name = "windows_x86_64_gnullvm"
2570+version = "0.42.2"
2571+source = "registry+https://github.com/rust-lang/crates.io-index"
2572+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
2573+
2574+[[package]]
2575+name = "windows_x86_64_gnullvm"
2576+version = "0.48.0"
2577+source = "registry+https://github.com/rust-lang/crates.io-index"
2578+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
2579+
2580+[[package]]
2581+name = "windows_x86_64_gnullvm"
2582+version = "0.52.0"
2583+source = "registry+https://github.com/rust-lang/crates.io-index"
2584+checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
2585+
2586+[[package]]
2587+name = "windows_x86_64_msvc"
2588+version = "0.42.2"
2589+source = "registry+https://github.com/rust-lang/crates.io-index"
2590+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
2591+
2592+[[package]]
2593+name = "windows_x86_64_msvc"
2594+version = "0.48.0"
14812595source = "registry+https://github.com/rust-lang/crates.io-index"
1482−checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
2596+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
14832597
14842598[[package]]
14852599name = "windows_x86_64_msvc"
1486−version = "0.36.1"
2600+version = "0.52.0"
2601+source = "registry+https://github.com/rust-lang/crates.io-index"
2602+checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
2603+
2604+[[package]]
2605+name = "winnow"
2606+version = "0.4.6"
2607+source = "registry+https://github.com/rust-lang/crates.io-index"
2608+checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
2609+dependencies = [
2610+ "memchr",
2611+]
2612+
2613+[[package]]
2614+name = "winnow"
2615+version = "0.5.28"
14872616source = "registry+https://github.com/rust-lang/crates.io-index"
1488−checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
2617+checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2"
2618+dependencies = [
2619+ "memchr",
2620+]
14892621
14902622[[package]]
14912623name = "xtask"
1492−version = "0.6.0"
2624+version = "23.10.0"
14932625dependencies = [
14942626 "helix-core",
14952627 "helix-loader",
@@ -1497,3 +2629,23 @@ dependencies = [
14972629 "helix-view",
14982630 "toml",
14992631]
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+24
@@ -1,12 +1,16 @@
11[workspace]
2+resolver = "2"
23members = [
34 "helix-core",
45 "helix-view",
56 "helix-term",
67 "helix-tui",
78 "helix-lsp",
9+ "helix-event",
810 "helix-dap",
911 "helix-loader",
12+ "helix-vcs",
13+ "helix-parsec",
1014 "xtask",
1115]
1216
@@ -24,3 +28,23 @@ lto = "fat"
2428codegen-units = 1
2529# strip = "debuginfo" # TODO: or strip = true
2630opt-level = 3
31+
32+[profile.integration]
33+inherits = "test"
34+package.helix-core.opt-level = 2
35+package.helix-tui.opt-level = 2
36+package.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+21−85
@@ -1,10 +1,24 @@
1−# Helix
1+<div align="center">
2+
3+<h1>
4+<picture>
5+ <source media="(prefers-color-scheme: dark)" srcset="logo_dark.svg">
6+ <source media="(prefers-color-scheme: light)" srcset="logo_light.svg">
7+ <img alt="Helix" height="128" src="logo_light.svg">
8+</picture>
9+</h1>
210
311[![Build status](https://github.com/helix-editor/helix/actions/workflows/build.yml/badge.svg)](https://github.com/helix-editor/helix/actions)
12+[![GitHub Release](https://img.shields.io/github/v/release/helix-editor/helix)](https://github.com/helix-editor/helix/releases/latest)
13+[![Documentation](https://shields.io/badge/-documentation-452859)](https://docs.helix-editor.com/)
14+[![GitHub contributors](https://img.shields.io/github/contributors/helix-editor/helix)](https://github.com/helix-editor/helix/graphs/contributors)
15+[![Matrix Space](https://img.shields.io/matrix/helix-community:matrix.org)](https://matrix.to/#/#helix-community:matrix.org)
16+
17+</div>
418
519![Screenshot](./screenshot.png)
620
7−A Kakoune / Neovim inspired editor, written in Rust.
21+A [Kakoune](https://github.com/mawww/kakoune) / [Neovim](https://github.com/neovim/neovim) inspired editor, written in Rust.
822
923The editing model is very heavily based on Kakoune; during development I found
1024myself agreeing with most of Kakoune's design decisions.
@@ -31,92 +45,10 @@ Note: Only certain languages have indentation definitions at the moment. Check
3145
3246# Installation
3347
34−Packages are available for various distributions (see [Installation docs](https://docs.helix-editor.com/install.html)).
35−
36−If you would like to build from source:
37−
38−```shell
39−git clone https://github.com/helix-editor/helix
40−cd helix
41−cargo install --path helix-term
42−```
43−
44−This will install the `hx` binary to `$HOME/.cargo/bin` and build tree-sitter grammars in `./runtime/grammars`.
45−
46−Helix needs its runtime files so make sure to copy/symlink the `runtime/` directory into the
47−config directory (for example `~/.config/helix/runtime` on Linux/macOS, or `%AppData%/helix/runtime` on Windows).
48−
49−| OS | Command |
50−| -------------------- | ------------------------------------------------ |
51−| Windows (Cmd) | `xcopy /e /i runtime %AppData%\helix\runtime` |
52−| Windows (PowerShell) | `xcopy /e /i runtime $Env:AppData\helix\runtime` |
53−| Linux / MacOS | `ln -s $PWD/runtime ~/.config/helix/runtime` |
54−
55−Starting with Windows Vista you can also create symbolic links on Windows. Note that this requires
56−elevated priviliges - i.e. PowerShell or Cmd must be run as administrator.
57−
58−**PowerShell:**
59−
60−```powershell
61−New-Item -ItemType SymbolicLink -Target "runtime" -Path "$Env:AppData\helix\runtime"
62−```
63−
64−**Cmd:**
65−
66−```cmd
67−cd %appdata%\helix
68−mklink /D runtime "<helix-repo>\runtime"
69−```
70−
71−The runtime location can be overridden via the `HELIX_RUNTIME` environment variable.
72−
73−> NOTE: if `HELIX_RUNTIME` is set prior to calling `cargo install --path helix-term`,
74−> tree-sitter grammars will be built in `$HELIX_RUNTIME/grammars`.
75−
76−If you plan on keeping the repo locally, an alternative to copying/symlinking
77−runtime files is to set `HELIX_RUNTIME=/path/to/helix/runtime`
78−(`HELIX_RUNTIME=$PWD/runtime` if you're in the helix repo directory).
79−
80−Packages already solve this for you by wrapping the `hx` binary with a wrapper
81−that sets the variable to the install dir.
82−
83−> NOTE: running via cargo also doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically
84−> detect the `runtime` directory in the project root.
85−
86−If you want to customize your `languages.toml` config,
87−tree-sitter grammars may be manually fetched and built with `hx --grammar fetch` and `hx --grammar build`.
88−
89−In order to use LSP features like auto-complete, you will need to
90−[install the appropriate Language Server](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
91−for a language.
48+[Installation documentation](https://docs.helix-editor.com/install.html).
9249
9350[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
9451
95−## Adding Helix to your desktop environment
96−
97−If installing from source, to use Helix in desktop environments that supports [XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html), including Gnome and KDE, copy the provided `.desktop` file to the correct folder:
98−
99−```bash
100−cp contrib/Helix.desktop ~/.local/share/applications
101−```
102−
103−To use another terminal than the default, you will need to modify the `.desktop` file. For example, to use `kitty`:
104−
105−```bash
106−sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
107−sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
108−```
109−
110−Please note: there is no icon for Helix yet, so the system default will be used.
111−
112−## MacOS
113−
114−Helix can be installed on MacOS through homebrew:
115−
116−```
117−brew install helix
118−```
119−
12052# Contributing
12153
12254Contributing guidelines can be found [here](./docs/CONTRIBUTING.md).
@@ -126,3 +58,7 @@ Contributing guidelines can be found [here](./docs/CONTRIBUTING.md).
12658Your question might already be answered on the [FAQ](https://github.com/helix-editor/helix/wiki/FAQ).
12759
12860Discuss the project on the community [Matrix Space](https://matrix.to/#/#helix-community:matrix.org) (make sure to join `#helix-editor:matrix.org` if you're on a client that doesn't support Matrix Spaces yet).
61+
62+# Credits
63+
64+Thanks to [@jakenvac](https://github.com/jakenvac) for designing the logo!
VERSIONdeleted−1
@@ -1 +0,0 @@
1−22.08.1
base16_theme.toml+1
@@ -7,6 +7,7 @@
77"ui.linenr.selected" = { fg = "white", bg = "black", modifiers = ["bold"] }
88"ui.selection" = { fg = "black", bg = "blue" }
99"ui.selection.primary" = { fg = "white", bg = "blue" }
10+"ui.text.inactive" = { fg = "gray" }
1011"comment" = { fg = "gray" }
1112"ui.statusline" = { fg = "black", bg = "white" }
1213"ui.statusline.inactive" = { fg = "gray", bg = "white" }
book/book.toml+6−1
@@ -3,9 +3,14 @@ authors = ["Blaž Hrastnik"]
33language = "en"
44multilingual = false
55src = "src"
6−edit-url-template = "https://github.com/helix-editor/helix/tree/master/book/{path}?mode=edit"
76
87[output.html]
98cname = "docs.helix-editor.com"
109default-theme = "colibri"
1110preferred-dark-theme = "colibri"
11+git-repository-url = "https://github.com/helix-editor/helix"
12+edit-url-template = "https://github.com/helix-editor/helix/edit/master/book/{path}"
13+additional-css = ["custom.css"]
14+
15+[output.html.search]
16+use-boolean-and = true
book/custom.cssnew file+231
@@ -0,0 +1,231 @@
1+html {
2+ font-family: "Inter", sans-serif;
3+}
4+
5+.sidebar .sidebar-scrollbox {
6+ padding: 0;
7+}
8+
9+.chapter {
10+ margin: 0.25rem 0;
11+}
12+
13+.chapter li.chapter-item {
14+ line-height: initial;
15+ margin: 0;
16+ padding: 1rem 1.5rem;
17+}
18+
19+.chapter .section li.chapter-item {
20+ line-height: inherit;
21+ padding: .5rem .5rem 0 .5rem;
22+}
23+
24+.content {
25+ overflow-y: auto;
26+ padding: 0 15px;
27+ padding-bottom: 50px;
28+}
29+
30+/* 2 1.75 1.5 1.25 1 .875 */
31+.content h1 { font-size: 2em }
32+.content h2 { font-size: 1.75em }
33+.content h3 { font-size: 1.5em }
34+.content h4 { font-size: 1.25em }
35+.content h5 { font-size: 1em }
36+.content h6 { font-size: .875em }
37+
38+.content h1,
39+.content h2,
40+.content h3,
41+.content h4 {
42+ font-weight: 500;
43+ margin-top: 1.275em;
44+ margin-bottom: .875em;
45+}
46+
47+.content p,
48+.content ol,
49+.content ul,
50+.content table {
51+ margin-top: 0;
52+ margin-bottom: .875em;
53+}
54+
55+.content ul li {
56+ margin-bottom: .25rem;
57+}
58+
59+.content ul {
60+ list-style-type: square;
61+}
62+
63+.content ul ul,
64+.content ol ul {
65+ margin-bottom: .5rem;
66+}
67+
68+.content li p {
69+ margin-bottom: .5em;
70+}
71+
72+blockquote {
73+ margin: 1.5rem 0;
74+ padding: 1rem 1.5rem;
75+ color: var(--fg);
76+ opacity: .9;
77+ background-color: var(--quote-bg);
78+ border-left: 4px solid var(--quote-border);
79+ border-top: none;
80+ border-bottom: none;
81+}
82+
83+blockquote *:last-child {
84+ margin-bottom: 0;
85+}
86+
87+table {
88+ width: 100%;
89+}
90+
91+table thead th {
92+ padding: .75rem;
93+ text-align: left;
94+ font-weight: 500;
95+ line-height: 1.5;
96+ width: auto;
97+}
98+
99+table td {
100+ padding: .75rem;
101+ border: none;
102+}
103+
104+table thead tr {
105+ border: none;
106+ border-bottom: 2px var(--table-border-color) solid;
107+}
108+
109+table tbody tr {
110+ border-bottom: 1px var(--table-border-line) solid;
111+}
112+
113+table tbody tr:nth-child(2n) {
114+ background: unset;
115+}
116+
117+pre code.hljs {
118+ display: block;
119+ overflow-x: auto;
120+ padding: 1em;
121+}
122+
123+code.hljs {
124+ padding: 3px 5px;
125+}
126+
127+.colibri {
128+ --bg: #3b224c;
129+ --fg: #bcbdd0;
130+ --heading-fg: #fff;
131+
132+ --sidebar-bg: #281733;
133+ --sidebar-fg: #c8c9db;
134+ --sidebar-non-existent: #505274;
135+ --sidebar-active: #a4a0e8;
136+ --sidebar-spacer: #2d334f;
137+
138+ --scrollbar: var(--sidebar-fg);
139+
140+ --icons: #737480;
141+ --icons-hover: #b7b9cc;
142+
143+ /* --links: #a4a0e8; */
144+ --links: #ECCDBA;
145+
146+ --inline-code-color: hsl(48.7, 7.8%, 70%);
147+
148+ --theme-popup-bg: #161923;
149+ --theme-popup-border: #737480;
150+ --theme-hover: rgba(0, 0, 0, .2);
151+
152+ --quote-bg: #281733;
153+ --quote-border: hsl(226, 15%, 22%);
154+
155+ --table-border-color: hsl(226, 23%, 76%);
156+ --table-header-bg: hsla(226, 23%, 31%, 0);
157+ --table-alternate-bg: hsl(226, 23%, 14%);
158+ --table-border-line: hsla(201deg, 20%, 92%, 0.2);
159+
160+ --searchbar-border-color: #aaa;
161+ --searchbar-bg: #aeaec6;
162+ --searchbar-fg: #000;
163+ --searchbar-shadow-color: #aaa;
164+ --searchresults-header-fg: #5f5f71;
165+ --searchresults-border-color: #5c5c68;
166+ --searchresults-li-bg: #242430;
167+ --search-mark-bg: #a2cff5;
168+}
169+
170+.colibri .content .header {
171+ color: #fff;
172+}
173+
174+/* highlight.js theme, :where() is used to avoid increasing specificity */
175+
176+:where(.colibri) .hljs {
177+ background: #2f1e2e;
178+ color: #a39e9b;
179+}
180+
181+:where(.colibri) .hljs-comment,
182+:where(.colibri) .hljs-quote {
183+ color: #8d8687;
184+}
185+
186+:where(.colibri) .hljs-link,
187+:where(.colibri) .hljs-meta,
188+:where(.colibri) .hljs-name,
189+:where(.colibri) .hljs-regexp,
190+:where(.colibri) .hljs-selector-class,
191+:where(.colibri) .hljs-selector-id,
192+:where(.colibri) .hljs-tag,
193+:where(.colibri) .hljs-template-variable,
194+:where(.colibri) .hljs-variable {
195+ color: #ef6155;
196+}
197+
198+:where(.colibri) .hljs-built_in,
199+:where(.colibri) .hljs-deletion,
200+:where(.colibri) .hljs-literal,
201+:where(.colibri) .hljs-number,
202+:where(.colibri) .hljs-params,
203+:where(.colibri) .hljs-type {
204+ color: #f99b15;
205+}
206+
207+:where(.colibri) .hljs-attribute,
208+:where(.colibri) .hljs-section,
209+:where(.colibri) .hljs-title {
210+ color: #fec418;
211+}
212+
213+:where(.colibri) .hljs-addition,
214+:where(.colibri) .hljs-bullet,
215+:where(.colibri) .hljs-string,
216+:where(.colibri) .hljs-symbol {
217+ color: #48b685;
218+}
219+
220+:where(.colibri) .hljs-keyword,
221+:where(.colibri) .hljs-selector-tag {
222+ color: #815ba4;
223+}
224+
225+:where(.colibri) .hljs-emphasis {
226+ font-style: italic;
227+}
228+
229+:where(.colibri) .hljs-strong {
230+ font-weight: 700;
231+}
book/src/SUMMARY.md+6−5
@@ -6,13 +6,14 @@
66- [Usage](./usage.md)
77 - [Keymap](./keymap.md)
88 - [Commands](./commands.md)
9− - [Language Support](./lang-support.md)
9+ - [Language support](./lang-support.md)
1010- [Migrating from Vim](./from-vim.md)
1111- [Configuration](./configuration.md)
1212 - [Themes](./themes.md)
13− - [Key Remapping](./remapping.md)
13+ - [Key remapping](./remapping.md)
1414 - [Languages](./languages.md)
1515- [Guides](./guides/README.md)
16− - [Adding Languages](./guides/adding_languages.md)
17− - [Adding Textobject Queries](./guides/textobject.md)
18− - [Adding Indent Queries](./guides/indent.md)
16+ - [Adding languages](./guides/adding_languages.md)
17+ - [Adding textobject queries](./guides/textobject.md)
18+ - [Adding indent queries](./guides/indent.md)
19+ - [Adding injection queries](./guides/injection.md)
book/src/commands.md+1−1
@@ -1,5 +1,5 @@
11# Commands
22
3−Command mode can be activated by pressing `:`, similar to Vim. Built-in commands:
3+Command mode can be activated by pressing `:`. The built-in commands are:
44
55{{#include ./generated/typable-cmd.md}}
book/src/configuration.md+164−43
@@ -2,10 +2,10 @@
22
33To override global configuration parameters, create a `config.toml` file located in your config directory:
44
5−* Linux and Mac: `~/.config/helix/config.toml`
6−* Windows: `%AppData%\helix\config.toml`
5+- Linux and Mac: `~/.config/helix/config.toml`
6+- Windows: `%AppData%\helix\config.toml`
77
8−> Hint: You can easily open the config file by typing `:config-open` within Helix normal mode.
8+> 💡 You can easily open the config file by typing `:config-open` within Helix normal mode.
99
1010Example config:
1111
@@ -25,12 +25,13 @@ select = "underline"
2525hidden = false
2626```
2727
28−You may also specify a file to use for configuration with the `-c` or
29−`--config` CLI argument: `hx -c path/to/custom-config.toml`.
28+You can use a custom configuration file by specifying it with the `-c` or
29+`--config` command line argument, for example `hx -c path/to/custom-config.toml`.
30+Additionally, you can reload the configuration file by sending the USR1
31+signal to the Helix process on Unix operating systems, such as by using the command `pkill -USR1 hx`.
3032
31−It is also possible to trigger configuration file reloading by sending the `USR1`
32−signal to the helix process, e.g. via `pkill -USR1 hx`. This is only supported
33−on unix operating systems.
33+Finally, you can have a `config.toml` local to a project by putting it under a `.helix` directory in your repository.
34+Its settings will be merged with the configuration directory `config.toml` and the built-in configuration.
3435
3536## Editor
3637
@@ -38,25 +39,34 @@ on unix operating systems.
3839
3940| Key | Description | Default |
4041|--|--|---------|
41−| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling. | `5` |
42−| `mouse` | Enable mouse mode. | `true` |
43−| `middle-click-paste` | Middle click paste support. | `true` |
44−| `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` |
45−| `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
46−| `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers. | `absolute` |
47−| `cursorline` | Highlight all lines with a cursor. | `false` |
48−| `cursorcolumn` | Highlight all columns with a cursor. | `false` |
49−| `gutters` | Gutters to display: Available are `diagnostics` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "line-numbers"]` |
50−| `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
51−| `auto-format` | Enable automatic formatting on save. | `true` |
52−| `auto-save` | Enable automatic saving on focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal. | `false` |
53−| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
42+| `scrolloff` | Number of lines of padding around the edge of the screen when scrolling | `5` |
43+| `mouse` | Enable mouse mode | `true` |
44+| `middle-click-paste` | Middle click paste support | `true` |
45+| `scroll-lines` | Number of lines to scroll per scroll wheel step | `3` |
46+| `shell` | Shell to use when running external commands | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
47+| `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers | `absolute` |
48+| `cursorline` | Highlight all lines with a cursor | `false` |
49+| `cursorcolumn` | Highlight all columns with a cursor | `false` |
50+| `gutters` | Gutters to display: Available are `diagnostics` and `diff` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
51+| `auto-completion` | Enable automatic pop up of auto-completion | `true` |
52+| `auto-format` | Enable automatic formatting on save | `true` |
53+| `auto-save` | Enable automatic saving on the focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal | `false` |
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` |
5456| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
55−| `auto-info` | Whether to display infoboxes | `true` |
56−| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. | `false` |
57−| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file. | `[]` |
57+| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
58+| `auto-info` | Whether to display info boxes | `true` |
59+| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative | `false` |
60+| `undercurl` | Set to `true` to override automatic detection of terminal undercurl support in the event of a false negative | `false` |
61+| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file | `[]` |
5862| `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` |
5963| `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` |
64+| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set | `80` |
65+| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml` | `[]` |
66+| `default-line-ending` | The line ending to use for new documents. Can be `native`, `lf`, `crlf`, `ff`, `cr` or `nel`. `native` uses the platform's native line ending (`crlf` on Windows, otherwise `lf`). | `native` |
67+| `insert-final-newline` | Whether to automatically insert a trailing line-ending on write if missing | `true` |
68+| `popup-border` | Draw border around `popup`, `menu`, `all`, or `none` | `none` |
69+| `indent-heuristic` | How the indentation for a newly inserted line is computed: `simple` just copies the indentation level from the previous line, `tree-sitter` computes the indentation based on the syntax tree and `hybrid` combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being `hybrid` -> `tree-sitter` -> `simple`). | `hybrid`
6070
6171### `[editor.statusline]` Section
6272
@@ -82,9 +92,9 @@ The `[editor.statusline]` key takes the following sub-keys:
8292
8393| Key | Description | Default |
8494| --- | --- | --- |
85−| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name"]` |
95+| `left` | A list of elements aligned to the left of the statusline | `["mode", "spinner", "file-name", "read-only-indicator", "file-modification-indicator"]` |
8696| `center` | A list of elements aligned to the middle of the statusline | `[]` |
87−| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "position", "file-encoding"]` |
97+| `right` | A list of elements aligned to the right of the statusline | `["diagnostics", "selections", "register", "position", "file-encoding"]` |
8898| `separator` | The character used to separate elements in the statusline | `"│"` |
8999| `mode.normal` | The text shown in the `mode` element for normal mode | `"NOR"` |
90100| `mode.insert` | The text shown in the `mode` element for insert mode | `"INS"` |
@@ -97,33 +107,48 @@ The following statusline elements can be configured:
97107| `mode` | The current editor mode (`mode.normal`/`mode.insert`/`mode.select`) |
98108| `spinner` | A progress spinner indicating LSP activity |
99109| `file-name` | The path/name of the opened file |
110+| `file-base-name` | The basename of the opened file |
111+| `file-modification-indicator` | The indicator to show whether the file is modified (a `[+]` appears when there are unsaved changes) |
100112| `file-encoding` | The encoding of the opened file if it differs from UTF-8 |
101113| `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 |
102115| `total-line-numbers` | The total line numbers of the opened file |
103116| `file-type` | The type of the opened file |
104117| `diagnostics` | The number of warnings and/or errors |
118+| `workspace-diagnostics` | The number of warnings and/or errors on workspace |
105119| `selections` | The number of active selections |
120+| `primary-selection-length` | The number of characters currently in primary selection |
106121| `position` | The cursor position |
107122| `position-percentage` | The cursor position as a percentage of the total number of lines |
108123| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
109124| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
125+| `version-control` | The current branch name or detached commit hash of the opened workspace |
126+| `register` | The current selected register |
110127
111128### `[editor.lsp]` Section
112129
113130| Key | Description | Default |
114131| --- | ----------- | ------- |
132+| `enable` | Enables LSP integration. Setting to false will completely disable language servers regardless of language settings.| `true` |
115133| `display-messages` | Display LSP progress messages below statusline[^1] | `false` |
116134| `auto-signature-help` | Enable automatic popup of signature help (parameter hints) | `true` |
135+| `display-inlay-hints` | Display inlay hints[^2] | `false` |
117136| `display-signature-help-docs` | Display docs under signature help popup | `true` |
137+| `snippets` | Enables snippet completions. Requires a server restart (`:lsp-restart`) to take effect after `:config-reload`/`:set`. | `true` |
138+| `goto-reference-include-declaration` | Include declaration in the goto references popup. | `true` |
118139
119140[^1]: By default, a progress spinner is shown in the statusline beside the file path.
120141
142+[^2]: You may also have to activate them in the LSP config for them to appear, not just in Helix. Inlay hints in Helix are still being improved on and may be a little bit laggy/janky under some circumstances. Please report any bugs you see so we can fix them!
143+
121144### `[editor.cursor-shape]` Section
122145
123−Defines the shape of cursor in each mode. Note that due to limitations
124−of the terminal environment, only the primary cursor can change shape.
146+Defines the shape of cursor in each mode.
125147Valid values for these options are `block`, `bar`, `underline`, or `hidden`.
126148
149+> 💡 Due to limitations of the terminal environment, only the primary cursor can
150+> change shape.
151+
127152| Key | Description | Default |
128153| --- | ----------- | ------- |
129154| `normal` | Cursor shape in [normal mode][normal mode] | `block` |
@@ -136,23 +161,37 @@ Valid values for these options are `block`, `bar`, `underline`, or `hidden`.
136161
137162### `[editor.file-picker]` Section
138163
139−Sets options for file picker and global search. All but the last key listed in
140−the default file-picker configuration below are IgnoreOptions: whether hidden
141−files and files listed within ignore files are ignored by (not visible in) the
142−helix file picker and global search. There is also one other key, `max-depth`
143−available, which is not defined by default.
164+Set options for file picker and global search. Ignoring a file means it is
165+not visible in the Helix file picker and global search.
144166
145167All git related options are only enabled in a git repository.
146168
147169| Key | Description | Default |
148170|--|--|---------|
149−|`hidden` | Enables ignoring hidden files. | true
150−|`parents` | Enables reading ignore files from parent directories. | true
151−|`ignore` | Enables reading `.ignore` files. | true
152−|`git-ignore` | Enables reading `.gitignore` files. | true
153−|`git-global` | Enables reading global .gitignore, whose path is specified in git's config: `core.excludefile` option. | true
154−|`git-exclude` | Enables reading `.git/info/exclude` files. | true
155−|`max-depth` | Set with an integer value for maximum depth to recurse. | Defaults to `None`.
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`
179+|`max-depth` | Set with an integer value for maximum depth to recurse | Defaults to `None`.
180+
181+Ignore files can be placed locally as `.ignore` or put in your home directory as `~/.ignore`. They support the usual ignore and negative ignore (unignore) rules used in `.gitignore` files.
182+
183+Additionally, you can use Helix-specific ignore files by creating a local `.helix/ignore` file in the current workspace or a global `ignore` file located in your Helix config directory:
184+- Linux and Mac: `~/.config/helix/ignore`
185+- Windows: `%AppData%\helix\ignore`
186+
187+Example:
188+
189+```ini
190+# unignore in file picker and global search
191+!.github/
192+!.gitignore
193+!.gitattributes
194+```
156195
157196### `[editor.auto-pairs]` Section
158197
@@ -204,7 +243,7 @@ Search specific options.
204243
205244| Key | Description | Default |
206245|--|--|---------|
207−| `smart-case` | Enable smart case regex searching (case insensitive unless pattern contains upper case characters) | `true` |
246+| `smart-case` | Enable smart case regex searching (case-insensitive unless pattern contains upper case characters) | `true` |
208247| `wrap-around`| Whether the search should wrap after depleting the matches | `true` |
209248
210249### `[editor.whitespace]` Section
@@ -213,7 +252,7 @@ Options for rendering whitespace with visible characters. Use `:set whitespace.r
213252
214253| Key | Description | Default |
215254|-----|-------------|---------|
216−| `render` | Whether to render whitespace. May either be `"all"` or `"none"`, or a table with sub-keys `space`, `tab`, and `newline`. | `"none"` |
255+| `render` | Whether to render whitespace. May either be `"all"` or `"none"`, or a table with sub-keys `space`, `nbsp`, `tab`, and `newline` | `"none"` |
217256| `characters` | Literal characters to use when rendering whitespace. Sub-keys may be any of `tab`, `space`, `nbsp`, `newline` or `tabpad` | See example below |
218257
219258Example
@@ -241,7 +280,7 @@ Options for rendering vertical indent guides.
241280
242281| Key | Description | Default |
243282| --- | --- | --- |
244−| `render` | Whether to render indent guides. | `false` |
283+| `render` | Whether to render indent guides | `false` |
245284| `character` | Literal character to use for rendering the indent guide | `│` |
246285| `skip-levels` | Number of indent levels to skip | `0` |
247286
@@ -253,3 +292,85 @@ render = true
253292character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽"
254293skip-levels = 1
255294```
295+
296+### `[editor.gutters]` Section
297+
298+For simplicity, `editor.gutters` accepts an array of gutter types, which will
299+use default settings for all gutter components.
300+
301+```toml
302+[editor]
303+gutters = ["diff", "diagnostics", "line-numbers", "spacer"]
304+```
305+
306+To customize the behavior of gutters, the `[editor.gutters]` section must
307+be used. This section contains top level settings, as well as settings for
308+specific gutter components as subsections.
309+
310+| Key | Description | Default |
311+| --- | --- | --- |
312+| `layout` | A vector of gutters to display | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
313+
314+Example:
315+
316+```toml
317+[editor.gutters]
318+layout = ["diff", "diagnostics", "line-numbers", "spacer"]
319+```
320+
321+#### `[editor.gutters.line-numbers]` Section
322+
323+Options for the line number gutter
324+
325+| Key | Description | Default |
326+| --- | --- | --- |
327+| `min-width` | The minimum number of characters to use | `3` |
328+
329+Example:
330+
331+```toml
332+[editor.gutters.line-numbers]
333+min-width = 1
334+```
335+
336+#### `[editor.gutters.diagnostics]` Section
337+
338+Currently unused
339+
340+#### `[editor.gutters.diff]` Section
341+
342+Currently unused
343+
344+#### `[editor.gutters.spacer]` Section
345+
346+Currently unused
347+
348+### `[editor.soft-wrap]` Section
349+
350+Options for soft wrapping lines that exceed the view width:
351+
352+| Key | Description | Default |
353+| --- | --- | --- |
354+| `enable` | Whether soft wrapping is enabled. | `false` |
355+| `max-wrap` | Maximum free space left at the end of the line. | `20` |
356+| `max-indent-retain` | Maximum indentation to carry over when soft wrapping a line. | `40` |
357+| `wrap-indicator` | Text inserted before soft wrapped lines, highlighted with `ui.virtual.wrap` | `↪ ` |
358+| `wrap-at-text-width` | Soft wrap at `text-width` instead of using the full viewport size. | `false` |
359+
360+Example:
361+
362+```toml
363+[editor.soft-wrap]
364+enable = true
365+max-wrap = 25 # increase value to reduce forced mid-word wrapping
366+max-indent-retain = 0
367+wrap-indicator = "" # set wrap-indicator to "" to hide it
368+```
369+
370+### `[editor.smart-tab]` Section
371+
372+
373+| Key | Description | Default |
374+|------------|-------------|---------|
375+| `enable` | If set to true, then when the cursor is in a position with non-whitespace to its left, instead of inserting a tab, it will run `move_parent_node_end`. If there is only whitespace to the left, then it inserts a tab as normal. With the default bindings, to explicitly insert a tab character, press Shift-tab. | `true` |
376+| `supersede-menu` | Normally, when a menu is on screen, such as when auto complete is triggered, the tab key is bound to cycling through the items. This means when menus are on screen, one cannot use the tab key to trigger the `smart-tab` command. If this option is set to true, the `smart-tab` command always takes precedence, which means one cannot use the tab key to cycle through menu items. One of the other bindings must be used instead, such as arrow keys or `C-n`/`C-p`. | `false` |
book/src/from-vim.md+3−3
@@ -2,9 +2,9 @@
22
33Helix's editing model is strongly inspired from Vim and Kakoune, and a notable
44difference from Vim (and the most striking similarity to Kakoune) is that Helix
5−follows the `selection → action` model. This means that the whatever you are
6−going to act on (a word, a paragraph, a line, etc) is selected first and the
7−action itself (delete, change, yank, etc) comes second. A cursor is simply a
5+follows the `selection → action` model. This means that whatever you are
6+going to act on (a word, a paragraph, a line, etc.) is selected first and the
7+action itself (delete, change, yank, etc.) comes second. A cursor is simply a
88single width selection.
99
1010See also Kakoune's [Migrating from Vim](https://github.com/mawww/kakoune/wiki/Migrating-from-Vim) and Helix's [Migrating from Vim](https://github.com/helix-editor/helix/wiki/Migrating-from-Vim).
book/src/generated/lang-support.md+84−26
@@ -1,68 +1,91 @@
11| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
22| --- | --- | --- | --- | --- |
3+| agda | ✓ | | | |
34| astro | ✓ | | | |
45| awk | ✓ | ✓ | | `awk-language-server` |
5−| bash | ✓ | | | `bash-language-server` |
6+| bash | ✓ | ✓ | ✓ | `bash-language-server` |
67| bass | ✓ | | | `bass` |
78| beancount | ✓ | | | |
9+| bibtex | ✓ | | | `texlab` |
10+| bicep | ✓ | | | `bicep-langserver` |
11+| blueprint | ✓ | | | `blueprint-compiler` |
812| c | ✓ | ✓ | ✓ | `clangd` |
913| c-sharp | ✓ | ✓ | | `OmniSharp` |
10−| cairo | ✓ | | | |
14+| cabal | | | | `haskell-language-server-wrapper` |
15+| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
16+| capnp | ✓ | | ✓ | |
1117| clojure | ✓ | | | `clojure-lsp` |
1218| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
1319| comment | ✓ | | | |
20+| common-lisp | ✓ | | ✓ | `cl-lsp` |
1421| cpon | ✓ | | ✓ | |
1522| cpp | ✓ | ✓ | ✓ | `clangd` |
23+| crystal | ✓ | ✓ | | `crystalline` |
1624| css | ✓ | | | `vscode-css-language-server` |
1725| cue | ✓ | | | `cuelsp` |
1826| d | ✓ | ✓ | ✓ | `serve-d` |
1927| dart | ✓ | | ✓ | `dart` |
28+| dbml | ✓ | | | |
2029| devicetree | ✓ | | | |
30+| dhall | ✓ | ✓ | | `dhall-lsp-server` |
2131| diff | ✓ | | | |
2232| dockerfile | ✓ | | | `docker-langserver` |
2333| dot | ✓ | | | `dot-language-server` |
34+| dtd | ✓ | | | |
2435| edoc | ✓ | | | |
2536| eex | ✓ | | | |
2637| ejs | ✓ | | | |
27−| elixir | ✓ | ✓ | | `elixir-ls` |
28−| elm | ✓ | | | `elm-language-server` |
38+| elixir | ✓ | ✓ | ✓ | `elixir-ls` |
39+| elm | ✓ | ✓ | | `elm-language-server` |
2940| elvish | ✓ | | | `elvish` |
3041| env | ✓ | | | |
3142| erb | ✓ | | | |
3243| erlang | ✓ | ✓ | | `erlang_ls` |
3344| esdl | ✓ | | | |
3445| fish | ✓ | ✓ | ✓ | |
46+| forth | ✓ | | | `forth-lsp` |
3547| fortran | ✓ | | ✓ | `fortls` |
36−| gdscript | ✓ | ✓ | | |
48+| fsharp | ✓ | | | `fsautocomplete` |
49+| gas | ✓ | ✓ | | |
50+| gdscript | ✓ | ✓ | ✓ | |
51+| gemini | ✓ | | | |
3752| git-attributes | ✓ | | | |
38−| git-commit | ✓ | | | |
53+| git-commit | ✓ | ✓ | | |
3954| git-config | ✓ | | | |
4055| git-ignore | ✓ | | | |
4156| git-rebase | ✓ | | | |
4257| gleam | ✓ | ✓ | | `gleam` |
4358| glsl | ✓ | ✓ | ✓ | |
44−| go | ✓ | ✓ | ✓ | `gopls` |
59+| gn | ✓ | | | |
60+| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
4561| godot-resource | ✓ | | | |
4662| gomod | ✓ | | | `gopls` |
4763| gotmpl | ✓ | | | `gopls` |
4864| gowork | ✓ | | | `gopls` |
49−| graphql | ✓ | | | |
65+| graphql | ✓ | | | `graphql-lsp` |
5066| hare | ✓ | | | |
51−| haskell | ✓ | | | `haskell-language-server-wrapper` |
67+| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
68+| haskell-persistent | ✓ | | | |
5269| hcl | ✓ | | ✓ | `terraform-ls` |
53−| heex | ✓ | ✓ | | |
70+| heex | ✓ | ✓ | | `elixir-ls` |
71+| hosts | ✓ | | | |
5472| html | ✓ | | | `vscode-html-language-server` |
73+| hurl | ✓ | | ✓ | |
5574| idris | | | | `idris2-lsp` |
5675| iex | ✓ | | | |
5776| ini | ✓ | | | |
58−| java | ✓ | | | `jdtls` |
77+| janet | ✓ | | | |
78+| java | ✓ | ✓ | ✓ | `jdtls` |
5979| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
80+| jinja | ✓ | | | |
6081| jsdoc | ✓ | | | |
6182| json | ✓ | | ✓ | `vscode-json-language-server` |
83+| json5 | ✓ | | | |
6284| jsonnet | ✓ | | | `jsonnet-language-server` |
6385| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
64−| julia | ✓ | | | `julia` |
65−| kdl | ✓ | | | |
86+| julia | ✓ | ✓ | ✓ | `julia` |
87+| just | ✓ | ✓ | ✓ | |
88+| kdl | ✓ | ✓ | ✓ | |
6689| kotlin | ✓ | | | `kotlin-language-server` |
6790| latex | ✓ | ✓ | | `texlab` |
6891| lean | ✓ | | | `lean` |
@@ -70,66 +93,101 @@
7093| llvm | ✓ | ✓ | ✓ | |
7194| llvm-mir | ✓ | ✓ | ✓ | |
7295| llvm-mir-yaml | ✓ | | ✓ | |
96+| log | ✓ | | | |
97+| lpf | ✓ | | | |
7398| lua | ✓ | ✓ | ✓ | `lua-language-server` |
7499| make | ✓ | | | |
100+| markdoc | ✓ | | | `markdoc-ls` |
75101| markdown | ✓ | | | `marksman` |
76102| markdown.inline | ✓ | | | |
103+| matlab | ✓ | ✓ | ✓ | |
104+| mermaid | ✓ | | | |
77105| meson | ✓ | | ✓ | |
78106| mint | | | | `mint` |
107+| msbuild | ✓ | | ✓ | |
108+| nasm | ✓ | ✓ | | |
79109| nickel | ✓ | | ✓ | `nls` |
80−| nix | ✓ | | | `rnix-lsp` |
81−| nu | ✓ | | | |
110+| nim | ✓ | ✓ | ✓ | `nimlangserver` |
111+| nix | ✓ | | | `nil` |
112+| nu | ✓ | | | `nu` |
113+| nunjucks | ✓ | | | |
82114| ocaml | ✓ | | ✓ | `ocamllsp` |
83115| ocaml-interface | ✓ | | | `ocamllsp` |
84−| odin | ✓ | | | `ols` |
116+| odin | ✓ | | ✓ | `ols` |
117+| opencl | ✓ | ✓ | ✓ | `clangd` |
85118| openscad | ✓ | | | `openscad-lsp` |
86119| org | ✓ | | | |
87120| pascal | ✓ | ✓ | | `pasls` |
88−| perl | ✓ | ✓ | ✓ | |
121+| passwd | ✓ | | | |
122+| pem | ✓ | | | |
123+| perl | ✓ | ✓ | ✓ | `perlnavigator` |
89124| php | ✓ | ✓ | ✓ | `intelephense` |
125+| po | ✓ | ✓ | | |
126+| pod | ✓ | | | |
127+| ponylang | ✓ | ✓ | ✓ | |
90128| prisma | ✓ | | | `prisma-language-server` |
91129| prolog | | | | `swipl` |
92−| protobuf | ✓ | | ✓ | |
93−| purescript | ✓ | | | `purescript-language-server` |
130+| protobuf | ✓ | | ✓ | `bufls`, `pb` |
131+| prql | ✓ | | | |
132+| purescript | ✓ | ✓ | | `purescript-language-server` |
94133| python | ✓ | ✓ | ✓ | `pylsp` |
134+| qml | ✓ | | ✓ | `qmlls` |
95135| r | ✓ | | | `R` |
96−| racket | | | | `racket` |
136+| racket | ✓ | | ✓ | `racket` |
97137| regex | ✓ | | | |
138+| rego | ✓ | | | `regols` |
98139| rescript | ✓ | ✓ | | `rescript-language-server` |
99140| rmarkdown | ✓ | | ✓ | `R` |
141+| robot | ✓ | | | `robotframework_ls` |
100142| ron | ✓ | | ✓ | |
143+| rst | ✓ | | | |
101144| ruby | ✓ | ✓ | ✓ | `solargraph` |
102145| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
146+| sage | ✓ | ✓ | | |
103147| scala | ✓ | | ✓ | `metals` |
104−| scheme | ✓ | | | |
148+| scheme | ✓ | | ✓ | |
105149| scss | ✓ | | | `vscode-css-language-server` |
106150| slint | ✓ | | ✓ | `slint-lsp` |
151+| smali | ✓ | | ✓ | |
152+| smithy | ✓ | | | `cs` |
107153| sml | ✓ | | | |
108154| solidity | ✓ | | | `solc` |
109155| sql | ✓ | | | |
110156| sshclientconfig | ✓ | | | |
111157| starlark | ✓ | ✓ | | |
112−| svelte | ✓ | | | `svelteserver` |
158+| strace | ✓ | | | |
159+| svelte | ✓ | | ✓ | `svelteserver` |
160+| sway | ✓ | ✓ | ✓ | `forc` |
113161| swift | ✓ | | | `sourcekit-lsp` |
162+| t32 | ✓ | | | |
114163| tablegen | ✓ | ✓ | ✓ | |
115164| task | ✓ | | | |
116−| tfvars | | | | `terraform-ls` |
165+| templ | ✓ | | | `templ` |
166+| tfvars | ✓ | | ✓ | `terraform-ls` |
167+| todotxt | ✓ | | | |
117168| toml | ✓ | | | `taplo` |
118169| tsq | ✓ | | | |
119170| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
120171| twig | ✓ | | | |
121172| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
173+| typst | ✓ | | | `typst-lsp` |
122174| ungrammar | ✓ | | | |
123−| v | ✓ | | | `vls` |
175+| unison | ✓ | | | |
176+| uxntal | ✓ | | | |
177+| v | ✓ | ✓ | ✓ | `v-analyzer` |
124178| vala | ✓ | | | `vala-language-server` |
125179| verilog | ✓ | ✓ | | `svlangserver` |
180+| vhdl | ✓ | | | `vhdl_ls` |
126181| vhs | ✓ | | | |
127−| vue | ✓ | | | `vls` |
182+| vue | ✓ | | | `vue-language-server` |
128183| wast | ✓ | | | |
129184| wat | ✓ | | | |
185+| webc | ✓ | | | |
130186| wgsl | ✓ | | | `wgsl_analyzer` |
131187| wit | ✓ | | ✓ | |
188+| wren | ✓ | ✓ | ✓ | |
132189| xit | ✓ | | | |
133190| xml | ✓ | | ✓ | |
134−| yaml | ✓ | | ✓ | `yaml-language-server` |
191+| yaml | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
192+| yuck | ✓ | | | |
135193| zig | ✓ | ✓ | ✓ | `zls` |
book/src/generated/typable-cmd.md+22−6
@@ -12,16 +12,19 @@
1212| `:buffer-next`, `:bn`, `:bnext` | Goto next buffer. |
1313| `:buffer-previous`, `:bp`, `:bprev` | Goto previous buffer. |
1414| `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) |
15−| `:write!`, `:w!` | Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write some/path.txt) |
15+| `:write!`, `:w!` | Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write! some/path.txt) |
16+| `:write-buffer-close`, `:wbc` | Write changes to disk and closes the buffer. Accepts an optional path (:write-buffer-close some/path.txt) |
17+| `:write-buffer-close!`, `:wbc!` | Force write changes to disk creating necessary subdirectories and closes the buffer. Accepts an optional path (:write-buffer-close! some/path.txt) |
1618| `:new`, `:n` | Create a new scratch buffer. |
1719| `:format`, `:fmt` | Format the file using the LSP formatter. |
18−| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
20+| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-16 for number of spaces.) |
1921| `:line-ending` | Set the document's default line ending. Options: crlf, lf. |
2022| `:earlier`, `:ear` | Jump back to an earlier point in edit history. Accepts a number of steps or a time span. |
2123| `:later`, `:lat` | Jump to a later point in edit history. Accepts a number of steps or a time span. |
2224| `:write-quit`, `:wq`, `:x` | Write changes to disk and close the current view. Accepts an optional path (:wq some/path.txt) |
2325| `:write-quit!`, `:wq!`, `:x!` | Write changes to disk and close the current view forcefully. Accepts an optional path (:wq! some/path.txt) |
2426| `:write-all`, `:wa` | Write changes from all buffers to disk. |
27+| `:write-all!`, `:wa!` | Forcefully write changes from all buffers to disk creating necessary subdirectories. |
2528| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. |
2629| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes). |
2730| `:quit-all`, `:qa` | Close all views. |
@@ -29,6 +32,7 @@
2932| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
3033| `:cquit!`, `:cq!` | Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2). |
3134| `:theme` | Change the editor theme (show current theme if no name specified). |
35+| `:yank-join` | Yank joined selections. A separator can be provided as first argument. Default value is newline. |
3236| `:clipboard-yank` | Yank main selection into system clipboard. |
3337| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
3438| `:primary-clipboard-yank` | Yank main selection into system primary clipboard. |
@@ -43,10 +47,15 @@
4347| `:change-current-directory`, `:cd` | Change the current working directory. |
4448| `:show-directory`, `:pwd` | Show the current working directory. |
4549| `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
46−| `:reload` | Discard changes and reload from the source file. |
47−| `:update` | Write changes only if the file has been modified. |
48−| `:lsp-restart` | Restarts the Language Server that is in use by the current doc |
50+| `: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. |
53+| `:update`, `:u` | Write changes only if the file has been modified. |
54+| `:lsp-workspace-command` | Open workspace command picker |
55+| `:lsp-restart` | Restarts the language servers used by the current doc |
56+| `:lsp-stop` | Stops the language servers that are used by the current doc |
4957| `: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. |
5059| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
5160| `:debug-remote`, `:dbg-tcp` | Connect to a debug adapter by TCP address and start a debugging session from a given template with given parameters. |
5261| `:debug-eval` | Evaluate expression in current debug context. |
@@ -56,8 +65,9 @@
5665| `:hsplit-new`, `:hnew` | Open a scratch buffer in a horizontal split. |
5766| `:tutor` | Open the tutorial. |
5867| `:goto`, `:g` | Goto line number. |
59−| `:set-language`, `:lang` | Set the language of current buffer. |
68+| `:set-language`, `:lang` | Set the language of current buffer (show current language if no value specified). |
6069| `:set-option`, `:set` | Set a config option at runtime.<br>For example to disable smart case search, use `:set search.smart-case false`. |
70+| `:toggle-option`, `:toggle` | Toggle a boolean config option at runtime.<br>For example to toggle smart case search, use `:toggle search.smart-case`. |
6171| `:get-option`, `:get` | Get the current value of a config option. |
6272| `:sort` | Sort ranges in selection. |
6373| `:rsort` | Sort ranges in selection in reverse order. |
@@ -65,8 +75,14 @@
6575| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
6676| `:config-reload` | Refresh user config. |
6777| `:config-open` | Open the user config.toml file. |
78+| `:config-open-workspace` | Open the workspace config.toml file. |
6879| `:log-open` | Open the helix log file. |
6980| `:insert-output` | Run shell command, inserting output before each selection. |
7081| `:append-output` | Run shell command, appending output after each selection. |
7182| `:pipe` | Pipe each selection to the shell command. |
83+| `:pipe-to` | Pipe each selection to the shell command, ignoring output. |
7284| `:run-shell-command`, `:sh` | Run a shell command |
85+| `:reset-diff-change`, `:diffget`, `:diffg` | Reset the diff change at the cursor position. |
86+| `:clear-register` | Clear given register. If no argument is provided, clear all registers. |
87+| `:redraw` | Clear and re-render the whole UI |
88+| `:move` | Move the current buffer and its corresponding file to a different path |
book/src/guides/README.md+1−1
@@ -1,4 +1,4 @@
11# Guides
22
33This section contains guides for adding new language server configurations,
4−tree-sitter grammars, textobject queries, etc.
4+tree-sitter grammars, textobject queries, and other similar items.
book/src/guides/adding_languages.md+45−35
@@ -1,45 +1,55 @@
1−# Adding languages
1+# Adding new languages to Helix
2+
3+In order to add a new language to Helix, you will need to follow the steps
4+below.
25
36## Language configuration
47
5−To add a new language, you need to add a `[[language]]` entry to the
6−`languages.toml` (see the [language configuration section]).
8+1. Add a new `[[language]]` entry in the `languages.toml` file and provide the
9+ necessary configuration for the new language. For more information on
10+ language configuration, refer to the
11+ [language configuration section](../languages.md) of the documentation.
12+ A new language server can be added by extending the `[language-server]` table in the same file.
13+2. If you are adding a new language or updating an existing language server
14+ configuration, run the command `cargo xtask docgen` to update the
15+ [Language Support](../lang-support.md) documentation.
716
8−When adding a new language or Language Server configuration for an existing
9−language, run `cargo xtask docgen` to add the new configuration to the
10−[Language Support][lang-support] docs before creating a pull request.
11−When adding a Language Server configuration, be sure to update the
12−[Language Server Wiki][install-lsp-wiki] with installation notes.
17+> 💡 If you are adding a new Language Server configuration, make sure to update
18+> the
19+> [Language Server Wiki](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
20+> with the installation instructions.
1321
1422## Grammar configuration
1523
16−If a tree-sitter grammar is available for the language, add a new `[[grammar]]`
17−entry to `languages.toml`.
18−
19−You may use the `source.path` key rather than `source.git` with an absolute path
20−to a locally available grammar for testing, but switch to `source.git` before
21−submitting a pull request.
24+1. If a tree-sitter grammar is available for the new language, add a new
25+ `[[grammar]]` entry to the `languages.toml` file.
26+2. If you are testing the grammar locally, you can use the `source.path` key
27+ with an absolute path to the grammar. However, before submitting a pull
28+ request, make sure to switch to using `source.git`.
2229
2330## Queries
2431
25−For a language to have syntax-highlighting and indentation among
26−other things, you have to add queries. Add a directory for your
27−language with the path `runtime/queries/<name>/`. The tree-sitter
28−[website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
29−gives more info on how to write queries.
30−
31−> NOTE: When evaluating queries, the first matching query takes
32−precedence, which is different from other editors like Neovim where
33−the last matching query supersedes the ones before it. See
34−[this issue][neovim-query-precedence] for an example.
35−
36−## Common Issues
37−
38−- If you get errors when running after switching branches, you may have to update the tree-sitter grammars. Run `hx --grammar fetch` to fetch the grammars and `hx --grammar build` to build any out-of-date grammars.
39−
40−- If a parser is segfaulting or you want to remove the parser, make sure to remove the compiled parser in `runtime/grammar/<name>.so`
41−
42−[language configuration section]: ../languages.md
43−[neovim-query-precedence]: https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090
44−[install-lsp-wiki]: https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers
45−[lang-support]: ../lang-support.md
32+1. In order to provide syntax highlighting and indentation for the new language,
33+ you will need to add queries.
34+2. Create a new directory for the language with the path
35+ `runtime/queries/<name>/`.
36+3. Refer to the
37+ [tree-sitter website](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#queries)
38+ for more information on writing queries.
39+4. A list of highlight captures can be found [on the themes page](https://docs.helix-editor.com/themes.html#scopes).
40+
41+> 💡 In Helix, the first matching query takes precedence when evaluating
42+> queries, which is different from other editors such as Neovim where the last
43+> matching query supersedes the ones before it. See
44+> [this issue](https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090)
45+> for an example.
46+
47+## Common issues
48+
49+- If you encounter errors when running Helix after switching branches, you may
50+ need to update the tree-sitter grammars. Run the command `hx --grammar fetch`
51+ to fetch the grammars and `hx --grammar build` to build any out-of-date
52+ grammars.
53+- If a parser is causing a segfault, or you want to remove it, make sure to
54+ remove the compiled parser located at `runtime/grammars/<name>.so`.
55+- If you are attempting to add queries and Helix is unable to locate them, ensure that the environment variable `HELIX_RUNTIME` is set to the location of the `runtime` folder you're developing in.
book/src/guides/indent.md+320−44
@@ -1,76 +1,308 @@
1−# Adding Indent Queries
1+# Adding indent queries
22
3−Helix uses tree-sitter to correctly indent new lines. This requires
4−a tree-sitter grammar and an `indent.scm` query file placed in
5−`runtime/queries/{language}/indents.scm`. The indentation for a line
6−is calculated by traversing the syntax tree from the lowest node at the
7−beginning of the new line. Each of these nodes contributes to the total
8−indent when it is captured by the query (in what way depends on the name
9−of the capture).
3+Helix uses tree-sitter to correctly indent new lines. This requires a tree-
4+sitter grammar and an `indent.scm` query file placed in `runtime/queries/
5+{language}/indents.scm`. The indentation for a line is calculated by traversing
6+the syntax tree from the lowest node at the beginning of the new line (see
7+[Indent queries](#indent-queries)). Each of these nodes contributes to the total
8+indent when it is captured by the query (in what way depends on the name of
9+the capture.
1010
1111Note that it matters where these added indents begin. For example,
1212multiple indent level increases that start on the same line only increase
13−the total indent level by 1.
13+the total indent level by 1. See [Capture types](#capture-types).
1414
15−## Scopes
15+By default, Helix uses the `hybrid` indentation heuristic. This means that
16+indent queries are not used to compute the expected absolute indentation of a
17+line but rather the expected difference in indentation between the new and an
18+already existing line. This difference is then added to the actual indentation
19+of the already existing line. Since this makes errors in the indent queries
20+harder to find, it is recommended to disable it when testing via
21+`:set indent-heuristic tree-sitter`. The rest of this guide assumes that
22+the `tree-sitter` heuristic is used.
1623
17−Added indents don't always apply to the whole node. For example, in most
18−cases when a node should be indented, we actually only want everything
19−except for its first line to be indented. For this, there are several
20−scopes (more scopes may be added in the future if required):
24+## Indent queries
2125
22−- `all`:
23−This scope applies to the whole captured node. This is only different from
24−`tail` when the captured node is the first node on its line.
26+When Helix is inserting a new line through `o`, `O`, or `<ret>`, to determine
27+the indent level for the new line, the query in `indents.scm` is run on the
28+document. The starting position of the query is the end of the line above where
29+a new line will be inserted.
2530
26−- `tail`:
27−This scope applies to everything except for the first line of the
28−captured node.
31+For `o`, the inserted line is the line below the cursor, so that starting
32+position of the query is the end of the current line.
2933
30−Every capture type has a default scope which should do the right thing
31−in most situations. When a different scope is required, this can be
32−changed by using a `#set!` declaration anywhere in the pattern:
33−```scm
34−(assignment_expression
35− right: (_) @indent
36− (#set! "scope" "all"))
34+```rust
35+fn need_hero(some_hero: Hero, life: Life) -> {
36+ matches!(some_hero, Hero { // ←─────────────────╮
37+ strong: true,//←╮ ↑ ↑ │
38+ fast: true, // │ │ ╰── query start │
39+ sure: true, // │ ╰───── cursor ├─ traversal
40+ soon: true, // ╰──────── new line inserted │ start node
41+ }) && // │
42+// ↑ │
43+// ╰───────────────────────────────────────────────╯
44+ some_hero > life
45+}
3746```
3847
39−## Capture Types
48+For `O`, the newly inserted line is the *current* line, so the starting position
49+of the query is the end of the line above the cursor.
4050
41−- `@indent` (default scope `tail`):
42−Increase the indent level by 1. Multiple occurrences in the same line
43−don't stack. If there is at least one `@indent` and one `@outdent`
44−capture on the same line, the indent level isn't changed at all.
51+```rust
52+fn need_hero(some_hero: Hero, life: Life) -> { // ←─╮
53+ matches!(some_hero, Hero { // ←╮ ↑ │
54+ strong: true,// ↑ ╭───╯ │ │
55+ fast: true, // │ │ query start ─╯ │
56+ sure: true, // ╰───┼ cursor ├─ traversal
57+ soon: true, // ╰ new line inserted │ start node
58+ }) && // │
59+ some_hero > life // │
60+} // ←──────────────────────────────────────────────╯
61+```
4562
46−- `@outdent` (default scope `all`):
47−Decrease the indent level by 1. The same rules as for `@indent` apply.
63+From this starting node, the syntax tree is traversed up until the root node.
64+Each indent capture is collected along the way, and then combined according to
65+their [capture types](#capture-types) and [scopes](#scopes) to a final indent
66+level for the line.
4867
49−- `@extend`:
50−Extend the range of this node to the end of the line and to lines that
51−are indented more than the line that this node starts on. This is useful
52−for languages like Python, where for the purpose of indentation some nodes
53−(like functions or classes) should also contain indented lines that follow them.
68+### Capture types
5469
70+- `@indent` (default scope `tail`):
71+ Increase the indent level by 1. Multiple occurrences in the same line *do not*
72+ stack. If there is at least one `@indent` and one `@outdent` capture on the
73+ same line, the indent level isn't changed at all.
74+- `@outdent` (default scope `all`):
75+ Decrease the indent level by 1. The same rules as for `@indent` apply.
76+- `@indent.always` (default scope `tail`):
77+ Increase the indent level by 1. Multiple occurrences on the same line *do*
78+ stack. The final indent level is `@indent.always` – `@outdent.always`. If
79+ an `@indent` and an `@indent.always` are on the same line, the `@indent` is
80+ ignored.
81+- `@outdent.always` (default scope `all`):
82+ Decrease the indent level by 1. The same rules as for `@indent.always` apply.
83+- `@align` (default scope `all`):
84+ Align everything inside this node to some anchor. The anchor is given
85+ by the start of the node captured by `@anchor` in the same pattern.
86+ Every pattern with an `@align` should contain exactly one `@anchor`.
87+ Indent (and outdent) for nodes below (in terms of their starting line)
88+ the `@align` node is added to the indentation required for alignment.
89+- `@extend`:
90+ Extend the range of this node to the end of the line and to lines that are
91+ indented more than the line that this node starts on. This is useful for
92+ languages like Python, where for the purpose of indentation some nodes (like
93+ functions or classes) should also contain indented lines that follow them.
5594- `@extend.prevent-once`:
56−Prevents the first extension of an ancestor of this node. For example, in Python
57−a return expression always ends the block that it is in. Note that this only stops the
58−extension of the next `@extend` capture. If multiple ancestors are captured,
59−only the extension of the innermost one is prevented. All other ancestors are unaffected
60−(regardless of whether the innermost ancestor would actually have been extended).
95+ Prevents the first extension of an ancestor of this node. For example, in Python
96+ a return expression always ends the block that it is in. Note that this only
97+ stops the extension of the next `@extend` capture. If multiple ancestors are
98+ captured, only the extension of the innermost one is prevented. All other
99+ ancestors are unaffected (regardless of whether the innermost ancestor would
100+ actually have been extended).
101+
102+#### `@indent` / `@outdent`
103+
104+Consider this example:
105+
106+```rust
107+fn shout(things: Vec<Thing>) {
108+ // ↑
109+ // ├───────────────────────╮ indent level
110+ // @indent ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
111+ // │
112+ let it_all = |out| { things.filter(|thing| { // │ 1
113+ // ↑ ↑ │
114+ // ├───────────────────────┼─────┼┄┄┄┄┄┄┄┄┄┄┄┄┄┄
115+ // @indent @indent │
116+ // │ 2
117+ thing.can_do_with(out) // │
118+ })}; // ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
119+ //↑↑↑ │ 1
120+} //╰┼┴──────────────────────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄
121+// 3x @outdent
122+```
123+
124+```scm
125+((block) @indent)
126+["}" ")"] @outdent
127+```
128+
129+Note how on the second line, we have two blocks begin on the same line. In this
130+case, since both captures occur on the same line, they are combined and only
131+result in a net increase of 1. Also note that the closing `}`s are part of the
132+`@indent` captures, but the 3 `@outdent`s also combine into 1 and result in that
133+line losing one indent level.
134+
135+#### `@extend` / `@extend.prevent-once`
136+
137+For an example of where `@extend` can be useful, consider Python, which is
138+whitespace-sensitive.
139+
140+```scm
141+]
142+ (parenthesized_expression)
143+ (function_definition)
144+ (class_definition)
145+] @indent
146+
147+```
148+
149+```python
150+class Hero:
151+ def __init__(self, strong, fast, sure, soon):# ←─╮
152+ self.is_strong = strong # │
153+ self.is_fast = fast # ╭─── query start │
154+ self.is_sure = sure # │ ╭─ cursor │
155+ self.is_soon = soon # │ │ │
156+ # ↑ ↑ │ │ │
157+ # │ ╰──────╯ │ │
158+ # ╰─────────────────────╯ │
159+ # ├─ traversal
160+ def need_hero(self, life): # │ start node
161+ return ( # │
162+ self.is_strong # │
163+ and self.is_fast # │
164+ and self.is_sure # │
165+ and self.is_soon # │
166+ and self > life # │
167+ ) # ←─────────────────────────────────────────╯
168+```
169+
170+Without braces to catch the scope of the function, the smallest descendant of
171+the cursor on a line feed ends up being the entire inside of the class. Because
172+of this, it will miss the entire function node and its indent capture, leading
173+to an indent level one too small.
174+
175+To address this case, `@extend` tells helix to "extend" the captured node's span
176+to the line feed and every consecutive line that has a greater indent level than
177+the line of the node.
178+
179+```scm
180+(parenthesized_expression) @indent
181+
182+]
183+ (function_definition)
184+ (class_definition)
185+] @indent @extend
186+
187+```
188+
189+```python
190+class Hero:
191+ def __init__(self, strong, fast, sure, soon):# ←─╮
192+ self.is_strong = strong # │
193+ self.is_fast = fast # ╭─── query start ├─ traversal
194+ self.is_sure = sure # │ ╭─ cursor │ start node
195+ self.is_soon = soon # │ │ ←───────────────╯
196+ # ↑ ↑ │ │
197+ # │ ╰──────╯ │
198+ # ╰─────────────────────╯
199+ def need_hero(self, life):
200+ return (
201+ self.is_strong
202+ and self.is_fast
203+ and self.is_sure
204+ and self.is_soon
205+ and self > life
206+ )
207+```
61208
209+Furthermore, there are some cases where extending to everything with a greater
210+indent level may not be desirable. Consider the `need_hero` function above. If
211+our cursor is on the last line of the returned expression.
212+
213+```python
214+class Hero:
215+ def __init__(self, strong, fast, sure, soon):
216+ self.is_strong = strong
217+ self.is_fast = fast
218+ self.is_sure = sure
219+ self.is_soon = soon
220+
221+ def need_hero(self, life):
222+ return (
223+ self.is_strong
224+ and self.is_fast
225+ and self.is_sure
226+ and self.is_soon
227+ and self > life
228+ ) # ←─── cursor
229+ #←────────── where cursor should go on new line
230+```
231+
232+In Python, the are a few tokens that will always end a scope, such as a return
233+statement. Since the scope ends, so should the indent level. But because the
234+function span is extended to every line with a greater indent level, a new line
235+would just continue on the same level. And an `@outdent` would not help us here
236+either, since it would cause everything in the parentheses to become outdented
237+as well.
238+
239+To help, we need to signal an end to the extension. We can do this with
240+`@extend.prevent-once`.
241+
242+```scm
243+(parenthesized_expression) @indent
244+
245+]
246+ (function_definition)
247+ (class_definition)
248+] @indent @extend
249+
250+(return_statement) @extend.prevent-once
251+```
252+
253+#### `@indent.always` / `@outdent.always`
254+
255+As mentioned before, normally if there is more than one `@indent` or `@outdent`
256+capture on the same line, they are combined.
257+
258+Sometimes, there are cases when you may want to ensure that every indent capture
259+is additive, regardless of how many occur on the same line. Consider this
260+example in YAML.
261+
262+```yaml
263+ - foo: bar
264+# ↑ ↑
265+# │ ╰─────────────── start of map
266+# ╰───────────────── start of list element
267+ baz: quux # ←─── cursor
268+ # ←───────────── where the cursor should go on a new line
269+ garply: waldo
270+ - quux:
271+ bar: baz
272+ xyzzy: thud
273+ fred: plugh
274+```
275+
276+In YAML, you often have lists of maps. In these cases, the syntax is such that
277+the list element and the map both start on the same line. But we really do want
278+to start an indentation for each of these so that subsequent keys in the map
279+hang over the list and align properly. This is where `@indent.always` helps.
280+
281+```scm
282+((block_sequence_item) @item @indent.always @extend
283+ (#not-one-line? @item))
284+
285+((block_mapping_pair
286+ key: (_) @key
287+ value: (_) @val
288+ (#not-same-line? @key @val)
289+ ) @indent.always @extend
290+)
291+```
62292
63293## Predicates
64294
65295In some cases, an S-expression cannot express exactly what pattern should be matched.
66296For that, tree-sitter allows for predicates to appear anywhere within a pattern,
67297similar to how `#set!` declarations work:
298+
68299```scm
69300(some_kind
70301 (child_kind) @indent
71302 (#predicate? arg1 arg2 ...)
72303)
73304```
305+
74306The number of arguments depends on the predicate that's used.
75307Each argument is either a capture (`@name`) or a string (`"some string"`).
76308The following predicates are supported by tree-sitter:
@@ -91,3 +323,47 @@ argument (a string).
91323
92324- `#same-line?`/`#not-same-line?`:
93325The captures given by the 2 arguments must/must not start on the same line.
326+
327+- `#one-line?`/`#not-one-line?`:
328+The captures given by the fist argument must/must span a total of one line.
329+
330+### Scopes
331+
332+Added indents don't always apply to the whole node. For example, in most
333+cases when a node should be indented, we actually only want everything
334+except for its first line to be indented. For this, there are several
335+scopes (more scopes may be added in the future if required):
336+
337+- `tail`:
338+This scope applies to everything except for the first line of the
339+captured node.
340+- `all`:
341+This scope applies to the whole captured node. This is only different from
342+`tail` when the captured node is the first node on its line.
343+
344+For example, imagine we have the following function
345+
346+```rust
347+fn aha() { // ←─────────────────────────────────────╮
348+ let take = "on me"; // ←──────────────╮ scope: │
349+ let take = "me on"; // ├─ "tail" ├─ (block) @indent
350+ let ill = be_gone_days(1 || 2); // │ │
351+} // ←───────────────────────────────────┴──────────┴─ "}" @outdent
352+ // scope: "all"
353+```
354+
355+We can write the following query with the `#set!` declaration:
356+
357+ ```scm
358+ ((block) @indent
359+ (#set! "scope" "tail"))
360+ ("}" @outdent
361+ (#set! "scope" "all"))
362+ ```
363+
364+As we can see, the "tail" scope covers the node, except for the first line.
365+Everything up to and including the closing brace gets an indent level of 1.
366+Then, on the closing brace, we encounter an outdent with a scope of "all", which
367+means the first line is included, and the indent level is cancelled out on this
368+line. (Note these scopes are the defaults for `@indent` and `@outdent`—they are
369+written explicitly for demonstration.)
book/src/guides/injection.mdnew file+57
@@ -0,0 +1,57 @@
1+# Adding Injection Queries
2+
3+Writing language injection queries allows one to highlight a specific node as a different language.
4+In addition to the [standard][upstream-docs] language injection options used by tree-sitter, there
5+are a few Helix specific extensions that allow for more control.
6+
7+And example of a simple query that would highlight all strings as bash in Nix:
8+```scm
9+((string_expression (string_fragment) @injection.content)
10+ (#set! injection.language "bash"))
11+```
12+
13+## Capture Types
14+
15+- `@injection.language` (standard):
16+The captured node may contain the language name used to highlight the node captured by
17+`@injection.content`.
18+
19+- `@injection.content` (standard):
20+Marks the content to be highlighted as the language captured with `@injection.language` _et al_.
21+
22+- `@injection.filename` (extension):
23+The captured node may contain a filename with a file-extension known to Helix,
24+highlighting `@injection.content` as that language. This uses the language extensions defined in
25+both the default languages.toml distributed with Helix, as well as user defined languages.
26+
27+- `@injection.shebang` (extension):
28+The captured node may contain a shebang used to choose a language to highlight as. This also uses
29+the shebangs defined in the default and user `languages.toml`.
30+
31+## Settings
32+
33+- `injection.combined` (standard):
34+Indicates that all the matching nodes in the tree should have their content parsed as one
35+nested document.
36+
37+- `injection.language` (standard):
38+Forces the captured content to be highlighted as the given language
39+
40+- `injection.include-children` (standard):
41+Indicates that the content node’s entire text should be re-parsed, including the text of its child
42+nodes. By default, child nodes’ text will be excluded from the injected document.
43+
44+- `injection.include-unnamed-children` (extension):
45+Same as `injection.include-children` but only for unnamed child nodes.
46+
47+## Predicates
48+
49+- `#eq?` (standard):
50+The first argument (a capture) must be equal to the second argument
51+(a capture or a string).
52+
53+- `#match?` (standard):
54+The first argument (a capture) must match the regex given in the
55+second argument (a string).
56+
57+[upstream-docs]: http://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
book/src/guides/textobject.md+7−9
@@ -1,14 +1,14 @@
1−# Adding Textobject Queries
1+# Adding textobject queries
22
3−Textobjects that are language specific ([like functions, classes, etc][textobjects])
4−require an accompanying tree-sitter grammar and a `textobjects.scm` query file
3+Helix supports textobjects that are language specific, such as functions, classes, etc.
4+These textobjects require an accompanying tree-sitter grammar and a `textobjects.scm` query file
55to work properly. Tree-sitter allows us to query the source code syntax tree
66and capture specific parts of it. The queries are written in a lisp dialect.
77More information on how to write queries can be found in the [official tree-sitter
88documentation][tree-sitter-queries].
99
1010Query files should be placed in `runtime/queries/{language}/textobjects.scm`
11−when contributing. Note that to test the query files locally you should put
11+when contributing to Helix. Note that to test the query files locally you should put
1212them under your local runtime directory (`~/.config/helix/runtime` on Linux
1313for example).
1414
@@ -28,9 +28,9 @@ The following [captures][tree-sitter-captures] are recognized:
2828
2929[Example query files][textobject-examples] can be found in the helix GitHub repository.
3030
31−## Queries for Textobject Based Navigation
31+## Queries for textobject based navigation
3232
33−[Tree-sitter based navigation][textobjects-nav] is done using captures in the
33+Tree-sitter based navigation in Helix is done using captures in the
3434following order:
3535
3636- `object.movement`
@@ -38,12 +38,10 @@ following order:
3838- `object.inside`
3939
4040For example if a `function.around` capture has been already defined for a language
41−in it's `textobjects.scm` file, function navigation should also work automatically.
41+in its `textobjects.scm` file, function navigation should also work automatically.
4242`function.movement` should be defined only if the node captured by `function.around`
4343doesn't make sense in a navigation context.
4444
45−[textobjects]: ../usage.md#textobjects
46−[textobjects-nav]: ../usage.md#tree-sitter-textobject-based-navigation
4745[tree-sitter-queries]: https://tree-sitter.github.io/tree-sitter/using-parsers#query-syntax
4846[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes
4947[textobject-examples]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+filename%3Atextobjects.scm&type=Code&ref=advsearch&l=&l=
book/src/install.md+244−91
@@ -1,167 +1,320 @@
1−# Installation
2−
3−We provide pre-built binaries on the [GitHub Releases page](https://github.com/helix-editor/helix/releases).
1+# Installing Helix
2+
3+<!--toc:start-->
4+- [Pre-built binaries](#pre-built-binaries)
5+- [Linux, macOS, Windows and OpenBSD packaging status](#linux-macos-windows-and-openbsd-packaging-status)
6+- [Linux](#linux)
7+ - [Ubuntu](#ubuntu)
8+ - [Fedora/RHEL](#fedorarhel)
9+ - [Arch Linux extra](#arch-linux-extra)
10+ - [NixOS](#nixos)
11+ - [Flatpak](#flatpak)
12+ - [Snap](#snap)
13+ - [AppImage](#appimage)
14+- [macOS](#macos)
15+ - [Homebrew Core](#homebrew-core)
16+ - [MacPorts](#macports)
17+- [Windows](#windows)
18+ - [Winget](#winget)
19+ - [Scoop](#scoop)
20+ - [Chocolatey](#chocolatey)
21+ - [MSYS2](#msys2)
22+- [Building from source](#building-from-source)
23+ - [Configuring Helix's runtime files](#configuring-helixs-runtime-files)
24+ - [Linux and macOS](#linux-and-macos)
25+ - [Windows](#windows)
26+ - [Multiple runtime directories](#multiple-runtime-directories)
27+ - [Validating the installation](#validating-the-installation)
28+ - [Configure the desktop shortcut](#configure-the-desktop-shortcut)
29+<!--toc:end-->
30+
31+To install Helix, follow the instructions specific to your operating system.
32+Note that:
33+
34+- To get the latest nightly version of Helix, you need to
35+ [build from source](#building-from-source).
36+
37+- To take full advantage of Helix, install the language servers for your
38+ preferred programming languages. See the
39+ [wiki](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
40+ for instructions.
41+
42+## Pre-built binaries
43+
44+Download pre-built binaries from the
45+[GitHub Releases page](https://github.com/helix-editor/helix/releases). Add the binary to your system's `$PATH` to use it from the command
46+line.
47+
48+## Linux, macOS, Windows and OpenBSD packaging status
449
550[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions)
651
7−## OSX
52+## Linux
53+
54+The following third party repositories are available:
55+
56+### Ubuntu
857
9−Helix is available in homebrew-core:
58+Add the `PPA` for Helix:
1059
60+```sh
61+sudo add-apt-repository ppa:maveonair/helix-editor
62+sudo apt update
63+sudo apt install helix
1164```
12−brew install helix
65+
66+### Fedora/RHEL
67+
68+```sh
69+sudo dnf install helix
1370```
1471
15−## Linux
72+### Arch Linux extra
73+
74+Releases are available in the `extra` repository:
75+
76+```sh
77+sudo pacman -S helix
78+```
79+Additionally, a [helix-git](https://aur.archlinux.org/packages/helix-git/) package is available
80+in the AUR, which builds the master branch.
1681
1782### NixOS
1883
19−A [flake](https://nixos.wiki/wiki/Flakes) containing the package is available in
20−the project root. The flake can also be used to spin up a reproducible development
21−shell for working on Helix with `nix develop`.
84+Helix is available in [nixpkgs](https://github.com/nixos/nixpkgs) through the `helix` attribute,
85+the unstable channel usually carries the latest release.
2286
23−Flake outputs are cached for each push to master using
24−[Cachix](https://www.cachix.org/). The flake is configured to
25−automatically make use of this cache assuming the user accepts
26−the new settings on first use.
87+Helix is also available as a [flake](https://nixos.wiki/wiki/Flakes) in the project
88+root. Use `nix develop` to spin up a reproducible development shell. Outputs are
89+cached for each push to master using [Cachix](https://www.cachix.org/). The
90+flake is configured to automatically make use of this cache assuming the user
91+accepts the new settings on first use.
2792
28−If you are using a version of Nix without flakes enabled you can
29−[install Cachix cli](https://docs.cachix.org/installation); `cachix use helix` will
30−configure Nix to use cached outputs when possible.
93+If you are using a version of Nix without flakes enabled,
94+[install Cachix CLI](https://docs.cachix.org/installation) and use
95+`cachix use helix` to configure Nix to use cached outputs when possible.
3196
32−### Arch Linux
97+### Flatpak
3398
34−Releases are available in the `community` repository.
99+Helix is available on [Flathub](https://flathub.org/en-GB/apps/com.helix_editor.Helix):
35100
36−A [helix-git](https://aur.archlinux.org/packages/helix-git/) package is also available on the AUR, which builds the master branch.
101+```sh
102+flatpak install flathub com.helix_editor.Helix
103+flatpak run com.helix_editor.Helix
104+```
37105
38−### Fedora Linux
106+### Snap
39107
40−You can install the COPR package for Helix via
108+Helix is available on [Snapcraft](https://snapcraft.io/helix) and can be installed with:
41109
110+```sh
111+snap install --classic helix
42112```
43−sudo dnf copr enable varlad/helix
44−sudo dnf install helix
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+
116+### AppImage
117+
118+Install Helix using the Linux [AppImage](https://appimage.org/) format.
119+Download the official Helix AppImage from the [latest releases](https://github.com/helix-editor/helix/releases/latest) page.
120+
121+```sh
122+chmod +x helix-*.AppImage # change permission for executable mode
123+./helix-*.AppImage # run helix
45124```
125+
126+## macOS
46127
47−### Void Linux
128+### Homebrew Core
48129
130+```sh
131+brew install helix
49132```
50−sudo xbps-install helix
133+
134+### MacPorts
135+
136+```sh
137+port install helix
51138```
52139
53140## Windows
54141
55−Helix can be installed using [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
142+Install on Windows using [Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/), [Scoop](https://scoop.sh/), [Chocolatey](https://chocolatey.org/)
56143or [MSYS2](https://msys2.org/).
57144
58−**Scoop:**
145+### Winget
146+Windows Package Manager winget command-line tool is by default available on Windows 11 and modern versions of Windows 10 as a part of the App Installer.
147+You can get [App Installer from the Microsoft Store](https://www.microsoft.com/p/app-installer/9nblggh4nns1#activetab=pivot:overviewtab). If it's already installed, make sure it is updated with the latest version.
59148
149+```sh
150+winget install Helix.Helix
60151```
152+
153+### Scoop
154+
155+```sh
61156scoop install helix
62157```
63158
64−**Chocolatey:**
159+### Chocolatey
65160
66−```
161+```sh
67162choco install helix
68163```
69164
70−**MSYS2:**
165+### MSYS2
71166
72−```
73−pacman -S mingw-w64-i686-helix
167+For 64-bit Windows 8.1 or above:
168+
169+```sh
170+pacman -S mingw-w64-ucrt-x86_64-helix
74171```
75172
76−or
173+## Building from source
77174
78−```
79−pacman -S mingw-w64-x86_64-helix
80−```
175+Requirements:
81176
82−or
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.
83180
84−```
85−pacman -S mingw-w64-ucrt-x86_64-helix
86−```
181+- The [Rust toolchain](https://www.rust-lang.org/tools/install)
182+- The [Git version control system](https://git-scm.com/)
183+- A C++14 compatible compiler to build the tree-sitter grammars, for example GCC or Clang
87184
88−## Build from source
185+If you are using the `musl-libc` standard library instead of `glibc` the following environment variable must be set during the build to ensure tree-sitter grammars can be loaded correctly:
89186
90−```
91−git clone https://github.com/helix-editor/helix
92−cd helix
93−cargo install --path helix-term
187+```sh
188+RUSTFLAGS="-C target-feature=-crt-static"
94189```
95190
96−This will install the `hx` binary to `$HOME/.cargo/bin` and build tree-sitter grammars in `./runtime/grammars`.
191+1. Clone the repository:
97192
98−Helix also needs its runtime files so make sure to copy/symlink the `runtime/` directory into the
99−config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overridden
100−via the `HELIX_RUNTIME` environment variable.
193+ ```sh
194+ git clone https://github.com/helix-editor/helix
195+ cd helix
196+ ```
101197
102−| OS | Command |
103−| -------------------- | ------------------------------------------------ |
104−| Windows (Cmd) | `xcopy /e /i runtime %AppData%\helix\runtime` |
105−| Windows (PowerShell) | `xcopy /e /i runtime $Env:AppData\helix\runtime` |
106−| Linux / MacOS | `ln -s $PWD/runtime ~/.config/helix/runtime` |
198+2. Compile from source:
107199
108−Starting with Windows Vista you can also create symbolic links on Windows. Note that this requires
109−elevated priviliges - i.e. PowerShell or Cmd must be run as administrator.
200+ ```sh
201+ cargo install --path helix-term --locked
202+ ```
110203
111−**PowerShell:**
204+ This command will create the `hx` executable and construct the tree-sitter
205+ grammars in the local `runtime` folder.
112206
113−```powershell
114−New-Item -ItemType SymbolicLink -Target "runtime" -Path "$Env:AppData\helix\runtime"
115−```
207+> 💡 Tree-sitter grammars can be fetched and compiled if not pre-packaged. Fetch
208+> grammars with `hx --grammar fetch` and compile them with
209+> `hx --grammar build`. This will install them in
210+> the `runtime` directory within the user's helix config directory (more
211+> [details below](#multiple-runtime-directories)).
212+
213+### Configuring Helix's runtime files
116214
117−**Cmd:**
215+#### Linux and macOS
118216
119−```cmd
120−cd %appdata%\helix
121−mklink /D runtime "<helix-repo>\runtime"
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:
220+
221+```sh
222+HELIX_RUNTIME=~/src/helix/runtime
122223```
123224
124−The runtime location can be overridden via the `HELIX_RUNTIME` environment variable.
225+Or, create a symbolic link:
125226
126−> NOTE: if `HELIX_RUNTIME` is set prior to calling `cargo install --path helix-term`,
127−> tree-sitter grammars will be built in `$HELIX_RUNTIME/grammars`.
227+```sh
228+ln -Ts $PWD/runtime ~/.config/helix/runtime
229+```
128230
129−If you plan on keeping the repo locally, an alternative to copying/symlinking
130−runtime files is to set `HELIX_RUNTIME=/path/to/helix/runtime`
131−(`HELIX_RUNTIME=$PWD/runtime` if you're in the helix repo directory).
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.
132232
133−To use Helix in desktop environments that supports [XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html), including Gnome and KDE, copy the provided `.desktop` file to the correct folder:
233+#### Windows
134234
135−```bash
136−cp contrib/Helix.desktop ~/.local/share/applications
235+Either set the `HELIX_RUNTIME` environment variable to point to the runtime files using the Windows setting (search for
236+`Edit environment variables for your account`) or use the `setx` command in
237+Cmd:
238+
239+```sh
240+setx HELIX_RUNTIME "%userprofile%\source\repos\helix\runtime"
137241```
138242
139−To use another terminal than the default, you will need to modify the `.desktop` file. For example, to use `kitty`:
243+> 💡 `%userprofile%` resolves to your user directory like
244+> `C:\Users\Your-Name\` for example.
140245
141−```bash
142−sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
143−sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
144−```
246+Or, create a symlink in `%appdata%\helix\` that links to the source code directory:
145247
146−Please note: there is no icon for Helix yet, so the system default will be used.
248+| Method | Command |
249+| ---------- | -------------------------------------------------------------------------------------- |
250+| PowerShell | `New-Item -ItemType Junction -Target "runtime" -Path "$Env:AppData\helix\runtime"` |
251+| Cmd | `cd %appdata%\helix` <br/> `mklink /D runtime "%userprofile%\src\helix\runtime"` |
147252
148−## Finishing up the installation
253+> 💡 On Windows, creating a symbolic link may require running PowerShell or
254+> Cmd as an administrator.
149255
150−To make sure everything is set up as expected you should finally run the helix healthcheck via
256+#### Multiple runtime directories
151257
152−```
258+When Helix finds multiple runtime directories it will search through them for files in the
259+following order:
260+
261+1. `runtime/` sibling directory to `$CARGO_MANIFEST_DIR` directory (this is intended for
262+ developing and testing helix only).
263+2. `runtime/` subdirectory of OS-dependent helix user config directory.
264+3. `$HELIX_RUNTIME`
265+4. Distribution-specific fallback directory (set at compile time—not run time—
266+ with the `HELIX_DEFAULT_RUNTIME` environment variable)
267+5. `runtime/` subdirectory of path to Helix executable.
268+
269+This order also sets the priority for selecting which file will be used if multiple runtime
270+directories have files with the same name.
271+
272+#### Note to packagers
273+
274+If you are making a package of Helix for end users, to provide a good out of
275+the box experience, you should set the `HELIX_DEFAULT_RUNTIME` environment
276+variable at build time (before invoking `cargo build`) to a directory which
277+will store the final runtime files after installation. For example, say you want
278+to package the runtime into `/usr/lib/helix/runtime`. The rough steps a build
279+script could follow are:
280+
281+1. `export HELIX_DEFAULT_RUNTIME=/usr/lib/helix/runtime`
282+1. `cargo build --profile opt --locked --path helix-term`
283+1. `cp -r runtime $BUILD_DIR/usr/lib/helix/`
284+1. `cp target/opt/hx $BUILD_DIR/usr/bin/hx`
285+
286+This way the resulting `hx` binary will always look for its runtime directory in
287+`/usr/lib/helix/runtime` if the user has no custom runtime in `~/.config/helix`
288+or `HELIX_RUNTIME`.
289+
290+### Validating the installation
291+
292+To make sure everything is set up as expected you should run the Helix health
293+check:
294+
295+```sh
153296hx --health
154297```
155298
156−For more information on the information displayed in the health check results refer to [Healthcheck](https://github.com/helix-editor/helix/wiki/Healthcheck).
299+For more information on the health check results refer to
300+[Health check](https://github.com/helix-editor/helix/wiki/Healthcheck).
301+
302+### Configure the desktop shortcut
157303
158−### Building tree-sitter grammars
304+If your desktop environment supports the
305+[XDG desktop menu](https://specifications.freedesktop.org/menu-spec/menu-spec-latest.html)
306+you can configure Helix to show up in the application menu by copying the
307+provided `.desktop` and icon files to their correct folders:
159308
160−Tree-sitter grammars must be fetched and compiled if not pre-packaged.
161−Fetch grammars with `hx --grammar fetch` (requires `git`) and compile them
162−with `hx --grammar build` (requires a C++ compiler).
309+```sh
310+cp contrib/Helix.desktop ~/.local/share/applications
311+cp contrib/helix.png ~/.icons # or ~/.local/share/icons
312+```
163313
164−### Installing language servers
314+To use another terminal than the system default, you can modify the `.desktop`
315+file. For example, to use `kitty`:
165316
166−Language servers can optionally be installed if you want their features (auto-complete, diagnostics etc.).
167−Follow the [instructions on the wiki page](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers) to add your language servers of choice.
317+```sh
318+sed -i "s|Exec=hx %F|Exec=kitty hx %F|g" ~/.local/share/applications/Helix.desktop
319+sed -i "s|Terminal=true|Terminal=false|g" ~/.local/share/applications/Helix.desktop
320+```
book/src/keymap.md+91−71
@@ -14,17 +14,19 @@
1414 - [Space mode](#space-mode)
1515 - [Popup](#popup)
1616 - [Unimpaired](#unimpaired)
17−- [Insert Mode](#insert-mode)
17+- [Insert mode](#insert-mode)
1818- [Select / extend mode](#select--extend-mode)
1919- [Picker](#picker)
2020- [Prompt](#prompt)
2121
2222> 💡 Mappings marked (**LSP**) require an active language server for the file.
2323
24−> 💡 Mappings marked (**TS**) require a tree-sitter grammar for the filetype.
24+> 💡 Mappings marked (**TS**) require a tree-sitter grammar for the file type.
2525
2626## Normal mode
2727
28+Normal mode is the default mode when you launch helix. You can return to it from other modes by pressing the `Escape` key.
29+
2830### Movement
2931
3032> NOTE: Unlike Vim, `f`, `F`, `t` and `T` are not confined to the current line.
@@ -32,8 +34,8 @@
3234| Key | Description | Command |
3335| ----- | ----------- | ------- |
3436| `h`, `Left` | Move left | `move_char_left` |
35−| `j`, `Down` | Move down | `move_line_down` |
36−| `k`, `Up` | Move up | `move_line_up` |
37+| `j`, `Down` | Move down | `move_visual_line_down` |
38+| `k`, `Up` | Move up | `move_visual_line_up` |
3739| `l`, `Right` | Move right | `move_char_right` |
3840| `w` | Move next word start | `move_next_word_start` |
3941| `b` | Move previous word start | `move_prev_word_start` |
@@ -109,8 +111,10 @@
109111| Key | Description | Command |
110112| ----- | ----------- | ------- |
111113| `s` | Select all regex matches inside selections | `select_regex` |
112−| `S` | Split selection into subselections on regex matches | `split_selection` |
114+| `S` | Split selection into sub selections on regex matches | `split_selection` |
113115| `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` |
114118| `&` | Align selection in columns | `align_selections` |
115119| `_` | Trim whitespace from the selection | `trim_selections` |
116120| `;` | Collapse selection onto a single cursor | `collapse_selection` |
@@ -129,7 +133,7 @@
129133| `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` |
130134| `Alt-x` | Shrink selection to line bounds (line-wise selection) | `shrink_to_line_bounds` |
131135| `J` | Join lines inside selection | `join_selections` |
132−| `Alt-J` | Join lines inside selection and select space | `join_selections_space` |
136+| `Alt-J` | Join lines inside selection and select the inserted space | `join_selections_space` |
133137| `K` | Keep selections matching the regex | `keep_selections` |
134138| `Alt-K` | Remove selections matching the regex | `remove_selections` |
135139| `Ctrl-c` | Comment/uncomment the selections | `toggle_comments` |
@@ -140,7 +144,7 @@
140144
141145### Search
142146
143−Search commands all operate on the `/` register by default. Use `"<char>` to operate on a different one.
147+Search commands all operate on the `/` register by default. To use a different register, use `"<char>`.
144148
145149| Key | Description | Command |
146150| ----- | ----------- | ------- |
@@ -165,15 +169,17 @@ These sub-modes are accessible from normal mode and typically switch back to nor
165169| `Ctrl-w` | Enter [window mode](#window-mode) | N/A |
166170| `Space` | Enter [space mode](#space-mode) | N/A |
167171
172+These modes (except command mode) can be configured by
173+[remapping keys](https://docs.helix-editor.com/remapping.html#minor-modes).
174+
168175#### View mode
169176
170177Accessed by typing `z` in [normal mode](#normal-mode).
171178
172179View mode is intended for scrolling and manipulating the view without changing
173180the selection. The "sticky" variant of this mode (accessed by typing `Z` in
174−normal mode) is persistent; use the Escape key to return to normal mode after
175−usage (useful when you're simply looking over text and not actively editing
176−it).
181+normal mode) is persistent and can be exited using the escape key. This is
182+useful when you're simply looking over text and not actively editing it.
177183
178184
179185| Key | Description | Command |
@@ -215,13 +221,15 @@ Jumps to various locations.
215221| `n` | Go to next buffer | `goto_next_buffer` |
216222| `p` | Go to previous buffer | `goto_previous_buffer` |
217223| `.` | Go to last modification in current file | `goto_last_modification` |
224+| `j` | Move down textual (instead of visual) line | `move_line_down` |
225+| `k` | Move up textual (instead of visual) line | `move_line_up` |
218226
219227#### Match mode
220228
221229Accessed by typing `m` in [normal mode](#normal-mode).
222230
223231See the relevant section in [Usage](./usage.md) for an explanation about
224−[surround](./usage.md#surround) and [textobject](./usage.md#textobjects) usage.
232+[surround](./usage.md#surround) and [textobject](./usage.md#navigating-using-tree-sitter-textobjects) usage.
225233
226234| Key | Description | Command |
227235| ----- | ----------- | ------- |
@@ -238,7 +246,7 @@ TODO: Mappings for selecting syntax nodes (a superset of `[`).
238246
239247Accessed by typing `Ctrl-w` in [normal mode](#normal-mode).
240248
241−This layer is similar to Vim keybindings as Kakoune does not support window.
249+This layer is similar to Vim keybindings as Kakoune does not support windows.
242250
243251| Key | Description | Command |
244252| ----- | ------------- | ------- |
@@ -264,30 +272,32 @@ Accessed by typing `Space` in [normal mode](#normal-mode).
264272
265273This layer is a kludge of mappings, mostly pickers.
266274
267−| Key | Description | Command |
268−| ----- | ----------- | ------- |
269−| `f` | Open file picker | `file_picker` |
270−| `F` | Open file picker at current working directory | `file_picker_in_current_directory` |
271−| `b` | Open buffer picker | `buffer_picker` |
272−| `j` | Open jumplist picker | `jumplist_picker` |
273−| `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` |
274−| `s` | Open document symbol picker (**LSP**) | `symbol_picker` |
275−| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` |
276−| `d` | Open document diagnostics picker (**LSP**) | `diagnostics_picker` |
277−| `D` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnostics_picker` |
278−| `r` | Rename symbol (**LSP**) | `rename_symbol` |
279−| `a` | Apply code action (**LSP**) | `code_action` |
280−| `'` | Open last fuzzy picker | `last_picker` |
281−| `w` | Enter [window mode](#window-mode) | N/A |
282−| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
283−| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
284−| `y` | Join and yank selections to clipboard | `yank_joined_to_clipboard` |
285−| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
286−| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
287−| `/` | Global search in workspace folder | `global_search` |
288−| `?` | Open command palette | `command_palette` |
289−
290−> TIP: Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file.
275+| Key | Description | Command |
276+| ----- | ----------- | ------- |
277+| `f` | Open file picker | `file_picker` |
278+| `F` | Open file picker at current working directory | `file_picker_in_current_directory` |
279+| `b` | Open buffer picker | `buffer_picker` |
280+| `j` | Open jumplist picker | `jumplist_picker` |
281+| `g` | Debug (experimental) | N/A |
282+| `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` |
283+| `s` | Open document symbol picker (**LSP**) | `symbol_picker` |
284+| `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` |
285+| `d` | Open document diagnostics picker (**LSP**) | `diagnostics_picker` |
286+| `D` | Open workspace diagnostics picker (**LSP**) | `workspace_diagnostics_picker` |
287+| `r` | Rename symbol (**LSP**) | `rename_symbol` |
288+| `a` | Apply code action (**LSP**) | `code_action` |
289+| `h` | Select symbol references (**LSP**) | `select_references_to_symbol_under_cursor` |
290+| `'` | Open last fuzzy picker | `last_picker` |
291+| `w` | Enter [window mode](#window-mode) | N/A |
292+| `p` | Paste system clipboard after selections | `paste_clipboard_after` |
293+| `P` | Paste system clipboard before selections | `paste_clipboard_before` |
294+| `y` | Yank selections to clipboard | `yank_to_clipboard` |
295+| `Y` | Yank main selection to clipboard | `yank_main_selection_to_clipboard` |
296+| `R` | Replace selections by clipboard contents | `replace_selections_with_clipboard` |
297+| `/` | Global search in workspace folder | `global_search` |
298+| `?` | Open command palette | `command_palette` |
299+
300+> 💡 Global search displays results in a fuzzy picker, use `Space + '` to bring it back up after opening a file.
291301
292302##### Popup
293303
@@ -297,40 +307,47 @@ Displays documentation for item under cursor.
297307| ---- | ----------- |
298308| `Ctrl-u` | Scroll up |
299309| `Ctrl-d` | Scroll down |
300−
310+
301311#### Unimpaired
302312
303−Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
313+These mappings are in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaired).
304314
305315| Key | Description | Command |
306316| ----- | ----------- | ------- |
307−| `[d` | Go to previous diagnostic (**LSP**) | `goto_prev_diag` |
308317| `]d` | Go to next diagnostic (**LSP**) | `goto_next_diag` |
309−| `[D` | Go to first diagnostic in document (**LSP**) | `goto_first_diag` |
318+| `[d` | Go to previous diagnostic (**LSP**) | `goto_prev_diag` |
310319| `]D` | Go to last diagnostic in document (**LSP**) | `goto_last_diag` |
320+| `[D` | Go to first diagnostic in document (**LSP**) | `goto_first_diag` |
311321| `]f` | Go to next function (**TS**) | `goto_next_function` |
312322| `[f` | Go to previous function (**TS**) | `goto_prev_function` |
313−| `]c` | Go to next class (**TS**) | `goto_next_class` |
314−| `[c` | Go to previous class (**TS**) | `goto_prev_class` |
323+| `]t` | Go to next type definition (**TS**) | `goto_next_class` |
324+| `[t` | Go to previous type definition (**TS**) | `goto_prev_class` |
315325| `]a` | Go to next argument/parameter (**TS**) | `goto_next_parameter` |
316326| `[a` | Go to previous argument/parameter (**TS**) | `goto_prev_parameter` |
317−| `]o` | Go to next comment (**TS**) | `goto_next_comment` |
318−| `[o` | Go to previous comment (**TS**) | `goto_prev_comment` |
319−| `]t` | Go to next test (**TS**) | `goto_next_test` |
320−| `]t` | Go to previous test (**TS**) | `goto_prev_test` |
327+| `]c` | Go to next comment (**TS**) | `goto_next_comment` |
328+| `[c` | Go to previous comment (**TS**) | `goto_prev_comment` |
329+| `]T` | Go to next test (**TS**) | `goto_next_test` |
330+| `[T` | Go to previous test (**TS**) | `goto_prev_test` |
321331| `]p` | Go to next paragraph | `goto_next_paragraph` |
322332| `[p` | Go to previous paragraph | `goto_prev_paragraph` |
323−| `[Space` | Add newline above | `add_newline_above` |
333+| `]g` | Go to next change | `goto_next_change` |
334+| `[g` | Go to previous change | `goto_prev_change` |
335+| `]G` | Go to last change | `goto_last_change` |
336+| `[G` | Go to first change | `goto_first_change` |
324337| `]Space` | Add newline below | `add_newline_below` |
338+| `[Space` | Add newline above | `add_newline_above` |
325339
326340## Insert mode
327341
328−Insert mode bindings are somewhat minimal by default. Helix is designed to
342+Accessed by typing `i` in [normal mode](#normal-mode).
343+
344+Insert mode bindings are minimal by default. Helix is designed to
329345be a modal editor, and this is reflected in the user experience and internal
330−mechanics. For example, changes to the text are only saved for undos when
331−escaping from insert mode to normal mode. For this reason, new users are
332−strongly encouraged to learn the modal editing paradigm to get the smoothest
333−experience.
346+mechanics. Changes to the text are only saved for undos when
347+escaping from insert mode to normal mode.
348+
349+> 💡 New users are strongly encouraged to learn the modal editing paradigm
350+> to get the smoothest experience.
334351
335352| Key | Description | Command |
336353| ----- | ----------- | ------- |
@@ -342,7 +359,7 @@ experience.
342359| `Alt-d`, `Alt-Delete` | Delete next word | `delete_word_forward` |
343360| `Ctrl-u` | Delete to start of line | `kill_to_line_start` |
344361| `Ctrl-k` | Delete to end of line | `kill_to_line_end` |
345−| `Ctrl-h`, `Backspace` | Delete previous char | `delete_char_backward` |
362+| `Ctrl-h`, `Backspace`, `Shift-Backspace` | Delete previous char | `delete_char_backward` |
346363| `Ctrl-d`, `Delete` | Delete next char | `delete_char_forward` |
347364| `Ctrl-j`, `Enter` | Insert new line | `insert_newline` |
348365
@@ -360,8 +377,8 @@ with modal editors.
360377| `Home` | Move to line start | `goto_line_start` |
361378| `End` | Move to line end | `goto_line_end_newline` |
362379
363−If you want to disable them in insert mode as you become more comfortable with modal editing, you can use
364−the following in your `config.toml`:
380+As you become more comfortable with modal editing, you may want to disable some
381+insert mode bindings. You can do this by editing your `config.toml` file.
365382
366383```toml
367384[keys.insert]
@@ -377,9 +394,11 @@ end = "no_op"
377394
378395## Select / extend mode
379396
380−This mode echoes Normal mode, but changes any movements to extend
397+Accessed by typing `v` in [normal mode](#normal-mode).
398+
399+Select mode echoes Normal mode, but changes any movements to extend
381400selections rather than replace them. Goto motions are also changed to
382−extend, so that `vgl` for example extends the selection to the end of
401+extend, so that `vgl`, for example, extends the selection to the end of
383402the line.
384403
385404Search is also affected. By default, `n` and `N` will remove the current
@@ -392,19 +411,20 @@ you to selectively add search terms to your selections.
392411
393412Keys to use within picker. Remapping currently not supported.
394413
395−| Key | Description |
396−| ----- | ------------- |
397−| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
398−| `Tab`, `Down`, `Ctrl-n` | Next entry |
399−| `PageUp`, `Ctrl-u` | Page up |
400−| `PageDown`, `Ctrl-d` | Page down |
401−| `Home` | Go to first entry |
402−| `End` | Go to last entry |
403−| `Enter` | Open selected |
404−| `Ctrl-s` | Open horizontally |
405−| `Ctrl-v` | Open vertically |
406−| `Ctrl-t` | Toggle preview |
407−| `Escape`, `Ctrl-c` | Close picker |
414+| Key | Description |
415+| ----- | ------------- |
416+| `Shift-Tab`, `Up`, `Ctrl-p` | Previous entry |
417+| `Tab`, `Down`, `Ctrl-n` | Next entry |
418+| `PageUp`, `Ctrl-u` | Page up |
419+| `PageDown`, `Ctrl-d` | Page down |
420+| `Home` | Go to first entry |
421+| `End` | Go to last entry |
422+| `Enter` | Open selected |
423+| `Alt-Enter` | Open selected in the background without closing the picker |
424+| `Ctrl-s` | Open horizontally |
425+| `Ctrl-v` | Open vertically |
426+| `Ctrl-t` | Toggle preview |
427+| `Escape`, `Ctrl-c` | Close picker |
408428
409429## Prompt
410430
@@ -423,7 +443,7 @@ Keys to use within prompt, Remapping currently not supported.
423443| `Alt-d`, `Alt-Delete`, `Ctrl-Delete` | Delete next word |
424444| `Ctrl-u` | Delete to start of line |
425445| `Ctrl-k` | Delete to end of line |
426−| `Backspace`, `Ctrl-h` | Delete previous char |
446+| `Backspace`, `Ctrl-h`, `Shift-Backspace` | Delete previous char |
427447| `Delete`, `Ctrl-d` | Delete next char |
428448| `Ctrl-s` | Insert a word under doc cursor, may be changed to Ctrl-r Ctrl-w later |
429449| `Ctrl-p`, `Up` | Select previous history |
book/src/lang-support.md+2−2
@@ -1,10 +1,10 @@
11# Language Support
22
3−The following languages and Language Servers are supported. In order to use
3+The following languages and Language Servers are supported. To use
44Language Server features, you must first [install][lsp-install-wiki] the
55appropriate Language Server.
66
7−Check the language support in your installed helix version with `hx --health`.
7+You can check the language support in your installed helix version with `hx --health`.
88
99Also see the [Language Configuration][lang-config] docs and the [Adding
1010Languages][adding-languages] guide for more language configuration information.
book/src/languages.md+118−38
@@ -5,26 +5,31 @@ in `languages.toml` files.
55
66## `languages.toml` files
77
8−There are three possible `languages.toml` files. The first is compiled into
9−Helix and lives in the [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml).
10−This provides the default configurations for languages and language servers.
8+There are three possible locations for a `languages.toml` file:
119
12−You may define a `languages.toml` in your [configuration directory](./configuration.md)
13−which overrides values from the built-in language configuration. For example
14−to disable auto-LSP-formatting in Rust:
10+1. In the Helix source code, which lives in the
11+ [Helix repository](https://github.com/helix-editor/helix/blob/master/languages.toml).
12+ It provides the default configurations for languages and language servers.
1513
16−```toml
17−# in <config_dir>/helix/languages.toml
14+2. In your [configuration directory](./configuration.md). This overrides values
15+ from the built-in language configuration. For example, to disable
16+ auto-LSP-formatting in Rust:
1817
19−[[language]]
20−name = "rust"
21−auto-format = false
22−```
18+ ```toml
19+ # in <config_dir>/helix/languages.toml
20+
21+ [language-server.mylang-lsp]
22+ command = "mylang-lsp"
2323
24−Language configuration may also be overridden local to a project by creating
25−a `languages.toml` file under a `.helix` directory. Its settings will be merged
26−with the language configuration in the configuration directory and the built-in
27−configuration.
24+ [[language]]
25+ name = "rust"
26+ auto-format = false
27+ ```
28+
29+3. In a `.helix` folder in your project. Language configuration may also be
30+ overridden local to a project by creating a `languages.toml` file in a
31+ `.helix` folder. Its settings will be merged with the language configuration
32+ in the configuration directory and the built-in configuration.
2833
2934## Language configuration
3035
@@ -35,12 +40,12 @@ Each language is configured by adding a `[[language]]` section to a
3540[[language]]
3641name = "mylang"
3742scope = "source.mylang"
38−injection-regex = "^mylang$"
43+injection-regex = "mylang"
3944file-types = ["mylang", "myl"]
4045comment-token = "#"
4146indent = { tab-width = 2, unit = " " }
42−language-server = { command = "mylang-lsp", args = ["--stdio"] }
4347formatter = { command = "mylang-formatter" , args = ["--stdin"] }
48+language-servers = [ "mylang-lsp" ]
4449```
4550
4651These configuration keys are available:
@@ -48,6 +53,7 @@ These configuration keys are available:
4853| Key | Description |
4954| ---- | ----------- |
5055| `name` | The name of the language |
56+| `language-id` | The language-id for language servers, checkout the table at [TextDocumentItem](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem) for the right id |
5157| `scope` | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.<name>` or `text.<name>` in case of markup languages |
5258| `injection-regex` | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. |
5359| `file-types` | The filetypes of the language, for example `["yml", "yaml"]`. See the file-type detection section below. |
@@ -56,16 +62,18 @@ These configuration keys are available:
5662| `auto-format` | Whether to autoformat this language when saving |
5763| `diagnostic-severity` | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) |
5864| `comment-token` | The token to use as a comment-token |
59−| `indent` | The indent to use. Has sub keys `tab-width` and `unit` |
60−| `language-server` | The Language Server to run. See the Language Server configuration section below. |
61−| `config` | Language Server configuration |
65+| `indent` | The indent to use. Has sub keys `unit` (the text inserted into the document when indenting; usually set to N spaces or `"\t"` for tabs) and `tab-width` (the number of spaces rendered for a tab) |
66+| `language-servers` | The Language Servers used for this language. See below for more information in the section [Configuring Language Servers for a language](#configuring-language-servers-for-a-language) |
6267| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
6368| `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 |
64−| `max-line-length` | Maximum line length. Used for the `:reflow` command |
69+| `soft-wrap` | [editor.softwrap](./configuration.md#editorsoft-wrap-section)
70+| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
71+| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml`. Overwrites the setting of the same name in `config.toml` if set. |
72+| `persistent-diagnostic-sources` | An array of LSP diagnostic sources assumed unchanged when the language server resends the same set of diagnostics. Helix can track the position for these diagnostics internally instead. Useful for diagnostics that are recomputed on save.
6573
6674### File-type detection and the `file-types` key
6775
68−Helix determines which language configuration to use with the `file-types` key
76+Helix determines which language configuration to use based on the `file-types` key
6977from the above section. `file-types` is a list of strings or tables, for
7078example:
7179
@@ -89,30 +97,102 @@ with the following priorities:
8997 replaced at runtime with the appropriate path separator for the operating
9098 system, so this rule would match against `.git\config` files on Windows.
9199
92−### Language Server configuration
100+## Language Server configuration
101+
102+Language servers are configured separately in the table `language-server` in the same file as the languages `languages.toml`
93103
94−The `language-server` field takes the following keys:
104+For example:
95105
96−| Key | Description |
97−| --- | ----------- |
98−| `command` | The name of the language server binary to execute. Binaries must be in `$PATH` |
99−| `args` | A list of arguments to pass to the language server binary |
100−| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
101−| `language-id` | The language name to pass to the language server. Some language servers support multiple languages and use this field to determine which one is being served in a buffer |
106+```toml
107+[language-server.mylang-lsp]
108+command = "mylang-lsp"
109+args = ["--stdio"]
110+config = { provideFormatter = true }
111+environment = { "ENV1" = "value1", "ENV2" = "value2" }
112+
113+[language-server.efm-lsp-prettier]
114+command = "efm-langserver"
115+
116+[language-server.efm-lsp-prettier.config]
117+documentFormatting = true
118+languages = { typescript = [ { formatCommand ="prettier --stdin-filepath ${INPUT}", formatStdin = true } ] }
119+```
102120
103−The top-level `config` field is used to configure the LSP initialization options. A `format`
104−sub-table within `config` can be used to pass extra formatting options to
105−[Document Formatting Requests](https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-16.md#document-formatting-request--leftwards_arrow_with_hook).
106−For example with typescript:
121+These are the available options for a language server.
122+
123+| Key | Description |
124+| ---- | ----------- |
125+| `command` | The name or path of the language server binary to execute. Binaries must be in `$PATH` |
126+| `args` | A list of arguments to pass to the language server binary |
127+| `config` | LSP initialization options |
128+| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
129+| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
130+
131+A `format` sub-table within `config` can be used to pass extra formatting options to
132+[Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting).
133+For example, with typescript:
107134
108135```toml
109−[[language]]
110−name = "typescript"
111−auto-format = true
136+[language-server.typescript-language-server]
112137# pass format options according to https://github.com/typescript-language-server/typescript-language-server#workspacedidchangeconfiguration omitting the "[language].format." prefix.
113138config = { format = { "semicolons" = "insert", "insertSpaceBeforeFunctionParenthesis" = true } }
114139```
115140
141+### Configuring Language Servers for a language
142+
143+The `language-servers` attribute in a language tells helix which language servers are used for this language.
144+
145+They have to be defined in the `[language-server]` table as described in the previous section.
146+
147+Different languages can use the same language server instance, e.g. `typescript-language-server` is used for javascript, jsx, tsx and typescript by default.
148+
149+In case multiple language servers are specified in the `language-servers` attribute of a `language`,
150+it's often useful to only enable/disable certain language-server features for these language servers.
151+
152+As an example, `efm-lsp-prettier` of the previous example is used only with a formatting command `prettier`,
153+so everything else should be handled by the `typescript-language-server` (which is configured by default).
154+The language configuration for typescript could look like this:
155+
156+```toml
157+[[language]]
158+name = "typescript"
159+language-servers = [ { name = "efm-lsp-prettier", only-features = [ "format" ] }, "typescript-language-server" ]
160+```
161+
162+or equivalent:
163+
164+```toml
165+[[language]]
166+name = "typescript"
167+language-servers = [ { name = "typescript-language-server", except-features = [ "format" ] }, "efm-lsp-prettier" ]
168+```
169+
170+Each requested LSP feature is prioritized in the order of the `language-servers` array.
171+For example, the first `goto-definition` supported language server (in this case `typescript-language-server`) will be taken for the relevant LSP request (command `goto_definition`).
172+The features `diagnostics`, `code-action`, `completion`, `document-symbols` and `workspace-symbols` are an exception to that rule, as they are working for all language servers at the same time and are merged together, if enabled for the language.
173+If no `except-features` or `only-features` is given, all features for the language server are enabled.
174+If a language server itself doesn't support a feature, the next language server array entry will be tried (and so on).
175+
176+The list of supported features is:
177+
178+- `format`
179+- `goto-definition`
180+- `goto-declaration`
181+- `goto-type-definition`
182+- `goto-reference`
183+- `goto-implementation`
184+- `signature-help`
185+- `hover`
186+- `document-highlight`
187+- `completion`
188+- `code-action`
189+- `workspace-command`
190+- `document-symbols`
191+- `workspace-symbols`
192+- `diagnostics`
193+- `rename-symbol`
194+- `inlay-hints`
195+
116196## Tree-sitter grammar configuration
117197
118198The source for a language's tree-sitter grammar is specified in a `[[grammar]]`
book/src/remapping.md+35−10
@@ -1,18 +1,18 @@
1−# Key Remapping
1+# Key remapping
22
3−One-way key remapping is temporarily supported via a simple TOML configuration
3+Helix currently supports one-way key remapping through a simple TOML configuration
44file. (More powerful solutions such as rebinding via commands will be
55available in the future).
66
7−To remap keys, write a `config.toml` file in your `helix` configuration
8−directory (default `~/.config/helix` in Linux systems) with a structure like
7+To remap keys, create a `config.toml` file in your `helix` configuration
8+directory (default `~/.config/helix` on Linux systems) with a structure like
99this:
1010
1111```toml
1212# At most one section each of 'keys.normal', 'keys.insert' and 'keys.select'
1313[keys.normal]
14−C-s = ":w" # Maps the Ctrl-s to the typable command :w which is an alias for :write (save file)
15−C-o = ":open ~/.config/helix/config.toml" # Maps the Ctrl-o to opening of the helix config file
14+C-s = ":w" # Maps Ctrl-s to the typable command :w which is an alias for :write (save file)
15+C-o = ":open ~/.config/helix/config.toml" # Maps Ctrl-o to opening of the helix config file
1616a = "move_char_left" # Maps the 'a' key to the move_char_left command
1717w = "move_line_up" # Maps the 'w' key move_line_up
1818"C-S-esc" = "extend_line" # Maps Ctrl-Shift-Escape to extend_line
@@ -20,10 +20,35 @@ g = { a = "code_action" } # Maps `ga` to show possible code actions
2020"ret" = ["open_below", "normal_mode"] # Maps the enter key to open_below then re-enter normal mode
2121
2222[keys.insert]
23−"A-x" = "normal_mode" # Maps Alt-X to enter normal mode
23+"A-x" = "normal_mode" # Maps Alt-X to enter normal mode
2424j = { k = "normal_mode" } # Maps `jk` to exit insert mode
2525```
26−> NOTE: Typable commands can also be remapped, remember to keep the `:` prefix to indicate it's a typable command.
26+
27+## Minor modes
28+
29+Minor modes are accessed by pressing a key (usually from normal mode), giving access to dedicated bindings. Bindings
30+can be modified or added by nesting definitions.
31+
32+```toml
33+[keys.insert.j]
34+k = "normal_mode" # Maps `jk` to exit insert mode
35+
36+[keys.normal.g]
37+a = "code_action" # Maps `ga` to show possible code actions
38+
39+# invert `j` and `k` in view mode
40+[keys.normal.z]
41+j = "scroll_up"
42+k = "scroll_down"
43+
44+# create a new minor mode bound to `+`
45+[keys.normal."+"]
46+m = ":run-shell-command make"
47+c = ":run-shell-command cargo build"
48+t = ":run-shell-command cargo test"
49+```
50+
51+## Special keys and modifiers
2752
2853Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes
2954`C-`, `S-` and `A-`. Special keys are encoded as follows:
@@ -50,5 +75,5 @@ Ctrl, Shift and Alt modifiers are encoded respectively with the prefixes
5075
5176Keys can be disabled by binding them to the `no_op` command.
5277
53−Commands can be found at [Keymap](https://docs.helix-editor.com/keymap.html) Commands.
54−> Commands can also be found in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) at the invocation of `static_commands!` macro and the `TypableCommandList`.
78+A list of commands is available in the [Keymap](https://docs.helix-editor.com/keymap.html) documentation
79+ and in the source code at [`helix-term/src/commands.rs`](https://github.com/helix-editor/helix/blob/master/helix-term/src/commands.rs) at the invocation of `static_commands!` macro and the `TypableCommandList`.
book/src/themes.md+130−93
@@ -1,14 +1,15 @@
11# Themes
22
3−To use a theme add `theme = "<name>"` to your [`config.toml`](./configuration.md) at the very top of the file before the first section or select it during runtime using `:theme <name>`.
3+To use a theme add `theme = "<name>"` to the top of your [`config.toml`](./configuration.md) file, or select it during runtime using `:theme <name>`.
44
55## Creating a theme
66
7−Create a file with the name of your theme as file name (i.e `mytheme.toml`) and place it in your `themes` directory (i.e `~/.config/helix/themes`). The directory might have to be created beforehand.
7+Create a file with the name of your theme as the file name (i.e `mytheme.toml`) and place it in your `themes` directory (i.e `~/.config/helix/themes` or `%AppData%\helix\themes` on Windows). The directory might have to be created beforehand.
88
9−The names "default" and "base16_default" are reserved for the builtin themes and cannot be overridden by user defined themes.
9+> 💡 The names "default" and "base16_default" are reserved for built-in themes
10+> and cannot be overridden by user-defined themes.
1011
11−The default theme.toml can be found [here](https://github.com/helix-editor/helix/blob/master/theme.toml), and user submitted themes [here](https://github.com/helix-editor/helix/blob/master/runtime/themes).
12+### Overview
1213
1314Each line in the theme file is specified as below:
1415
@@ -16,7 +17,7 @@ Each line in the theme file is specified as below:
1617key = { fg = "#ffffff", bg = "#000000", underline = { color = "#ff0000", style = "curl"}, modifiers = ["bold", "italic"] }
1718```
1819
19−where `key` represents what you want to style, `fg` specifies the foreground color, `bg` the background color, `underline` the underline `style`/`color`, and `modifiers` is a list of style modifiers. `bg`, `underline` and `modifiers` can be omitted to defer to the defaults.
20+Where `key` represents what you want to style, `fg` specifies the foreground color, `bg` the background color, `underline` the underline `style`/`color`, and `modifiers` is a list of style modifiers. `bg`, `underline` and `modifiers` can be omitted to defer to the defaults.
2021
2122To specify only the foreground color:
2223
@@ -24,15 +25,30 @@ To specify only the foreground color:
2425key = "#ffffff"
2526```
2627
27−if the key contains a dot `'.'`, it must be quoted to prevent it being parsed as a [dotted key](https://toml.io/en/v1.0.0#keys).
28+If the key contains a dot `'.'`, it must be quoted to prevent it being parsed as a [dotted key](https://toml.io/en/v1.0.0#keys).
2829
2930```toml
3031"key.key" = "#ffffff"
3132```
3233
34+For inspiration, you can find the default `theme.toml`
35+[here](https://github.com/helix-editor/helix/blob/master/theme.toml) and
36+user-submitted themes
37+[here](https://github.com/helix-editor/helix/blob/master/runtime/themes).
38+
39+### Using the linter
40+
41+Use the supplied linting tool to check for errors and missing scopes:
42+
43+```sh
44+cargo xtask themelint onedark # replace onedark with <name>
45+```
46+
47+## The details of theme creation
48+
3349### Color palettes
3450
35−It's recommended define a palette of named colors, and refer to them from the
51+It's recommended to define a palette of named colors, and refer to them in the
3652configuration values in your theme. To do this, add a table called
3753`palette` to your theme file:
3854
@@ -45,8 +61,8 @@ white = "#ffffff"
4561black = "#000000"
4662```
4763
48−Remember that the `[palette]` table includes all keys after its header,
49−so you should define the palette after normal theme options.
64+Keep in mind that the `[palette]` table includes all keys after its header,
65+so it should be defined after the normal theme options.
5066
5167The default palette uses the terminal's default 16 colors, and the colors names
5268are listed below. The `[palette]` section in the config file takes precedence
@@ -54,6 +70,7 @@ over it and is merged into the default palette.
5470
5571| Color Name |
5672| --- |
73+| `default` |
5774| `black` |
5875| `red` |
5976| `green` |
@@ -73,9 +90,8 @@ over it and is merged into the default palette.
7390
7491### Modifiers
7592
76−The following values may be used as modifiers.
77−
78−Less common modifiers might not be supported by your terminal emulator.
93+The following values may be used as modifier, provided they are supported by
94+your terminal emulator.
7995
8096| Modifier |
8197| --- |
@@ -89,27 +105,26 @@ Less common modifiers might not be supported by your terminal emulator.
89105| `hidden` |
90106| `crossed_out` |
91107
92−> Note: The `underlined` modifier is deprecated and only available for backwards compatibility.
108+> 💡 The `underlined` modifier is deprecated and only available for backwards compatibility.
93109> Its behavior is equivalent to setting `underline.style="line"`.
94110
95−### Underline Style
96−
97−One of the following values may be used as a value for `underline.style`.
111+### Underline style
98112
99−Some styles might not be supported by your terminal emulator.
113+One of the following values may be used as a value for `underline.style`, providing it is
114+supported by your terminal emulator.
100115
101116| Modifier |
102117| --- |
103118| `line` |
104119| `curl` |
105120| `dashed` |
106−| `dot` |
121+| `dotted` |
107122| `double_line` |
108123
109124
110125### Inheritance
111126
112−Extend upon other themes by setting the `inherits` property to an existing theme.
127+Extend other themes by setting the `inherits` property to an existing theme.
113128
114129```toml
115130inherits = "boo_berry"
@@ -124,25 +139,28 @@ berry = "#2A2A4D"
124139
125140### Scopes
126141
127−The following is a list of scopes available to use for styling.
142+The following is a list of scopes available to use for styling:
128143
129144#### Syntax highlighting
130145
131146These keys match [tree-sitter scopes](https://tree-sitter.github.io/tree-sitter/syntax-highlighting#theme).
132147
133−For a given highlight produced, styling will be determined based on the longest matching theme key. For example, the highlight `function.builtin.static` would match the key `function.builtin` rather than `function`.
148+When determining styling for a highlight, the longest matching theme key will be used. For example, if the highlight is `function.builtin.static`, the key `function.builtin` will be used instead of `function`.
134149
135150We use a similar set of scopes as
136−[SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also
151+[Sublime Text](https://www.sublimetext.com/docs/scope_naming.html). See also
137152[TextMate](https://macromates.com/manual/en/language_grammars) scopes.
138153
139−- `attribute` - Class attributes, html tag attributes
154+- `attribute` - Class attributes, HTML tag attributes
140155
141156- `type` - Types
142157 - `builtin` - Primitive types provided by the language (`int`, `usize`)
158+ - `parameter` - Generic type parameters (`T`)
159+ - `enum`
160+ - `variant`
143161- `constructor`
144162
145−- `constant` (TODO: constant.other.placeholder for %v)
163+- `constant` (TODO: constant.other.placeholder for `%v`)
146164 - `builtin` Special constants provided by the language (`true`, `false`, `nil` etc)
147165 - `boolean`
148166 - `character`
@@ -160,11 +178,11 @@ We use a similar set of scopes as
160178
161179- `comment` - Code comments
162180 - `line` - Single line comments (`//`)
163− - `block` - Block comments (e.g. (`/* */`)
181+ - `block` - Block comments (e.g. (`/* */`)
164182 - `documentation` - Documentation comments (e.g. `///` in Rust)
165183
166184- `variable` - Variables
167− - `builtin` - Reserved language variables (`self`, `this`, `super`, etc)
185+ - `builtin` - Reserved language variables (`self`, `this`, `super`, etc.)
168186 - `parameter` - Function parameters
169187 - `other`
170188 - `member` - Fields of composite data types (e.g. structs, unions)
@@ -184,10 +202,10 @@ We use a similar set of scopes as
184202 - `return`
185203 - `exception`
186204 - `operator` - `or`, `in`
187− - `directive` - Preprocessor directives (`#if` in C)
205+ - `directive` - Preprocessor directives (`#if` in C)
188206 - `function` - `fn`, `func`
189207 - `storage` - Keywords describing how things are stored
190− - `type` - The type of something, `class`, `function`, `var`, `let`, etc.
208+ - `type` - The type of something, `class`, `function`, `var`, `let`, etc.
191209 - `modifier` - Storage modifiers like `static`, `mut`, `const`, `ref`, etc.
192210
193211- `operator` - `||`, `+=`, `>`
@@ -199,9 +217,12 @@ We use a similar set of scopes as
199217 - `special` (preprocessor in C)
200218
201219- `tag` - Tags (e.g. `<body>` in HTML)
220+ - `builtin`
202221
203222- `namespace`
204223
224+- `special`
225+
205226- `markup`
206227 - `heading`
207228 - `marker`
@@ -209,12 +230,15 @@ We use a similar set of scopes as
209230 - `list`
210231 - `unnumbered`
211232 - `numbered`
233+ - `checked`
234+ - `unchecked`
212235 - `bold`
213236 - `italic`
237+ - `strikethrough`
214238 - `link`
215− - `url` - urls pointed to by links
216− - `label` - non-url link references
217− - `text` - url and image descriptions in links
239+ - `url` - URLs pointed to by links
240+ - `label` - non-URL link references
241+ - `text` - URL and image descriptions in links
218242 - `quote`
219243 - `raw`
220244 - `inline`
@@ -222,79 +246,92 @@ We use a similar set of scopes as
222246
223247- `diff` - version control changes
224248 - `plus` - additions
249+ - `gutter` - gutter indicator
225250 - `minus` - deletions
251+ - `gutter` - gutter indicator
226252 - `delta` - modifications
227253 - `moved` - renamed or moved files/changes
254+ - `gutter` - gutter indicator
228255
229256#### Interface
230257
231−These scopes are used for theming the editor interface.
258+These scopes are used for theming the editor interface:
232259
233260- `markup`
234261 - `normal`
235− - `completion` - for completion doc popup ui
236− - `hover` - for hover popup ui
262+ - `completion` - for completion doc popup UI
263+ - `hover` - for hover popup UI
237264 - `heading`
238− - `completion` - for completion doc popup ui
239− - `hover` - for hover popup ui
265+ - `completion` - for completion doc popup UI
266+ - `hover` - for hover popup UI
240267 - `raw`
241268 - `inline`
242− - `completion` - for completion doc popup ui
243− - `hover` - for hover popup ui
244−
245−
246−| Key | Notes |
247−| --- | --- |
248−| `ui.background` | |
249−| `ui.background.separator` | Picker separator below input line |
250−| `ui.cursor` | |
251−| `ui.cursor.insert` | |
252−| `ui.cursor.select` | |
253−| `ui.cursor.match` | Matching bracket etc. |
254−| `ui.cursor.primary` | Cursor with primary selection |
255−| `ui.gutter` | Gutter |
256−| `ui.gutter.selected` | Gutter for the line the cursor is on |
257−| `ui.linenr` | Line numbers |
258−| `ui.linenr.selected` | Line number for the line the cursor is on |
259−| `ui.statusline` | Statusline |
260−| `ui.statusline.inactive` | Statusline (unfocused document) |
261−| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
262−| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
263−| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
264−| `ui.statusline.separator` | Separator character in statusline |
265−| `ui.popup` | Documentation popups (e.g Space + k) |
266−| `ui.popup.info` | Prompt for multiple key options |
267−| `ui.window` | Border lines separating splits |
268−| `ui.help` | Description box for commands |
269−| `ui.text` | Command prompts, popup text, etc. |
270−| `ui.text.focus` | |
271−| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
272−| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
273−| `ui.virtual.whitespace` | Visible whitespace characters |
274−| `ui.virtual.indent-guide` | Vertical indent width guides |
275−| `ui.menu` | Code and command completion menus |
276−| `ui.menu.selected` | Selected autocomplete item |
277−| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
278−| `ui.selection` | For selections in the editing area |
279−| `ui.selection.primary` | |
280−| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
281−| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
282−| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
283−| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
284−| `warning` | Diagnostics warning (gutter) |
285−| `error` | Diagnostics error (gutter) |
286−| `info` | Diagnostics info (gutter) |
287−| `hint` | Diagnostics hint (gutter) |
288−| `diagnostic` | Diagnostics fallback style (editing area) |
289−| `diagnostic.hint` | Diagnostics hint (editing area) |
290−| `diagnostic.info` | Diagnostics info (editing area) |
291−| `diagnostic.warning` | Diagnostics warning (editing area) |
292−| `diagnostic.error` | Diagnostics error (editing area) |
293−
294−You can check compliance to spec with
295−
296−```shell
297−cargo xtask themelint onedark # replace onedark with <name>
298−```
269+ - `completion` - for completion doc popup UI
270+ - `hover` - for hover popup UI
271+
272+
273+| Key | Notes |
274+| --- | --- |
275+| `ui.background` | |
276+| `ui.background.separator` | Picker separator below input line |
277+| `ui.cursor` | |
278+| `ui.cursor.normal` | |
279+| `ui.cursor.insert` | |
280+| `ui.cursor.select` | |
281+| `ui.cursor.match` | Matching bracket etc. |
282+| `ui.cursor.primary` | Cursor with primary selection |
283+| `ui.cursor.primary.normal` | |
284+| `ui.cursor.primary.insert` | |
285+| `ui.cursor.primary.select` | |
286+| `ui.debug.breakpoint` | Breakpoint indicator, found in the gutter |
287+| `ui.debug.active` | Indicator for the line at which debugging execution is paused at, found in the gutter |
288+| `ui.gutter` | Gutter |
289+| `ui.gutter.selected` | Gutter for the line the cursor is on |
290+| `ui.highlight.frameline` | Line at which debugging execution is paused at |
291+| `ui.linenr` | Line numbers |
292+| `ui.linenr.selected` | Line number for the line the cursor is on |
293+| `ui.statusline` | Statusline |
294+| `ui.statusline.inactive` | Statusline (unfocused document) |
295+| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
296+| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
297+| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
298+| `ui.statusline.separator` | Separator character in statusline |
299+| `ui.bufferline` | Style for the buffer line |
300+| `ui.bufferline.active` | Style for the active buffer in buffer line |
301+| `ui.bufferline.background` | Style for bufferline background |
302+| `ui.popup` | Documentation popups (e.g. Space + k) |
303+| `ui.popup.info` | Prompt for multiple key options |
304+| `ui.window` | Borderlines separating splits |
305+| `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 |
308+| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) |
309+| `ui.text.info` | The key: command text in `ui.popup.info` boxes |
310+| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) |
311+| `ui.virtual.whitespace` | Visible whitespace characters |
312+| `ui.virtual.indent-guide` | Vertical indent width guides |
313+| `ui.virtual.inlay-hint` | Default style for inlay hints of all kinds |
314+| `ui.virtual.inlay-hint.parameter` | Style for inlay hints of kind `parameter` (LSPs are not required to set a kind) |
315+| `ui.virtual.inlay-hint.type` | Style for inlay hints of kind `type` (LSPs are not required to set a kind) |
316+| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) |
317+| `ui.menu` | Code and command completion menus |
318+| `ui.menu.selected` | Selected autocomplete item |
319+| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar |
320+| `ui.selection` | For selections in the editing area |
321+| `ui.selection.primary` | |
322+| `ui.highlight` | Highlighted lines in the picker preview |
323+| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) |
324+| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) |
325+| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) |
326+| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) |
327+| `warning` | Diagnostics warning (gutter) |
328+| `error` | Diagnostics error (gutter) |
329+| `info` | Diagnostics info (gutter) |
330+| `hint` | Diagnostics hint (gutter) |
331+| `diagnostic` | Diagnostics fallback style (editing area) |
332+| `diagnostic.hint` | Diagnostics hint (editing area) |
333+| `diagnostic.info` | Diagnostics info (editing area) |
334+| `diagnostic.warning` | Diagnostics warning (editing area) |
335+| `diagnostic.error` | Diagnostics error (editing area) |
299336
300337[editor-section]: ./configuration.md#editor-section
book/src/usage.md+131−92
@@ -1,65 +1,151 @@
1−# Usage
1+# Using Helix
22
3−(Currently not fully documented, see the [keymappings](./keymap.md) list for more.)
3+<!--toc:start-->
4+- [Registers](#registers)
5+ - [User-defined registers](#user-defined-registers)
6+ - [Special registers](#special-registers)
7+- [Surround](#surround)
8+- [Selecting and manipulating text with textobjects](#selecting-and-manipulating-text-with-textobjects)
9+- [Navigating using tree-sitter textobjects](#navigating-using-tree-sitter-textobjects)
10+- [Moving the selection with syntax-aware motions](#moving-the-selection-with-syntax-aware-motions)
11+<!--toc:end-->
412
5−See [tutor](https://github.com/helix-editor/helix/blob/master/runtime/tutor) (accessible via `hx --tutor` or `:tutor`) for a vimtutor-like introduction.
13+For a full interactive introduction to Helix, refer to the
14+[tutor](https://github.com/helix-editor/helix/blob/master/runtime/tutor) which
15+can be accessed via the command `hx --tutor` or `:tutor`.
16+
17+> 💡 Currently, not all functionality is fully documented, please refer to the
18+> [key mappings](./keymap.md) list.
619
720## Registers
821
9−Vim-like registers can be used to yank and store text to be pasted later. Usage is similar, with `"` being used to select a register:
22+In Helix, registers are storage locations for text and other data, such as the
23+result of a search. Registers can be used to cut, copy, and paste text, similar
24+to the clipboard in other text editors. Usage is similar to Vim, with `"` being
25+used to select a register.
26+
27+### User-defined registers
28+
29+Helix allows you to create your own named registers for storing text, for
30+example:
1031
1132- `"ay` - Yank the current selection to register `a`.
1233- `"op` - Paste the text in register `o` after the selection.
1334
14−If there is a selected register before invoking a change or delete command, the selection will be stored in the register and the action will be carried out:
35+If a register is selected before invoking a change or delete command, the selection will be stored in the register and the action will be carried out:
1536
1637- `"hc` - Store the selection in register `h` and then change it (delete and enter insert mode).
1738- `"md` - Store the selection in register `m` and delete it.
1839
19−### Special Registers
40+### Default registers
41+
42+Commands that use registers, like yank (`y`), use a default register if none is specified.
43+These registers are used as defaults:
2044
2145| Register character | Contains |
2246| --- | --- |
2347| `/` | Last search |
2448| `:` | Last executed command |
2549| `"` | Last yanked text |
26−| `_` | Black hole |
50+| `@` | Last recorded macro |
2751
28−> There is no special register for copying to system clipboard, instead special commands and keybindings are provided. See the [keymap](keymap.md#space-mode) for the specifics.
29−> The black hole register works as a no-op register, meaning no data will be written to / read from it.
52+### Special registers
53+
54+Some registers have special behavior when read from and written to.
55+
56+| Register character | When read | When written |
57+| --- | --- | --- |
58+| `_` | No values are returned | All values are discarded |
59+| `#` | Selection indices (first selection is `1`, second is `2`, etc.) | This register is not writable |
60+| `.` | Contents of the current selections | This register is not writable |
61+| `%` | Name of the current file | This register is not writable |
62+| `+` | Reads from the system clipboard | Joins and yanks to the system clipboard |
63+| `*` | Reads from the primary clipboard | Joins and yanks to the primary clipboard |
64+
65+When yanking multiple selections to the clipboard registers, the selections
66+are joined with newlines. Pasting from these registers will paste multiple
67+selections if the clipboard was last yanked to by the Helix session. Otherwise
68+the clipboard contents are pasted as one selection.
3069
3170## Surround
3271
33−Functionality similar to [vim-surround](https://github.com/tpope/vim-surround) is built into
34−helix. The keymappings have been inspired from [vim-sandwich](https://github.com/machakann/vim-sandwich):
72+Helix includes built-in functionality similar to [vim-surround](https://github.com/tpope/vim-surround).
73+The keymappings have been inspired from [vim-sandwich](https://github.com/machakann/vim-sandwich):
3574
36−![surround demo](https://user-images.githubusercontent.com/23398472/122865801-97073180-d344-11eb-8142-8f43809982c6.gif)
75+![Surround demo](https://user-images.githubusercontent.com/23398472/122865801-97073180-d344-11eb-8142-8f43809982c6.gif)
3776
38−- `ms` - Add surround characters
39−- `mr` - Replace surround characters
40−- `md` - Delete surround characters
77+| Key Sequence | Action |
78+| --------------------------------- | --------------------------------------- |
79+| `ms<char>` (after selecting text) | Add surround characters to selection |
80+| `mr<char_to_replace><new_char>` | Replace the closest surround characters |
81+| `md<char_to_delete>` | Delete the closest surround characters |
4182
42−`ms` acts on a selection, so select the text first and use `ms<char>`. `mr` and `md` work
43−on the closest pairs found and selections are not required; use counts to act in outer pairs.
83+You can use counts to act on outer pairs.
4484
45−It can also act on multiple selections (yay!). For example, to change every occurrence of `(use)` to `[use]`:
85+Surround can also act on multiple selections. For example, to change every occurrence of `(use)` to `[use]`:
4686
47−- `%` to select the whole file
48−- `s` to split the selections on a search term
49−- Input `use` and hit Enter
50−- `mr([` to replace the parens with square brackets
87+1. `%` to select the whole file
88+2. `s` to split the selections on a search term
89+3. Input `use` and hit Enter
90+4. `mr([` to replace the parentheses with square brackets
5191
52−Multiple characters are currently not supported, but planned.
92+Multiple characters are currently not supported, but planned for future release.
5393
54−## Syntax-tree Motions
94+## Selecting and manipulating text with textobjects
5595
56−`Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) move the primary
57−selection according to the selection's place in the syntax tree. Let's walk
58−through an example to get familiar with them. Many languages have a syntax like
59−so for function calls:
96+In Helix, textobjects are a way to select, manipulate and operate on a piece of
97+text in a structured way. They allow you to refer to blocks of text based on
98+their structure or purpose, such as a word, sentence, paragraph, or even a
99+function or block of code.
60100
61−```
62−func(arg1, arg2, arg3)
101+![Textobject demo](https://user-images.githubusercontent.com/23398472/124231131-81a4bb00-db2d-11eb-9d10-8e577ca7b177.gif)
102+![Textobject tree-sitter demo](https://user-images.githubusercontent.com/23398472/132537398-2a2e0a54-582b-44ab-a77f-eb818942203d.gif)
103+
104+- `ma` - Select around the object (`va` in Vim, `<alt-a>` in Kakoune)
105+- `mi` - Select inside the object (`vi` in Vim, `<alt-i>` in Kakoune)
106+
107+| Key after `mi` or `ma` | Textobject selected |
108+| --- | --- |
109+| `w` | Word |
110+| `W` | WORD |
111+| `p` | Paragraph |
112+| `(`, `[`, `'`, etc. | Specified surround pairs |
113+| `m` | The closest surround pair |
114+| `f` | Function |
115+| `t` | Type (or Class) |
116+| `a` | Argument/parameter |
117+| `c` | Comment |
118+| `T` | Test |
119+| `g` | Change |
120+
121+> 💡 `f`, `t`, etc. need a tree-sitter grammar active for the current
122+document and a special tree-sitter query file to work properly. [Only
123+some grammars][lang-support] currently have the query file implemented.
124+Contributions are welcome!
125+
126+## Navigating using tree-sitter textobjects
127+
128+Navigating between functions, classes, parameters, and other elements is
129+possible using tree-sitter and textobject queries. For
130+example to move to the next function use `]f`, to move to previous
131+type use `[t`, and so on.
132+
133+![Tree-sitter-nav-demo][tree-sitter-nav-demo]
134+
135+For the full reference see the [unimpaired][unimpaired-keybinds] section of the key bind
136+documentation.
137+
138+> 💡 This feature relies on tree-sitter textobjects
139+> and requires the corresponding query file to work properly.
140+
141+## Moving the selection with syntax-aware motions
142+
143+`Alt-p`, `Alt-o`, `Alt-i`, and `Alt-n` (or `Alt` and arrow keys) allow you to move the
144+selection according to its location in the syntax tree. For example, many languages have the
145+following syntax for function calls:
146+
147+```js
148+func(arg1, arg2, arg3);
63149```
64150
65151A function call might be parsed by tree-sitter into a tree like the following.
@@ -93,76 +179,29 @@ a more intuitive tree format:
93179 └──────────┘ └──────────┘ └──────────┘
94180```
95181
96−Say we have a selection that wraps `arg1`. The selection is on the `arg1` leaf
97−in the tree above.
182+If you have a selection that wraps `arg1` (see the tree above), and you use
183+`Alt-n`, it will select the next sibling in the syntax tree: `arg2`.
98184
99−```
185+```js
186+// before
100187func([arg1], arg2, arg3)
188+// after
189+func(arg1, [arg2], arg3);
101190```
102191
103−Using `Alt-n` would select the next sibling in the syntax tree: `arg2`.
104−
105−```
106−func(arg1, [arg2], arg3)
107−```
108−
109−While `Alt-o` would expand the selection to the parent node. In the tree above we
110−can see that we would select the `arguments` node.
192+Similarly, `Alt-o` will expand the selection to the parent node, in this case, the
193+arguments node.
111194
112−```
113−func[(arg1, arg2, arg3)]
195+```js
196+func[(arg1, arg2, arg3)];
114197```
115198
116199There is also some nuanced behavior that prevents you from getting stuck on a
117−node with no sibling. If we have a selection on `arg1`, `Alt-p` would bring us
118−to the previous child node. Since `arg1` doesn't have a sibling to its left,
119−though, we climb the syntax tree and then take the previous selection. So
120−`Alt-p` will move the selection over to the "func" `identifier`.
121−
122−```
123−[func](arg1, arg2, arg3)
124−```
125−
126−## Textobjects
127−
128−![textobject-demo](https://user-images.githubusercontent.com/23398472/124231131-81a4bb00-db2d-11eb-9d10-8e577ca7b177.gif)
129−![textobject-treesitter-demo](https://user-images.githubusercontent.com/23398472/132537398-2a2e0a54-582b-44ab-a77f-eb818942203d.gif)
130−
131−- `ma` - Select around the object (`va` in Vim, `<alt-a>` in Kakoune)
132−- `mi` - Select inside the object (`vi` in Vim, `<alt-i>` in Kakoune)
133−
134−| Key after `mi` or `ma` | Textobject selected |
135−| --- | --- |
136−| `w` | Word |
137−| `W` | WORD |
138−| `p` | Paragraph |
139−| `(`, `[`, `'`, etc | Specified surround pairs |
140−| `m` | Closest surround pair |
141−| `f` | Function |
142−| `c` | Class |
143−| `a` | Argument/parameter |
144−| `o` | Comment |
145−| `t` | Test |
146−
147−> NOTE: `f`, `c`, etc need a tree-sitter grammar active for the current
148−document and a special tree-sitter query file to work properly. [Only
149−some grammars][lang-support] currently have the query file implemented.
150−Contributions are welcome!
151−
152−## Tree-sitter Textobject Based Navigation
153−
154−Navigating between functions, classes, parameters, etc is made
155−possible by leveraging tree-sitter and textobjects queries. For
156−example to move to the next function use `]f`, to move to previous
157−class use `[c`, and so on.
158−
159−![tree-sitter-nav-demo][tree-sitter-nav-demo]
160−
161−See the [unimpaired][unimpaired-keybinds] section of the keybind
162−documentation for the full reference.
163−
164−> NOTE: This feature is dependent on tree-sitter based textobjects
165−and therefore requires the corresponding query file to work properly.
200+node with no sibling. When using `Alt-p` with a selection on `arg1`, the previous
201+child node will be selected. In the event that `arg1` does not have a previous
202+sibling, the selection will move up the syntax tree and select the previous
203+element. As a result, using `Alt-p` with a selection on `arg1` will move the
204+selection to the "func" `identifier`.
166205
167206[lang-support]: ./lang-support.md
168207[unimpaired-keybinds]: ./keymap.md#unimpaired
book/theme/book.jsdeleted−660
@@ -1,660 +0,0 @@
1−"use strict";
2−
3−// Fix back button cache problem
4−window.onunload = function () { };
5−
6−// Global variable, shared between modules
7−function playground_text(playground) {
8− let code_block = playground.querySelector("code");
9−
10− if (window.ace && code_block.classList.contains("editable")) {
11− let editor = window.ace.edit(code_block);
12− return editor.getValue();
13− } else {
14− return code_block.textContent;
15− }
16−}
17−
18−(function codeSnippets() {
19− function fetch_with_timeout(url, options, timeout = 6000) {
20− return Promise.race([
21− fetch(url, options),
22− new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
23− ]);
24− }
25−
26− var playgrounds = Array.from(document.querySelectorAll(".playground"));
27− if (playgrounds.length > 0) {
28− fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
29− headers: {
30− 'Content-Type': "application/json",
31− },
32− method: 'POST',
33− mode: 'cors',
34− })
35− .then(response => response.json())
36− .then(response => {
37− // get list of crates available in the rust playground
38− let playground_crates = response.crates.map(item => item["id"]);
39− playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));
40− });
41− }
42−
43− function handle_crate_list_update(playground_block, playground_crates) {
44− // update the play buttons after receiving the response
45− update_play_button(playground_block, playground_crates);
46−
47− // and install on change listener to dynamically update ACE editors
48− if (window.ace) {
49− let code_block = playground_block.querySelector("code");
50− if (code_block.classList.contains("editable")) {
51− let editor = window.ace.edit(code_block);
52− editor.addEventListener("change", function (e) {
53− update_play_button(playground_block, playground_crates);
54− });
55− // add Ctrl-Enter command to execute rust code
56− editor.commands.addCommand({
57− name: "run",
58− bindKey: {
59− win: "Ctrl-Enter",
60− mac: "Ctrl-Enter"
61− },
62− exec: _editor => run_rust_code(playground_block)
63− });
64− }
65− }
66− }
67−
68− // updates the visibility of play button based on `no_run` class and
69− // used crates vs ones available on http://play.rust-lang.org
70− function update_play_button(pre_block, playground_crates) {
71− var play_button = pre_block.querySelector(".play-button");
72−
73− // skip if code is `no_run`
74− if (pre_block.querySelector('code').classList.contains("no_run")) {
75− play_button.classList.add("hidden");
76− return;
77− }
78−
79− // get list of `extern crate`'s from snippet
80− var txt = playground_text(pre_block);
81− var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
82− var snippet_crates = [];
83− var item;
84− while (item = re.exec(txt)) {
85− snippet_crates.push(item[1]);
86− }
87−
88− // check if all used crates are available on play.rust-lang.org
89− var all_available = snippet_crates.every(function (elem) {
90− return playground_crates.indexOf(elem) > -1;
91− });
92−
93− if (all_available) {
94− play_button.classList.remove("hidden");
95− } else {
96− play_button.classList.add("hidden");
97− }
98− }
99−
100− function run_rust_code(code_block) {
101− var result_block = code_block.querySelector(".result");
102− if (!result_block) {
103− result_block = document.createElement('code');
104− result_block.className = 'result hljs language-bash';
105−
106− code_block.append(result_block);
107− }
108−
109− let text = playground_text(code_block);
110− let classes = code_block.querySelector('code').classList;
111− let has_2018 = classes.contains("edition2018");
112− let edition = has_2018 ? "2018" : "2015";
113−
114− var params = {
115− version: "stable",
116− optimize: "0",
117− code: text,
118− edition: edition
119− };
120−
121− if (text.indexOf("#![feature") !== -1) {
122− params.version = "nightly";
123− }
124−
125− result_block.innerText = "Running...";
126−
127− fetch_with_timeout("https://play.rust-lang.org/evaluate.json", {
128− headers: {
129− 'Content-Type': "application/json",
130− },
131− method: 'POST',
132− mode: 'cors',
133− body: JSON.stringify(params)
134− })
135− .then(response => response.json())
136− .then(response => result_block.innerText = response.result)
137− .catch(error => result_block.innerText = "Playground Communication: " + error.message);
138− }
139−
140− // Syntax highlighting Configuration
141− hljs.configure({
142− tabReplace: ' ', // 4 spaces
143− languages: [], // Languages used for auto-detection
144− });
145−
146− let code_nodes = Array
147− .from(document.querySelectorAll('code'))
148− // Don't highlight `inline code` blocks in headers.
149− .filter(function (node) {return !node.parentElement.classList.contains("header"); });
150−
151− if (window.ace) {
152− // language-rust class needs to be removed for editable
153− // blocks or highlightjs will capture events
154− Array
155− .from(document.querySelectorAll('code.editable'))
156− .forEach(function (block) { block.classList.remove('language-rust'); });
157−
158− Array
159− .from(document.querySelectorAll('code:not(.editable)'))
160− .forEach(function (block) { hljs.highlightBlock(block); });
161− } else {
162− code_nodes.forEach(function (block) { hljs.highlightBlock(block); });
163− }
164−
165− // Adding the hljs class gives code blocks the color css
166− // even if highlighting doesn't apply
167− code_nodes.forEach(function (block) { block.classList.add('hljs'); });
168−
169− Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
170−
171− var lines = Array.from(block.querySelectorAll('.boring'));
172− // If no lines were hidden, return
173− if (!lines.length) { return; }
174− block.classList.add("hide-boring");
175−
176− var buttons = document.createElement('div');
177− buttons.className = 'buttons';
178− buttons.innerHTML = "<button class=\"fa fa-eye\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
179−
180− // add expand button
181− var pre_block = block.parentNode;
182− pre_block.insertBefore(buttons, pre_block.firstChild);
183−
184− pre_block.querySelector('.buttons').addEventListener('click', function (e) {
185− if (e.target.classList.contains('fa-eye')) {
186− e.target.classList.remove('fa-eye');
187− e.target.classList.add('fa-eye-slash');
188− e.target.title = 'Hide lines';
189− e.target.setAttribute('aria-label', e.target.title);
190−
191− block.classList.remove('hide-boring');
192− } else if (e.target.classList.contains('fa-eye-slash')) {
193− e.target.classList.remove('fa-eye-slash');
194− e.target.classList.add('fa-eye');
195− e.target.title = 'Show hidden lines';
196− e.target.setAttribute('aria-label', e.target.title);
197−
198− block.classList.add('hide-boring');
199− }
200− });
201− });
202−
203− if (window.playground_copyable) {
204− Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
205− var pre_block = block.parentNode;
206− if (!pre_block.classList.contains('playground')) {
207− var buttons = pre_block.querySelector(".buttons");
208− if (!buttons) {
209− buttons = document.createElement('div');
210− buttons.className = 'buttons';
211− pre_block.insertBefore(buttons, pre_block.firstChild);
212− }
213−
214− var clipButton = document.createElement('button');
215− clipButton.className = 'fa fa-copy clip-button';
216− clipButton.title = 'Copy to clipboard';
217− clipButton.setAttribute('aria-label', clipButton.title);
218− clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
219−
220− buttons.insertBefore(clipButton, buttons.firstChild);
221− }
222− });
223− }
224−
225− // Process playground code blocks
226− Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) {
227− // Add play button
228− var buttons = pre_block.querySelector(".buttons");
229− if (!buttons) {
230− buttons = document.createElement('div');
231− buttons.className = 'buttons';
232− pre_block.insertBefore(buttons, pre_block.firstChild);
233− }
234−
235− var runCodeButton = document.createElement('button');
236− runCodeButton.className = 'fa fa-play play-button';
237− runCodeButton.hidden = true;
238− runCodeButton.title = 'Run this code';
239− runCodeButton.setAttribute('aria-label', runCodeButton.title);
240−
241− buttons.insertBefore(runCodeButton, buttons.firstChild);
242− runCodeButton.addEventListener('click', function (e) {
243− run_rust_code(pre_block);
244− });
245−
246− if (window.playground_copyable) {
247− var copyCodeClipboardButton = document.createElement('button');
248− copyCodeClipboardButton.className = 'fa fa-copy clip-button';
249− copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
250− copyCodeClipboardButton.title = 'Copy to clipboard';
251− copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
252−
253− buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
254− }
255−
256− let code_block = pre_block.querySelector("code");
257− if (window.ace && code_block.classList.contains("editable")) {
258− var undoChangesButton = document.createElement('button');
259− undoChangesButton.className = 'fa fa-history reset-button';
260− undoChangesButton.title = 'Undo changes';
261− undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
262−
263− buttons.insertBefore(undoChangesButton, buttons.firstChild);
264−
265− undoChangesButton.addEventListener('click', function () {
266− let editor = window.ace.edit(code_block);
267− editor.setValue(editor.originalCode);
268− editor.clearSelection();
269− });
270− }
271− });
272−})();
273−
274−(function themes() {
275− var html = document.querySelector('html');
276− var themeToggleButton = document.getElementById('theme-toggle');
277− var themePopup = document.getElementById('theme-list');
278− var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
279− var stylesheets = {
280− ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
281− tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
282− highlight: document.querySelector("[href$='highlight.css']"),
283− };
284−
285− function showThemes() {
286− themePopup.style.display = 'block';
287− themeToggleButton.setAttribute('aria-expanded', true);
288− themePopup.querySelector("button#" + get_theme()).focus();
289− }
290−
291− function hideThemes() {
292− themePopup.style.display = 'none';
293− themeToggleButton.setAttribute('aria-expanded', false);
294− themeToggleButton.focus();
295− }
296−
297− function get_theme() {
298− var theme;
299− try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
300− if (theme === null || theme === undefined) {
301− return default_theme;
302− } else {
303− return theme;
304− }
305− }
306−
307− function set_theme(theme, store = true) {
308− let ace_theme;
309−
310− if (theme == 'coal' || theme == 'navy') {
311− stylesheets.ayuHighlight.disabled = true;
312− stylesheets.tomorrowNight.disabled = false;
313− stylesheets.highlight.disabled = true;
314−
315− ace_theme = "ace/theme/tomorrow_night";
316− } else if (theme == 'ayu') {
317− stylesheets.ayuHighlight.disabled = false;
318− stylesheets.tomorrowNight.disabled = true;
319− stylesheets.highlight.disabled = true;
320− ace_theme = "ace/theme/tomorrow_night";
321− } else {
322− stylesheets.ayuHighlight.disabled = true;
323− stylesheets.tomorrowNight.disabled = true;
324− stylesheets.highlight.disabled = false;
325− ace_theme = "ace/theme/dawn";
326− }
327−
328− setTimeout(function () {
329− themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor;
330− }, 1);
331−
332− if (window.ace && window.editors) {
333− window.editors.forEach(function (editor) {
334− editor.setTheme(ace_theme);
335− });
336− }
337−
338− var previousTheme = get_theme();
339−
340− if (store) {
341− try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
342− }
343−
344− html.classList.remove(previousTheme);
345− html.classList.add(theme);
346− }
347−
348− // Set theme
349− var theme = get_theme();
350−
351− set_theme(theme, false);
352−
353− themeToggleButton.addEventListener('click', function () {
354− if (themePopup.style.display === 'block') {
355− hideThemes();
356− } else {
357− showThemes();
358− }
359− });
360−
361− themePopup.addEventListener('click', function (e) {
362− var theme = e.target.id || e.target.parentElement.id;
363− set_theme(theme);
364− });
365−
366− themePopup.addEventListener('focusout', function(e) {
367− // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
368− if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
369− hideThemes();
370− }
371− });
372−
373− // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
374− document.addEventListener('click', function(e) {
375− if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
376− hideThemes();
377− }
378− });
379−
380− document.addEventListener('keydown', function (e) {
381− if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
382− if (!themePopup.contains(e.target)) { return; }
383−
384− switch (e.key) {
385− case 'Escape':
386− e.preventDefault();
387− hideThemes();
388− break;
389− case 'ArrowUp':
390− e.preventDefault();
391− var li = document.activeElement.parentElement;
392− if (li && li.previousElementSibling) {
393− li.previousElementSibling.querySelector('button').focus();
394− }
395− break;
396− case 'ArrowDown':
397− e.preventDefault();
398− var li = document.activeElement.parentElement;
399− if (li && li.nextElementSibling) {
400− li.nextElementSibling.querySelector('button').focus();
401− }
402− break;
403− case 'Home':
404− e.preventDefault();
405− themePopup.querySelector('li:first-child button').focus();
406− break;
407− case 'End':
408− e.preventDefault();
409− themePopup.querySelector('li:last-child button').focus();
410− break;
411− }
412− });
413−})();
414−
415−(function sidebar() {
416− var html = document.querySelector("html");
417− var sidebar = document.getElementById("sidebar");
418− var sidebarLinks = document.querySelectorAll('#sidebar a');
419− var sidebarToggleButton = document.getElementById("sidebar-toggle");
420− var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
421− var firstContact = null;
422−
423− function showSidebar() {
424− html.classList.remove('sidebar-hidden')
425− html.classList.add('sidebar-visible');
426− Array.from(sidebarLinks).forEach(function (link) {
427− link.setAttribute('tabIndex', 0);
428− });
429− sidebarToggleButton.setAttribute('aria-expanded', true);
430− sidebar.setAttribute('aria-hidden', false);
431− try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
432− }
433−
434−
435− var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
436−
437− function toggleSection(ev) {
438− ev.currentTarget.parentElement.classList.toggle('expanded');
439− }
440−
441− Array.from(sidebarAnchorToggles).forEach(function (el) {
442− el.addEventListener('click', toggleSection);
443− });
444−
445− function hideSidebar() {
446− html.classList.remove('sidebar-visible')
447− html.classList.add('sidebar-hidden');
448− Array.from(sidebarLinks).forEach(function (link) {
449− link.setAttribute('tabIndex', -1);
450− });
451− sidebarToggleButton.setAttribute('aria-expanded', false);
452− sidebar.setAttribute('aria-hidden', true);
453− try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
454− }
455−
456− // Toggle sidebar
457− sidebarToggleButton.addEventListener('click', function sidebarToggle() {
458− if (html.classList.contains("sidebar-hidden")) {
459− var current_width = parseInt(
460− document.documentElement.style.getPropertyValue('--sidebar-width'), 10);
461− if (current_width < 150) {
462− document.documentElement.style.setProperty('--sidebar-width', '150px');
463− }
464− showSidebar();
465− } else if (html.classList.contains("sidebar-visible")) {
466− hideSidebar();
467− } else {
468− if (getComputedStyle(sidebar)['transform'] === 'none') {
469− hideSidebar();
470− } else {
471− showSidebar();
472− }
473− }
474− });
475−
476− sidebarResizeHandle.addEventListener('mousedown', initResize, false);
477−
478− function initResize(e) {
479− window.addEventListener('mousemove', resize, false);
480− window.addEventListener('mouseup', stopResize, false);
481− html.classList.add('sidebar-resizing');
482− }
483− function resize(e) {
484− var pos = (e.clientX - sidebar.offsetLeft);
485− if (pos < 20) {
486− hideSidebar();
487− } else {
488− if (html.classList.contains("sidebar-hidden")) {
489− showSidebar();
490− }
491− pos = Math.min(pos, window.innerWidth - 100);
492− document.documentElement.style.setProperty('--sidebar-width', pos + 'px');
493− }
494− }
495− //on mouseup remove windows functions mousemove & mouseup
496− function stopResize(e) {
497− html.classList.remove('sidebar-resizing');
498− window.removeEventListener('mousemove', resize, false);
499− window.removeEventListener('mouseup', stopResize, false);
500− }
501−
502− document.addEventListener('touchstart', function (e) {
503− firstContact = {
504− x: e.touches[0].clientX,
505− time: Date.now()
506− };
507− }, { passive: true });
508−
509− document.addEventListener('touchmove', function (e) {
510− if (!firstContact)
511− return;
512−
513− var curX = e.touches[0].clientX;
514− var xDiff = curX - firstContact.x,
515− tDiff = Date.now() - firstContact.time;
516−
517− if (tDiff < 250 && Math.abs(xDiff) >= 150) {
518− if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
519− showSidebar();
520− else if (xDiff < 0 && curX < 300)
521− hideSidebar();
522−
523− firstContact = null;
524− }
525− }, { passive: true });
526−
527− // Scroll sidebar to current active section
528− var activeSection = document.getElementById("sidebar").querySelector(".active");
529− if (activeSection) {
530− // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
531− activeSection.scrollIntoView({ block: 'center' });
532− }
533−})();
534−
535−(function chapterNavigation() {
536− document.addEventListener('keydown', function (e) {
537− if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
538− if (window.search && window.search.hasFocus()) { return; }
539−
540− switch (e.key) {
541− case 'ArrowRight':
542− e.preventDefault();
543− var nextButton = document.querySelector('.nav-chapters.next');
544− if (nextButton) {
545− window.location.href = nextButton.href;
546− }
547− break;
548− case 'ArrowLeft':
549− e.preventDefault();
550− var previousButton = document.querySelector('.nav-chapters.previous');
551− if (previousButton) {
552− window.location.href = previousButton.href;
553− }
554− break;
555− }
556− });
557−})();
558−
559−(function clipboard() {
560− var clipButtons = document.querySelectorAll('.clip-button');
561−
562− function hideTooltip(elem) {
563− elem.firstChild.innerText = "";
564− elem.className = 'fa fa-copy clip-button';
565− }
566−
567− function showTooltip(elem, msg) {
568− elem.firstChild.innerText = msg;
569− elem.className = 'fa fa-copy tooltipped';
570− }
571−
572− var clipboardSnippets = new ClipboardJS('.clip-button', {
573− text: function (trigger) {
574− hideTooltip(trigger);
575− let playground = trigger.closest("pre");
576− return playground_text(playground);
577− }
578− });
579−
580− Array.from(clipButtons).forEach(function (clipButton) {
581− clipButton.addEventListener('mouseout', function (e) {
582− hideTooltip(e.currentTarget);
583− });
584− });
585−
586− clipboardSnippets.on('success', function (e) {
587− e.clearSelection();
588− showTooltip(e.trigger, "Copied!");
589− });
590−
591− clipboardSnippets.on('error', function (e) {
592− showTooltip(e.trigger, "Clipboard error!");
593− });
594−})();
595−
596−(function scrollToTop () {
597− var menuTitle = document.querySelector('.menu-title');
598−
599− menuTitle.addEventListener('click', function () {
600− document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
601− });
602−})();
603−
604−(function controlMenu() {
605− var menu = document.getElementById('menu-bar');
606−
607− (function controlPosition() {
608− var scrollTop = document.scrollingElement.scrollTop;
609− var prevScrollTop = scrollTop;
610− var minMenuY = -menu.clientHeight - 50;
611− // When the script loads, the page can be at any scroll (e.g. if you reforesh it).
612− menu.style.top = scrollTop + 'px';
613− // Same as parseInt(menu.style.top.slice(0, -2), but faster
614− var topCache = menu.style.top.slice(0, -2);
615− menu.classList.remove('sticky');
616− var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster
617− document.addEventListener('scroll', function () {
618− scrollTop = Math.max(document.scrollingElement.scrollTop, 0);
619− // `null` means that it doesn't need to be updated
620− var nextSticky = null;
621− var nextTop = null;
622− var scrollDown = scrollTop > prevScrollTop;
623− var menuPosAbsoluteY = topCache - scrollTop;
624− if (scrollDown) {
625− nextSticky = false;
626− if (menuPosAbsoluteY > 0) {
627− nextTop = prevScrollTop;
628− }
629− } else {
630− if (menuPosAbsoluteY > 0) {
631− nextSticky = true;
632− } else if (menuPosAbsoluteY < minMenuY) {
633− nextTop = prevScrollTop + minMenuY;
634− }
635− }
636− if (nextSticky === true && stickyCache === false) {
637− menu.classList.add('sticky');
638− stickyCache = true;
639− } else if (nextSticky === false && stickyCache === true) {
640− menu.classList.remove('sticky');
641− stickyCache = false;
642− }
643− if (nextTop !== null) {
644− menu.style.top = nextTop + 'px';
645− topCache = nextTop;
646− }
647− prevScrollTop = scrollTop;
648− }, { passive: true });
649− })();
650− (function controlBorder() {
651− menu.classList.remove('bordered');
652− document.addEventListener('scroll', function () {
653− if (menu.offsetTop === 0) {
654− menu.classList.remove('bordered');
655− } else {
656− menu.classList.add('bordered');
657− }
658− }, { passive: true });
659− })();
660−})();
book/theme/css/chrome.cssdeleted−499
@@ -1,499 +0,0 @@
1−/* CSS for UI elements (a.k.a. chrome) */
2−
3−@import 'variables.css';
4−
5−::-webkit-scrollbar {
6− background: var(--bg);
7−}
8−::-webkit-scrollbar-thumb {
9− background: var(--scrollbar);
10−}
11−html {
12− scrollbar-color: var(--scrollbar) var(--bg);
13−}
14−#searchresults a,
15−.content a:link,
16−a:visited,
17−a > .hljs {
18− color: var(--links);
19−}
20−.content a:hover {
21− text-decoration: underline;
22−}
23−
24−/* Menu Bar */
25−
26−#menu-bar,
27−#menu-bar-hover-placeholder {
28− z-index: 101;
29− margin: auto calc(0px - var(--page-padding));
30−}
31−#menu-bar {
32− position: relative;
33− display: flex;
34− flex-wrap: wrap;
35− background-color: var(--bg);
36− border-bottom-color: var(--bg);
37− border-bottom-width: 1px;
38− border-bottom-style: solid;
39−}
40−#menu-bar.sticky,
41−.js #menu-bar-hover-placeholder:hover + #menu-bar,
42−.js #menu-bar:hover,
43−.js.sidebar-visible #menu-bar {
44− position: -webkit-sticky;
45− position: sticky;
46− top: 0 !important;
47−}
48−#menu-bar-hover-placeholder {
49− position: sticky;
50− position: -webkit-sticky;
51− top: 0;
52− height: var(--menu-bar-height);
53−}
54−#menu-bar.bordered {
55− border-bottom-color: var(--table-border-color);
56−}
57−#menu-bar i, #menu-bar .icon-button {
58− position: relative;
59− padding: 0 8px;
60− z-index: 10;
61− line-height: var(--menu-bar-height);
62− cursor: pointer;
63− transition: color 0.5s;
64−}
65−@media only screen and (max-width: 420px) {
66− #menu-bar i, #menu-bar .icon-button {
67− padding: 0 5px;
68− }
69−}
70−
71−.icon-button {
72− border: none;
73− background: none;
74− padding: 0;
75− color: inherit;
76−}
77−.icon-button i {
78− margin: 0;
79−}
80−
81−.right-buttons {
82− margin: 0 15px;
83−}
84−.right-buttons a {
85− text-decoration: none;
86−}
87−
88−.left-buttons {
89− display: flex;
90− margin: 0 5px;
91−}
92−.no-js .left-buttons {
93− display: none;
94−}
95−
96−.menu-title {
97− display: inline-block;
98− font-weight: 200;
99− font-size: 2.4rem;
100− line-height: var(--menu-bar-height);
101− text-align: center;
102− margin: 0;
103− flex: 1;
104− white-space: nowrap;
105− overflow: hidden;
106− text-overflow: ellipsis;
107−}
108−.js .menu-title {
109− cursor: pointer;
110−}
111−
112−.menu-bar,
113−.menu-bar:visited,
114−.nav-chapters,
115−.nav-chapters:visited,
116−.mobile-nav-chapters,
117−.mobile-nav-chapters:visited,
118−.menu-bar .icon-button,
119−.menu-bar a i {
120− color: var(--icons);
121−}
122−
123−.menu-bar i:hover,
124−.menu-bar .icon-button:hover,
125−.nav-chapters:hover,
126−.mobile-nav-chapters i:hover {
127− color: var(--icons-hover);
128−}
129−
130−/* Nav Icons */
131−
132−.nav-chapters {
133− font-size: 2.5em;
134− text-align: center;
135− text-decoration: none;
136−
137− position: fixed;
138− top: 0;
139− bottom: 0;
140− margin: 0;
141− max-width: 150px;
142− min-width: 90px;
143−
144− display: flex;
145− justify-content: center;
146− align-content: center;
147− flex-direction: column;
148−
149− transition: color 0.5s, background-color 0.5s;
150−}
151−
152−.nav-chapters:hover {
153− text-decoration: none;
154− background-color: var(--theme-hover);
155− transition: background-color 0.15s, color 0.15s;
156−}
157−
158−.nav-wrapper {
159− margin-top: 50px;
160− display: none;
161−}
162−
163−.mobile-nav-chapters {
164− font-size: 2.5em;
165− text-align: center;
166− text-decoration: none;
167− width: 90px;
168− border-radius: 5px;
169− background-color: var(--sidebar-bg);
170−}
171−
172−.previous {
173− float: left;
174−}
175−
176−.next {
177− float: right;
178− right: var(--page-padding);
179−}
180−
181−@media only screen and (max-width: 1080px) {
182− .nav-wide-wrapper { display: none; }
183− .nav-wrapper { display: block; }
184−}
185−
186−@media only screen and (max-width: 1380px) {
187− .sidebar-visible .nav-wide-wrapper { display: none; }
188− .sidebar-visible .nav-wrapper { display: block; }
189−}
190−
191−/* Inline code */
192−
193−:not(pre) > .hljs {
194− display: inline;
195− padding: 0.1em 0.3em;
196− border-radius: 3px;
197−}
198−
199−:not(pre):not(a):not(td):not(p) > .hljs {
200− color: var(--inline-code-color);
201− overflow-x: initial;
202−}
203−
204−a:hover > .hljs {
205− text-decoration: underline;
206−}
207−
208−pre {
209− position: relative;
210−}
211−pre > .buttons {
212− position: absolute;
213− z-index: 100;
214− right: 5px;
215− top: 5px;
216−
217− color: var(--sidebar-fg);
218− cursor: pointer;
219−}
220−pre > .buttons :hover {
221− color: var(--sidebar-active);
222−}
223−pre > .buttons i {
224− margin-left: 8px;
225−}
226−pre > .buttons button {
227− color: inherit;
228− background: transparent;
229− border: none;
230− cursor: inherit;
231−}
232−pre > .result {
233− margin-top: 10px;
234−}
235−
236−/* Search */
237−
238−#searchresults a {
239− text-decoration: none;
240−}
241−
242−mark {
243− border-radius: 2px;
244− padding: 0 3px 1px 3px;
245− margin: 0 -3px -1px -3px;
246− background-color: var(--search-mark-bg);
247− transition: background-color 300ms linear;
248− cursor: pointer;
249−}
250−
251−mark.fade-out {
252− background-color: rgba(0,0,0,0) !important;
253− cursor: auto;
254−}
255−
256−.searchbar-outer {
257− margin-left: auto;
258− margin-right: auto;
259− max-width: var(--content-max-width);
260−}
261−
262−#searchbar {
263− width: 100%;
264− margin: 5px auto 0px auto;
265− padding: 10px 16px;
266− transition: box-shadow 300ms ease-in-out;
267− border: 1px solid var(--searchbar-border-color);
268− border-radius: 3px;
269− background-color: var(--searchbar-bg);
270− color: var(--searchbar-fg);
271−}
272−#searchbar:focus,
273−#searchbar.active {
274− box-shadow: 0 0 3px var(--searchbar-shadow-color);
275−}
276−
277−.searchresults-header {
278− font-weight: bold;
279− font-size: 1em;
280− padding: 18px 0 0 5px;
281− color: var(--searchresults-header-fg);
282−}
283−
284−.searchresults-outer {
285− margin-left: auto;
286− margin-right: auto;
287− max-width: var(--content-max-width);
288− border-bottom: 1px dashed var(--searchresults-border-color);
289−}
290−
291−ul#searchresults {
292− list-style: none;
293− padding-left: 20px;
294−}
295−ul#searchresults li {
296− margin: 10px 0px;
297− padding: 2px;
298− border-radius: 2px;
299−}
300−ul#searchresults li.focus {
301− background-color: var(--searchresults-li-bg);
302−}
303−ul#searchresults span.teaser {
304− display: block;
305− clear: both;
306− margin: 5px 0 0 20px;
307− font-size: 0.8em;
308−}
309−ul#searchresults span.teaser em {
310− font-weight: bold;
311− font-style: normal;
312−}
313−
314−/* Sidebar */
315−
316−.sidebar {
317− position: fixed;
318− left: 0;
319− top: 0;
320− bottom: 0;
321− width: var(--sidebar-width);
322− font-size: 0.875em;
323− box-sizing: border-box;
324− -webkit-overflow-scrolling: touch;
325− overscroll-behavior-y: contain;
326− background-color: var(--sidebar-bg);
327− color: var(--sidebar-fg);
328−}
329−.sidebar-resizing {
330− -moz-user-select: none;
331− -webkit-user-select: none;
332− -ms-user-select: none;
333− user-select: none;
334−}
335−.js:not(.sidebar-resizing) .sidebar {
336− transition: transform 0.3s; /* Animation: slide away */
337−}
338−.sidebar code {
339− line-height: 2em;
340−}
341−.sidebar .sidebar-scrollbox {
342− overflow-y: auto;
343− position: absolute;
344− top: 0;
345− bottom: 0;
346− left: 0;
347− right: 0;
348−}
349−.sidebar .sidebar-resize-handle {
350− position: absolute;
351− cursor: col-resize;
352− width: 0;
353− right: 0;
354− top: 0;
355− bottom: 0;
356−}
357−.js .sidebar .sidebar-resize-handle {
358− cursor: col-resize;
359− width: 5px;
360−}
361−.sidebar-hidden .sidebar {
362− transform: translateX(calc(0px - var(--sidebar-width)));
363−}
364−.sidebar::-webkit-scrollbar {
365− background: var(--sidebar-bg);
366−}
367−.sidebar::-webkit-scrollbar-thumb {
368− background: var(--scrollbar);
369−}
370−
371−.sidebar-visible .page-wrapper {
372− transform: translateX(var(--sidebar-width));
373−}
374−@media only screen and (min-width: 620px) {
375− .sidebar-visible .page-wrapper {
376− transform: none;
377− margin-left: var(--sidebar-width);
378− }
379−}
380−
381−.chapter {
382− list-style: none outside none;
383− padding-left: 0;
384− margin: .25rem 0;
385−}
386−
387−.chapter ol {
388− width: 100%;
389−}
390−
391−.chapter li {
392− display: flex;
393− color: var(--sidebar-non-existent);
394−}
395−.chapter li a {
396− display: block;
397− text-decoration: none;
398− color: var(--sidebar-fg);
399−}
400−
401−.chapter li a:hover {
402− color: var(--sidebar-active);
403−}
404−
405−.chapter li a.active {
406− color: var(--sidebar-active);
407−}
408−
409−.chapter li > a.toggle {
410− cursor: pointer;
411− display: block;
412− margin-left: auto;
413− padding: 0 10px;
414− user-select: none;
415− opacity: 0.68;
416−}
417−
418−.chapter li > a.toggle div {
419− transition: transform 0.5s;
420−}
421−
422−/* collapse the section */
423−.chapter li:not(.expanded) + li > ol {
424− display: none;
425−}
426−
427−.chapter li.chapter-item {
428− padding: 1rem 1.5rem;
429−}
430−
431−.chapter .section li.chapter-item {
432− padding: .5rem .5rem 0 .5rem;
433−}
434−
435−.chapter li.expanded > a.toggle div {
436− transform: rotate(90deg);
437−}
438−
439−.spacer {
440− width: 100%;
441− height: 3px;
442− margin: 5px 0px;
443−}
444−.chapter .spacer {
445− background-color: var(--sidebar-spacer);
446−}
447−
448−@media (-moz-touch-enabled: 1), (pointer: coarse) {
449− .chapter li a { padding: 5px 0; }
450− .spacer { margin: 10px 0; }
451−}
452−
453−.section {
454− list-style: none outside none;
455− padding-left: 2rem;
456− line-height: 1.9em;
457−}
458−
459−/* Theme Menu Popup */
460−
461−.theme-popup {
462− position: absolute;
463− left: 10px;
464− top: var(--menu-bar-height);
465− z-index: 1000;
466− border-radius: 4px;
467− font-size: 0.7em;
468− color: var(--fg);
469− background: var(--theme-popup-bg);
470− border: 1px solid var(--theme-popup-border);
471− margin: 0;
472− padding: 0;
473− list-style: none;
474− display: none;
475−}
476−.theme-popup .default {
477− color: var(--icons);
478−}
479−.theme-popup .theme {
480− width: 100%;
481− border: 0;
482− margin: 0;
483− padding: 2px 10px;
484− line-height: 25px;
485− white-space: nowrap;
486− text-align: left;
487− cursor: pointer;
488− color: inherit;
489− background: inherit;
490− font-size: inherit;
491−}
492−.theme-popup .theme:hover {
493− background-color: var(--theme-hover);
494−}
495−.theme-popup .theme:hover:first-child,
496−.theme-popup .theme:hover:last-child {
497− border-top-left-radius: inherit;
498− border-top-right-radius: inherit;
499−}
book/theme/css/general.cssdeleted−233
@@ -1,233 +0,0 @@
1−/* Base styles and content styles */
2−
3−@import 'variables.css';
4−
5−:root {
6− /* Browser default font-size is 16px, this way 1 rem = 10px */
7− font-size: 62.5%;
8−}
9−
10−/* TODO: replace with self hosted fonts */
11−
12−html {
13− font-family: "Inter", sans-serif;
14− color: var(--fg);
15− background-color: var(--bg);
16− text-size-adjust: none;
17−}
18−
19−/* @supports (font-variation-settings: normal) { */
20−/* html { font-family: 'Inter var', sans-serif; } */
21−/* } */
22−
23−body {
24− margin: 0;
25− font-size: 1.6rem;
26− overflow-x: hidden;
27−}
28−
29−code {
30− font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
31− font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
32−}
33−
34−/* Don't change font size in headers. */
35−h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
36− font-size: unset;
37−}
38−
39−.left { float: left; }
40−.right { float: right; }
41−.boring { opacity: 0.6; }
42−.hide-boring .boring { display: none; }
43−.hidden { display: none !important; }
44−
45−h2, h3 { margin-top: 2.5em; }
46−h4, h5 { margin-top: 2em; }
47−
48−.header + .header h3,
49−.header + .header h4,
50−.header + .header h5 {
51− margin-top: 1em;
52−}
53−
54−h1:target::before,
55−h2:target::before,
56−h3:target::before,
57−h4:target::before,
58−h5:target::before,
59−h6:target::before {
60− display: inline-block;
61− content: "»";
62− margin-left: -30px;
63− width: 30px;
64−}
65−
66−/* This is broken on Safari as of version 14, but is fixed
67− in Safari Technology Preview 117 which I think will be Safari 14.2.
68− https://bugs.webkit.org/show_bug.cgi?id=218076
69−*/
70−:target {
71− scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
72−}
73−
74−.page {
75− outline: 0;
76− padding: 0 var(--page-padding);
77− margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */
78−}
79−.page-wrapper {
80− box-sizing: border-box;
81−}
82−.js:not(.sidebar-resizing) .page-wrapper {
83− transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */
84−}
85−
86−.content {
87− overflow-y: auto;
88− padding: 0 15px;
89− padding-bottom: 50px;
90−}
91−.content main {
92− margin-left: auto;
93− margin-right: auto;
94− max-width: var(--content-max-width);
95−}
96−
97−
98−/* 2 1.75 1.5 1.25 1 .875 */
99−
100−.content h1 { font-size: 2em }
101−.content h2 { font-size: 1.75em }
102−.content h3 { font-size: 1.5em }
103−.content h4 { font-size: 1.25em }
104−.content h5 { font-size: 1em }
105−.content h6 { font-size: .875em }
106−
107−.content h1, .content h2, .content h3, .content h4 {
108− font-weight: 500;
109− margin-top: 1.275em;
110− margin-bottom: .875em;
111−}
112−.content p, .content ol, .content ul, .content table {
113− margin-top: 0;
114− margin-bottom: .875em;
115−}
116−
117−.content ul li {
118−margin-bottom: .25rem;
119−}
120−.content ul {
121− list-style-type: square;
122−}
123−.content ul ul, .content ol ul {
124− margin-bottom: .5rem;
125−}
126−.content li p {
127− margin-bottom: .5em;
128−}
129−
130−.content p { line-height: 1.45em; }
131−.content ol { line-height: 1.45em; }
132−.content ul { line-height: 1.45em; }
133−.content a { text-decoration: none; }
134−.content a:hover { text-decoration: underline; }
135−.content img { max-width: 100%; }
136−.content .header:link,
137−.content .header:visited {
138− color: var(--fg);
139− color: var(--heading-fg);
140−}
141−.content .header:link,
142−.content .header:visited:hover {
143− text-decoration: none;
144−}
145−
146−table {
147− margin: 0 auto;
148− border-collapse: collapse;
149− width: 100%;
150−}
151−table td {
152− padding: .75rem;
153− width: auto;
154−}
155−table thead {
156− background: var(--table-header-bg);
157−}
158−table thead td {
159− font-weight: 700;
160− border: none;
161−}
162−table thead th {
163− padding: .75rem;
164− text-align: left;
165− font-weight: 500;
166− line-height: 1.5;
167− width: auto;
168−}
169−table thead tr {
170− border-bottom: 2px var(--table-border-color) solid;
171−}
172−table tbody tr {
173− border-bottom: 1px var(--table-border-line) solid;
174−}
175−/* Alternate background colors for rows */
176−table tbody tr:nth-child(2n) {
177− /* background: var(--table-alternate-bg); */
178−}
179−
180−
181−blockquote {
182− margin: 1.5rem 0;
183− padding: 1rem 1.5rem;
184− color: var(--fg);
185− opacity: .9;
186− background-color: var(--quote-bg);
187− border-left: 4px solid var(--quote-border);
188−}
189−blockquote *:last-child {
190− margin-bottom: 0;
191−}
192−
193−
194−:not(.footnote-definition) + .footnote-definition,
195−.footnote-definition + :not(.footnote-definition) {
196− margin-top: 2em;
197−}
198−.footnote-definition {
199− font-size: 0.9em;
200− margin: 0.5em 0;
201−}
202−.footnote-definition p {
203− display: inline;
204−}
205−
206−.tooltiptext {
207− position: absolute;
208− visibility: hidden;
209− color: #fff;
210− background-color: #333;
211− transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */
212− left: -8px; /* Half of the width of the icon */
213− top: -35px;
214− font-size: 0.8em;
215− text-align: center;
216− border-radius: 6px;
217− padding: 5px 8px;
218− margin: 5px;
219− z-index: 1000;
220−}
221−.tooltipped .tooltiptext {
222− visibility: visible;
223−}
224−
225−.chapter li.part-title {
226− color: var(--sidebar-fg);
227− margin: 5px 0px;
228− font-weight: bold;
229−}
230−
231−.result-no-output {
232− font-style: italic;
233−}
book/theme/css/print.cssdeleted−54
@@ -1,54 +0,0 @@
1−
2−#sidebar,
3−#menu-bar,
4−.nav-chapters,
5−.mobile-nav-chapters {
6− display: none;
7−}
8−
9−#page-wrapper.page-wrapper {
10− transform: none;
11− margin-left: 0px;
12− overflow-y: initial;
13−}
14−
15−#content {
16− max-width: none;
17− margin: 0;
18− padding: 0;
19−}
20−
21−.page {
22− overflow-y: initial;
23−}
24−
25−code {
26− background-color: #666666;
27− border-radius: 5px;
28−
29− /* Force background to be printed in Chrome */
30− -webkit-print-color-adjust: exact;
31−}
32−
33−pre > .buttons {
34− z-index: 2;
35−}
36−
37−a, a:visited, a:active, a:hover {
38− color: #4183c4;
39− text-decoration: none;
40−}
41−
42−h1, h2, h3, h4, h5, h6 {
43− page-break-inside: avoid;
44− page-break-after: avoid;
45−}
46−
47−pre, code {
48− page-break-inside: avoid;
49− white-space: pre-wrap;
50−}
51−
52−.fa {
53− display: none !important;
54−}
book/theme/css/variables.cssdeleted−342
@@ -1,342 +0,0 @@
1−
2−/* Globals */
3−
4−:root {
5− --sidebar-width: 300px;
6− --page-padding: 15px;
7− --content-max-width: 750px;
8− --menu-bar-height: 50px;
9−}
10−
11−/* Themes */
12−
13−.ayu {
14− --bg: hsl(210, 25%, 8%);
15− --fg: #c5c5c5;
16−
17− --sidebar-bg: #14191f;
18− --sidebar-fg: #c8c9db;
19− --sidebar-non-existent: #5c6773;
20− --sidebar-active: #ffb454;
21− --sidebar-spacer: #2d334f;
22−
23− --scrollbar: var(--sidebar-fg);
24−
25− --icons: #737480;
26− --icons-hover: #b7b9cc;
27−
28− --links: #0096cf;
29−
30− --inline-code-color: #ffb454;
31−
32− --theme-popup-bg: #14191f;
33− --theme-popup-border: #5c6773;
34− --theme-hover: #191f26;
35−
36− --quote-bg: hsl(226, 15%, 17%);
37− --quote-border: hsl(226, 15%, 22%);
38−
39− --table-border-color: hsl(210, 25%, 13%);
40− --table-header-bg: hsl(210, 25%, 28%);
41− --table-alternate-bg: hsl(210, 25%, 11%);
42−
43− --searchbar-border-color: #848484;
44− --searchbar-bg: #424242;
45− --searchbar-fg: #fff;
46− --searchbar-shadow-color: #d4c89f;
47− --searchresults-header-fg: #666;
48− --searchresults-border-color: #888;
49− --searchresults-li-bg: #252932;
50− --search-mark-bg: #e3b171;
51−}
52−
53−.coal {
54− --bg: hsl(200, 7%, 8%);
55− --fg: #98a3ad;
56−
57− --sidebar-bg: #292c2f;
58− --sidebar-fg: #a1adb8;
59− --sidebar-non-existent: #505254;
60− --sidebar-active: #3473ad;
61− --sidebar-spacer: #393939;
62−
63− --scrollbar: var(--sidebar-fg);
64−
65− --icons: #43484d;
66− --icons-hover: #b3c0cc;
67−
68− --links: #2b79a2;
69−
70− --inline-code-color: #c5c8c6;
71−
72− --theme-popup-bg: #141617;
73− --theme-popup-border: #43484d;
74− --theme-hover: #1f2124;
75−
76− --quote-bg: hsl(234, 21%, 18%);
77− --quote-border: hsl(234, 21%, 23%);
78−
79− --table-border-color: hsl(200, 7%, 13%);
80− --table-header-bg: hsl(200, 7%, 28%);
81− --table-alternate-bg: hsl(200, 7%, 11%);
82−
83− --searchbar-border-color: #aaa;
84− --searchbar-bg: #b7b7b7;
85− --searchbar-fg: #000;
86− --searchbar-shadow-color: #aaa;
87− --searchresults-header-fg: #666;
88− --searchresults-border-color: #98a3ad;
89− --searchresults-li-bg: #2b2b2f;
90− --search-mark-bg: #355c7d;
91−}
92−
93−.light {
94− --bg: hsl(0, 0%, 100%);
95− --fg: hsl(0, 0%, 0%);
96−
97− --sidebar-bg: #fafafa;
98− --sidebar-fg: hsl(0, 0%, 0%);
99− --sidebar-non-existent: #aaaaaa;
100− --sidebar-active: #1f1fff;
101− --sidebar-spacer: #f4f4f4;
102−
103− --scrollbar: #8F8F8F;
104−
105− --icons: #747474;
106− --icons-hover: #000000;
107−
108− --links: #20609f;
109−
110− --inline-code-color: #301900;
111−
112− --theme-popup-bg: #fafafa;
113− --theme-popup-border: #cccccc;
114− --theme-hover: #e6e6e6;
115−
116− --quote-bg: hsl(197, 37%, 96%);
117− --quote-border: hsl(197, 37%, 91%);
118−
119− --table-border-color: hsl(0, 0%, 95%);
120− --table-header-bg: hsl(0, 0%, 80%);
121− --table-alternate-bg: hsl(0, 0%, 97%);
122−
123− --searchbar-border-color: #aaa;
124− --searchbar-bg: #fafafa;
125− --searchbar-fg: #000;
126− --searchbar-shadow-color: #aaa;
127− --searchresults-header-fg: #666;
128− --searchresults-border-color: #888;
129− --searchresults-li-bg: #e4f2fe;
130− --search-mark-bg: #a2cff5;
131−}
132−
133−.navy {
134− --bg: hsl(226, 23%, 11%);
135− --fg: #bcbdd0;
136−
137− --sidebar-bg: #282d3f;
138− --sidebar-fg: #c8c9db;
139− --sidebar-non-existent: #505274;
140− --sidebar-active: #2b79a2;
141− --sidebar-spacer: #2d334f;
142−
143− --scrollbar: var(--sidebar-fg);
144−
145− --icons: #737480;
146− --icons-hover: #b7b9cc;
147−
148− --links: #2b79a2;
149−
150− --inline-code-color: #c5c8c6;
151−
152− --theme-popup-bg: #161923;
153− --theme-popup-border: #737480;
154− --theme-hover: #282e40;
155−
156− --quote-bg: hsl(226, 15%, 17%);
157− --quote-border: hsl(226, 15%, 22%);
158−
159− --table-border-color: hsl(226, 23%, 16%);
160− --table-header-bg: hsl(226, 23%, 31%);
161− --table-alternate-bg: hsl(226, 23%, 14%);
162−
163− --searchbar-border-color: #aaa;
164− --searchbar-bg: #aeaec6;
165− --searchbar-fg: #000;
166− --searchbar-shadow-color: #aaa;
167− --searchresults-header-fg: #5f5f71;
168− --searchresults-border-color: #5c5c68;
169− --searchresults-li-bg: #242430;
170− --search-mark-bg: #a2cff5;
171−}
172−
173−.rust {
174− --bg: hsl(60, 9%, 87%);
175− --fg: #262625;
176−
177− --sidebar-bg: #3b2e2a;
178− --sidebar-fg: #c8c9db;
179− --sidebar-non-existent: #505254;
180− --sidebar-active: #e69f67;
181− --sidebar-spacer: #45373a;
182−
183− --scrollbar: var(--sidebar-fg);
184−
185− --icons: #737480;
186− --icons-hover: #262625;
187−
188− --links: #2b79a2;
189−
190− --inline-code-color: #6e6b5e;
191−
192− --theme-popup-bg: #e1e1db;
193− --theme-popup-border: #b38f6b;
194− --theme-hover: #99908a;
195−
196− --quote-bg: hsl(60, 5%, 75%);
197− --quote-border: hsl(60, 5%, 70%);
198−
199− --table-border-color: hsl(60, 9%, 82%);
200− --table-header-bg: #b3a497;
201− --table-alternate-bg: hsl(60, 9%, 84%);
202−
203− --searchbar-border-color: #aaa;
204− --searchbar-bg: #fafafa;
205− --searchbar-fg: #000;
206− --searchbar-shadow-color: #aaa;
207− --searchresults-header-fg: #666;
208− --searchresults-border-color: #888;
209− --searchresults-li-bg: #dec2a2;
210− --search-mark-bg: #e69f67;
211−}
212−
213−@media (prefers-color-scheme: dark) {
214− .light.no-js {
215− --bg: hsl(200, 7%, 8%);
216− --fg: #98a3ad;
217−
218− --sidebar-bg: #292c2f;
219− --sidebar-fg: #a1adb8;
220− --sidebar-non-existent: #505254;
221− --sidebar-active: #3473ad;
222− --sidebar-spacer: #393939;
223−
224− --scrollbar: var(--sidebar-fg);
225−
226− --icons: #43484d;
227− --icons-hover: #b3c0cc;
228−
229− --links: #2b79a2;
230−
231− --inline-code-color: #c5c8c6;
232−
233− --theme-popup-bg: #141617;
234− --theme-popup-border: #43484d;
235− --theme-hover: #1f2124;
236−
237− --quote-bg: hsl(234, 21%, 18%);
238− --quote-border: hsl(234, 21%, 23%);
239−
240− --table-border-color: hsl(200, 7%, 13%);
241− --table-header-bg: hsl(200, 7%, 28%);
242− --table-alternate-bg: hsl(200, 7%, 11%);
243−
244− --searchbar-border-color: #aaa;
245− --searchbar-bg: #b7b7b7;
246− --searchbar-fg: #000;
247− --searchbar-shadow-color: #aaa;
248− --searchresults-header-fg: #666;
249− --searchresults-border-color: #98a3ad;
250− --searchresults-li-bg: #2b2b2f;
251− --search-mark-bg: #355c7d;
252− }
253−}
254−
255−.colibri {
256− --bg: #3b224c;
257− --fg: #bcbdd0;
258− --heading-fg: #fff;
259−
260− --sidebar-bg: #281733;
261− --sidebar-fg: #c8c9db;
262− --sidebar-non-existent: #505274;
263− --sidebar-active: #a4a0e8;
264− --sidebar-spacer: #2d334f;
265−
266− --scrollbar: var(--sidebar-fg);
267−
268− --icons: #737480;
269− --icons-hover: #b7b9cc;
270−
271− /* --links: #a4a0e8; */
272− --links: #ECCDBA;
273−
274− --inline-code-color: hsl(48.7, 7.8%, 70%);
275−
276− --theme-popup-bg: #161923;
277− --theme-popup-border: #737480;
278− --theme-hover: rgba(0,0,0, .2);
279−
280− --quote-bg: #281733;
281− --quote-border: hsl(226, 15%, 22%);
282−
283− --table-border-color: hsl(226, 23%, 76%);
284− --table-header-bg: hsla(226, 23%, 31%, 0);
285− --table-alternate-bg: hsl(226, 23%, 14%);
286− --table-border-line: hsla(201deg, 20%, 92%, 0.2);
287−
288− --searchbar-border-color: #aaa;
289− --searchbar-bg: #aeaec6;
290− --searchbar-fg: #000;
291− --searchbar-shadow-color: #aaa;
292− --searchresults-header-fg: #5f5f71;
293− --searchresults-border-color: #5c5c68;
294− --searchresults-li-bg: #242430;
295− --search-mark-bg: #a2cff5;
296−}
297−
298−.colibri {
299−/*
300− --bg: #ffffff;
301− --fg: #452859;
302− --fg: #5a5977;
303− --heading-fg: #281733;
304−
305− --sidebar-bg: #281733;
306− --sidebar-fg: #c8c9db;
307− --sidebar-non-existent: #505274;
308− --sidebar-active: #a4a0e8;
309− --sidebar-spacer: #2d334f;
310−
311− --scrollbar: var(--sidebar-fg);
312−
313− --icons: #737480;
314− --icons-hover: #b7b9cc;
315−
316− --links: #6F44F0;
317−
318− --inline-code-color: #a39e9b;
319−
320− --theme-popup-bg: #161923;
321− --theme-popup-border: #737480;
322− --theme-hover: rgba(0,0,0, .2);
323−
324− --quote-bg: rgba(0, 0, 0, 0);
325− --quote-border: hsl(226, 15%, 75%);
326−
327− --table-border-color: #5a5977;
328− --table-border-color: hsl(201deg 10% 67%);
329− --table-header-bg: hsl(0, 0%, 100%);
330− --table-alternate-bg: hsl(0, 0%, 97%);
331− --table-border-line: hsl(201deg, 20%, 92%);
332−
333− --searchbar-border-color: #aaa;
334− --searchbar-bg: #aeaec6;
335− --searchbar-fg: #000;
336− --searchbar-shadow-color: #aaa;
337− --searchresults-header-fg: #5f5f71;
338− --searchresults-border-color: #5c5c68;
339− --searchresults-li-bg: #242430;
340− --search-mark-bg: #a2cff5;
341−*/
342−}
book/theme/favicon.png

Binary file not shown.

book/theme/favicon.svg+1−22
@@ -1,22 +1 @@
1−<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 199.7 184.2">
2− <style>
3− @media (prefers-color-scheme: dark) {
4− svg { fill: white; }
5− }
6− </style>
7−<path d="M189.5,36.8c0.2,2.8,0,5.1-0.6,6.8L153,162c-0.6,2.1-2,3.7-4.2,5c-2.2,1.2-4.4,1.9-6.7,1.9H31.4c-9.6,0-15.3-2.8-17.3-8.4
8− c-0.8-2.2-0.8-3.9,0.1-5.2c0.9-1.2,2.4-1.8,4.6-1.8H123c7.4,0,12.6-1.4,15.4-4.1s5.7-8.9,8.6-18.4l32.9-108.6
9− c1.8-5.9,1-11.1-2.2-15.6S169.9,0,164,0H72.7c-1,0-3.1,0.4-6.1,1.1l0.1-0.4C64.5,0.2,62.6,0,61,0.1s-3,0.5-4.3,1.4
10− c-1.3,0.9-2.4,1.8-3.2,2.8S52,6.5,51.2,8.1c-0.8,1.6-1.4,3-1.9,4.3s-1.1,2.7-1.8,4.2c-0.7,1.5-1.3,2.7-2,3.7c-0.5,0.6-1.2,1.5-2,2.5
11− s-1.6,2-2.2,2.8s-0.9,1.5-1.1,2.2c-0.2,0.7-0.1,1.8,0.2,3.2c0.3,1.4,0.4,2.4,0.4,3.1c-0.3,3-1.4,6.9-3.3,11.6
12− c-1.9,4.7-3.6,8.1-5.1,10.1c-0.3,0.4-1.2,1.3-2.6,2.7c-1.4,1.4-2.3,2.6-2.6,3.7c-0.3,0.4-0.3,1.5-0.1,3.4c0.3,1.8,0.4,3.1,0.3,3.8
13− c-0.3,2.7-1.3,6.3-3,10.8c-1.7,4.5-3.4,8.2-5,11c-0.2,0.5-0.9,1.4-2,2.8c-1.1,1.4-1.8,2.5-2,3.4c-0.2,0.6-0.1,1.8,0.1,3.4
14− c0.2,1.6,0.2,2.8-0.1,3.6c-0.6,3-1.8,6.7-3.6,11c-1.8,4.3-3.6,7.9-5.4,11c-0.5,0.8-1.1,1.7-2,2.8c-0.8,1.1-1.5,2-2,2.8
15− s-0.8,1.6-1,2.5c-0.1,0.5,0,1.3,0.4,2.3c0.3,1.1,0.4,1.9,0.4,2.6c-0.1,1.1-0.2,2.6-0.5,4.4c-0.2,1.8-0.4,2.9-0.4,3.2
16− c-1.8,4.8-1.7,9.9,0.2,15.2c2.2,6.2,6.2,11.5,11.9,15.8c5.7,4.3,11.7,6.4,17.8,6.4h110.7c5.2,0,10.1-1.7,14.7-5.2s7.7-7.8,9.2-12.9
17− l33-108.6c1.8-5.8,1-10.9-2.2-15.5C194.9,39.7,192.6,38,189.5,36.8z M59.6,122.8L73.8,80c0,0,7,0,10.8,0s28.8-1.7,25.4,17.5
18− c-3.4,19.2-18.8,25.2-36.8,25.4S59.6,122.8,59.6,122.8z M78.6,116.8c4.7-0.1,18.9-2.9,22.1-17.1S89.2,86.3,89.2,86.3l-8.9,0
19− l-10.2,30.5C70.2,116.9,74,116.9,78.6,116.8z M75.3,68.7L89,26.2h9.8l0.8,34l23.6-34h9.9l-13.6,42.5h-7.1l12.5-35.4l-24.5,35.4h-6.8
20− l-0.8-35L82,68.7H75.3z"/>
21−</svg>
22−<!-- Original image Copyright Dave Gandy — CC BY 4.0 License -->
1+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" viewBox="663.38 37.57 575.35 903.75"> <g transform="matrix(1,0,0,1,-31352.7,-1817.25)"> <g transform="matrix(1,0,0,1,31062.7,-20.8972)"> <g transform="matrix(1,0,0,1,-130.173,0.00185558)"> <path d="M1083.58,1875.72L1635.06,2194.12C1649.8,2202.63 1658.88,2218.37 1658.88,2235.39C1658.88,2264.98 1658.88,2311.74 1658.88,2341.33C1658.88,2349.84 1656.61,2358.03 1652.5,2365.16C1652.5,2365.16 1214.7,2112.4 1107.2,2050.33C1092.58,2041.89 1083.58,2026.29 1083.58,2009.41C1083.58,1963.5 1083.58,1875.72 1083.58,1875.72Z" style="fill:#706bc8;"></path> </g> <g transform="matrix(1,0,0,1,-130.173,0.00185558)"> <path d="M1635.26,2604.84C1649.88,2613.28 1658.88,2628.87 1658.88,2645.75C1658.88,2691.67 1658.88,2779.44 1658.88,2779.44L1107.41,2461.05C1092.66,2452.53 1083.58,2436.8 1083.58,2419.78C1083.58,2390.19 1083.58,2343.42 1083.58,2313.84C1083.58,2305.32 1085.85,2297.13 1089.96,2290.01C1089.96,2290.01 1527.76,2542.77 1635.26,2604.84Z" style="fill:#55c5e4;"></path> </g> <g transform="matrix(1,0,0,1,216.062,984.098)"> <path d="M790.407,1432.56C785.214,1435.55 780.717,1439.9 777.509,1445.46C767.862,1462.16 773.473,1483.76 790.004,1493.59L789.998,1493.59L761.173,1476.95C746.427,1468.44 737.344,1452.71 737.344,1435.68C737.344,1406.09 737.344,1359.33 737.344,1329.74C737.344,1312.71 746.427,1296.98 761.173,1288.47L1259.59,1000.74L1259.83,1000.6C1264.92,997.617 1269.33,993.314 1272.48,987.844C1282.13,971.136 1276.52,949.544 1259.99,939.707L1260,939.707L1288.82,956.349C1303.57,964.862 1312.65,980.595 1312.65,997.622C1312.65,1027.21 1312.65,1073.97 1312.65,1103.56C1312.65,1120.59 1303.57,1136.32 1288.82,1144.83L1259.19,1161.94L1259.59,1161.68L790.407,1432.56Z" style="fill:#84ddea;"></path> </g> <g transform="matrix(1,0,0,1,216.062,984.098)"> <path d="M790.407,1686.24C785.214,1689.23 780.717,1693.58 777.509,1699.13C767.862,1715.84 773.473,1737.43 790.004,1747.27L789.998,1747.27L761.173,1730.63C746.427,1722.12 737.344,1706.38 737.344,1689.36C737.344,1659.77 737.344,1613.01 737.344,1583.42C737.344,1566.39 746.427,1550.66 761.173,1542.15L1259.59,1254.42L1259.83,1254.28C1264.92,1251.29 1269.33,1246.99 1272.48,1241.52C1282.13,1224.81 1276.52,1203.22 1259.99,1193.38L1260,1193.38L1288.82,1210.03C1303.57,1218.54 1312.65,1234.27 1312.65,1251.3C1312.65,1280.89 1312.65,1327.65 1312.65,1357.24C1312.65,1374.26 1303.57,1390 1288.82,1398.51L1259.19,1415.61L1259.59,1415.36L790.407,1686.24Z" style="fill:#997bc8;"></path></g></g></g> </svg>
book/theme/highlight.cssdeleted−56
@@ -1,56 +0,0 @@
1−pre code.hljs {
2− display:block;
3− overflow-x:auto;
4− padding:1em
5−}
6−code.hljs {
7− padding:3px 5px
8−}
9−.hljs {
10− background:#2f1e2e;
11− color:#a39e9b
12−}
13−.hljs-comment,
14−.hljs-quote {
15− color:#8d8687
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:#ef6155
27−}
28−.hljs-built_in,
29−.hljs-deletion,
30−.hljs-literal,
31−.hljs-number,
32−.hljs-params,
33−.hljs-type {
34− color:#f99b15
35−}
36−.hljs-attribute,
37−.hljs-section,
38−.hljs-title {
39− color:#fec418
40−}
41−.hljs-addition,
42−.hljs-bullet,
43−.hljs-string,
44−.hljs-symbol {
45− color:#48b685
46−}
47−.hljs-keyword,
48−.hljs-selector-tag {
49− color:#815ba4
50−}
51−.hljs-emphasis {
52− font-style:italic
53−}
54−.hljs-strong {
55− font-weight:700
56−}
book/theme/highlight.jsdeleted−6
@@ -1,6 +0,0 @@
1−/*
2− Highlight.js 10.1.1 (93fd0d73)
3− License: BSD-3-Clause
4− Copyright (c) 2006-2020, Ivan Sagalaev
5−*/
6−var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset<n[0].offset?e:n:"start"===n[0].event?e:n:e.length?e:n}function c(e){s+="<"+a(e)+[].map.call(e.attributes,(function(e){return" "+e.nodeName+'="'+t(e.value)+'"'})).join("")+">"}function u(e){s+="</"+a(e)+">"}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="</span>",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=`<span class="${e}">`}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:k,mergeStreams:O}=i,M=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void O.addText(A);e=m(y.subLanguage,A,!0,k[y.subLanguage]),k[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void O.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),O.addText(t)}(),A=""}function h(e){return e.className&&O.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"<unnamed>")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&O.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i<e.length;i++){var s=r+=1,o=d(e[i]);for(i>0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"<br>":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ /]*>/g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:"</?",end:">"}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}());hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin:/</,end:/>/},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%<?\^\+\*]/}]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,i]},a={className:"variable",begin:/\$\([\w-]+\s/,end:/\)/,keywords:{built_in:"subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value"},contains:[i]},r={begin:"^"+e.UNDERSCORE_IDENT_RE+"\\s*(?=[:+?]?=)"},s={className:"section",begin:/^[^\s]+:/,end:/$/,contains:[i]};return{name:"Makefile",aliases:["mk","mak"],keywords:{$pattern:/[\w-]+/,keyword:"define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath"},contains:[e.HASH_COMMENT_MODE,i,n,a,r,{className:"meta",begin:/^\.PHONY:/,end:/$/,keywords:{$pattern:/[\.\w]+/,"meta-keyword":".PHONY"}},s]}}}());hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("xml",function(){"use strict";return function(e){var n={className:"symbol",begin:"&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;"},a={begin:"\\s",contains:[{className:"meta-keyword",begin:"#?[a-z_][a-z1-9_-]+",illegal:"\\n"}]},s=e.inherit(a,{begin:"\\(",end:"\\)"}),t=e.inherit(e.APOS_STRING_MODE,{className:"meta-string"}),i=e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),c={endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:"attr",begin:"[A-Za-z0-9\\._:-]+",relevance:0},{begin:/=\s*/,relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/,contains:[n]},{begin:/'/,end:/'/,contains:[n]},{begin:/[^\s"'=<>`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"<![a-z]",end:">",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"<![a-z]",end:">",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:"<style(?=\\s|>)",end:">",keywords:{name:"style"},contains:[c],starts:{end:"</style>",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:"<script(?=\\s|>)",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"</?",end:"/?>",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"</",contains:[].concat(_,u,d,[s,{begin:"\\b(deque|list|queue|priority_queue|pair|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",end:">",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin:/</,end:/>/,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}());hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}());hljs.registerLanguage("d",function(){"use strict";return function(e){var a={$pattern:e.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("haskell",function(){"use strict";return function(e){var n={variants:[e.COMMENT("--","$"),e.COMMENT("{-","-}",{contains:["self"]})]},i={className:"meta",begin:"{-#",end:"#-}"},a={className:"meta",begin:"^#",end:"$"},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",end:"\\)",illegal:'"',contains:[i,a,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[s,l,n]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[i,s,l,{begin:"{",end:"}",contains:l.contains},n]},{beginKeywords:"default",end:"$",contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}}());hljs.registerLanguage("handlebars",function(){"use strict";function e(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(n){const a={"builtin-name":"action bindattr collection component concat debugger each each-in get hash if in input link-to loc log lookup mut outlet partial query-params render template textarea unbound unless view with yield"},t=/\[.*?\]/,s=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,i=e("(",/'.*?'/,"|",/".*?"/,"|",t,"|",s,"|",/\.|\//,")+"),r=e("(",t,"|",s,")(?==)"),l={begin:i,lexemes:/[\w.\/]+/},c=n.inherit(l,{keywords:{literal:"true false undefined null"}}),o={begin:/\(/,end:/\)/},m={className:"attr",begin:r,relevance:0,starts:{begin:/=/,end:/=/,starts:{contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,c,o]}}},d={contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{begin:/as\s+\|/,keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},m,c,o],returnEnd:!0},g=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/\)/})});o.contains=[g];const u=n.inherit(l,{keywords:a,className:"name",starts:n.inherit(d,{end:/}}/})}),b=n.inherit(l,{keywords:a,className:"name"}),h=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/}}/})});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},n.COMMENT(/\{\{!--/,/--\}\}/),n.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[u],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[b]},{className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[u]},{className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[b]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[h]},{className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[h]}]}}}());hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:"</",contains:[e.C_LINE_COMMENT_MODE,e.COMMENT("/\\*","\\*/",{contains:["self"]}),e.inherit(e.QUOTE_STRING_MODE,{begin:/b?"/,illegal:null}),{className:"string",variants:[{begin:/r(#*)"(.|\n)*?"\1(?!#)/},{begin:/b?'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/}]},{className:"symbol",begin:/'[a-zA-Z_][a-zA-Z0-9_]*/},{className:"number",variants:[{begin:"\\b0b([01_]+)"+n},{begin:"\\b0o([0-7_]+)"+n},{begin:"\\b0x([A-Fa-f0-9_]+)"+n},{begin:"\\b(\\d[\\d_]*(\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)"+n}],relevance:0},{className:"function",beginKeywords:"fn",end:"(\\(|<)",excludeEnd:!0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"meta",begin:"#\\!?\\[",end:"\\]",contains:[{className:"meta-string",begin:/"/,end:/"/}]},{className:"class",beginKeywords:"type",end:";",contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"\\S"},{className:"class",beginKeywords:"trait enum struct union",end:"{",contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"[\\w\\d]"},{begin:e.IDENT_RE+"::",keywords:{built_in:t}},{begin:"->"}]}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("ini",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(...n){return n.map(n=>e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}());hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"</",contains:[{className:"built_in",begin:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.C_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"string",variants:[{begin:'@"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]}]},{className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(e.QUOTE_STRING_MODE,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}());hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"</?",end:">",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}());hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}());hljs.registerLanguage("x86asm",function(){"use strict";return function(s){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+s.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}());hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin:/</,end:/>/,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin:/</,end:/>/,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}());hljs.registerLanguage("armasm",function(){"use strict";return function(s){const e={variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),s.COMMENT("[;@]","$",{relevance:0}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}());hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:"</",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"string",variants:[e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{begin:"`",end:"`"}]},{className:"number",variants:[{begin:e.C_NUMBER_RE+"[i]",relevance:1},e.C_NUMBER_MODE]},{begin:/:=/},{className:"function",beginKeywords:"func",end:"\\s*(\\{|$)",excludeEnd:!0,contains:[e.TITLE_MODE,{className:"params",begin:/\(/,end:/\)/,keywords:n,illegal:/["']/}]}]}}}());hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}());hljs.registerLanguage("python",function(){"use strict";return function(e){var n={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}());hljs.registerLanguage("scala",function(){"use strict";return function(e){var n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},a={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},i={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[t]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}());hljs.registerLanguage("julia",function(){"use strict";return function(e){var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},a={keywords:t,illegal:/<\//},n={className:"subst",begin:/\$\(/,end:/\)/,keywords:t},o={className:"variable",begin:"\\$"+r},i={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]},l={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],begin:"`",end:"`"},s={className:"meta",begin:"@"+r};return a.name="Julia",a.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,s,{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}],n.contains=a.contains,a}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("r",function(){"use strict";return function(e){var n="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{name:"R",contains:[e.HASH_COMMENT_MODE,{begin:n,keywords:{$pattern:n,keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}());hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:"</>"},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}());hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}());
book/theme/index.hbs+87−51
@@ -1,11 +1,11 @@
11<!DOCTYPE HTML>
2−<html lang="{{ language }}" class="sidebar-visible no-js {{ default_theme }}">
2+<html lang="{{ language }}" class="{{ default_theme }}" dir="{{ text_direction }}">
33 <head>
44 <!-- Book generated using mdBook -->
55 <meta charset="UTF-8">
66 <title>{{ title }}</title>
77 {{#if is_print }}
8− <meta name="robots" content="noindex" />
8+ <meta name="robots" content="noindex">
99 {{/if}}
1010 {{#if base_url}}
1111 <base href="{{ base_url }}">
@@ -15,10 +15,9 @@
1515 <!-- Custom HTML head -->
1616 {{> head}}
1717
18− <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
1918 <meta name="description" content="{{ description }}">
2019 <meta name="viewport" content="width=device-width, initial-scale=1">
21− <meta name="theme-color" content="#ffffff" />
20+ <meta name="theme-color" content="#ffffff">
2221
2322 {{#if favicon_svg}}
2423 <link rel="icon" href="{{ path_to_root }}favicon.svg">
@@ -35,8 +34,6 @@
3534
3635 <!-- Fonts -->
3736 <link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
38− <link rel="preconnect" href="https://fonts.gstatic.com">
39− <link href="https://fonts.googleapis.com/css2?family=Inter:wght@200;400;500;700&display=swap" rel="stylesheet">
4037 {{#if copy_fonts}}
4138 <link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
4239 {{/if}}
@@ -53,18 +50,19 @@
5350
5451 {{#if mathjax_support}}
5552 <!-- MathJax -->
56− <script async type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
53+ <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
5754 {{/if}}
5855 </head>
59− <body>
56+ <body class="sidebar-visible no-js">
57+ <div id="body-container">
6058 <!-- Provide site root to javascript -->
61− <script type="text/javascript">
59+ <script>
6260 var path_to_root = "{{ path_to_root }}";
6361 var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
6462 </script>
6563
6664 <!-- Work around some values being stored in localStorage wrapped in quotes -->
67− <script type="text/javascript">
65+ <script>
6866 try {
6967 var theme = localStorage.getItem('mdbook-theme');
7068 var sidebar = localStorage.getItem('mdbook-sidebar');
@@ -80,27 +78,34 @@
8078 </script>
8179
8280 <!-- Set the theme before any content is loaded, prevents flash -->
83− <script type="text/javascript">
81+ <script>
8482 var theme;
8583 try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
8684 if (theme === null || theme === undefined) { theme = default_theme; }
8785 var html = document.querySelector('html');
88− html.classList.remove('no-js')
8986 html.classList.remove('{{ default_theme }}')
9087 html.classList.add(theme);
91− html.classList.add('js');
88+ var body = document.querySelector('body');
89+ body.classList.remove('no-js')
90+ body.classList.add('js');
9291 </script>
9392
93+ <input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
94+
9495 <!-- Hide / unhide sidebar before it is displayed -->
95− <script type="text/javascript">
96− var html = document.querySelector('html');
97− var sidebar = 'hidden';
96+ <script>
97+ var body = document.querySelector('body');
98+ var sidebar = null;
99+ var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
98100 if (document.body.clientWidth >= 1080) {
99101 try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
100102 sidebar = sidebar || 'visible';
103+ } else {
104+ sidebar = 'hidden';
101105 }
102− html.classList.remove('sidebar-visible');
103− html.classList.add("sidebar-" + sidebar);
106+ sidebar_toggle.checked = sidebar === 'visible';
107+ body.classList.remove('sidebar-visible');
108+ body.classList.add("sidebar-" + sidebar);
104109 </script>
105110
106111 <nav id="sidebar" class="sidebar" aria-label="Table of contents">
@@ -110,26 +115,48 @@
110115 <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
111116 </nav>
112117
118+ <!-- Track and set sidebar scroll position -->
119+ <script>
120+ var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
121+ sidebarScrollbox.addEventListener('click', function(e) {
122+ if (e.target.tagName === 'A') {
123+ sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
124+ }
125+ }, { passive: true });
126+ var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
127+ sessionStorage.removeItem('sidebar-scroll');
128+ if (sidebarScrollTop) {
129+ // preserve sidebar scroll position when navigating via links within sidebar
130+ sidebarScrollbox.scrollTop = sidebarScrollTop;
131+ } else {
132+ // scroll sidebar to current active section when navigating via "next/previous chapter" buttons
133+ var activeSection = document.querySelector('#sidebar .active');
134+ if (activeSection) {
135+ activeSection.scrollIntoView({ block: 'center' });
136+ }
137+ }
138+ </script>
139+
113140 <div id="page-wrapper" class="page-wrapper">
114141
115142 <div class="page">
116143 {{> header}}
117144 <div id="menu-bar-hover-placeholder"></div>
118− <div id="menu-bar" class="menu-bar sticky bordered">
145+ <div id="menu-bar" class="menu-bar sticky">
119146 <div class="left-buttons">
120− <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
147+ <label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
121148 <i class="fa fa-bars"></i>
122− </button>
149+ </label>
123150 <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
124151 <i class="fa fa-paint-brush"></i>
125152 </button>
126153 <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
127− <li role="none"><button role="menuitem" class="theme" id="light">{{ theme_option "Light" }}</button></li>
128− <li role="none"><button role="menuitem" class="theme" id="rust">{{ theme_option "Rust" }}</button></li>
129− <li role="none"><button role="menuitem" class="theme" id="coal">{{ theme_option "Coal" }}</button></li>
130− <li role="none"><button role="menuitem" class="theme" id="navy">{{ theme_option "Navy" }}</button></li>
131− <li role="none"><button role="menuitem" class="theme" id="ayu">{{ theme_option "Ayu" }}</button></li>
132− <li role="none"><button role="menuitem" class="theme" id="colibri">{{ theme_option "Colibri" }}</button></li>
154+ <li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
155+ <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
156+ <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
157+ <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
158+ <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
159+ <li role="none"><button role="menuitem" class="theme" id="colibri">Colibri</button></li>
133160 </ul>
134161 {{#if search_enabled}}
135162 <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
@@ -151,13 +178,19 @@
151178 <i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
152179 </a>
153180 {{/if}}
181+ {{#if git_repository_edit_url}}
182+ <a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
183+ <i id="git-edit-button" class="fa fa-edit"></i>
184+ </a>
185+ {{/if}}
186+
154187 </div>
155188 </div>
156189
157190 {{#if search_enabled}}
158191 <div id="search-wrapper" class="hidden">
159192 <form id="searchbar-outer" class="searchbar-outer">
160− <input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
193+ <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
161194 </form>
162195 <div id="searchresults-outer" class="searchresults-outer hidden">
163196 <div id="searchresults-header" class="searchresults-header"></div>
@@ -168,7 +201,7 @@
168201 {{/if}}
169202
170203 <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
171− <script type="text/javascript">
204+ <script>
172205 document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
173206 document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
174207 Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
@@ -190,7 +223,7 @@
190223 {{/previous}}
191224
192225 {{#next}}
193− <a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
226+ <a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
194227 <i class="fa fa-angle-right"></i>
195228 </a>
196229 {{/next}}
@@ -208,7 +241,7 @@
208241 {{/previous}}
209242
210243 {{#next}}
211− <a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
244+ <a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
212245 <i class="fa fa-angle-right"></i>
213246 </a>
214247 {{/next}}
@@ -216,10 +249,12 @@
216249
217250 </div>
218251
219− {{#if livereload}}
252+ {{#if live_reload_endpoint}}
220253 <!-- Livereload script (if served using the cli tool) -->
221− <script type="text/javascript">
222− var socket = new WebSocket("{{{livereload}}}");
254+ <script>
255+ const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
256+ const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
257+ const socket = new WebSocket(wsAddress);
223258 socket.onmessage = function (event) {
224259 if (event.data === "reload") {
225260 socket.close();
@@ -235,7 +270,7 @@
235270
236271 {{#if google_analytics}}
237272 <!-- Google Analytics Tag -->
238− <script type="text/javascript">
273+ <script>
239274 var localAddrs = ["localhost", "127.0.0.1", ""];
240275
241276 // make sure we don't activate google analytics if the developer is
@@ -253,43 +288,43 @@
253288 {{/if}}
254289
255290 {{#if playground_line_numbers}}
256− <script type="text/javascript">
291+ <script>
257292 window.playground_line_numbers = true;
258293 </script>
259294 {{/if}}
260295
261296 {{#if playground_copyable}}
262− <script type="text/javascript">
297+ <script>
263298 window.playground_copyable = true;
264299 </script>
265300 {{/if}}
266301
267302 {{#if playground_js}}
268− <script src="{{ path_to_root }}ace.js" type="text/javascript" charset="utf-8"></script>
269− <script src="{{ path_to_root }}editor.js" type="text/javascript" charset="utf-8"></script>
270− <script src="{{ path_to_root }}mode-rust.js" type="text/javascript" charset="utf-8"></script>
271− <script src="{{ path_to_root }}theme-dawn.js" type="text/javascript" charset="utf-8"></script>
272− <script src="{{ path_to_root }}theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
303+ <script src="{{ path_to_root }}ace.js"></script>
304+ <script src="{{ path_to_root }}editor.js"></script>
305+ <script src="{{ path_to_root }}mode-rust.js"></script>
306+ <script src="{{ path_to_root }}theme-dawn.js"></script>
307+ <script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
273308 {{/if}}
274309
275310 {{#if search_js}}
276− <script src="{{ path_to_root }}elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
277− <script src="{{ path_to_root }}mark.min.js" type="text/javascript" charset="utf-8"></script>
278− <script src="{{ path_to_root }}searcher.js" type="text/javascript" charset="utf-8"></script>
311+ <script src="{{ path_to_root }}elasticlunr.min.js"></script>
312+ <script src="{{ path_to_root }}mark.min.js"></script>
313+ <script src="{{ path_to_root }}searcher.js"></script>
279314 {{/if}}
280315
281− <script src="{{ path_to_root }}clipboard.min.js" type="text/javascript" charset="utf-8"></script>
282− <script src="{{ path_to_root }}highlight.js" type="text/javascript" charset="utf-8"></script>
283− <script src="{{ path_to_root }}book.js" type="text/javascript" charset="utf-8"></script>
316+ <script src="{{ path_to_root }}clipboard.min.js"></script>
317+ <script src="{{ path_to_root }}highlight.js"></script>
318+ <script src="{{ path_to_root }}book.js"></script>
284319
285320 <!-- Custom JS scripts -->
286321 {{#each additional_js}}
287− <script type="text/javascript" src="{{ ../path_to_root }}{{this}}"></script>
322+ <script src="{{ ../path_to_root }}{{this}}"></script>
288323 {{/each}}
289324
290325 {{#if is_print}}
291326 {{#if mathjax_support}}
292− <script type="text/javascript">
327+ <script>
293328 window.addEventListener('load', function() {
294329 MathJax.Hub.Register.StartupHook('End', function() {
295330 window.setTimeout(window.print, 100);
@@ -297,7 +332,7 @@
297332 });
298333 </script>
299334 {{else}}
300− <script type="text/javascript">
335+ <script>
301336 window.addEventListener('load', function() {
302337 window.setTimeout(window.print, 100);
303338 });
@@ -305,5 +340,6 @@
305340 {{/if}}
306341 {{/if}}
307342
343+ </div>
308344 </body>
309345</html>
contrib/Helix.appdata.xmlnew file+108
@@ -0,0 +1,108 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<component type="desktop-application">
3+ <id>com.helix_editor.Helix</id>
4+ <metadata_license>CC0-1.0</metadata_license>
5+ <project_license>MPL-2.0</project_license>
6+ <name>Helix</name>
7+ <summary>A post-modern text editor</summary>
8+ <summary xml:lang="ar">مُحَرِّرُ نُصُوصٍ سَابِقٌ لِعَهدِه</summary>
9+
10+ <description>
11+ <p>
12+ Helix is a terminal-based text editor inspired by Kakoune / Neovim and written in Rust.
13+ </p>
14+ <ul>
15+ <li>Vim-like modal editing</li>
16+ <li>Multiple selections</li>
17+ <li>Built-in language server support</li>
18+ <li>Smart, incremental syntax highlighting and code editing via tree-sitter</li>
19+ </ul>
20+ </description>
21+ <description xml:lang="ar">
22+ <p>
23+ مُحَرِّرُ نُصُوصٍ يَعمَلُ فِي الطَّرَفِيَّة، مُستَلهَمٌ مِن Kakoune وَ Neovim وَمَكتُوبٌ بِلُغَةِ رَست البَرمَجِيَّة.
24+ </p>
25+ <ul>
26+ <li>تَحرِيرٌ وَضعِيٌّ شَبيهٌ بِـVim</li>
27+ <li>تَحدِيدَاتٌ لِلنَّصِ مُتَعَدِّدَة</li>
28+ <li>دَعْمٌ مُدمَجٌ لِخَوادِمِ اللُّغَات</li>
29+ <li>تَحرِيرُ التَّعلِيمَاتِ البَّرمَجِيَّةِ مَعَ تَمييزٍ لِلتَّركِيبِ النَّحُويِّ بِواسِطَةِ tree-sitter</li>
30+ </ul>
31+ </description>
32+
33+ <launchable type="desktop-id">Helix.desktop</launchable>
34+
35+ <screenshots>
36+ <screenshot type="default">
37+ <caption>Helix with default theme</caption>
38+ <image>https://github.com/helix-editor/helix/raw/d4565b4404cabc522bd60822abd374755581d751/screenshot.png</image>
39+ </screenshot>
40+ </screenshots>
41+
42+ <url type="homepage">https://helix-editor.com/</url>
43+ <url type="donation">https://opencollective.com/helix-editor</url>
44+ <url type="help">https://docs.helix-editor.com/</url>
45+ <url type="vcs-browser">https://github.com/helix-editor/helix</url>
46+ <url type="bugtracker">https://github.com/helix-editor/helix/issues</url>
47+
48+ <content_rating type="oars-1.1" />
49+
50+ <releases>
51+ <release version="23.10" date="2023-10-24">
52+ <url>https://helix-editor.com/news/release-23-10-highlights/</url>
53+ </release>
54+ <release version="23.05" date="2023-05-18">
55+ <url>https://github.com/helix-editor/helix/releases/tag/23.05</url>
56+ </release>
57+ <release version="23.03" date="2023-03-31">
58+ <url>https://helix-editor.com/news/release-23-03-highlights/</url>
59+ </release>
60+ <release version="22.12" date="2022-12-6">
61+ <url>https://helix-editor.com/news/release-22-12-highlights/</url>
62+ </release>
63+ <release version="22.08" date="2022-8-31">
64+ <url>https://helix-editor.com/news/release-22-08-highlights/</url>
65+ </release>
66+ <release version="22.05" date="2022-5-28">
67+ <url>https://helix-editor.com/news/release-22-05-highlights/</url>
68+ </release>
69+ <release version="22.03" date="2022-3-28">
70+ <url>https://helix-editor.com/news/release-22-03-highlights/</url>
71+ </release>
72+ </releases>
73+
74+ <requires>
75+ <control>keyboard</control>
76+ </requires>
77+
78+ <categories>
79+ <category>Utility</category>
80+ <category>TextEditor</category>
81+ </categories>
82+
83+ <keywords>
84+ <keyword>text</keyword>
85+ <keyword>editor</keyword>
86+ <keyword>development</keyword>
87+ <keyword>programming</keyword>
88+ </keywords>
89+
90+ <provides>
91+ <binary>hx</binary>
92+ <mediatype>text/english</mediatype>
93+ <mediatype>text/plain</mediatype>
94+ <mediatype>text/x-makefile</mediatype>
95+ <mediatype>text/x-c++hdr</mediatype>
96+ <mediatype>text/x-c++src</mediatype>
97+ <mediatype>text/x-chdr</mediatype>
98+ <mediatype>text/x-csrc</mediatype>
99+ <mediatype>text/x-java</mediatype>
100+ <mediatype>text/x-moc</mediatype>
101+ <mediatype>text/x-pascal</mediatype>
102+ <mediatype>text/x-tcl</mediatype>
103+ <mediatype>text/x-tex</mediatype>
104+ <mediatype>application/x-shellscript</mediatype>
105+ <mediatype>text/x-c</mediatype>
106+ <mediatype>text/x-c++</mediatype>
107+ </provides>
108+</component>
contrib/Helix.desktop+3−1
@@ -1,6 +1,7 @@
11[Desktop Entry]
22Name=Helix
33GenericName=Text Editor
4+GenericName[ar]=مُحَرِّرُ نُصُوص
45GenericName[de]=Texteditor
56GenericName[fr]=Éditeur de texte
67GenericName[ru]=Текстовый редактор
@@ -9,7 +10,7 @@ GenericName[tr]=Metin Düzenleyici
910Comment=Edit text files
1011Comment[af]=Redigeer tekslêers
1112Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ
12−Comment[ar]=حرّر ملفات نصية
13+Comment[ar]=مُحَرِّرُ مِلَفَّاتٍ نَصِّيَّة
1314Comment[az]=Mətn fayllarını redaktə edin
1415Comment[be]=Рэдагаваньне тэкставых файлаў
1516Comment[bg]=Редактиране на текстови файлове
@@ -79,6 +80,7 @@ Exec=hx %F
7980Terminal=true
8081Type=Application
8182Keywords=Text;editor;
83+Keywords[ar]=نص;نصوص;محرر;
8284Keywords[fr]=Texte;éditeur;
8385Keywords[ru]=текст;текстовый редактор;
8486Keywords[sr]=Текст;едитор;
contrib/completion/hx.bash+1−1
@@ -19,5 +19,5 @@ _hx() {
1919 COMPREPLY=($(compgen -fd -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config --log" -- $2))
2020 ;;
2121 esac
22−} && complete -F _hx hx
22+} && complete -o filenames -F _hx hx
2323
contrib/helix.png

Binary file not shown.

docs/CONTRIBUTING.md+21−5
@@ -8,7 +8,10 @@ Some suggestions to get started:
88- Help with packaging on various distributions needed!
99- To use print debugging to the [Helix log file][log-file], you must:
1010 * Print using `log::info!`, `warn!`, or `error!`. (`log::info!("helix!")`)
11− * Pass the appropriate verbosity level option for the desired log level. (`hx -v <file>` for info, more `v`s for higher severity inclusive)
11+ * Pass the appropriate verbosity level option for the desired log level. (`hx -v <file>` for info, more `v`s for higher verbosity)
12+ * Want to display the logs in a separate file instead of using the `:log-open` command in your compiled Helix editor? Start your debug version with `cargo run -- --log foo.log` and in a new terminal use `tail -f foo.log`
13+- Instead of running a release version of Helix, while developing you may want to run in debug mode with `cargo run` which is way faster to compile
14+- Looking for even faster compile times? Give a try to [mold](https://github.com/rui314/mold)
1215- If your preferred language is missing, integrating a tree-sitter grammar for
1316 it and defining syntax highlight queries for it is straight forward and
1417 doesn't require much knowledge of the internals.
@@ -26,11 +29,23 @@ files, run
2629cargo xtask docgen
2730```
2831
29−inside the project. We use [xtask][xtask] as an ad-hoc task runner and
30−thus do not require any dependencies other than `cargo` (You don't have
31−to `cargo install` anything either).
32+inside the project. We use [xtask][xtask] as an ad-hoc task runner.
3233
33−# Integration tests
34+To preview the book itself, install [mdbook][mdbook]. Then, run
35+
36+```shell
37+mdbook serve book
38+```
39+
40+and visit [http://localhost:3000](http://localhost:3000).
41+
42+# Testing
43+
44+## Unit tests/Documentation tests
45+
46+Run `cargo test --workspace` to run unit tests and documentation tests in all packages.
47+
48+## Integration tests
3449
3550Integration tests for helix-term can be run with `cargo integration-test`. Code
3651contributors are strongly encouraged to write integration tests for their code.
@@ -49,4 +64,5 @@ The current MSRV and future changes to the MSRV are listed in the [Firefox docum
4964[architecture.md]: ./architecture.md
5065[docs]: https://docs.helix-editor.com/
5166[xtask]: https://github.com/matklad/cargo-xtask
67+[mdbook]: https://rust-lang.github.io/mdBook/guide/installation.html
5268[helpers.rs]: ../helix-term/tests/test/helpers.rs
docs/releases.md+14−6
@@ -1,15 +1,23 @@
11## Checklist
22
33Helix releases are versioned in the Calendar Versioning scheme:
4−`YY.0M(.MICRO)`, for example `22.05` for May of 2022. In these instructions
5−we'll use `<tag>` as a placeholder for the tag being published.
4+`YY.0M(.MICRO)`, for example, `22.05` for May of 2022, or in a patch release,
5+`22.05.1`. In these instructions we'll use `<tag>` as a placeholder for the tag
6+being published.
67
78* Merge the changelog PR
9+* Add new `<release>` entry in `contrib/Helix.appdata.xml` with release information according to the [AppStream spec](https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Releases.html)
810* Tag and push
911 * `git tag -s -m "<tag>" -a <tag> && git push`
1012 * Make sure to switch to master and pull first
11−* Edit the `VERSION` file and change the date to the next planned release
12− * Releases are planned to happen every two months, so `22.05` would change to `22.07`
13+* Edit the `Cargo.toml` file and change the date in the `version` field to the next planned release
14+ * Due to Cargo having a strict requirement on SemVer with 3 or more version
15+ numbers, a `0` is required in the micro version; however, unless we are
16+ publishing a patch release after a major release, the `.0` is dropped in
17+ the user facing version.
18+ * Releases are planned to happen every two months, so `22.05.0` would change to `22.07.0`
19+ * If we are pushing a patch/bugfix release in the same month as the previous
20+ release, bump the micro version, e.g. `22.07.0` to `22.07.1`
1321* Wait for the Release CI to finish
1422 * It will automatically turn the git tag into a GitHub release when it uploads artifacts
1523* Edit the new release
@@ -29,7 +37,7 @@ we'll use `<tag>` as a placeholder for the tag being published.
2937
3038The changelog is currently created manually by reading through commits in the
3139log since the last release. GitHub's compare view is a nice way to approach
32−this. For example when creating the 22.07 release notes, this compare link
40+this. For example, when creating the 22.07 release notes, this compare link
3341may be used
3442
3543```
@@ -56,4 +64,4 @@ versions for convenience:
5664> release. For the full log, check out the git log.
5765
5866Typically, small changes like dependencies or documentation updates, refactors,
59−or meta changes like GitHub Actions work are left out.
67+or meta changes like GitHub Actions work are left out.
docs/vision.md+1−1
@@ -20,5 +20,5 @@ Vision statements are all well and good, but are also vague and subjective. Her
2020* **Built-in tools** for working with code bases efficiently. Most projects aren't a single file, and an editor should handle that as a first-class use case. In Helix's case, this means (among other things) a fuzzy-search file navigator and LSP support.
2121* **Edit anything** that comes up when coding, within reason. Whether it's a 200 MB XML file, a megabyte of minified javascript on a single line, or Japanese text encoded in ShiftJIS, you should be able to open it and edit it without problems. (Note: this doesn't mean handle every esoteric use case. Sometimes you do just need a specialized tool, and Helix isn't that.)
2222* **Configurable**, within reason. Although the defaults should be good, not everyone will agree on what "good" is. Within the bounds of Helix's core interaction models, it should be reasonably configurable so that it can be "good" for more people. This means, for example, custom key maps among other things.
23−* **Extensible**, within reason. Although we want Helix to be productive out-of-the-box, it's not practical or desirable to cram every useful feature and use case into the core editor. The basics should be built-in, but you should be able to extend it with additional functionality as needed. Right now we're thinking Wasm-based plugins.
23+* **Extensible**, within reason. Although we want Helix to be productive out-of-the-box, it's not practical or desirable to cram every useful feature and use case into the core editor. The basics should be built-in, but you should be able to extend it with additional functionality as needed.
2424* **Clean code base.** Sometimes other factors (e.g. significant performance gains, important features, correctness, etc.) will trump strict readability, but we nevertheless want to keep the code base straightforward and easy to understand to the extent we can.
flake.lock+39−140
@@ -1,113 +1,35 @@
11{
22 "nodes": {
3− "all-cabal-json": {
4− "flake": false,
5− "locked": {
6− "lastModified": 1665552503,
7− "narHash": "sha256-r14RmRSwzv5c+bWKUDaze6pXM7nOsiz1H8nvFHJvufc=",
8− "owner": "nix-community",
9− "repo": "all-cabal-json",
10− "rev": "d7c0434eebffb305071404edcf9d5cd99703878e",
11− "type": "github"
12− },
13− "original": {
14− "owner": "nix-community",
15− "ref": "hackage",
16− "repo": "all-cabal-json",
17− "type": "github"
18− }
19− },
203 "crane": {
21− "flake": false,
22− "locked": {
23− "lastModified": 1661875961,
24− "narHash": "sha256-f1h/2c6Teeu1ofAHWzrS8TwBPcnN+EEu+z1sRVmMQTk=",
25− "owner": "ipetkov",
26− "repo": "crane",
27− "rev": "d9f394e4e20e97c2a60c3ad82c2b6ef99be19e24",
28− "type": "github"
29− },
30− "original": {
31− "owner": "ipetkov",
32− "repo": "crane",
33− "type": "github"
34− }
35− },
36− "devshell": {
37− "flake": false,
38− "locked": {
39− "lastModified": 1667210711,
40− "narHash": "sha256-IoErjXZAkzYWHEpQqwu/DeRNJGFdR7X2OGbkhMqMrpw=",
41− "owner": "numtide",
42− "repo": "devshell",
43− "rev": "96a9dd12b8a447840cc246e17a47b81a4268bba7",
44− "type": "github"
45− },
46− "original": {
47− "owner": "numtide",
48− "repo": "devshell",
49− "type": "github"
50− }
51− },
52− "dream2nix": {
534 "inputs": {
54− "alejandra": [
55− "nci",
56− "nixpkgs"
57− ],
58− "all-cabal-json": "all-cabal-json",
59− "crane": "crane",
60− "devshell": [
61− "nci",
62− "devshell"
63− ],
64− "flake-utils-pre-commit": [
65− "nci",
66− "nixpkgs"
67− ],
68− "ghc-utils": "ghc-utils",
69− "gomod2nix": [
70− "nci",
71− "nixpkgs"
72− ],
73− "mach-nix": [
74− "nci",
75− "nixpkgs"
76− ],
775 "nixpkgs": [
78− "nci",
79− "nixpkgs"
80− ],
81− "poetry2nix": [
82− "nci",
83− "nixpkgs"
84− ],
85− "pre-commit-hooks": [
86− "nci",
876 "nixpkgs"
887 ]
898 },
909 "locked": {
91− "lastModified": 1667429039,
92− "narHash": "sha256-Lu6da25JioHzerkLHAHSO9suCQFzJ/XBjkcGCIbasLM=",
93− "owner": "nix-community",
94− "repo": "dream2nix",
95− "rev": "5252794e58eedb02d607fa3187ffead7becc81b0",
10+ "lastModified": 1701025348,
11+ "narHash": "sha256-42GHmYH+GF7VjwGSt+fVT1CQuNpGanJbNgVHTAZppUM=",
12+ "owner": "ipetkov",
13+ "repo": "crane",
14+ "rev": "42afaeb1a0325194a7cdb526332d2cb92fddd07b",
9615 "type": "github"
9716 },
9817 "original": {
99− "owner": "nix-community",
100− "repo": "dream2nix",
18+ "owner": "ipetkov",
19+ "repo": "crane",
10120 "type": "github"
10221 }
10322 },
10423 "flake-utils": {
24+ "inputs": {
25+ "systems": "systems"
26+ },
10527 "locked": {
106− "lastModified": 1659877975,
107− "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
28+ "lastModified": 1694529238,
29+ "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
10830 "owner": "numtide",
10931 "repo": "flake-utils",
110− "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
32+ "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
11133 "type": "github"
11234 },
11335 "original": {
@@ -116,54 +38,13 @@
11638 "type": "github"
11739 }
11840 },
119− "ghc-utils": {
120− "flake": false,
121− "locked": {
122− "lastModified": 1662774800,
123− "narHash": "sha256-1Rd2eohGUw/s1tfvkepeYpg8kCEXiIot0RijapUjAkE=",
124− "ref": "refs/heads/master",
125− "rev": "bb3a2d3dc52ff0253fb9c2812bd7aa2da03e0fea",
126− "revCount": 1072,
127− "type": "git",
128− "url": "https://gitlab.haskell.org/bgamari/ghc-utils"
129− },
130− "original": {
131− "type": "git",
132− "url": "https://gitlab.haskell.org/bgamari/ghc-utils"
133− }
134− },
135− "nci": {
136− "inputs": {
137− "devshell": "devshell",
138− "dream2nix": "dream2nix",
139− "nixpkgs": [
140− "nixpkgs"
141− ],
142− "rust-overlay": [
143− "rust-overlay"
144− ]
145− },
146− "locked": {
147− "lastModified": 1667542401,
148− "narHash": "sha256-mdWjP5tjSf8n6FAtpSgL23kX4+eWBwLrSYo9iY3mA8Q=",
149− "owner": "yusdacra",
150− "repo": "nix-cargo-integration",
151− "rev": "cd5e5cbd81c80dc219455dd3b1e0ddb55fae51ec",
152− "type": "github"
153− },
154− "original": {
155− "owner": "yusdacra",
156− "repo": "nix-cargo-integration",
157− "type": "github"
158− }
159− },
16041 "nixpkgs": {
16142 "locked": {
162− "lastModified": 1667482890,
163− "narHash": "sha256-pua0jp87iwN7NBY5/ypx0s9L9CG49Ju/NI4wGwurHc4=",
43+ "lastModified": 1700794826,
44+ "narHash": "sha256-RyJTnTNKhO0yqRpDISk03I/4A67/dp96YRxc86YOPgU=",
16445 "owner": "nixos",
16546 "repo": "nixpkgs",
166− "rev": "a2a777538d971c6b01c6e54af89ddd6567c055e8",
47+ "rev": "5a09cb4b393d58f9ed0d9ca1555016a8543c2ac8",
16748 "type": "github"
16849 },
16950 "original": {
@@ -175,24 +56,27 @@
17556 },
17657 "root": {
17758 "inputs": {
178− "nci": "nci",
59+ "crane": "crane",
60+ "flake-utils": "flake-utils",
17961 "nixpkgs": "nixpkgs",
18062 "rust-overlay": "rust-overlay"
18163 }
18264 },
18365 "rust-overlay": {
18466 "inputs": {
185− "flake-utils": "flake-utils",
67+ "flake-utils": [
68+ "flake-utils"
69+ ],
18670 "nixpkgs": [
18771 "nixpkgs"
18872 ]
18973 },
19074 "locked": {
191− "lastModified": 1667487142,
192− "narHash": "sha256-bVuzLs1ZVggJAbJmEDVO9G6p8BH3HRaolK70KXvnWnU=",
75+ "lastModified": 1701137803,
76+ "narHash": "sha256-0LcPAdql5IhQSUXJx3Zna0dYTgdIoYO7zUrsKgiBd04=",
19377 "owner": "oxalica",
19478 "repo": "rust-overlay",
195− "rev": "cf668f737ac986c0a89e83b6b2e3c5ddbd8cf33b",
79+ "rev": "9dd940c967502f844eacea52a61e9596268d4f70",
19680 "type": "github"
19781 },
19882 "original": {
@@ -200,6 +84,21 @@
20084 "repo": "rust-overlay",
20185 "type": "github"
20286 }
87+ },
88+ "systems": {
89+ "locked": {
90+ "lastModified": 1681028828,
91+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
92+ "owner": "nix-systems",
93+ "repo": "default",
94+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
95+ "type": "github"
96+ },
97+ "original": {
98+ "owner": "nix-systems",
99+ "repo": "default",
100+ "type": "github"
101+ }
203102 }
204103 },
205104 "root": "root",
flake.nix+166−145
@@ -3,173 +3,194 @@
33
44 inputs = {
55 nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
6+ flake-utils.url = "github:numtide/flake-utils";
67 rust-overlay = {
78 url = "github:oxalica/rust-overlay";
8− inputs.nixpkgs.follows = "nixpkgs";
9+ inputs = {
10+ nixpkgs.follows = "nixpkgs";
11+ flake-utils.follows = "flake-utils";
12+ };
913 };
10− nci = {
11− url = "github:yusdacra/nix-cargo-integration";
14+ crane = {
15+ url = "github:ipetkov/crane";
1216 inputs.nixpkgs.follows = "nixpkgs";
13− inputs.rust-overlay.follows = "rust-overlay";
1417 };
1518 };
1619
1720 outputs = {
1821 self,
1922 nixpkgs,
20− nci,
23+ crane,
24+ flake-utils,
25+ rust-overlay,
2126 ...
22− }: let
23− lib = nixpkgs.lib;
24− ncl = nci.lib.nci-lib;
25− mkRootPath = rel:
26− builtins.path {
27− path = "${toString ./.}/${rel}";
28− name = rel;
27+ }:
28+ flake-utils.lib.eachDefaultSystem (system: let
29+ pkgs = import nixpkgs {
30+ inherit system;
31+ overlays = [(import rust-overlay)];
2932 };
30− filteredSource = let
31− pathsToIgnore = [
32− ".envrc"
33− ".ignore"
34− ".github"
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− # split the nix store path into its components
50− components = lib.splitString "/" path;
51− # drop off the `/nix/hash-source` section from the path
52− relPathComponents = lib.drop 4 components;
53− # reassemble the path components
54− relPath = lib.concatStringsSep "/" relPathComponents;
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;
5570 in
56− lib.all (p: ! (lib.hasPrefix p relPath)) pathsToIgnore;
57− in
58− builtins.path {
59− name = "helix-source";
60− path = toString ./.;
61− # filter out unnecessary paths
62− filter = ignorePaths;
63− };
64− outputs = nci.lib.makeOutputs {
65− root = ./.;
66− config = common: {
67− outputs = {
68− # rename helix-term to helix since it's our main package
69− rename = {"helix-term" = "helix";};
70− # Set default app to hx (binary is from helix-term release build)
71− # Set default package to helix-term release build
72− defaults = {
73− app = "hx";
74− package = "helix";
75− };
71+ builtins.path {
72+ name = "helix-source";
73+ path = toString ./.;
74+ # filter out unnecessary paths
75+ filter = ignorePaths;
7676 };
77− cCompiler.package = with common.pkgs;
78− if stdenv.isLinux
79− then gcc
80− else clang;
81− shell = {
82− packages = with common.pkgs;
83− [lld_13 cargo-flamegraph rust-analyzer]
84− ++ (lib.optional (stdenv.isx86_64 && stdenv.isLinux) cargo-tarpaulin)
85− ++ (lib.optional stdenv.isLinux lldb);
86− env = [
87− {
88− name = "HELIX_RUNTIME";
89− eval = "$PWD/runtime";
90− }
91− {
92− name = "RUST_BACKTRACE";
93− value = "1";
94− }
95− {
96− name = "RUSTFLAGS";
97− value =
98− if common.pkgs.stdenv.isLinux
99− then "-C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment"
100− else "";
101− }
102− ];
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 {};
91+ passthru =
92+ (old.passthru or {})
93+ // {
94+ unwrapped = old;
95+ };
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;
111+ };
103112 };
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;
104151 };
105− pkgConfig = common: {
106− helix-term = {
107− # Wrap helix with runtime
108− wrapper = _: old: let
109− inherit (common) pkgs;
110− makeOverridableHelix = old: config: let
111− grammars = pkgs.callPackage ./grammars.nix config;
112− runtimeDir = pkgs.runCommand "helix-runtime" {} ''
113− mkdir -p $out
114− ln -s ${mkRootPath "runtime"}/* $out
115− rm -r $out/grammars
116− ln -s ${grammars} $out/grammars
117− '';
118− helix-wrapped =
119− common.internal.pkgsSet.utils.wrapDerivation old
120− {
121− nativeBuildInputs = [pkgs.makeWrapper];
122− makeWrapperArgs = config.makeWrapperArgs or [];
123− }
124− ''
125− rm -rf $out/bin
126− mkdir -p $out/bin
127− ln -sf ${old}/bin/* $out/bin/
128− wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
129− '';
130− in
131− helix-wrapped
132− // {override = makeOverridableHelix old;};
133− in
134− makeOverridableHelix old {};
135− overrides.fix-build.overrideAttrs = prev: {
136− src = filteredSource;
137152
138− # disable fetching and building of tree-sitter grammars in the helix-term build.rs
139− HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
153+ checks = {
154+ # Build the crate itself
155+ inherit (self.packages.${system}) helix;
140156
141− buildInputs = ncl.addBuildInputs prev [common.config.cCompiler.package.cc.lib];
157+ clippy = craneLibMSRV.cargoClippy (commonArgs
158+ // {
159+ inherit cargoArtifacts;
160+ cargoClippyExtraArgs = "--all-targets -- --deny warnings";
161+ });
142162
143− # link languages and theme toml files since helix-term expects them (for tests)
144− preConfigure = ''
145− ${prev.preConfigure or ""}
146− ${
147− lib.concatMapStringsSep
148− "\n"
149− (path: "ln -sf ${mkRootPath path} ..")
150− ["languages.toml" "theme.toml" "base16_theme.toml"]
151− }
152− '';
163+ fmt = craneLibMSRV.cargoFmt commonArgs;
153164
154− meta.mainProgram = "hx";
155− };
156− };
165+ doc = craneLibMSRV.cargoDoc (commonArgs
166+ // {
167+ inherit cargoArtifacts;
168+ });
169+
170+ test = craneLibMSRV.cargoTest (commonArgs
171+ // {
172+ inherit cargoArtifacts;
173+ });
157174 };
158− };
159− in
160− outputs
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+ })
161190 // {
162− packages =
163− lib.mapAttrs
164− (
165− system: packages:
166− packages
167− // {
168− helix-unwrapped = packages.helix.passthru.unwrapped;
169− helix-unwrapped-debug = packages.helix-debug.passthru.unwrapped;
170− }
171− )
172− outputs.packages;
191+ overlays.default = final: prev: {
192+ inherit (self.packages.${final.system}) helix;
193+ };
173194 };
174195
175196 nixConfig = {
grammars.nix+24−21
@@ -5,6 +5,7 @@
55 runCommand,
66 yj,
77 includeGrammarIf ? _: true,
8+ grammarOverlays ? [],
89 ...
910}: let
1011 # HACK: nix < 2.6 has a bug in the toml parser, so we convert to JSON
@@ -48,22 +49,22 @@
4849 then sourceGitHub
4950 else sourceGit;
5051 in
51− stdenv.mkDerivation rec {
52+ stdenv.mkDerivation {
5253 # see https://github.com/NixOS/nixpkgs/blob/fbdd1a7c0bc29af5325e0d7dd70e804a972eb465/pkgs/development/tools/parsing/tree-sitter/grammar.nix
5354
5455 pname = "helix-tree-sitter-${grammar.name}";
5556 version = grammar.source.rev;
5657
57− src =
58− if builtins.hasAttr "subpath" grammar.source
59− then "${source}/${grammar.source.subpath}"
60− else source;
58+ src = source;
59+ sourceRoot = if builtins.hasAttr "subpath" grammar.source then
60+ "source/${grammar.source.subpath}"
61+ else
62+ "source";
6163
62− dontUnpack = true;
6364 dontConfigure = true;
6465
6566 FLAGS = [
66− "-I${src}/src"
67+ "-Isrc"
6768 "-g"
6869 "-O3"
6970 "-fPIC"
@@ -76,13 +77,13 @@
7677 buildPhase = ''
7778 runHook preBuild
7879
79− if [[ -e "$src/src/scanner.cc" ]]; then
80− $CXX -c "$src/src/scanner.cc" -o scanner.o $FLAGS
81− elif [[ -e "$src/src/scanner.c" ]]; then
82− $CC -c "$src/src/scanner.c" -o scanner.o $FLAGS
80+ if [[ -e src/scanner.cc ]]; then
81+ $CXX -c src/scanner.cc -o scanner.o $FLAGS
82+ elif [[ -e src/scanner.c ]]; then
83+ $CC -c src/scanner.c -o scanner.o $FLAGS
8384 fi
8485
85− $CC -c "$src/src/parser.c" -o parser.o $FLAGS
86+ $CC -c src/parser.c -o parser.o $FLAGS
8687 $CXX -shared -o $NAME.so *.o
8788
8889 ls -al
@@ -105,15 +106,17 @@
105106 '';
106107 };
107108 grammarsToBuild = builtins.filter includeGrammarIf gitGrammars;
108− builtGrammars =
109− builtins.map (grammar: {
110− inherit (grammar) name;
111− artifact = buildGrammar grammar;
112− })
113− grammarsToBuild;
114− grammarLinks =
115− builtins.map (grammar: "ln -s ${grammar.artifact}/${grammar.name}.so $out/${grammar.name}.so")
116− builtGrammars;
109+ builtGrammars = builtins.map (grammar: {
110+ inherit (grammar) name;
111+ value = buildGrammar grammar;
112+ }) grammarsToBuild;
113+ extensibleGrammars =
114+ lib.makeExtensible (self: builtins.listToAttrs builtGrammars);
115+ overlayedGrammars = lib.pipe extensibleGrammars
116+ (builtins.map (overlay: grammar: grammar.extend overlay) grammarOverlays);
117+ grammarLinks = lib.mapAttrsToList
118+ (name: artifact: "ln -s ${artifact}/${name}.so $out/${name}.so")
119+ (lib.filterAttrs (n: v: lib.isDerivation v) overlayedGrammars);
117120in
118121 runCommand "consolidated-helix-grammars" {} ''
119122 mkdir -p $out
helix-core/Cargo.toml+24−16
@@ -1,49 +1,57 @@
11[package]
22name = "helix-core"
3−version = "0.6.0"
4−authors = ["Blaž Hrastnik <blaz@mxxn.io>"]
5−edition = "2021"
6−license = "MPL-2.0"
73description = "Helix editor core editing primitives"
8−categories = ["editor"]
9−repository = "https://github.com/helix-editor/helix"
10−homepage = "https://helix-editor.com"
114include = ["src/**/*", "README.md"]
5+version.workspace = true
6+authors.workspace = true
7+edition.workspace = true
8+license.workspace = true
9+rust-version.workspace = true
10+categories.workspace = true
11+repository.workspace = true
12+homepage.workspace = true
1213
1314[features]
1415unicode-lines = ["ropey/unicode_lines"]
1516integration = []
1617
1718[dependencies]
18−helix-loader = { version = "0.6", path = "../helix-loader" }
19+helix-loader = { path = "../helix-loader" }
1920
20−ropey = { version = "1.5", default-features = false, features = ["simd"] }
21−smallvec = "1.10"
21+ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
22+smallvec = "1.11"
2223smartstring = "1.0.1"
2324unicode-segmentation = "1.10"
2425unicode-width = "0.1"
2526unicode-general-category = "0.6"
2627# slab = "0.4.2"
2728slotmap = "1.0"
28−tree-sitter = "0.20"
29−once_cell = "1.16"
29+tree-sitter.workspace = true
30+once_cell = "1.19"
3031arc-swap = "1"
3132regex = "1"
32−bitflags = "1.3"
33+bitflags = "2.4"
34+ahash = "0.8.6"
35+hashbrown = { version = "0.14.3", features = ["raw"] }
36+dunce = "1.0"
3337
3438log = "0.4"
3539serde = { version = "1.0", features = ["derive"] }
3640serde_json = "1.0"
37−toml = "0.5"
41+toml = "0.7"
3842
39−similar = "2.2"
43+imara-diff = "0.1.0"
4044
4145encoding_rs = "0.8"
4246
4347chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }
4448
45−etcetera = "0.4"
49+etcetera = "0.8"
4650textwrap = "0.16.0"
4751
52+nucleo.workspace = true
53+parking_lot = "0.12"
54+
4855[dev-dependencies]
4956quickcheck = { version = "1", default-features = false }
57+indoc = "2.0.4"
helix-core/src/auto_pairs.rs+1−1
@@ -17,7 +17,7 @@ pub const DEFAULT_PAIRS: &[(char, char)] = &[
1717];
1818
1919/// The type that represents the collection of auto pairs,
20−/// keyed by the opener.
20+/// keyed by both opener and closer.
2121#[derive(Debug, Clone)]
2222pub struct AutoPairs(HashMap<char, Pair>);
2323
helix-core/src/comment.rs+34−25
@@ -45,7 +45,7 @@ fn find_line_comment(
4545
4646 // determine margin of 0 or 1 for uncommenting; if any comment token is not followed by a space,
4747 // a margin of 0 is used for all lines.
48− if matches!(line_slice.get_char(pos + token_len), Some(c) if c != ' ') {
48+ if !matches!(line_slice.get_char(pos + token_len), Some(c) if c == ' ') {
4949 margin = 0;
5050 }
5151
@@ -68,7 +68,7 @@ pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&st
6868 let mut min_next_line = 0;
6969 for selection in selection {
7070 let (start, end) = selection.line_range(text);
71− let start = start.max(min_next_line).min(text.len_lines());
71+ let start = start.clamp(min_next_line, text.len_lines());
7272 let end = (end + 1).min(text.len_lines());
7373
7474 lines.extend(start..end);
@@ -100,43 +100,52 @@ mod test {
100100
101101 #[test]
102102 fn test_find_line_comment() {
103− use crate::State;
104−
105103 // four lines, two space indented, except for line 1 which is blank.
106− let doc = Rope::from(" 1\n\n 2\n 3");
107−
108− let mut state = State::new(doc);
104+ let mut doc = Rope::from(" 1\n\n 2\n 3");
109105 // select whole document
110− state.selection = Selection::single(0, state.doc.len_chars() - 1);
106+ let mut selection = Selection::single(0, doc.len_chars() - 1);
111107
112− let text = state.doc.slice(..);
108+ let text = doc.slice(..);
113109
114110 let res = find_line_comment("//", text, 0..3);
115− // (commented = true, to_change = [line 0, line 2], min = col 2, margin = 1)
116− assert_eq!(res, (false, vec![0, 2], 2, 1));
111+ // (commented = true, to_change = [line 0, line 2], min = col 2, margin = 0)
112+ assert_eq!(res, (false, vec![0, 2], 2, 0));
117113
118114 // comment
119− let transaction = toggle_line_comments(&state.doc, &state.selection, None);
120− transaction.apply(&mut state.doc);
121− state.selection = state.selection.map(transaction.changes());
115+ let transaction = toggle_line_comments(&doc, &selection, None);
116+ transaction.apply(&mut doc);
117+ selection = selection.map(transaction.changes());
122118
123− assert_eq!(state.doc, " // 1\n\n // 2\n // 3");
119+ assert_eq!(doc, " // 1\n\n // 2\n // 3");
124120
125121 // uncomment
126− let transaction = toggle_line_comments(&state.doc, &state.selection, None);
127− transaction.apply(&mut state.doc);
128− state.selection = state.selection.map(transaction.changes());
129− assert_eq!(state.doc, " 1\n\n 2\n 3");
122+ let transaction = toggle_line_comments(&doc, &selection, None);
123+ transaction.apply(&mut doc);
124+ selection = selection.map(transaction.changes());
125+ assert_eq!(doc, " 1\n\n 2\n 3");
126+ assert!(selection.len() == 1); // to ignore the selection unused warning
130127
131128 // 0 margin comments
132− state.doc = Rope::from(" //1\n\n //2\n //3");
129+ doc = Rope::from(" //1\n\n //2\n //3");
130+ // reset the selection.
131+ selection = Selection::single(0, doc.len_chars() - 1);
132+
133+ let transaction = toggle_line_comments(&doc, &selection, None);
134+ transaction.apply(&mut doc);
135+ selection = selection.map(transaction.changes());
136+ assert_eq!(doc, " 1\n\n 2\n 3");
137+ assert!(selection.len() == 1); // to ignore the selection unused warning
138+
139+ // 0 margin comments, with no space
140+ doc = Rope::from("//");
133141 // reset the selection.
134− state.selection = Selection::single(0, state.doc.len_chars() - 1);
142+ selection = Selection::single(0, doc.len_chars() - 1);
135143
136− let transaction = toggle_line_comments(&state.doc, &state.selection, None);
137− transaction.apply(&mut state.doc);
138− state.selection = state.selection.map(transaction.changes());
139− assert_eq!(state.doc, " 1\n\n 2\n 3");
144+ let transaction = toggle_line_comments(&doc, &selection, None);
145+ transaction.apply(&mut doc);
146+ selection = selection.map(transaction.changes());
147+ assert_eq!(doc, "");
148+ assert!(selection.len() == 1); // to ignore the selection unused warning
140149
141150 // TODO: account for uncommenting with uneven comment indentation
142151 }
helix-core/src/diagnostic.rs+7−1
@@ -35,14 +35,20 @@ pub enum DiagnosticTag {
3535 Deprecated,
3636}
3737
38−/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html)
38+/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.94.0/lsp_types/struct.Diagnostic.html)
3939#[derive(Debug, Clone)]
4040pub struct Diagnostic {
4141 pub range: Range,
42+ // whether this diagnostic ends at the end of(or inside) a word
43+ pub ends_at_word: bool,
44+ pub starts_at_word: bool,
45+ pub zero_width: bool,
4246 pub line: usize,
4347 pub message: String,
4448 pub severity: Option<Severity>,
4549 pub code: Option<NumberOrString>,
50+ pub language_server_id: usize,
4651 pub tags: Vec<DiagnosticTag>,
4752 pub source: Option<String>,
53+ pub data: Option<serde_json::Value>,
4854}
helix-core/src/diff.rs+202−45
@@ -1,58 +1,194 @@
1−use crate::{Rope, Transaction};
1+use std::ops::Range;
2+use std::time::Instant;
23
3−/// Compares `old` and `new` to generate a [`Transaction`] describing
4−/// the steps required to get from `old` to `new`.
5−pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction {
6− // `similar` only works on contiguous data, so a `Rope` has
7− // to be temporarily converted into a `String`.
8− let old_converted = old.to_string();
9− let new_converted = new.to_string();
10−
11− // A timeout is set so after 1 seconds, the algorithm will start
12− // approximating. This is especially important for big `Rope`s or
13− // `Rope`s that are extremely dissimilar to each other.
14− let mut config = similar::TextDiff::configure();
15− config.timeout(std::time::Duration::from_secs(1));
16−
17− let diff = config.diff_chars(&old_converted, &new_converted);
18−
19− // The current position of the change needs to be tracked to
20− // construct the `Change`s.
21− let mut pos = 0;
22− Transaction::change(
23− old,
24− diff.ops()
4+use imara_diff::intern::InternedInput;
5+use imara_diff::Algorithm;
6+use ropey::RopeSlice;
7+
8+use crate::{ChangeSet, Rope, Tendril, Transaction};
9+
10+/// A `imara_diff::Sink` that builds a `ChangeSet` for a character diff of a hunk
11+struct CharChangeSetBuilder<'a> {
12+ res: &'a mut ChangeSet,
13+ hunk: &'a InternedInput<char>,
14+ pos: u32,
15+}
16+
17+impl imara_diff::Sink for CharChangeSetBuilder<'_> {
18+ type Out = ();
19+ fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
20+ self.res.retain((before.start - self.pos) as usize);
21+ self.res.delete(before.len());
22+ self.pos = before.end;
23+
24+ let res = self.hunk.after[after.start as usize..after.end as usize]
25+ .iter()
26+ .map(|&token| self.hunk.interner[token])
27+ .collect();
28+
29+ self.res.insert(res);
30+ }
31+
32+ fn finish(self) -> Self::Out {
33+ self.res.retain(self.hunk.before.len() - self.pos as usize);
34+ }
35+}
36+
37+struct LineChangeSetBuilder<'a> {
38+ res: ChangeSet,
39+ after: RopeSlice<'a>,
40+ file: &'a InternedInput<RopeSlice<'a>>,
41+ current_hunk: InternedInput<char>,
42+ pos: u32,
43+}
44+
45+impl imara_diff::Sink for LineChangeSetBuilder<'_> {
46+ type Out = ChangeSet;
47+
48+ fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
49+ let len = self.file.before[self.pos as usize..before.start as usize]
2550 .iter()
26− .map(|op| op.as_tag_tuple())
27− .filter_map(|(tag, old_range, new_range)| {
28− // `old_pos..pos` is equivalent to `start..end` for where
29− // the change should be applied.
30− let old_pos = pos;
31− pos += old_range.end - old_range.start;
32−
33− match tag {
34− // Semantically, inserts and replacements are the same thing.
35− similar::DiffTag::Insert | similar::DiffTag::Replace => {
36− // This is the text from the `new` rope that should be
37− // inserted into `old`.
38− let text: &str = {
39− let start = new.char_to_byte(new_range.start);
40− let end = new.char_to_byte(new_range.end);
41− &new_converted[start..end]
42− };
43− Some((old_pos, pos, Some(text.into())))
51+ .map(|&it| self.file.interner[it].len_chars())
52+ .sum();
53+ self.res.retain(len);
54+ self.pos = before.end;
55+
56+ // do not perform diffs on large hunks
57+ let len_before = before.end - before.start;
58+ let len_after = after.end - after.start;
59+
60+ // Pure insertions/removals do not require a character diff.
61+ // Very large changes are ignored because their character diff is expensive to compute
62+ // TODO adjust heuristic to detect large changes?
63+ if len_before == 0
64+ || len_after == 0
65+ || len_after > 5 * len_before
66+ || 5 * len_after < len_before && len_before > 10
67+ || len_before + len_after > 200
68+ {
69+ let remove = self.file.before[before.start as usize..before.end as usize]
70+ .iter()
71+ .map(|&it| self.file.interner[it].len_chars())
72+ .sum();
73+ self.res.delete(remove);
74+ let mut fragment = Tendril::new();
75+ if len_after > 500 {
76+ // copying a rope line by line is slower then copying the entire
77+ // rope. Use to_string for very large changes instead..
78+ if self.file.after.len() == after.end as usize {
79+ if after.start == 0 {
80+ fragment = self.after.to_string().into();
81+ } else {
82+ let start = self.after.line_to_char(after.start as usize);
83+ fragment = self.after.slice(start..).to_string().into();
4484 }
45− similar::DiffTag::Delete => Some((old_pos, pos, None)),
46− similar::DiffTag::Equal => None,
85+ } else if after.start == 0 {
86+ let end = self.after.line_to_char(after.end as usize);
87+ fragment = self.after.slice(..end).to_string().into();
88+ } else {
89+ let start = self.after.line_to_char(after.start as usize);
90+ let end = self.after.line_to_char(after.end as usize);
91+ fragment = self.after.slice(start..end).to_string().into();
4792 }
48− }),
49− )
93+ } else {
94+ for &line in &self.file.after[after.start as usize..after.end as usize] {
95+ for chunk in self.file.interner[line].chunks() {
96+ fragment.push_str(chunk)
97+ }
98+ }
99+ };
100+ self.res.insert(fragment);
101+ } else {
102+ // for reasonably small hunks, generating a ChangeSet from char diff can save memory
103+ // TODO use a tokenizer (word diff?) for improved performance
104+ let hunk_before = self.file.before[before.start as usize..before.end as usize]
105+ .iter()
106+ .flat_map(|&it| self.file.interner[it].chars());
107+ let hunk_after = self.file.after[after.start as usize..after.end as usize]
108+ .iter()
109+ .flat_map(|&it| self.file.interner[it].chars());
110+ self.current_hunk.update_before(hunk_before);
111+ self.current_hunk.update_after(hunk_after);
112+
113+ // the histogram heuristic does not work as well
114+ // for characters because the same characters often reoccur
115+ // use myer diff instead
116+ imara_diff::diff(
117+ Algorithm::Myers,
118+ &self.current_hunk,
119+ CharChangeSetBuilder {
120+ res: &mut self.res,
121+ hunk: &self.current_hunk,
122+ pos: 0,
123+ },
124+ );
125+
126+ self.current_hunk.clear();
127+ }
128+ }
129+
130+ fn finish(mut self) -> Self::Out {
131+ let len = self.file.before[self.pos as usize..]
132+ .iter()
133+ .map(|&it| self.file.interner[it].len_chars())
134+ .sum();
135+
136+ self.res.retain(len);
137+ self.res
138+ }
139+}
140+
141+struct RopeLines<'a>(RopeSlice<'a>);
142+
143+impl<'a> imara_diff::intern::TokenSource for RopeLines<'a> {
144+ type Token = RopeSlice<'a>;
145+ type Tokenizer = ropey::iter::Lines<'a>;
146+
147+ fn tokenize(&self) -> Self::Tokenizer {
148+ self.0.lines()
149+ }
150+
151+ fn estimate_tokens(&self) -> u32 {
152+ // we can provide a perfect estimate which is very nice for performance
153+ self.0.len_lines() as u32
154+ }
155+}
156+
157+/// Compares `old` and `new` to generate a [`Transaction`] describing
158+/// the steps required to get from `old` to `new`.
159+pub fn compare_ropes(before: &Rope, after: &Rope) -> Transaction {
160+ let start = Instant::now();
161+ let res = ChangeSet::with_capacity(32);
162+ let after = after.slice(..);
163+ let file = InternedInput::new(RopeLines(before.slice(..)), RopeLines(after));
164+ let builder = LineChangeSetBuilder {
165+ res,
166+ file: &file,
167+ after,
168+ pos: 0,
169+ current_hunk: InternedInput::default(),
170+ };
171+
172+ let res = imara_diff::diff(Algorithm::Histogram, &file, builder).into();
173+
174+ log::debug!(
175+ "rope diff took {}s",
176+ Instant::now().duration_since(start).as_secs_f64()
177+ );
178+ res
50179}
51180
52181#[cfg(test)]
53182mod tests {
54183 use super::*;
55184
185+ fn test_identity(a: &str, b: &str) {
186+ let mut old = Rope::from(a);
187+ let new = Rope::from(b);
188+ compare_ropes(&old, &new).apply(&mut old);
189+ assert_eq!(old, new);
190+ }
191+
56192 quickcheck::quickcheck! {
57193 fn test_compare_ropes(a: String, b: String) -> bool {
58194 let mut old = Rope::from(a);
@@ -61,4 +197,25 @@ mod tests {
61197 old == new
62198 }
63199 }
200+
201+ #[test]
202+ fn equal_files() {
203+ test_identity("foo", "foo");
204+ }
205+
206+ #[test]
207+ fn trailing_newline() {
208+ test_identity("foo\n", "foo");
209+ test_identity("foo", "foo\n");
210+ }
211+
212+ #[test]
213+ fn new_file() {
214+ test_identity("", "foo");
215+ }
216+
217+ #[test]
218+ fn deleted_file() {
219+ test_identity("foo", "");
220+ }
64221}
helix-core/src/doc_formatter.rsnew file+384
@@ -0,0 +1,384 @@
1+//! The `DocumentFormatter` forms the bridge between the raw document text
2+//! and onscreen positioning. It yields the text graphemes as an iterator
3+//! and traverses (part) of the document text. During that traversal it
4+//! handles grapheme detection, softwrapping and annotations.
5+//! It yields `FormattedGrapheme`s and their corresponding visual coordinates.
6+//!
7+//! As both virtual text and softwrapping can insert additional lines into the document
8+//! it is generally not possible to find the start of the previous visual line.
9+//! Instead the `DocumentFormatter` starts at the last "checkpoint" (usually a linebreak)
10+//! called a "block" and the caller must advance it as needed.
11+
12+use std::borrow::Cow;
13+use std::fmt::Debug;
14+use std::mem::{replace, take};
15+
16+#[cfg(test)]
17+mod test;
18+
19+use unicode_segmentation::{Graphemes, UnicodeSegmentation};
20+
21+use crate::graphemes::{Grapheme, GraphemeStr};
22+use crate::syntax::Highlight;
23+use crate::text_annotations::TextAnnotations;
24+use crate::{Position, RopeGraphemes, RopeSlice};
25+
26+/// TODO make Highlight a u32 to reduce the size of this enum to a single word.
27+#[derive(Debug, Clone, Copy)]
28+pub enum GraphemeSource {
29+ Document {
30+ codepoints: u32,
31+ },
32+ /// Inline virtual text can not be highlighted with a `Highlight` iterator
33+ /// because it's not part of the document. Instead the `Highlight`
34+ /// is emitted right by the document formatter
35+ VirtualText {
36+ highlight: Option<Highlight>,
37+ },
38+}
39+
40+#[derive(Debug, Clone)]
41+pub struct FormattedGrapheme<'a> {
42+ pub grapheme: Grapheme<'a>,
43+ pub source: GraphemeSource,
44+}
45+
46+impl<'a> FormattedGrapheme<'a> {
47+ pub fn new(
48+ g: GraphemeStr<'a>,
49+ visual_x: usize,
50+ tab_width: u16,
51+ source: GraphemeSource,
52+ ) -> FormattedGrapheme<'a> {
53+ FormattedGrapheme {
54+ grapheme: Grapheme::new(g, visual_x, tab_width),
55+ source,
56+ }
57+ }
58+ /// Returns whether this grapheme is virtual inline text
59+ pub fn is_virtual(&self) -> bool {
60+ matches!(self.source, GraphemeSource::VirtualText { .. })
61+ }
62+
63+ pub fn placeholder() -> Self {
64+ FormattedGrapheme {
65+ grapheme: Grapheme::Other { g: " ".into() },
66+ source: GraphemeSource::Document { codepoints: 0 },
67+ }
68+ }
69+
70+ pub fn doc_chars(&self) -> usize {
71+ match self.source {
72+ GraphemeSource::Document { codepoints } => codepoints as usize,
73+ GraphemeSource::VirtualText { .. } => 0,
74+ }
75+ }
76+
77+ pub fn is_whitespace(&self) -> bool {
78+ self.grapheme.is_whitespace()
79+ }
80+
81+ pub fn width(&self) -> usize {
82+ self.grapheme.width()
83+ }
84+
85+ pub fn is_word_boundary(&self) -> bool {
86+ self.grapheme.is_word_boundary()
87+ }
88+}
89+
90+#[derive(Debug, Clone)]
91+pub struct TextFormat {
92+ pub soft_wrap: bool,
93+ pub tab_width: u16,
94+ pub max_wrap: u16,
95+ pub max_indent_retain: u16,
96+ pub wrap_indicator: Box<str>,
97+ pub wrap_indicator_highlight: Option<Highlight>,
98+ pub viewport_width: u16,
99+}
100+
101+// test implementation is basically only used for testing or when softwrap is always disabled
102+impl Default for TextFormat {
103+ fn default() -> Self {
104+ TextFormat {
105+ soft_wrap: false,
106+ tab_width: 4,
107+ max_wrap: 3,
108+ max_indent_retain: 4,
109+ wrap_indicator: Box::from(" "),
110+ viewport_width: 17,
111+ wrap_indicator_highlight: None,
112+ }
113+ }
114+}
115+
116+#[derive(Debug)]
117+pub struct DocumentFormatter<'t> {
118+ text_fmt: &'t TextFormat,
119+ annotations: &'t TextAnnotations,
120+
121+ /// The visual position at the end of the last yielded word boundary
122+ visual_pos: Position,
123+ graphemes: RopeGraphemes<'t>,
124+ /// The character pos of the `graphemes` iter used for inserting annotations
125+ char_pos: usize,
126+ /// The line pos of the `graphemes` iter used for inserting annotations
127+ line_pos: usize,
128+ exhausted: bool,
129+
130+ /// Line breaks to be reserved for virtual text
131+ /// at the next line break
132+ virtual_lines: usize,
133+ inline_anntoation_graphemes: Option<(Graphemes<'t>, Option<Highlight>)>,
134+
135+ // softwrap specific
136+ /// The indentation of the current line
137+ /// Is set to `None` if the indentation level is not yet known
138+ /// because no non-whitespace graphemes have been encountered yet
139+ indent_level: Option<usize>,
140+ /// In case a long word needs to be split a single grapheme might need to be wrapped
141+ /// while the rest of the word stays on the same line
142+ peeked_grapheme: Option<(FormattedGrapheme<'t>, usize)>,
143+ /// A first-in first-out (fifo) buffer for the Graphemes of any given word
144+ word_buf: Vec<FormattedGrapheme<'t>>,
145+ /// The index of the next grapheme that will be yielded from the `word_buf`
146+ word_i: usize,
147+}
148+
149+impl<'t> DocumentFormatter<'t> {
150+ /// Creates a new formatter at the last block before `char_idx`.
151+ /// A block is a chunk which always ends with a linebreak.
152+ /// This is usually just a normal line break.
153+ /// However very long lines are always wrapped at constant intervals that can be cheaply calculated
154+ /// to avoid pathological behaviour.
155+ pub fn new_at_prev_checkpoint(
156+ text: RopeSlice<'t>,
157+ text_fmt: &'t TextFormat,
158+ annotations: &'t TextAnnotations,
159+ char_idx: usize,
160+ ) -> (Self, usize) {
161+ // TODO divide long lines into blocks to avoid bad performance for long lines
162+ let block_line_idx = text.char_to_line(char_idx.min(text.len_chars()));
163+ let block_char_idx = text.line_to_char(block_line_idx);
164+ annotations.reset_pos(block_char_idx);
165+ (
166+ DocumentFormatter {
167+ text_fmt,
168+ annotations,
169+ visual_pos: Position { row: 0, col: 0 },
170+ graphemes: RopeGraphemes::new(text.slice(block_char_idx..)),
171+ char_pos: block_char_idx,
172+ exhausted: false,
173+ virtual_lines: 0,
174+ indent_level: None,
175+ peeked_grapheme: None,
176+ word_buf: Vec::with_capacity(64),
177+ word_i: 0,
178+ line_pos: block_line_idx,
179+ inline_anntoation_graphemes: None,
180+ },
181+ block_char_idx,
182+ )
183+ }
184+
185+ fn next_inline_annotation_grapheme(&mut self) -> Option<(&'t str, Option<Highlight>)> {
186+ loop {
187+ if let Some(&mut (ref mut annotation, highlight)) =
188+ self.inline_anntoation_graphemes.as_mut()
189+ {
190+ if let Some(grapheme) = annotation.next() {
191+ return Some((grapheme, highlight));
192+ }
193+ }
194+
195+ if let Some((annotation, highlight)) =
196+ self.annotations.next_inline_annotation_at(self.char_pos)
197+ {
198+ self.inline_anntoation_graphemes = Some((
199+ UnicodeSegmentation::graphemes(&*annotation.text, true),
200+ highlight,
201+ ))
202+ } else {
203+ return None;
204+ }
205+ }
206+ }
207+
208+ fn advance_grapheme(&mut self, col: usize) -> Option<FormattedGrapheme<'t>> {
209+ let (grapheme, source) =
210+ if let Some((grapheme, highlight)) = self.next_inline_annotation_grapheme() {
211+ (grapheme.into(), GraphemeSource::VirtualText { highlight })
212+ } else if let Some(grapheme) = self.graphemes.next() {
213+ self.virtual_lines += self.annotations.annotation_lines_at(self.char_pos);
214+ let codepoints = grapheme.len_chars() as u32;
215+
216+ let overlay = self.annotations.overlay_at(self.char_pos);
217+ let grapheme = match overlay {
218+ Some((overlay, _)) => overlay.grapheme.as_str().into(),
219+ None => Cow::from(grapheme).into(),
220+ };
221+
222+ self.char_pos += codepoints as usize;
223+ (grapheme, GraphemeSource::Document { codepoints })
224+ } else {
225+ if self.exhausted {
226+ return None;
227+ }
228+ self.exhausted = true;
229+ // EOF grapheme is required for rendering
230+ // and correct position computations
231+ return Some(FormattedGrapheme {
232+ grapheme: Grapheme::Other { g: " ".into() },
233+ source: GraphemeSource::Document { codepoints: 0 },
234+ });
235+ };
236+
237+ let grapheme = FormattedGrapheme::new(grapheme, col, self.text_fmt.tab_width, source);
238+
239+ Some(grapheme)
240+ }
241+
242+ /// Move a word to the next visual line
243+ fn wrap_word(&mut self, virtual_lines_before_word: usize) -> usize {
244+ // softwrap this word to the next line
245+ let indent_carry_over = if let Some(indent) = self.indent_level {
246+ if indent as u16 <= self.text_fmt.max_indent_retain {
247+ indent as u16
248+ } else {
249+ 0
250+ }
251+ } else {
252+ // ensure the indent stays 0
253+ self.indent_level = Some(0);
254+ 0
255+ };
256+
257+ self.visual_pos.col = indent_carry_over as usize;
258+ self.virtual_lines -= virtual_lines_before_word;
259+ self.visual_pos.row += 1 + virtual_lines_before_word;
260+ let mut i = 0;
261+ let mut word_width = 0;
262+ let wrap_indicator = UnicodeSegmentation::graphemes(&*self.text_fmt.wrap_indicator, true)
263+ .map(|g| {
264+ i += 1;
265+ let grapheme = FormattedGrapheme::new(
266+ g.into(),
267+ self.visual_pos.col + word_width,
268+ self.text_fmt.tab_width,
269+ GraphemeSource::VirtualText {
270+ highlight: self.text_fmt.wrap_indicator_highlight,
271+ },
272+ );
273+ word_width += grapheme.width();
274+ grapheme
275+ });
276+ self.word_buf.splice(0..0, wrap_indicator);
277+
278+ for grapheme in &mut self.word_buf[i..] {
279+ let visual_x = self.visual_pos.col + word_width;
280+ grapheme
281+ .grapheme
282+ .change_position(visual_x, self.text_fmt.tab_width);
283+ word_width += grapheme.width();
284+ }
285+ word_width
286+ }
287+
288+ fn advance_to_next_word(&mut self) {
289+ self.word_buf.clear();
290+ let mut word_width = 0;
291+ let virtual_lines_before_word = self.virtual_lines;
292+ let mut virtual_lines_before_grapheme = self.virtual_lines;
293+
294+ loop {
295+ // softwrap word if necessary
296+ if word_width + self.visual_pos.col >= self.text_fmt.viewport_width as usize {
297+ // wrapping this word would move too much text to the next line
298+ // split the word at the line end instead
299+ if word_width > self.text_fmt.max_wrap as usize {
300+ // Usually we stop accomulating graphemes as soon as softwrapping becomes necessary.
301+ // However if the last grapheme is multiple columns wide it might extend beyond the EOL.
302+ // The condition below ensures that this grapheme is not cutoff and instead wrapped to the next line
303+ if word_width + self.visual_pos.col > self.text_fmt.viewport_width as usize {
304+ self.peeked_grapheme = self.word_buf.pop().map(|grapheme| {
305+ (grapheme, self.virtual_lines - virtual_lines_before_grapheme)
306+ });
307+ self.virtual_lines = virtual_lines_before_grapheme;
308+ }
309+ return;
310+ }
311+
312+ word_width = self.wrap_word(virtual_lines_before_word);
313+ }
314+
315+ virtual_lines_before_grapheme = self.virtual_lines;
316+
317+ let grapheme = if let Some((grapheme, virtual_lines)) = self.peeked_grapheme.take() {
318+ self.virtual_lines += virtual_lines;
319+ grapheme
320+ } else if let Some(grapheme) = self.advance_grapheme(self.visual_pos.col + word_width) {
321+ grapheme
322+ } else {
323+ return;
324+ };
325+
326+ // Track indentation
327+ if !grapheme.is_whitespace() && self.indent_level.is_none() {
328+ self.indent_level = Some(self.visual_pos.col);
329+ } else if grapheme.grapheme == Grapheme::Newline {
330+ self.indent_level = None;
331+ }
332+
333+ let is_word_boundary = grapheme.is_word_boundary();
334+ word_width += grapheme.width();
335+ self.word_buf.push(grapheme);
336+
337+ if is_word_boundary {
338+ return;
339+ }
340+ }
341+ }
342+
343+ /// returns the document line pos of the **next** grapheme that will be yielded
344+ pub fn line_pos(&self) -> usize {
345+ self.line_pos
346+ }
347+
348+ /// returns the visual pos of the **next** grapheme that will be yielded
349+ pub fn visual_pos(&self) -> Position {
350+ self.visual_pos
351+ }
352+}
353+
354+impl<'t> Iterator for DocumentFormatter<'t> {
355+ type Item = (FormattedGrapheme<'t>, Position);
356+
357+ fn next(&mut self) -> Option<Self::Item> {
358+ let grapheme = if self.text_fmt.soft_wrap {
359+ if self.word_i >= self.word_buf.len() {
360+ self.advance_to_next_word();
361+ self.word_i = 0;
362+ }
363+ let grapheme = replace(
364+ self.word_buf.get_mut(self.word_i)?,
365+ FormattedGrapheme::placeholder(),
366+ );
367+ self.word_i += 1;
368+ grapheme
369+ } else {
370+ self.advance_grapheme(self.visual_pos.col)?
371+ };
372+
373+ let pos = self.visual_pos;
374+ if grapheme.grapheme == Grapheme::Newline {
375+ self.visual_pos.row += 1;
376+ self.visual_pos.row += take(&mut self.virtual_lines);
377+ self.visual_pos.col = 0;
378+ self.line_pos += 1;
379+ } else {
380+ self.visual_pos.col += grapheme.width();
381+ }
382+ Some((grapheme, pos))
383+ }
384+}
helix-core/src/doc_formatter/test.rsnew file+182
@@ -0,0 +1,182 @@
1+use std::rc::Rc;
2+
3+use crate::doc_formatter::{DocumentFormatter, TextFormat};
4+use crate::text_annotations::{InlineAnnotation, Overlay, TextAnnotations};
5+
6+impl TextFormat {
7+ fn new_test(softwrap: bool) -> Self {
8+ TextFormat {
9+ soft_wrap: softwrap,
10+ tab_width: 2,
11+ max_wrap: 3,
12+ max_indent_retain: 4,
13+ wrap_indicator: ".".into(),
14+ wrap_indicator_highlight: None,
15+ // use a prime number to allow lining up too often with repeat
16+ viewport_width: 17,
17+ }
18+ }
19+}
20+
21+impl<'t> DocumentFormatter<'t> {
22+ fn collect_to_str(&mut self) -> String {
23+ use std::fmt::Write;
24+ let mut res = String::new();
25+ let viewport_width = self.text_fmt.viewport_width;
26+ let mut line = 0;
27+
28+ for (grapheme, pos) in self {
29+ if pos.row != line {
30+ line += 1;
31+ assert_eq!(pos.row, line);
32+ write!(res, "\n{}", ".".repeat(pos.col)).unwrap();
33+ assert!(
34+ pos.col <= viewport_width as usize,
35+ "softwrapped failed {}<={viewport_width}",
36+ pos.col
37+ );
38+ }
39+ write!(res, "{}", grapheme.grapheme).unwrap();
40+ }
41+
42+ res
43+ }
44+}
45+
46+fn softwrap_text(text: &str) -> String {
47+ DocumentFormatter::new_at_prev_checkpoint(
48+ text.into(),
49+ &TextFormat::new_test(true),
50+ &TextAnnotations::default(),
51+ 0,
52+ )
53+ .0
54+ .collect_to_str()
55+}
56+
57+#[test]
58+fn basic_softwrap() {
59+ assert_eq!(
60+ softwrap_text(&"foo ".repeat(10)),
61+ "foo foo foo foo \n.foo foo foo foo \n.foo foo "
62+ );
63+ assert_eq!(
64+ softwrap_text(&"fooo ".repeat(10)),
65+ "fooo fooo fooo \n.fooo fooo fooo \n.fooo fooo fooo \n.fooo "
66+ );
67+
68+ // check that we don't wrap unnecessarily
69+ assert_eq!(softwrap_text("\t\txxxx1xxxx2xx\n"), " xxxx1xxxx2xx \n ");
70+}
71+
72+#[test]
73+fn softwrap_indentation() {
74+ assert_eq!(
75+ softwrap_text("\t\tfoo1 foo2 foo3 foo4 foo5 foo6\n"),
76+ " foo1 foo2 \n.....foo3 foo4 \n.....foo5 foo6 \n "
77+ );
78+ assert_eq!(
79+ softwrap_text("\t\t\tfoo1 foo2 foo3 foo4 foo5 foo6\n"),
80+ " foo1 foo2 \n.foo3 foo4 foo5 \n.foo6 \n "
81+ );
82+}
83+
84+#[test]
85+fn long_word_softwrap() {
86+ assert_eq!(
87+ softwrap_text("\t\txxxx1xxxx2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9xxx\n"),
88+ " xxxx1xxxx2xxx\n.....x3xxxx4xxxx5\n.....xxxx6xxxx7xx\n.....xx8xxxx9xxx \n "
89+ );
90+ assert_eq!(
91+ softwrap_text("xxxxxxxx1xxxx2xxx\n"),
92+ "xxxxxxxx1xxxx2xxx\n. \n "
93+ );
94+ assert_eq!(
95+ softwrap_text("\t\txxxx1xxxx 2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9xxx\n"),
96+ " xxxx1xxxx \n.....2xxxx3xxxx4x\n.....xxx5xxxx6xxx\n.....x7xxxx8xxxx9\n.....xxx \n "
97+ );
98+ assert_eq!(
99+ softwrap_text("\t\txxxx1xxx 2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9xxx\n"),
100+ " xxxx1xxx 2xxx\n.....x3xxxx4xxxx5\n.....xxxx6xxxx7xx\n.....xx8xxxx9xxx \n "
101+ );
102+}
103+
104+fn overlay_text(text: &str, char_pos: usize, softwrap: bool, overlays: &[Overlay]) -> String {
105+ DocumentFormatter::new_at_prev_checkpoint(
106+ text.into(),
107+ &TextFormat::new_test(softwrap),
108+ TextAnnotations::default().add_overlay(overlays.into(), None),
109+ char_pos,
110+ )
111+ .0
112+ .collect_to_str()
113+}
114+
115+#[test]
116+fn overlay() {
117+ assert_eq!(
118+ overlay_text(
119+ "foobar",
120+ 0,
121+ false,
122+ &[Overlay::new(0, "X"), Overlay::new(2, "\t")],
123+ ),
124+ "Xo bar "
125+ );
126+ assert_eq!(
127+ overlay_text(
128+ &"foo ".repeat(10),
129+ 0,
130+ true,
131+ &[
132+ Overlay::new(2, "\t"),
133+ Overlay::new(5, "\t"),
134+ Overlay::new(16, "X"),
135+ ]
136+ ),
137+ "fo f o foo \n.foo Xoo foo foo \n.foo foo foo "
138+ );
139+}
140+
141+fn annotate_text(text: &str, softwrap: bool, annotations: &[InlineAnnotation]) -> String {
142+ DocumentFormatter::new_at_prev_checkpoint(
143+ text.into(),
144+ &TextFormat::new_test(softwrap),
145+ TextAnnotations::default().add_inline_annotations(annotations.into(), None),
146+ 0,
147+ )
148+ .0
149+ .collect_to_str()
150+}
151+
152+#[test]
153+fn annotation() {
154+ assert_eq!(
155+ annotate_text("bar", false, &[InlineAnnotation::new(0, "foo")]),
156+ "foobar "
157+ );
158+ assert_eq!(
159+ annotate_text(
160+ &"foo ".repeat(10),
161+ true,
162+ &[InlineAnnotation::new(0, "foo ")]
163+ ),
164+ "foo foo foo foo \n.foo foo foo foo \n.foo foo foo "
165+ );
166+}
167+#[test]
168+fn annotation_and_overlay() {
169+ assert_eq!(
170+ DocumentFormatter::new_at_prev_checkpoint(
171+ "bbar".into(),
172+ &TextFormat::new_test(false),
173+ TextAnnotations::default()
174+ .add_inline_annotations(Rc::new([InlineAnnotation::new(0, "fooo")]), None)
175+ .add_overlay(Rc::new([Overlay::new(0, "\t")]), None),
176+ 0,
177+ )
178+ .0
179+ .collect_to_str(),
180+ "fooo bar "
181+ );
182+}
helix-core/src/fuzzy.rsnew file+43
@@ -0,0 +1,43 @@
1+use std::ops::DerefMut;
2+
3+use nucleo::pattern::{Atom, AtomKind, CaseMatching};
4+use nucleo::Config;
5+use parking_lot::Mutex;
6+
7+pub struct LazyMutex<T> {
8+ inner: Mutex<Option<T>>,
9+ init: fn() -> T,
10+}
11+
12+impl<T> LazyMutex<T> {
13+ pub const fn new(init: fn() -> T) -> Self {
14+ Self {
15+ inner: Mutex::new(None),
16+ init,
17+ }
18+ }
19+
20+ pub fn lock(&self) -> impl DerefMut<Target = T> + '_ {
21+ parking_lot::MutexGuard::map(self.inner.lock(), |val| val.get_or_insert_with(self.init))
22+ }
23+}
24+
25+pub static MATCHER: LazyMutex<nucleo::Matcher> = LazyMutex::new(nucleo::Matcher::default);
26+
27+/// convenience function to easily fuzzy match
28+/// on a (relatively small list of inputs). This is not recommended for building a full tui
29+/// application that can match large numbers of matches as all matching is done on the current
30+/// thread, effectively blocking the UI
31+pub fn fuzzy_match<T: AsRef<str>>(
32+ pattern: &str,
33+ items: impl IntoIterator<Item = T>,
34+ path: bool,
35+) -> Vec<(T, u16)> {
36+ let mut matcher = MATCHER.lock();
37+ matcher.config = Config::DEFAULT;
38+ if path {
39+ matcher.config.set_match_paths();
40+ }
41+ let pattern = Atom::new(pattern, CaseMatching::Smart, AtomKind::Fuzzy, false);
42+ pattern.match_list(items, &mut matcher)
43+}
helix-core/src/graphemes.rs+182−1
@@ -5,7 +5,88 @@ use ropey::{iter::Chunks, str_utils::byte_to_char_idx, RopeSlice};
55use unicode_segmentation::{GraphemeCursor, GraphemeIncomplete};
66use unicode_width::UnicodeWidthStr;
77
8−use std::fmt;
8+use std::borrow::Cow;
9+use std::fmt::{self, Debug, Display};
10+use std::marker::PhantomData;
11+use std::ops::Deref;
12+use std::ptr::NonNull;
13+use std::{slice, str};
14+
15+use crate::chars::{char_is_whitespace, char_is_word};
16+use crate::LineEnding;
17+
18+#[inline]
19+pub fn tab_width_at(visual_x: usize, tab_width: u16) -> usize {
20+ tab_width as usize - (visual_x % tab_width as usize)
21+}
22+
23+#[derive(Debug, Clone, PartialEq, Eq)]
24+pub enum Grapheme<'a> {
25+ Newline,
26+ Tab { width: usize },
27+ Other { g: GraphemeStr<'a> },
28+}
29+
30+impl<'a> Grapheme<'a> {
31+ pub fn new(g: GraphemeStr<'a>, visual_x: usize, tab_width: u16) -> Grapheme<'a> {
32+ match g {
33+ g if g == "\t" => Grapheme::Tab {
34+ width: tab_width_at(visual_x, tab_width),
35+ },
36+ _ if LineEnding::from_str(&g).is_some() => Grapheme::Newline,
37+ _ => Grapheme::Other { g },
38+ }
39+ }
40+
41+ pub fn change_position(&mut self, visual_x: usize, tab_width: u16) {
42+ if let Grapheme::Tab { width } = self {
43+ *width = tab_width_at(visual_x, tab_width)
44+ }
45+ }
46+
47+ /// Returns the a visual width of this grapheme,
48+ #[inline]
49+ pub fn width(&self) -> usize {
50+ match *self {
51+ // width is not cached because we are dealing with
52+ // ASCII almost all the time which already has a fastpath
53+ // it's okay to convert to u16 here because no codepoint has a width larger
54+ // than 2 and graphemes are usually atmost two visible codepoints wide
55+ Grapheme::Other { ref g } => grapheme_width(g),
56+ Grapheme::Tab { width } => width,
57+ Grapheme::Newline => 1,
58+ }
59+ }
60+
61+ pub fn is_whitespace(&self) -> bool {
62+ !matches!(&self, Grapheme::Other { g } if !g.chars().all(char_is_whitespace))
63+ }
64+
65+ // TODO currently word boundaries are used for softwrapping.
66+ // This works best for programming languages and well for prose.
67+ // This could however be improved in the future by considering unicode
68+ // character classes but
69+ pub fn is_word_boundary(&self) -> bool {
70+ !matches!(&self, Grapheme::Other { g,.. } if g.chars().all(char_is_word))
71+ }
72+}
73+
74+impl Display for Grapheme<'_> {
75+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76+ match *self {
77+ Grapheme::Newline => write!(f, " "),
78+ Grapheme::Tab { width } => {
79+ for _ in 0..width {
80+ write!(f, " ")?;
81+ }
82+ Ok(())
83+ }
84+ Grapheme::Other { ref g } => {
85+ write!(f, "{g}")
86+ }
87+ }
88+ }
89+}
990
1091#[must_use]
1192pub fn grapheme_width(g: &str) -> usize {
@@ -27,6 +108,8 @@ pub fn grapheme_width(g: &str) -> usize {
27108 // We use max(1) here because all grapeheme clusters--even illformed
28109 // ones--should have at least some width so they can be edited
29110 // properly.
111+ // TODO properly handle unicode width for all codepoints
112+ // example of where unicode width is currently wrong: 🤦🏼‍♂️ (taken from https://hsivonen.fi/string-length/)
30113 UnicodeWidthStr::width(g).max(1)
31114 }
32115}
@@ -341,3 +424,101 @@ impl<'a> Iterator for RopeGraphemes<'a> {
341424 }
342425 }
343426}
427+
428+/// A highly compressed Cow<'a, str> that holds
429+/// atmost u31::MAX bytes and is readonly
430+pub struct GraphemeStr<'a> {
431+ ptr: NonNull<u8>,
432+ len: u32,
433+ phantom: PhantomData<&'a str>,
434+}
435+
436+impl GraphemeStr<'_> {
437+ const MASK_OWNED: u32 = 1 << 31;
438+
439+ fn compute_len(&self) -> usize {
440+ (self.len & !Self::MASK_OWNED) as usize
441+ }
442+}
443+
444+impl Deref for GraphemeStr<'_> {
445+ type Target = str;
446+ fn deref(&self) -> &Self::Target {
447+ unsafe {
448+ let bytes = slice::from_raw_parts(self.ptr.as_ptr(), self.compute_len());
449+ str::from_utf8_unchecked(bytes)
450+ }
451+ }
452+}
453+
454+impl Drop for GraphemeStr<'_> {
455+ fn drop(&mut self) {
456+ if self.len & Self::MASK_OWNED != 0 {
457+ // free allocation
458+ unsafe {
459+ drop(Box::from_raw(slice::from_raw_parts_mut(
460+ self.ptr.as_ptr(),
461+ self.compute_len(),
462+ )));
463+ }
464+ }
465+ }
466+}
467+
468+impl<'a> From<&'a str> for GraphemeStr<'a> {
469+ fn from(g: &'a str) -> Self {
470+ GraphemeStr {
471+ ptr: unsafe { NonNull::new_unchecked(g.as_bytes().as_ptr() as *mut u8) },
472+ len: i32::try_from(g.len()).unwrap() as u32,
473+ phantom: PhantomData,
474+ }
475+ }
476+}
477+
478+impl<'a> From<String> for GraphemeStr<'a> {
479+ fn from(g: String) -> Self {
480+ let len = g.len();
481+ let ptr = Box::into_raw(g.into_bytes().into_boxed_slice()) as *mut u8;
482+ GraphemeStr {
483+ ptr: unsafe { NonNull::new_unchecked(ptr) },
484+ len: (i32::try_from(len).unwrap() as u32) | Self::MASK_OWNED,
485+ phantom: PhantomData,
486+ }
487+ }
488+}
489+
490+impl<'a> From<Cow<'a, str>> for GraphemeStr<'a> {
491+ fn from(g: Cow<'a, str>) -> Self {
492+ match g {
493+ Cow::Borrowed(g) => g.into(),
494+ Cow::Owned(g) => g.into(),
495+ }
496+ }
497+}
498+
499+impl<T: Deref<Target = str>> PartialEq<T> for GraphemeStr<'_> {
500+ fn eq(&self, other: &T) -> bool {
501+ self.deref() == other.deref()
502+ }
503+}
504+impl PartialEq<str> for GraphemeStr<'_> {
505+ fn eq(&self, other: &str) -> bool {
506+ self.deref() == other
507+ }
508+}
509+impl Eq for GraphemeStr<'_> {}
510+impl Debug for GraphemeStr<'_> {
511+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512+ Debug::fmt(self.deref(), f)
513+ }
514+}
515+impl Display for GraphemeStr<'_> {
516+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
517+ Display::fmt(self.deref(), f)
518+ }
519+}
520+impl Clone for GraphemeStr<'_> {
521+ fn clone(&self) -> Self {
522+ self.deref().to_owned().into()
523+ }
524+}
helix-core/src/history.rs+34−6
@@ -1,9 +1,15 @@
1−use crate::{Assoc, ChangeSet, Range, Rope, State, Transaction};
1+use crate::{Assoc, ChangeSet, Range, Rope, Selection, Transaction};
22use once_cell::sync::Lazy;
33use regex::Regex;
44use std::num::NonZeroUsize;
55use std::time::{Duration, Instant};
66
7+#[derive(Debug, Clone)]
8+pub struct State {
9+ pub doc: Rope,
10+ pub selection: Selection,
11+}
12+
713/// Stores the history of changes to a buffer.
814///
915/// Currently the history is represented as a vector of revisions. The vector
@@ -48,7 +54,7 @@ pub struct History {
4854}
4955
5056/// A single point in history. See [History] for more information.
51−#[derive(Debug)]
57+#[derive(Debug, Clone)]
5258struct Revision {
5359 parent: usize,
5460 last_child: Option<NonZeroUsize>,
@@ -66,8 +72,8 @@ impl Default for History {
6672 revisions: vec![Revision {
6773 parent: 0,
6874 last_child: None,
69− transaction: Transaction::from(ChangeSet::new(&Rope::new())),
70− inversion: Transaction::from(ChangeSet::new(&Rope::new())),
75+ transaction: Transaction::from(ChangeSet::new("".into())),
76+ inversion: Transaction::from(ChangeSet::new("".into())),
7177 timestamp: Instant::now(),
7278 }],
7379 current: 0,
@@ -113,6 +119,21 @@ impl History {
113119 self.current == 0
114120 }
115121
122+ /// Returns the changes since the given revision composed into a transaction.
123+ /// Returns None if there are no changes between the current and given revisions.
124+ pub fn changes_since(&self, revision: usize) -> Option<Transaction> {
125+ let lca = self.lowest_common_ancestor(revision, self.current);
126+ let up = self.path_up(revision, lca);
127+ let down = self.path_up(self.current, lca);
128+ let up_txns = up
129+ .iter()
130+ .rev()
131+ .map(|&n| self.revisions[n].inversion.clone());
132+ let down_txns = down.iter().map(|&n| self.revisions[n].transaction.clone());
133+
134+ down_txns.chain(up_txns).reduce(|acc, tx| tx.compose(acc))
135+ }
136+
116137 /// Undo the last edit.
117138 pub fn undo(&mut self) -> Option<&Transaction> {
118139 if self.at_root() {
@@ -366,12 +387,16 @@ impl std::str::FromStr for UndoKind {
366387#[cfg(test)]
367388mod test {
368389 use super::*;
390+ use crate::Selection;
369391
370392 #[test]
371393 fn test_undo_redo() {
372394 let mut history = History::default();
373395 let doc = Rope::from("hello");
374− let mut state = State::new(doc);
396+ let mut state = State {
397+ doc,
398+ selection: Selection::point(0),
399+ };
375400
376401 let transaction1 =
377402 Transaction::change(&state.doc, vec![(5, 5, Some(" world!".into()))].into_iter());
@@ -420,7 +445,10 @@ mod test {
420445 fn test_earlier_later() {
421446 let mut history = History::default();
422447 let doc = Rope::from("a\n");
423− let mut state = State::new(doc);
448+ let mut state = State {
449+ doc,
450+ selection: Selection::point(0),
451+ };
424452
425453 fn undo(history: &mut History, state: &mut State) {
426454 if let Some(transaction) = history.undo() {
helix-core/src/increment/date_time.rs+75−246
@@ -1,114 +1,53 @@
1−use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
1+use chrono::{Duration, NaiveDate, NaiveDateTime, NaiveTime};
22use once_cell::sync::Lazy;
33use regex::Regex;
4−use ropey::RopeSlice;
5−
6−use std::borrow::Cow;
7−use std::cmp;
84use std::fmt::Write;
95
10−use super::Increment;
11−use crate::{Range, Tendril};
6+/// Increment a Date or DateTime
7+///
8+/// If just a Date is selected the day will be incremented.
9+/// If a DateTime is selected the second will be incremented.
10+pub fn increment(selected_text: &str, amount: i64) -> Option<String> {
11+ if selected_text.is_empty() {
12+ return None;
13+ }
1214
13−#[derive(Debug, PartialEq, Eq)]
14−pub struct DateTimeIncrementor {
15− date_time: NaiveDateTime,
16− range: Range,
17− fmt: &'static str,
18− field: DateField,
19−}
15+ FORMATS.iter().find_map(|format| {
16+ let captures = format.regex.captures(selected_text)?;
17+ if captures.len() - 1 != format.fields.len() {
18+ return None;
19+ }
2020
21−impl DateTimeIncrementor {
22− pub fn from_range(text: RopeSlice, range: Range) -> Option<DateTimeIncrementor> {
23− let range = if range.is_empty() {
24− if range.anchor < text.len_chars() {
25− // Treat empty range as a cursor range.
26− range.put_cursor(text, range.anchor + 1, true)
27− } else {
28− // The range is empty and at the end of the text.
29− return None;
21+ let date_time = captures.get(0)?;
22+ let has_date = format.fields.iter().any(|f| f.unit.is_date());
23+ let has_time = format.fields.iter().any(|f| f.unit.is_time());
24+ let date_time = &selected_text[date_time.start()..date_time.end()];
25+ match (has_date, has_time) {
26+ (true, true) => {
27+ let date_time = NaiveDateTime::parse_from_str(date_time, format.fmt).ok()?;
28+ Some(
29+ date_time
30+ .checked_add_signed(Duration::minutes(amount))?
31+ .format(format.fmt)
32+ .to_string(),
33+ )
3034 }
31− } else {
32− range
33− };
34−
35− FORMATS.iter().find_map(|format| {
36− let from = range.from().saturating_sub(format.max_len);
37− let to = (range.from() + format.max_len).min(text.len_chars());
38−
39− let (from_in_text, to_in_text) = (range.from() - from, range.to() - from);
40− let text: Cow<str> = text.slice(from..to).into();
41−
42− let captures = format.regex.captures(&text)?;
43− if captures.len() - 1 != format.fields.len() {
44− return None;
35+ (true, false) => {
36+ let date = NaiveDate::parse_from_str(date_time, format.fmt).ok()?;
37+ Some(
38+ date.checked_add_signed(Duration::days(amount))?
39+ .format(format.fmt)
40+ .to_string(),
41+ )
4542 }
46−
47− let date_time = captures.get(0)?;
48− let offset = range.from() - from_in_text;
49− let range = Range::new(date_time.start() + offset, date_time.end() + offset);
50−
51− let field = captures
52− .iter()
53− .skip(1)
54− .enumerate()
55− .find_map(|(i, capture)| {
56− let capture = capture?;
57− let capture_range = capture.range();
58−
59− if capture_range.contains(&from_in_text)
60− && capture_range.contains(&(to_in_text - 1))
61− {
62− Some(format.fields[i])
63− } else {
64− None
65− }
66− })?;
67−
68− let has_date = format.fields.iter().any(|f| f.unit.is_date());
69− let has_time = format.fields.iter().any(|f| f.unit.is_time());
70−
71− let date_time = &text[date_time.start()..date_time.end()];
72− let date_time = match (has_date, has_time) {
73− (true, true) => NaiveDateTime::parse_from_str(date_time, format.fmt).ok()?,
74− (true, false) => {
75− let date = NaiveDate::parse_from_str(date_time, format.fmt).ok()?;
76−
77− date.and_hms(0, 0, 0)
78− }
79− (false, true) => {
80− let time = NaiveTime::parse_from_str(date_time, format.fmt).ok()?;
81−
82− NaiveDate::from_ymd(0, 1, 1).and_time(time)
83− }
84− (false, false) => return None,
85− };
86−
87− Some(DateTimeIncrementor {
88− date_time,
89− range,
90− fmt: format.fmt,
91− field,
92− })
93− })
94− }
95−}
96−
97−impl Increment for DateTimeIncrementor {
98− fn increment(&self, amount: i64) -> (Range, Tendril) {
99− let date_time = match self.field.unit {
100− DateUnit::Years => add_years(self.date_time, amount),
101− DateUnit::Months => add_months(self.date_time, amount),
102− DateUnit::Days => add_duration(self.date_time, Duration::days(amount)),
103− DateUnit::Hours => add_duration(self.date_time, Duration::hours(amount)),
104− DateUnit::Minutes => add_duration(self.date_time, Duration::minutes(amount)),
105− DateUnit::Seconds => add_duration(self.date_time, Duration::seconds(amount)),
106− DateUnit::AmPm => toggle_am_pm(self.date_time),
43+ (false, true) => {
44+ let time = NaiveTime::parse_from_str(date_time, format.fmt).ok()?;
45+ let (adjusted_time, _) = time.overflowing_add_signed(Duration::minutes(amount));
46+ Some(adjusted_time.format(format.fmt).to_string())
47+ }
48+ (false, false) => None,
10749 }
108− .unwrap_or(self.date_time);
109−
110− (self.range, date_time.format(self.fmt).to_string().into())
111− }
50+ })
11251}
11352
11453static FORMATS: Lazy<Vec<Format>> = Lazy::new(|| {
@@ -144,7 +83,7 @@ impl Format {
14483 fn new(fmt: &'static str) -> Self {
14584 let mut remaining = fmt;
14685 let mut fields = Vec::new();
147− let mut regex = String::new();
86+ let mut regex = "^".to_string();
14887 let mut max_len = 0;
14988
15089 while let Some(i) = remaining.find('%') {
@@ -166,6 +105,7 @@ impl Format {
166105 write!(regex, "({})", field.regex).unwrap();
167106 remaining = &after[spec_len..];
168107 }
108+ regex += "$";
169109
170110 let regex = Regex::new(&regex).unwrap();
171111
@@ -305,155 +245,47 @@ impl DateUnit {
305245 }
306246}
307247
308−fn ndays_in_month(year: i32, month: u32) -> u32 {
309− // The first day of the next month...
310− let (y, m) = if month == 12 {
311− (year + 1, 1)
312− } else {
313− (year, month + 1)
314− };
315− let d = NaiveDate::from_ymd(y, m, 1);
316−
317− // ...is preceded by the last day of the original month.
318− d.pred().day()
319−}
320−
321−fn add_months(date_time: NaiveDateTime, amount: i64) -> Option<NaiveDateTime> {
322− let month = (date_time.month0() as i64).checked_add(amount)?;
323− let year = date_time.year() + i32::try_from(month / 12).ok()?;
324− let year = if month.is_negative() { year - 1 } else { year };
325−
326− // Normalize month
327− let month = month % 12;
328− let month = if month.is_negative() {
329− month + 12
330− } else {
331− month
332− } as u32
333− + 1;
334−
335− let day = cmp::min(date_time.day(), ndays_in_month(year, month));
336−
337− Some(NaiveDate::from_ymd(year, month, day).and_time(date_time.time()))
338−}
339−
340−fn add_years(date_time: NaiveDateTime, amount: i64) -> Option<NaiveDateTime> {
341− let year = i32::try_from((date_time.year() as i64).checked_add(amount)?).ok()?;
342− let ndays = ndays_in_month(year, date_time.month());
343−
344− if date_time.day() > ndays {
345− let d = NaiveDate::from_ymd(year, date_time.month(), ndays);
346− Some(d.succ().and_time(date_time.time()))
347− } else {
348− date_time.with_year(year)
349− }
350−}
351−
352−fn add_duration(date_time: NaiveDateTime, duration: Duration) -> Option<NaiveDateTime> {
353− date_time.checked_add_signed(duration)
354−}
355−
356−fn toggle_am_pm(date_time: NaiveDateTime) -> Option<NaiveDateTime> {
357− if date_time.hour() < 12 {
358− add_duration(date_time, Duration::hours(12))
359− } else {
360− add_duration(date_time, Duration::hours(-12))
361− }
362−}
363−
364248#[cfg(test)]
365249mod test {
366250 use super::*;
367− use crate::Rope;
368251
369252 #[test]
370253 fn test_increment_date_times() {
371254 let tests = [
372255 // (original, cursor, amount, expected)
373− ("2020-02-28", 0, 1, "2021-02-28"),
374− ("2020-02-29", 0, 1, "2021-03-01"),
375− ("2020-01-31", 5, 1, "2020-02-29"),
376− ("2020-01-20", 5, 1, "2020-02-20"),
377− ("2021-01-01", 5, -1, "2020-12-01"),
378− ("2021-01-31", 5, -2, "2020-11-30"),
379− ("2020-02-28", 8, 1, "2020-02-29"),
380− ("2021-02-28", 8, 1, "2021-03-01"),
381− ("2021-02-28", 0, -1, "2020-02-28"),
382− ("2021-03-01", 0, -1, "2020-03-01"),
383− ("2020-02-29", 5, -1, "2020-01-29"),
384− ("2020-02-20", 5, -1, "2020-01-20"),
385− ("2020-02-29", 8, -1, "2020-02-28"),
386− ("2021-03-01", 8, -1, "2021-02-28"),
387− ("1980/12/21", 8, 100, "1981/03/31"),
388− ("1980/12/21", 8, -100, "1980/09/12"),
389− ("1980/12/21", 8, 1000, "1983/09/17"),
390− ("1980/12/21", 8, -1000, "1978/03/27"),
391− ("2021-11-24 07:12:23", 0, 1, "2022-11-24 07:12:23"),
392− ("2021-11-24 07:12:23", 5, 1, "2021-12-24 07:12:23"),
393− ("2021-11-24 07:12:23", 8, 1, "2021-11-25 07:12:23"),
394− ("2021-11-24 07:12:23", 11, 1, "2021-11-24 08:12:23"),
395− ("2021-11-24 07:12:23", 14, 1, "2021-11-24 07:13:23"),
396− ("2021-11-24 07:12:23", 17, 1, "2021-11-24 07:12:24"),
397− ("2021/11/24 07:12:23", 0, 1, "2022/11/24 07:12:23"),
398− ("2021/11/24 07:12:23", 5, 1, "2021/12/24 07:12:23"),
399− ("2021/11/24 07:12:23", 8, 1, "2021/11/25 07:12:23"),
400− ("2021/11/24 07:12:23", 11, 1, "2021/11/24 08:12:23"),
401− ("2021/11/24 07:12:23", 14, 1, "2021/11/24 07:13:23"),
402− ("2021/11/24 07:12:23", 17, 1, "2021/11/24 07:12:24"),
403− ("2021-11-24 07:12", 0, 1, "2022-11-24 07:12"),
404− ("2021-11-24 07:12", 5, 1, "2021-12-24 07:12"),
405− ("2021-11-24 07:12", 8, 1, "2021-11-25 07:12"),
406− ("2021-11-24 07:12", 11, 1, "2021-11-24 08:12"),
407− ("2021-11-24 07:12", 14, 1, "2021-11-24 07:13"),
408− ("2021/11/24 07:12", 0, 1, "2022/11/24 07:12"),
409− ("2021/11/24 07:12", 5, 1, "2021/12/24 07:12"),
410− ("2021/11/24 07:12", 8, 1, "2021/11/25 07:12"),
411− ("2021/11/24 07:12", 11, 1, "2021/11/24 08:12"),
412− ("2021/11/24 07:12", 14, 1, "2021/11/24 07:13"),
413− ("Wed Nov 24 2021", 0, 1, "Thu Nov 25 2021"),
414− ("Wed Nov 24 2021", 4, 1, "Fri Dec 24 2021"),
415− ("Wed Nov 24 2021", 8, 1, "Thu Nov 25 2021"),
416− ("Wed Nov 24 2021", 11, 1, "Thu Nov 24 2022"),
417− ("24-Nov-2021", 0, 1, "25-Nov-2021"),
418− ("24-Nov-2021", 3, 1, "24-Dec-2021"),
419− ("24-Nov-2021", 7, 1, "24-Nov-2022"),
420− ("2021 Nov 24", 0, 1, "2022 Nov 24"),
421− ("2021 Nov 24", 5, 1, "2021 Dec 24"),
422− ("2021 Nov 24", 9, 1, "2021 Nov 25"),
423− ("Nov 24, 2021", 0, 1, "Dec 24, 2021"),
424− ("Nov 24, 2021", 4, 1, "Nov 25, 2021"),
425− ("Nov 24, 2021", 8, 1, "Nov 24, 2022"),
426− ("7:21:53 am", 0, 1, "8:21:53 am"),
427− ("7:21:53 am", 3, 1, "7:22:53 am"),
428− ("7:21:53 am", 5, 1, "7:21:54 am"),
429− ("7:21:53 am", 8, 1, "7:21:53 pm"),
430− ("7:21:53 AM", 0, 1, "8:21:53 AM"),
431− ("7:21:53 AM", 3, 1, "7:22:53 AM"),
432− ("7:21:53 AM", 5, 1, "7:21:54 AM"),
433− ("7:21:53 AM", 8, 1, "7:21:53 PM"),
434− ("7:21 am", 0, 1, "8:21 am"),
435− ("7:21 am", 3, 1, "7:22 am"),
436− ("7:21 am", 5, 1, "7:21 pm"),
437− ("7:21 AM", 0, 1, "8:21 AM"),
438− ("7:21 AM", 3, 1, "7:22 AM"),
439− ("7:21 AM", 5, 1, "7:21 PM"),
440− ("23:24:23", 1, 1, "00:24:23"),
441− ("23:24:23", 3, 1, "23:25:23"),
442− ("23:24:23", 6, 1, "23:24:24"),
443− ("23:24", 1, 1, "00:24"),
444− ("23:24", 3, 1, "23:25"),
256+ ("2020-02-28", 1, "2020-02-29"),
257+ ("2020-02-29", 1, "2020-03-01"),
258+ ("2020-01-31", 1, "2020-02-01"),
259+ ("2020-01-20", 1, "2020-01-21"),
260+ ("2021-01-01", -1, "2020-12-31"),
261+ ("2021-01-31", -2, "2021-01-29"),
262+ ("2020-02-28", 1, "2020-02-29"),
263+ ("2021-02-28", 1, "2021-03-01"),
264+ ("2021-03-01", -1, "2021-02-28"),
265+ ("2020-02-29", -1, "2020-02-28"),
266+ ("2020-02-20", -1, "2020-02-19"),
267+ ("2021-03-01", -1, "2021-02-28"),
268+ ("1980/12/21", 100, "1981/03/31"),
269+ ("1980/12/21", -100, "1980/09/12"),
270+ ("1980/12/21", 1000, "1983/09/17"),
271+ ("1980/12/21", -1000, "1978/03/27"),
272+ ("2021-11-24 07:12:23", 1, "2021-11-24 07:13:23"),
273+ ("2021-11-24 07:12", 1, "2021-11-24 07:13"),
274+ ("Wed Nov 24 2021", 1, "Thu Nov 25 2021"),
275+ ("24-Nov-2021", 1, "25-Nov-2021"),
276+ ("2021 Nov 24", 1, "2021 Nov 25"),
277+ ("Nov 24, 2021", 1, "Nov 25, 2021"),
278+ ("7:21:53 am", 1, "7:22:53 am"),
279+ ("7:21:53 AM", 1, "7:22:53 AM"),
280+ ("7:21 am", 1, "7:22 am"),
281+ ("23:24:23", 1, "23:25:23"),
282+ ("23:24", 1, "23:25"),
283+ ("23:59", 1, "00:00"),
284+ ("23:59:59", 1, "00:00:59"),
445285 ];
446286
447− for (original, cursor, amount, expected) in tests {
448− let rope = Rope::from_str(original);
449− let range = Range::new(cursor, cursor + 1);
450− assert_eq!(
451− DateTimeIncrementor::from_range(rope.slice(..), range)
452− .unwrap()
453− .increment(amount)
454− .1,
455− Tendril::from(expected)
456− );
287+ for (original, amount, expected) in tests {
288+ assert_eq!(increment(original, amount).unwrap(), expected);
457289 }
458290 }
459291
@@ -482,10 +314,7 @@ mod test {
482314 ];
483315
484316 for invalid in tests {
485− let rope = Rope::from_str(invalid);
486− let range = Range::new(0, 1);
487−
488− assert_eq!(DateTimeIncrementor::from_range(rope.slice(..), range), None)
317+ assert_eq!(increment(invalid, 1), None)
489318 }
490319 }
491320}
helix-core/src/increment/integer.rsnew file+235
@@ -0,0 +1,235 @@
1+const SEPARATOR: char = '_';
2+
3+/// Increment an integer.
4+///
5+/// Supported bases:
6+/// 2 with prefix 0b
7+/// 8 with prefix 0o
8+/// 10 with no prefix
9+/// 16 with prefix 0x
10+///
11+/// An integer can contain `_` as a separator but may not start or end with a separator.
12+/// Base 10 integers can go negative, but bases 2, 8, and 16 cannot.
13+/// All addition and subtraction is saturating.
14+pub fn increment(selected_text: &str, amount: i64) -> Option<String> {
15+ if selected_text.is_empty()
16+ || selected_text.ends_with(SEPARATOR)
17+ || selected_text.starts_with(SEPARATOR)
18+ {
19+ return None;
20+ }
21+
22+ let radix = if selected_text.starts_with("0x") {
23+ 16
24+ } else if selected_text.starts_with("0o") {
25+ 8
26+ } else if selected_text.starts_with("0b") {
27+ 2
28+ } else {
29+ 10
30+ };
31+
32+ // Get separator indexes from right to left.
33+ let separator_rtl_indexes: Vec<usize> = selected_text
34+ .chars()
35+ .rev()
36+ .enumerate()
37+ .filter_map(|(i, c)| if c == SEPARATOR { Some(i) } else { None })
38+ .collect();
39+
40+ let word: String = selected_text.chars().filter(|&c| c != SEPARATOR).collect();
41+
42+ let mut new_text = if radix == 10 {
43+ let number = &word;
44+ let value = i128::from_str_radix(number, radix).ok()?;
45+ let new_value = value.saturating_add(amount as i128);
46+
47+ let format_length = match (value.is_negative(), new_value.is_negative()) {
48+ (true, false) => number.len() - 1,
49+ (false, true) => number.len() + 1,
50+ _ => number.len(),
51+ } - separator_rtl_indexes.len();
52+
53+ if number.starts_with('0') || number.starts_with("-0") {
54+ format!("{:01$}", new_value, format_length)
55+ } else {
56+ format!("{}", new_value)
57+ }
58+ } else {
59+ let number = &word[2..];
60+ let value = u128::from_str_radix(number, radix).ok()?;
61+ let new_value = (value as i128).saturating_add(amount as i128);
62+ let new_value = if new_value < 0 { 0 } else { new_value };
63+ let format_length = selected_text.len() - 2 - separator_rtl_indexes.len();
64+
65+ match radix {
66+ 2 => format!("0b{:01$b}", new_value, format_length),
67+ 8 => format!("0o{:01$o}", new_value, format_length),
68+ 16 => {
69+ let (lower_count, upper_count): (usize, usize) =
70+ number.chars().fold((0, 0), |(lower, upper), c| {
71+ (
72+ lower + c.is_ascii_lowercase() as usize,
73+ upper + c.is_ascii_uppercase() as usize,
74+ )
75+ });
76+ if upper_count > lower_count {
77+ format!("0x{:01$X}", new_value, format_length)
78+ } else {
79+ format!("0x{:01$x}", new_value, format_length)
80+ }
81+ }
82+ _ => unimplemented!("radix not supported: {}", radix),
83+ }
84+ };
85+
86+ // Add separators from original number.
87+ for &rtl_index in &separator_rtl_indexes {
88+ if rtl_index < new_text.len() {
89+ let new_index = new_text.len().saturating_sub(rtl_index);
90+ if new_index > 0 {
91+ new_text.insert(new_index, SEPARATOR);
92+ }
93+ }
94+ }
95+
96+ // Add in additional separators if necessary.
97+ if new_text.len() > selected_text.len() && !separator_rtl_indexes.is_empty() {
98+ let spacing = match separator_rtl_indexes.as_slice() {
99+ [.., b, a] => a - b - 1,
100+ _ => separator_rtl_indexes[0],
101+ };
102+
103+ let prefix_length = if radix == 10 { 0 } else { 2 };
104+ if let Some(mut index) = new_text.find(SEPARATOR) {
105+ while index - prefix_length > spacing {
106+ index -= spacing;
107+ new_text.insert(index, SEPARATOR);
108+ }
109+ }
110+ }
111+
112+ Some(new_text)
113+}
114+
115+#[cfg(test)]
116+mod test {
117+ use super::*;
118+
119+ #[test]
120+ fn test_increment_basic_decimal_numbers() {
121+ let tests = [
122+ ("100", 1, "101"),
123+ ("100", -1, "99"),
124+ ("99", 1, "100"),
125+ ("100", 1000, "1100"),
126+ ("100", -1000, "-900"),
127+ ("-1", 1, "0"),
128+ ("-1", 2, "1"),
129+ ("1", -1, "0"),
130+ ("1", -2, "-1"),
131+ ];
132+
133+ for (original, amount, expected) in tests {
134+ assert_eq!(increment(original, amount).unwrap(), expected);
135+ }
136+ }
137+
138+ #[test]
139+ fn test_increment_basic_hexadecimal_numbers() {
140+ let tests = [
141+ ("0x0100", 1, "0x0101"),
142+ ("0x0100", -1, "0x00ff"),
143+ ("0x0001", -1, "0x0000"),
144+ ("0x0000", -1, "0x0000"),
145+ ("0xffffffffffffffff", 1, "0x10000000000000000"),
146+ ("0xffffffffffffffff", 2, "0x10000000000000001"),
147+ ("0xffffffffffffffff", -1, "0xfffffffffffffffe"),
148+ ("0xABCDEF1234567890", 1, "0xABCDEF1234567891"),
149+ ("0xabcdef1234567890", 1, "0xabcdef1234567891"),
150+ ];
151+
152+ for (original, amount, expected) in tests {
153+ assert_eq!(increment(original, amount).unwrap(), expected);
154+ }
155+ }
156+
157+ #[test]
158+ fn test_increment_basic_octal_numbers() {
159+ let tests = [
160+ ("0o0107", 1, "0o0110"),
161+ ("0o0110", -1, "0o0107"),
162+ ("0o0001", -1, "0o0000"),
163+ ("0o7777", 1, "0o10000"),
164+ ("0o1000", -1, "0o0777"),
165+ ("0o0107", 10, "0o0121"),
166+ ("0o0000", -1, "0o0000"),
167+ ("0o1777777777777777777777", 1, "0o2000000000000000000000"),
168+ ("0o1777777777777777777777", 2, "0o2000000000000000000001"),
169+ ("0o1777777777777777777777", -1, "0o1777777777777777777776"),
170+ ];
171+
172+ for (original, amount, expected) in tests {
173+ assert_eq!(increment(original, amount).unwrap(), expected);
174+ }
175+ }
176+
177+ #[test]
178+ fn test_increment_basic_binary_numbers() {
179+ let tests = [
180+ ("0b00000100", 1, "0b00000101"),
181+ ("0b00000100", -1, "0b00000011"),
182+ ("0b00000100", 2, "0b00000110"),
183+ ("0b00000100", -2, "0b00000010"),
184+ ("0b00000001", -1, "0b00000000"),
185+ ("0b00111111", 10, "0b01001001"),
186+ ("0b11111111", 1, "0b100000000"),
187+ ("0b10000000", -1, "0b01111111"),
188+ ("0b0000", -1, "0b0000"),
189+ (
190+ "0b1111111111111111111111111111111111111111111111111111111111111111",
191+ 1,
192+ "0b10000000000000000000000000000000000000000000000000000000000000000",
193+ ),
194+ (
195+ "0b1111111111111111111111111111111111111111111111111111111111111111",
196+ 2,
197+ "0b10000000000000000000000000000000000000000000000000000000000000001",
198+ ),
199+ (
200+ "0b1111111111111111111111111111111111111111111111111111111111111111",
201+ -1,
202+ "0b1111111111111111111111111111111111111111111111111111111111111110",
203+ ),
204+ ];
205+
206+ for (original, amount, expected) in tests {
207+ assert_eq!(increment(original, amount).unwrap(), expected);
208+ }
209+ }
210+
211+ #[test]
212+ fn test_increment_with_separators() {
213+ let tests = [
214+ ("999_999", 1, "1_000_000"),
215+ ("1_000_000", -1, "999_999"),
216+ ("-999_999", -1, "-1_000_000"),
217+ ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"),
218+ ("0x0000_0000", -1, "0x0000_0000"),
219+ ("0x0000_0000_0000", -1, "0x0000_0000_0000"),
220+ ("0b01111111_11111111", 1, "0b10000000_00000000"),
221+ ("0b11111111_11111111", 1, "0b1_00000000_00000000"),
222+ ];
223+
224+ for (original, amount, expected) in tests {
225+ assert_eq!(increment(original, amount).unwrap(), expected);
226+ }
227+ }
228+
229+ #[test]
230+ fn test_leading_and_trailing_separators_arent_a_match() {
231+ assert_eq!(increment("9_", 1), None);
232+ assert_eq!(increment("_9", 1), None);
233+ assert_eq!(increment("_9_", 1), None);
234+ }
235+}
helix-core/src/increment/mod.rs+7−5
@@ -1,8 +1,10 @@
1−pub mod date_time;
2−pub mod number;
1+mod date_time;
2+mod integer;
33
4−use crate::{Range, Tendril};
4+pub fn integer(selected_text: &str, amount: i64) -> Option<String> {
5+ integer::increment(selected_text, amount)
6+}
57
6−pub trait Increment {
7− fn increment(&self, amount: i64) -> (Range, Tendril);
8+pub fn date_time(selected_text: &str, amount: i64) -> Option<String> {
9+ date_time::increment(selected_text, amount)
810}
helix-core/src/increment/number.rsdeleted−507
@@ -1,507 +0,0 @@
1−use std::borrow::Cow;
2−
3−use ropey::RopeSlice;
4−
5−use super::Increment;
6−
7−use crate::{
8− textobject::{textobject_word, TextObject},
9− Range, Tendril,
10−};
11−
12−#[derive(Debug, PartialEq, Eq)]
13−pub struct NumberIncrementor<'a> {
14− value: i64,
15− radix: u32,
16− range: Range,
17−
18− text: RopeSlice<'a>,
19−}
20−
21−impl<'a> NumberIncrementor<'a> {
22− /// Return information about number under rang if there is one.
23− pub fn from_range(text: RopeSlice, range: Range) -> Option<NumberIncrementor> {
24− // If the cursor is on the minus sign of a number we want to get the word textobject to the
25− // right of it.
26− let range = if range.to() < text.len_chars()
27− && range.to() - range.from() <= 1
28− && text.char(range.from()) == '-'
29− {
30− Range::new(range.from() + 1, range.to() + 1)
31− } else {
32− range
33− };
34−
35− let range = textobject_word(text, range, TextObject::Inside, 1, false);
36−
37− // If there is a minus sign to the left of the word object, we want to include it in the range.
38− let range = if range.from() > 0 && text.char(range.from() - 1) == '-' {
39− range.extend(range.from() - 1, range.from())
40− } else {
41− range
42− };
43−
44− let word: String = text
45− .slice(range.from()..range.to())
46− .chars()
47− .filter(|&c| c != '_')
48− .collect();
49− let (radix, prefixed) = if word.starts_with("0x") {
50− (16, true)
51− } else if word.starts_with("0o") {
52− (8, true)
53− } else if word.starts_with("0b") {
54− (2, true)
55− } else {
56− (10, false)
57− };
58−
59− let number = if prefixed { &word[2..] } else { &word };
60−
61− let value = i128::from_str_radix(number, radix).ok()?;
62− if (value.is_positive() && value.leading_zeros() < 64)
63− || (value.is_negative() && value.leading_ones() < 64)
64− {
65− return None;
66− }
67−
68− let value = value as i64;
69− Some(NumberIncrementor {
70− range,
71− value,
72− radix,
73− text,
74− })
75− }
76−}
77−
78−impl<'a> Increment for NumberIncrementor<'a> {
79− fn increment(&self, amount: i64) -> (Range, Tendril) {
80− let old_text: Cow<str> = self.text.slice(self.range.from()..self.range.to()).into();
81− let old_length = old_text.len();
82− let new_value = self.value.wrapping_add(amount);
83−
84− // Get separator indexes from right to left.
85− let separator_rtl_indexes: Vec<usize> = old_text
86− .chars()
87− .rev()
88− .enumerate()
89− .filter_map(|(i, c)| if c == '_' { Some(i) } else { None })
90− .collect();
91−
92− let format_length = if self.radix == 10 {
93− match (self.value.is_negative(), new_value.is_negative()) {
94− (true, false) => old_length - 1,
95− (false, true) => old_length + 1,
96− _ => old_text.len(),
97− }
98− } else {
99− old_text.len() - 2
100− } - separator_rtl_indexes.len();
101−
102− let mut new_text = match self.radix {
103− 2 => format!("0b{:01$b}", new_value, format_length),
104− 8 => format!("0o{:01$o}", new_value, format_length),
105− 10 if old_text.starts_with('0') || old_text.starts_with("-0") => {
106− format!("{:01$}", new_value, format_length)
107− }
108− 10 => format!("{}", new_value),
109− 16 => {
110− let (lower_count, upper_count): (usize, usize) =
111− old_text.chars().skip(2).fold((0, 0), |(lower, upper), c| {
112− (
113− lower + usize::from(c.is_ascii_lowercase()),
114− upper + usize::from(c.is_ascii_uppercase()),
115− )
116− });
117− if upper_count > lower_count {
118− format!("0x{:01$X}", new_value, format_length)
119− } else {
120− format!("0x{:01$x}", new_value, format_length)
121− }
122− }
123− _ => unimplemented!("radix not supported: {}", self.radix),
124− };
125−
126− // Add separators from original number.
127− for &rtl_index in &separator_rtl_indexes {
128− if rtl_index < new_text.len() {
129− let new_index = new_text.len() - rtl_index;
130− new_text.insert(new_index, '_');
131− }
132− }
133−
134− // Add in additional separators if necessary.
135− if new_text.len() > old_length && !separator_rtl_indexes.is_empty() {
136− let spacing = match separator_rtl_indexes.as_slice() {
137− [.., b, a] => a - b - 1,
138− _ => separator_rtl_indexes[0],
139− };
140−
141− let prefix_length = if self.radix == 10 { 0 } else { 2 };
142− if let Some(mut index) = new_text.find('_') {
143− while index - prefix_length > spacing {
144− index -= spacing;
145− new_text.insert(index, '_');
146− }
147− }
148− }
149−
150− (self.range, new_text.into())
151− }
152−}
153−
154−#[cfg(test)]
155−mod test {
156− use super::*;
157− use crate::Rope;
158−
159− #[test]
160− fn test_decimal_at_point() {
161− let rope = Rope::from_str("Test text 12345 more text.");
162− let range = Range::point(12);
163− assert_eq!(
164− NumberIncrementor::from_range(rope.slice(..), range),
165− Some(NumberIncrementor {
166− range: Range::new(10, 15),
167− value: 12345,
168− radix: 10,
169− text: rope.slice(..),
170− })
171− );
172− }
173−
174− #[test]
175− fn test_uppercase_hexadecimal_at_point() {
176− let rope = Rope::from_str("Test text 0x123ABCDEF more text.");
177− let range = Range::point(12);
178− assert_eq!(
179− NumberIncrementor::from_range(rope.slice(..), range),
180− Some(NumberIncrementor {
181− range: Range::new(10, 21),
182− value: 0x123ABCDEF,
183− radix: 16,
184− text: rope.slice(..),
185− })
186− );
187− }
188−
189− #[test]
190− fn test_lowercase_hexadecimal_at_point() {
191− let rope = Rope::from_str("Test text 0xfa3b4e more text.");
192− let range = Range::point(12);
193− assert_eq!(
194− NumberIncrementor::from_range(rope.slice(..), range),
195− Some(NumberIncrementor {
196− range: Range::new(10, 18),
197− value: 0xfa3b4e,
198− radix: 16,
199− text: rope.slice(..),
200− })
201− );
202− }
203−
204− #[test]
205− fn test_octal_at_point() {
206− let rope = Rope::from_str("Test text 0o1074312 more text.");
207− let range = Range::point(12);
208− assert_eq!(
209− NumberIncrementor::from_range(rope.slice(..), range),
210− Some(NumberIncrementor {
211− range: Range::new(10, 19),
212− value: 0o1074312,
213− radix: 8,
214− text: rope.slice(..),
215− })
216− );
217− }
218−
219− #[test]
220− fn test_binary_at_point() {
221− let rope = Rope::from_str("Test text 0b10111010010101 more text.");
222− let range = Range::point(12);
223− assert_eq!(
224− NumberIncrementor::from_range(rope.slice(..), range),
225− Some(NumberIncrementor {
226− range: Range::new(10, 26),
227− value: 0b10111010010101,
228− radix: 2,
229− text: rope.slice(..),
230− })
231− );
232− }
233−
234− #[test]
235− fn test_negative_decimal_at_point() {
236− let rope = Rope::from_str("Test text -54321 more text.");
237− let range = Range::point(12);
238− assert_eq!(
239− NumberIncrementor::from_range(rope.slice(..), range),
240− Some(NumberIncrementor {
241− range: Range::new(10, 16),
242− value: -54321,
243− radix: 10,
244− text: rope.slice(..),
245− })
246− );
247− }
248−
249− #[test]
250− fn test_decimal_with_leading_zeroes_at_point() {
251− let rope = Rope::from_str("Test text 000045326 more text.");
252− let range = Range::point(12);
253− assert_eq!(
254− NumberIncrementor::from_range(rope.slice(..), range),
255− Some(NumberIncrementor {
256− range: Range::new(10, 19),
257− value: 45326,
258− radix: 10,
259− text: rope.slice(..),
260− })
261− );
262− }
263−
264− #[test]
265− fn test_negative_decimal_cursor_on_minus_sign() {
266− let rope = Rope::from_str("Test text -54321 more text.");
267− let range = Range::point(10);
268− assert_eq!(
269− NumberIncrementor::from_range(rope.slice(..), range),
270− Some(NumberIncrementor {
271− range: Range::new(10, 16),
272− value: -54321,
273− radix: 10,
274− text: rope.slice(..),
275− })
276− );
277− }
278−
279− #[test]
280− fn test_number_under_range_start_of_rope() {
281− let rope = Rope::from_str("100");
282− let range = Range::point(0);
283− assert_eq!(
284− NumberIncrementor::from_range(rope.slice(..), range),
285− Some(NumberIncrementor {
286− range: Range::new(0, 3),
287− value: 100,
288− radix: 10,
289− text: rope.slice(..),
290− })
291− );
292− }
293−
294− #[test]
295− fn test_number_under_range_end_of_rope() {
296− let rope = Rope::from_str("100");
297− let range = Range::point(2);
298− assert_eq!(
299− NumberIncrementor::from_range(rope.slice(..), range),
300− Some(NumberIncrementor {
301− range: Range::new(0, 3),
302− value: 100,
303− radix: 10,
304− text: rope.slice(..),
305− })
306− );
307− }
308−
309− #[test]
310− fn test_number_surrounded_by_punctuation() {
311− let rope = Rope::from_str(",100;");
312− let range = Range::point(1);
313− assert_eq!(
314− NumberIncrementor::from_range(rope.slice(..), range),
315− Some(NumberIncrementor {
316− range: Range::new(1, 4),
317− value: 100,
318− radix: 10,
319− text: rope.slice(..),
320− })
321− );
322− }
323−
324− #[test]
325− fn test_not_a_number_point() {
326− let rope = Rope::from_str("Test text 45326 more text.");
327− let range = Range::point(6);
328− assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None);
329− }
330−
331− #[test]
332− fn test_number_too_large_at_point() {
333− let rope = Rope::from_str("Test text 0xFFFFFFFFFFFFFFFFF more text.");
334− let range = Range::point(12);
335− assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None);
336− }
337−
338− #[test]
339− fn test_number_cursor_one_right_of_number() {
340− let rope = Rope::from_str("100 ");
341− let range = Range::point(3);
342− assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None);
343− }
344−
345− #[test]
346− fn test_number_cursor_one_left_of_number() {
347− let rope = Rope::from_str(" 100");
348− let range = Range::point(0);
349− assert_eq!(NumberIncrementor::from_range(rope.slice(..), range), None);
350− }
351−
352− #[test]
353− fn test_increment_basic_decimal_numbers() {
354− let tests = [
355− ("100", 1, "101"),
356− ("100", -1, "99"),
357− ("99", 1, "100"),
358− ("100", 1000, "1100"),
359− ("100", -1000, "-900"),
360− ("-1", 1, "0"),
361− ("-1", 2, "1"),
362− ("1", -1, "0"),
363− ("1", -2, "-1"),
364− ];
365−
366− for (original, amount, expected) in tests {
367− let rope = Rope::from_str(original);
368− let range = Range::point(0);
369− assert_eq!(
370− NumberIncrementor::from_range(rope.slice(..), range)
371− .unwrap()
372− .increment(amount)
373− .1,
374− Tendril::from(expected)
375− );
376− }
377− }
378−
379− #[test]
380− fn test_increment_basic_hexadecimal_numbers() {
381− let tests = [
382− ("0x0100", 1, "0x0101"),
383− ("0x0100", -1, "0x00ff"),
384− ("0x0001", -1, "0x0000"),
385− ("0x0000", -1, "0xffffffffffffffff"),
386− ("0xffffffffffffffff", 1, "0x0000000000000000"),
387− ("0xffffffffffffffff", 2, "0x0000000000000001"),
388− ("0xffffffffffffffff", -1, "0xfffffffffffffffe"),
389− ("0xABCDEF1234567890", 1, "0xABCDEF1234567891"),
390− ("0xabcdef1234567890", 1, "0xabcdef1234567891"),
391− ];
392−
393− for (original, amount, expected) in tests {
394− let rope = Rope::from_str(original);
395− let range = Range::point(0);
396− assert_eq!(
397− NumberIncrementor::from_range(rope.slice(..), range)
398− .unwrap()
399− .increment(amount)
400− .1,
401− Tendril::from(expected)
402− );
403− }
404− }
405−
406− #[test]
407− fn test_increment_basic_octal_numbers() {
408− let tests = [
409− ("0o0107", 1, "0o0110"),
410− ("0o0110", -1, "0o0107"),
411− ("0o0001", -1, "0o0000"),
412− ("0o7777", 1, "0o10000"),
413− ("0o1000", -1, "0o0777"),
414− ("0o0107", 10, "0o0121"),
415− ("0o0000", -1, "0o1777777777777777777777"),
416− ("0o1777777777777777777777", 1, "0o0000000000000000000000"),
417− ("0o1777777777777777777777", 2, "0o0000000000000000000001"),
418− ("0o1777777777777777777777", -1, "0o1777777777777777777776"),
419− ];
420−
421− for (original, amount, expected) in tests {
422− let rope = Rope::from_str(original);
423− let range = Range::point(0);
424− assert_eq!(
425− NumberIncrementor::from_range(rope.slice(..), range)
426− .unwrap()
427− .increment(amount)
428− .1,
429− Tendril::from(expected)
430− );
431− }
432− }
433−
434− #[test]
435− fn test_increment_basic_binary_numbers() {
436− let tests = [
437− ("0b00000100", 1, "0b00000101"),
438− ("0b00000100", -1, "0b00000011"),
439− ("0b00000100", 2, "0b00000110"),
440− ("0b00000100", -2, "0b00000010"),
441− ("0b00000001", -1, "0b00000000"),
442− ("0b00111111", 10, "0b01001001"),
443− ("0b11111111", 1, "0b100000000"),
444− ("0b10000000", -1, "0b01111111"),
445− (
446− "0b0000",
447− -1,
448− "0b1111111111111111111111111111111111111111111111111111111111111111",
449− ),
450− (
451− "0b1111111111111111111111111111111111111111111111111111111111111111",
452− 1,
453− "0b0000000000000000000000000000000000000000000000000000000000000000",
454− ),
455− (
456− "0b1111111111111111111111111111111111111111111111111111111111111111",
457− 2,
458− "0b0000000000000000000000000000000000000000000000000000000000000001",
459− ),
460− (
461− "0b1111111111111111111111111111111111111111111111111111111111111111",
462− -1,
463− "0b1111111111111111111111111111111111111111111111111111111111111110",
464− ),
465− ];
466−
467− for (original, amount, expected) in tests {
468− let rope = Rope::from_str(original);
469− let range = Range::point(0);
470− assert_eq!(
471− NumberIncrementor::from_range(rope.slice(..), range)
472− .unwrap()
473− .increment(amount)
474− .1,
475− Tendril::from(expected)
476− );
477− }
478− }
479−
480− #[test]
481− fn test_increment_with_separators() {
482− let tests = [
483− ("999_999", 1, "1_000_000"),
484− ("1_000_000", -1, "999_999"),
485− ("-999_999", -1, "-1_000_000"),
486− ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"),
487− ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"),
488− ("0x0000_0000_0001", 0x1_ffff_0000, "0x0001_ffff_0001"),
489− ("0x0000_0000", -1, "0xffff_ffff_ffff_ffff"),
490− ("0x0000_0000_0000", -1, "0xffff_ffff_ffff_ffff"),
491− ("0b01111111_11111111", 1, "0b10000000_00000000"),
492− ("0b11111111_11111111", 1, "0b1_00000000_00000000"),
493− ];
494−
495− for (original, amount, expected) in tests {
496− let rope = Rope::from_str(original);
497− let range = Range::point(0);
498− assert_eq!(
499− NumberIncrementor::from_range(rope.slice(..), range)
500− .unwrap()
501− .increment(amount)
502− .1,
503− Tendril::from(expected)
504− );
505− }
506− }
507−}
helix-core/src/indent.rs+702−200
@@ -1,12 +1,14 @@
1−use std::collections::HashMap;
1+use std::{borrow::Cow, collections::HashMap};
22
33use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
44
55use crate::{
66 chars::{char_is_line_ending, char_is_whitespace},
7− syntax::{LanguageConfiguration, RopeProvider, Syntax},
7+ find_first_non_whitespace_char,
8+ graphemes::{grapheme_width, tab_width_at},
9+ syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
810 tree_sitter::Node,
9− Rope, RopeSlice,
11+ Position, Rope, RopeGraphemes, RopeSlice,
1012};
1113
1214/// Enum representing indentation style.
@@ -18,6 +20,10 @@ pub enum IndentStyle {
1820 Spaces(u8),
1921}
2022
23+// 16 spaces
24+const INDENTS: &str = " ";
25+pub const MAX_INDENT: u8 = 16;
26+
2127impl IndentStyle {
2228 /// Creates an `IndentStyle` from an indentation string.
2329 ///
@@ -26,10 +32,10 @@ impl IndentStyle {
2632 #[inline]
2733 pub fn from_str(indent: &str) -> Self {
2834 // XXX: do we care about validating the input more than this? Probably not...?
29− debug_assert!(!indent.is_empty() && indent.len() <= 8);
35+ debug_assert!(!indent.is_empty() && indent.len() <= MAX_INDENT as usize);
3036
3137 if indent.starts_with(' ') {
32− IndentStyle::Spaces(indent.len() as u8)
38+ IndentStyle::Spaces(indent.len().clamp(1, MAX_INDENT as usize) as u8)
3339 } else {
3440 IndentStyle::Tabs
3541 }
@@ -39,23 +45,24 @@ impl IndentStyle {
3945 pub fn as_str(&self) -> &'static str {
4046 match *self {
4147 IndentStyle::Tabs => "\t",
42− IndentStyle::Spaces(1) => " ",
43− IndentStyle::Spaces(2) => " ",
44− IndentStyle::Spaces(3) => " ",
45− IndentStyle::Spaces(4) => " ",
46− IndentStyle::Spaces(5) => " ",
47− IndentStyle::Spaces(6) => " ",
48− IndentStyle::Spaces(7) => " ",
49− IndentStyle::Spaces(8) => " ",
50−
51− // Unsupported indentation style. This should never happen,
52− // but just in case fall back to two spaces.
5348 IndentStyle::Spaces(n) => {
54− debug_assert!(n > 0 && n <= 8); // Always triggers. `debug_panic!()` wanted.
55− " "
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]
5655 }
5756 }
5857 }
58+
59+ #[inline]
60+ pub fn indent_width(&self, tab_width: usize) -> usize {
61+ match *self {
62+ IndentStyle::Tabs => tab_width,
63+ IndentStyle::Spaces(width) => width as usize,
64+ }
65+ }
5966}
6067
6168/// Attempts to detect the indentation style used in a document.
@@ -66,9 +73,9 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
6673 // Build a histogram of the indentation *increases* between
6774 // subsequent lines, ignoring lines that are all whitespace.
6875 //
69− // Index 0 is for tabs, the rest are 1-8 spaces.
70− let histogram: [usize; 9] = {
71− let mut histogram = [0; 9];
76+ // Index 0 is for tabs, the rest are 1-MAX_INDENT spaces.
77+ let histogram: [usize; MAX_INDENT as usize + 1] = {
78+ let mut histogram = [0; MAX_INDENT as usize + 1];
7279 let mut prev_line_is_tabs = false;
7380 let mut prev_line_leading_count = 0usize;
7481
@@ -127,7 +134,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
127134 histogram[0] += 1;
128135 } else {
129136 let amount = leading_count - prev_line_leading_count;
130− if amount <= 8 {
137+ if amount <= MAX_INDENT as usize {
131138 histogram[amount] += 1;
132139 }
133140 }
@@ -177,17 +184,67 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
177184
178185/// To determine indentation of a newly inserted line, figure out the indentation at the last col
179186/// of the previous line.
180−pub fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
187+pub fn indent_level_for_line(line: RopeSlice, tab_width: usize, indent_width: usize) -> usize {
181188 let mut len = 0;
182189 for ch in line.chars() {
183190 match ch {
184− '\t' => len += tab_width,
191+ '\t' => len += tab_width_at(len, tab_width as u16),
185192 ' ' => len += 1,
186193 _ => break,
187194 }
188195 }
189196
190− len / tab_width
197+ len / indent_width
198+}
199+
200+/// Create a string of tabs & spaces that has the same visual width as the given RopeSlice (independent of the tab width).
201+fn whitespace_with_same_width(text: RopeSlice) -> String {
202+ let mut s = String::new();
203+ for grapheme in RopeGraphemes::new(text) {
204+ if grapheme == "\t" {
205+ s.push('\t');
206+ } else {
207+ s.extend(std::iter::repeat(' ').take(grapheme_width(&Cow::from(grapheme))));
208+ }
209+ }
210+ s
211+}
212+
213+fn add_indent_level(
214+ mut base_indent: String,
215+ added_indent_level: isize,
216+ indent_style: &IndentStyle,
217+ tab_width: usize,
218+) -> String {
219+ if added_indent_level >= 0 {
220+ // Adding a non-negative indent is easy, we can simply append the indent string
221+ base_indent.push_str(&indent_style.as_str().repeat(added_indent_level as usize));
222+ base_indent
223+ } else {
224+ // In this case, we want to return a prefix of `base_indent`.
225+ // Since the width of a tab depends on its offset, we cannot simply iterate over
226+ // the chars of `base_indent` in reverse until we have the desired indent reduction,
227+ // instead we iterate over them twice in forward direction.
228+ let base_indent_rope = RopeSlice::from(base_indent.as_str());
229+ #[allow(deprecated)]
230+ let base_indent_width =
231+ crate::visual_coords_at_pos(base_indent_rope, base_indent_rope.len_chars(), tab_width)
232+ .col;
233+ let target_indent_width = base_indent_width
234+ .saturating_sub((-added_indent_level) as usize * indent_style.indent_width(tab_width));
235+ #[allow(deprecated)]
236+ let char_end_idx = crate::pos_at_visual_coords(
237+ base_indent_rope,
238+ Position {
239+ row: 0,
240+ col: target_indent_width,
241+ },
242+ tab_width,
243+ );
244+ let byte_end_idx = base_indent_rope.char_to_byte(char_end_idx);
245+ base_indent.truncate(byte_end_idx);
246+ base_indent
247+ }
191248}
192249
193250/// Computes for node and all ancestors whether they are the first node on their line.
@@ -231,68 +288,140 @@ fn get_first_in_line(mut node: Node, new_line_byte_pos: Option<usize>) -> Vec<bo
231288/// This is usually constructed in one of 2 ways:
232289/// - Successively add indent captures to get the (added) indent from a single line
233290/// - Successively add the indent results for each line
234−#[derive(Default)]
235−pub struct Indentation {
236− /// The total indent (the number of indent levels) is defined as max(0, indent-outdent).
237− /// The string that this results in depends on the indent style (spaces or tabs, etc.)
291+/// The string that this indentation defines starts with the string contained in the align field (unless it is None), followed by:
292+/// - max(0, indent - outdent) tabs, if tabs are used for indentation
293+/// - max(0, indent - outdent)*indent_width spaces, if spaces are used for indentation
294+#[derive(Default, Debug, PartialEq, Eq, Clone)]
295+pub struct Indentation<'a> {
238296 indent: usize,
297+ indent_always: usize,
239298 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>>,
240303}
241−impl Indentation {
304+
305+impl<'a> Indentation<'a> {
242306 /// Add some other [Indentation] to this.
243− /// The added indent should be the total added indent from one line
244− fn add_line(&mut self, added: &Indentation) {
245− if added.indent > 0 && added.outdent == 0 {
246− self.indent += 1;
247− } else if added.outdent > 0 && added.indent == 0 {
248− self.outdent += 1;
307+ /// The added indent should be the total added indent from one line.
308+ /// Indent should always be added starting from the bottom (or equivalently, the innermost tree-sitter node).
309+ fn add_line(&mut self, added: Indentation<'a>) {
310+ // Align overrides the indent from outer scopes.
311+ if self.align.is_some() {
312+ return;
249313 }
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;
250322 }
323+
251324 /// Add an indent capture to this indent.
252− /// All the captures that are added in this way should be on the same line.
253− fn add_capture(&mut self, added: IndentCaptureType) {
325+ /// Only captures that apply to the same line should be added together in this way (otherwise use `add_line`)
326+ /// and the captures should be added starting from the innermost tree-sitter node (currently this only matters
327+ /// if multiple `@align` patterns occur on the same line).
328+ fn add_capture(&mut self, added: IndentCaptureType<'a>) {
254329 match added {
255330 IndentCaptureType::Indent => {
256− self.indent = 1;
331+ if self.indent_always == 0 {
332+ self.indent = 1;
333+ }
334+ }
335+ IndentCaptureType::IndentAlways => {
336+ // any time we encounter an `indent.always` on the same line, we
337+ // want to cancel out all regular indents
338+ self.indent_always += 1;
339+ self.indent = 0;
257340 }
258341 IndentCaptureType::Outdent => {
259− self.outdent = 1;
342+ if self.outdent_always == 0 {
343+ self.outdent = 1;
344+ }
345+ }
346+ IndentCaptureType::OutdentAlways => {
347+ self.outdent_always += 1;
348+ self.outdent = 0;
349+ }
350+ IndentCaptureType::Align(align) => {
351+ if self.align.is_none() {
352+ self.align = Some(align);
353+ }
260354 }
261355 }
262356 }
263− fn as_string(&self, indent_style: &IndentStyle) -> String {
264− let indent_level = if self.indent >= self.outdent {
265− self.indent - self.outdent
357+ fn net_indent(&self) -> isize {
358+ (self.indent + self.indent_always) as isize
359+ - ((self.outdent + self.outdent_always) as isize)
360+ }
361+ /// Convert `self` into a string, taking into account the computed and actual indentation of some other line.
362+ fn relative_indent(
363+ &self,
364+ other_computed_indent: &Self,
365+ other_leading_whitespace: RopeSlice,
366+ indent_style: &IndentStyle,
367+ tab_width: usize,
368+ ) -> Option<String> {
369+ if self.align == other_computed_indent.align {
370+ // If self and baseline are either not aligned to anything or both aligned the same way,
371+ // we can simply take `other_leading_whitespace` and add some indent / outdent to it (in the second
372+ // case, the alignment should already be accounted for in `other_leading_whitespace`).
373+ let indent_diff = self.net_indent() - other_computed_indent.net_indent();
374+ Some(add_indent_level(
375+ String::from(other_leading_whitespace),
376+ indent_diff,
377+ indent_style,
378+ tab_width,
379+ ))
266380 } else {
267− log::warn!("Encountered more outdent than indent nodes while calculating indentation: {} outdent, {} indent", self.outdent, self.indent);
268− 0
269− };
270− indent_style.as_str().repeat(indent_level)
381+ // If the alignment of both lines is different, we cannot compare their indentation in any meaningful way
382+ None
383+ }
384+ }
385+ pub fn to_string(&self, indent_style: &IndentStyle, tab_width: usize) -> String {
386+ add_indent_level(
387+ self.align
388+ .map_or_else(String::new, whitespace_with_same_width),
389+ self.net_indent(),
390+ indent_style,
391+ tab_width,
392+ )
271393 }
272394}
273395
274396/// An indent definition which corresponds to a capture from the indent query
275−struct IndentCapture {
276− capture_type: IndentCaptureType,
397+#[derive(Debug)]
398+struct IndentCapture<'a> {
399+ capture_type: IndentCaptureType<'a>,
277400 scope: IndentScope,
278401}
279−#[derive(Clone, Copy)]
280−enum IndentCaptureType {
402+#[derive(Debug, Clone, PartialEq)]
403+enum IndentCaptureType<'a> {
281404 Indent,
405+ IndentAlways,
282406 Outdent,
407+ OutdentAlways,
408+ /// Alignment given as a string of whitespace
409+ Align(RopeSlice<'a>),
283410}
284−impl IndentCaptureType {
411+
412+impl<'a> IndentCaptureType<'a> {
285413 fn default_scope(&self) -> IndentScope {
286414 match self {
287− IndentCaptureType::Indent => IndentScope::Tail,
288− IndentCaptureType::Outdent => IndentScope::All,
415+ IndentCaptureType::Indent | IndentCaptureType::IndentAlways => IndentScope::Tail,
416+ IndentCaptureType::Outdent | IndentCaptureType::OutdentAlways => IndentScope::All,
417+ IndentCaptureType::Align(_) => IndentScope::All,
289418 }
290419 }
291420}
292421/// This defines which part of a node an [IndentCapture] applies to.
293422/// Each [IndentCaptureType] has a default scope, but the scope can be changed
294423/// with `#set!` property declarations.
295−#[derive(Clone, Copy)]
424+#[derive(Debug, Clone, Copy)]
296425enum IndentScope {
297426 /// The indent applies to the whole node
298427 All,
@@ -302,6 +431,7 @@ enum IndentScope {
302431
303432/// A capture from the indent query which does not define an indent but extends
304433/// the range of a node. This is used before the indent is calculated.
434+#[derive(Debug)]
305435enum ExtendCapture {
306436 Extend,
307437 PreventOnce,
@@ -310,24 +440,41 @@ enum ExtendCapture {
310440/// The result of running a tree-sitter indent query. This stores for
311441/// each node (identified by its ID) the relevant captures (already filtered
312442/// by predicates).
313−struct IndentQueryResult {
314− indent_captures: HashMap<usize, Vec<IndentCapture>>,
443+#[derive(Debug)]
444+struct IndentQueryResult<'a> {
445+ indent_captures: HashMap<usize, Vec<IndentCapture<'a>>>,
315446 extend_captures: HashMap<usize, Vec<ExtendCapture>>,
316447}
317448
318−fn query_indents(
449+fn get_node_start_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
450+ let mut node_line = node.start_position().row;
451+ // Adjust for the new line that will be inserted
452+ if new_line_byte_pos.map_or(false, |pos| node.start_byte() >= pos) {
453+ node_line += 1;
454+ }
455+ node_line
456+}
457+fn get_node_end_line(node: Node, new_line_byte_pos: Option<usize>) -> usize {
458+ let mut node_line = node.end_position().row;
459+ // Adjust for the new line that will be inserted (with a strict inequality since end_byte is exclusive)
460+ if new_line_byte_pos.map_or(false, |pos| node.end_byte() > pos) {
461+ node_line += 1;
462+ }
463+ node_line
464+}
465+
466+fn query_indents<'a>(
319467 query: &Query,
320468 syntax: &Syntax,
321469 cursor: &mut QueryCursor,
322− text: RopeSlice,
470+ text: RopeSlice<'a>,
323471 range: std::ops::Range<usize>,
324− // Position of the (optional) newly inserted line break.
325− // Given as (line, byte_pos)
326− new_line_break: Option<(usize, usize)>,
327−) -> IndentQueryResult {
472+ new_line_byte_pos: Option<usize>,
473+) -> IndentQueryResult<'a> {
328474 let mut indent_captures: HashMap<usize, Vec<IndentCapture>> = HashMap::new();
329475 let mut extend_captures: HashMap<usize, Vec<ExtendCapture>> = HashMap::new();
330476 cursor.set_byte_range(range);
477+
331478 // Iterate over all captures from the query
332479 for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
333480 // Skip matches where not all custom predicates are fulfilled
@@ -354,21 +501,13 @@ fn query_indents(
354501 Some(QueryPredicateArg::Capture(capt1)),
355502 Some(QueryPredicateArg::Capture(capt2))
356503 ) => {
357− let get_line_num = |node: Node| {
358− let mut node_line = node.start_position().row;
359− // Adjust for the new line that will be inserted
360− if let Some((line, byte)) = new_line_break {
361− if node_line==line && node.start_byte()>=byte {
362− node_line += 1;
363− }
364− }
365− node_line
366− };
367504 let n1 = m.nodes_for_capture_index(*capt1).next();
368505 let n2 = m.nodes_for_capture_index(*capt2).next();
369506 match (n1, n2) {
370507 (Some(n1), Some(n2)) => {
371− let same_line = get_line_num(n1)==get_line_num(n2);
508+ let n1_line = get_node_start_line(n1, new_line_byte_pos);
509+ let n2_line = get_node_start_line(n2, new_line_byte_pos);
510+ let same_line = n1_line == n2_line;
372511 same_line==(pred.operator.as_ref()=="same-line?")
373512 }
374513 _ => true,
@@ -379,6 +518,23 @@ fn query_indents(
379518 }
380519 }
381520 }
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+ },
382538 _ => {
383539 panic!(
384540 "Invalid indent query: Unknown predicate (\"{}\")",
@@ -389,11 +545,28 @@ fn query_indents(
389545 }) {
390546 continue;
391547 }
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;
392553 for capture in m.captures {
393554 let capture_name = query.capture_names()[capture.index as usize].as_str();
394555 let capture_type = match capture_name {
395556 "indent" => IndentCaptureType::Indent,
557+ "indent.always" => IndentCaptureType::IndentAlways,
396558 "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+ }
397570 "extend" => {
398571 extend_captures
399572 .entry(capture.node.id())
@@ -443,17 +616,40 @@ fn query_indents(
443616 }
444617 }
445618 }
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+ }
446638 indent_captures
447− .entry(capture.node.id())
448− // Most entries only need to contain a single IndentCapture
639+ .entry(node_id)
449640 .or_insert_with(|| Vec::with_capacity(1))
450− .push(indent_capture);
641+ .push(capture);
451642 }
452643 }
453− IndentQueryResult {
644+
645+ let result = IndentQueryResult {
454646 indent_captures,
455647 extend_captures,
456− }
648+ };
649+
650+ log::trace!("indent result = {:?}", result);
651+
652+ result
457653}
458654
459655/// Handle extend queries. deepest_preceding is the deepest descendant of node that directly precedes the cursor position.
@@ -461,117 +657,89 @@ fn query_indents(
461657/// so that the indent computation starts with the correct syntax node.
462658fn extend_nodes<'a>(
463659 node: &mut Node<'a>,
464− deepest_preceding: Option<Node<'a>>,
660+ mut deepest_preceding: Node<'a>,
465661 extend_captures: &HashMap<usize, Vec<ExtendCapture>>,
466662 text: RopeSlice,
467663 line: usize,
468664 tab_width: usize,
665+ indent_width: usize,
469666) {
470− if let Some(mut deepest_preceding) = deepest_preceding {
471− let mut stop_extend = false;
472− while deepest_preceding != *node {
473− let mut extend_node = false;
474− // This will be set to true if this node is captured, regardless of whether
475− // it actually will be extended (e.g. because the cursor isn't indented
476− // more than the node).
477− let mut node_captured = false;
478− if let Some(captures) = extend_captures.get(&deepest_preceding.id()) {
479− for capture in captures {
480− match capture {
481− ExtendCapture::PreventOnce => {
482− stop_extend = true;
483− }
484− ExtendCapture::Extend => {
485− node_captured = true;
486− // We extend the node if
487− // - the cursor is on the same line as the end of the node OR
488− // - the line that the cursor is on is more indented than the
489− // first line of the node
490− if deepest_preceding.end_position().row == line {
667+ let mut stop_extend = false;
668+
669+ while deepest_preceding != *node {
670+ let mut extend_node = false;
671+ // This will be set to true if this node is captured, regardless of whether
672+ // it actually will be extended (e.g. because the cursor isn't indented
673+ // more than the node).
674+ let mut node_captured = false;
675+ if let Some(captures) = extend_captures.get(&deepest_preceding.id()) {
676+ for capture in captures {
677+ match capture {
678+ ExtendCapture::PreventOnce => {
679+ stop_extend = true;
680+ }
681+ ExtendCapture::Extend => {
682+ node_captured = true;
683+ // We extend the node if
684+ // - the cursor is on the same line as the end of the node OR
685+ // - the line that the cursor is on is more indented than the
686+ // first line of the node
687+ if deepest_preceding.end_position().row == line {
688+ extend_node = true;
689+ } else {
690+ let cursor_indent =
691+ indent_level_for_line(text.line(line), tab_width, indent_width);
692+ let node_indent = indent_level_for_line(
693+ text.line(deepest_preceding.start_position().row),
694+ tab_width,
695+ indent_width,
696+ );
697+ if cursor_indent > node_indent {
491698 extend_node = true;
492− } else {
493− let cursor_indent =
494− indent_level_for_line(text.line(line), tab_width);
495− let node_indent = indent_level_for_line(
496− text.line(deepest_preceding.start_position().row),
497− tab_width,
498− );
499− if cursor_indent > node_indent {
500− extend_node = true;
501− }
502699 }
503700 }
504701 }
505702 }
506703 }
507− // If we encountered some `StopExtend` capture before, we don't
508− // extend the node even if we otherwise would
509− if node_captured && stop_extend {
510− stop_extend = false;
511− } else if extend_node && !stop_extend {
512− *node = deepest_preceding;
513− break;
514− }
515− // This parent always exists since node is an ancestor of deepest_preceding
516− deepest_preceding = deepest_preceding.parent().unwrap();
704+ }
705+ // If we encountered some `StopExtend` capture before, we don't
706+ // extend the node even if we otherwise would
707+ if node_captured && stop_extend {
708+ stop_extend = false;
709+ } else if extend_node && !stop_extend {
710+ *node = deepest_preceding;
711+ break;
712+ }
713+ // If the tree contains a syntax error, `deepest_preceding` may not
714+ // have a parent despite being a descendant of `node`.
715+ deepest_preceding = match deepest_preceding.parent() {
716+ Some(parent) => parent,
717+ None => return,
517718 }
518719 }
519720}
520721
521−/// Use the syntax tree to determine the indentation for a given position.
522−/// This can be used in 2 ways:
523−///
524−/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
525−/// - In this case, pos should be inside the first tree-sitter node on that line.
526−/// In most cases, this can just be the first non-whitespace on that line.
527−/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
528−/// after pos were moved to a new line.
529−///
530−/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
531−/// Each of these nodes produces some [Indentation] for:
532−///
533−/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
534−/// - The line after the node. This is defined by:
535−/// - The scope `tail`.
536−/// - The scope `all` if this node is not the first node on its line.
537−/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
538−/// The indents from different nodes for the same line are then combined.
539−/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
540−///
541−/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
542−/// ```ignore
543−/// some_function(|| {
544−/// // Both the function parameters as well as the contained block should be indented.
545−/// // Because they are on the same line, this only yields one indent level
546−/// });
547−/// ```
548−///
549−/// ```ignore
550−/// some_function(
551−/// param1,
552−/// || {
553−/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
554−/// },
555−/// );
556−/// ```
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.
557725#[allow(clippy::too_many_arguments)]
558−pub fn treesitter_indent_for_pos(
726+fn init_indent_query<'a, 'b>(
559727 query: &Query,
560− syntax: &Syntax,
561− indent_style: &IndentStyle,
728+ syntax: &'a Syntax,
729+ text: RopeSlice<'b>,
562730 tab_width: usize,
563− text: RopeSlice,
731+ indent_width: usize,
564732 line: usize,
565− pos: usize,
566− new_line: bool,
567−) -> Option<String> {
568− let byte_pos = text.char_to_byte(pos);
733+ byte_pos: usize,
734+ new_line_byte_pos: Option<usize>,
735+) -> Option<(Node<'a>, HashMap<usize, Vec<IndentCapture<'b>>>)> {
569736 // The innermost tree-sitter node which is considered for the indent
570737 // computation. It may change if some predeceding node is extended
571738 let mut node = syntax
572739 .tree()
573740 .root_node()
574741 .descendant_for_byte_range(byte_pos, byte_pos)?;
742+
575743 let (query_result, deepest_preceding) = {
576744 // The query range should intersect with all nodes directly preceding
577745 // the position of the indent query in case one of them is extended.
@@ -602,37 +770,105 @@ pub fn treesitter_indent_for_pos(
602770 &mut cursor,
603771 text,
604772 query_range,
605− new_line.then(|| (line, byte_pos)),
773+ new_line_byte_pos,
606774 );
607775 ts_parser.cursors.push(cursor);
608776 (query_result, deepest_preceding)
609777 })
610778 };
611− let indent_captures = query_result.indent_captures;
612779 let extend_captures = query_result.extend_captures;
613780
614781 // Check for extend captures, potentially changing the node that the indent calculation starts with
615− extend_nodes(
616− &mut node,
617− deepest_preceding,
618− &extend_captures,
782+ if let Some(deepest_preceding) = deepest_preceding {
783+ extend_nodes(
784+ &mut node,
785+ deepest_preceding,
786+ &extend_captures,
787+ text,
788+ line,
789+ tab_width,
790+ indent_width,
791+ );
792+ }
793+ Some((node, query_result.indent_captures))
794+}
795+
796+/// Use the syntax tree to determine the indentation for a given position.
797+/// This can be used in 2 ways:
798+///
799+/// - To get the correct indentation for an existing line (new_line=false), not necessarily equal to the current indentation.
800+/// - In this case, pos should be inside the first tree-sitter node on that line.
801+/// In most cases, this can just be the first non-whitespace on that line.
802+/// - To get the indentation for a new line (new_line=true). This behaves like the first usecase if the part of the current line
803+/// after pos were moved to a new line.
804+///
805+/// The indentation is determined by traversing all the tree-sitter nodes containing the position.
806+/// Each of these nodes produces some [Indentation] for:
807+///
808+/// - The line of the (beginning of the) node. This is defined by the scope `all` if this is the first node on its line.
809+/// - The line after the node. This is defined by:
810+/// - The scope `tail`.
811+/// - The scope `all` if this node is not the first node on its line.
812+/// Intuitively, `all` applies to everything contained in this node while `tail` applies to everything except for the first line of the node.
813+/// The indents from different nodes for the same line are then combined.
814+/// The result [Indentation] is simply the sum of the [Indentation] for all lines.
815+///
816+/// Specifying which line exactly an [Indentation] applies to is important because indents on the same line combine differently than indents on different lines:
817+/// ```ignore
818+/// some_function(|| {
819+/// // Both the function parameters as well as the contained block should be indented.
820+/// // Because they are on the same line, this only yields one indent level
821+/// });
822+/// ```
823+///
824+/// ```ignore
825+/// some_function(
826+/// param1,
827+/// || {
828+/// // Here we get 2 indent levels because the 'parameters' and the 'block' node begin on different lines
829+/// },
830+/// );
831+/// ```
832+#[allow(clippy::too_many_arguments)]
833+pub fn treesitter_indent_for_pos<'a>(
834+ query: &Query,
835+ syntax: &Syntax,
836+ tab_width: usize,
837+ indent_width: usize,
838+ text: RopeSlice<'a>,
839+ line: usize,
840+ pos: usize,
841+ new_line: bool,
842+) -> Option<Indentation<'a>> {
843+ let byte_pos = text.char_to_byte(pos);
844+ let new_line_byte_pos = new_line.then_some(byte_pos);
845+ let (mut node, mut indent_captures) = init_indent_query(
846+ query,
847+ syntax,
619848 text,
620− line,
621849 tab_width,
622− );
623− let mut first_in_line = get_first_in_line(node, new_line.then(|| byte_pos));
850+ indent_width,
851+ line,
852+ byte_pos,
853+ new_line_byte_pos,
854+ )?;
855+ let mut first_in_line = get_first_in_line(node, new_line.then_some(byte_pos));
624856
625857 let mut result = Indentation::default();
626858 // We always keep track of all the indent changes on one line, in order to only indent once
627859 // even if there are multiple "indent" nodes on the same line
628860 let mut indent_for_line = Indentation::default();
629861 let mut indent_for_line_below = Indentation::default();
862+
630863 loop {
631864 // This can safely be unwrapped because `first_in_line` contains
632865 // one entry for each ancestor of the node (which is what we iterate over)
633866 let is_first = *first_in_line.last().unwrap();
634− // Apply all indent definitions for this node
635− if let Some(definitions) = indent_captures.get(&node.id()) {
867+
868+ // Apply all indent definitions for this node.
869+ // Since we only iterate over each node once, we can remove the
870+ // corresponding captures from the HashMap to avoid cloning them.
871+ if let Some(definitions) = indent_captures.remove(&node.id()) {
636872 for definition in definitions {
637873 match definition.scope {
638874 IndentScope::All => {
@@ -650,28 +886,22 @@ pub fn treesitter_indent_for_pos(
650886 }
651887
652888 if let Some(parent) = node.parent() {
653− let mut node_line = node.start_position().row;
654− let mut parent_line = parent.start_position().row;
655− if node_line == line && new_line {
656− // Also consider the line that will be inserted
657− if node.start_byte() >= byte_pos {
658− node_line += 1;
659− }
660− if parent.start_byte() >= byte_pos {
661− parent_line += 1;
662− }
663− };
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+
664892 if node_line != parent_line {
893+ // Don't add indent for the line below the line of the query
665894 if node_line < line + (new_line as usize) {
666− // Don't add indent for the line below the line of the query
667− result.add_line(&indent_for_line_below);
895+ result.add_line(indent_for_line_below);
668896 }
897+
669898 if node_line == parent_line + 1 {
670899 indent_for_line_below = indent_for_line;
671900 } else {
672− result.add_line(&indent_for_line);
901+ result.add_line(indent_for_line);
673902 indent_for_line_below = Indentation::default();
674903 }
904+
675905 indent_for_line = Indentation::default();
676906 }
677907
@@ -683,13 +913,13 @@ pub fn treesitter_indent_for_pos(
683913 if (node.start_position().row < line)
684914 || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
685915 {
686− result.add_line(&indent_for_line_below);
916+ result.add_line(indent_for_line_below);
687917 }
688− result.add_line(&indent_for_line);
918+ result.add_line(indent_for_line);
689919 break;
690920 }
691921 }
692− Some(result.as_string(indent_style))
922+ Some(result)
693923}
694924
695925/// Returns the indentation for a new line.
@@ -698,6 +928,7 @@ pub fn treesitter_indent_for_pos(
698928pub fn indent_for_newline(
699929 language_config: Option<&LanguageConfiguration>,
700930 syntax: Option<&Syntax>,
931+ indent_heuristic: &IndentationHeuristic,
701932 indent_style: &IndentStyle,
702933 tab_width: usize,
703934 text: RopeSlice,
@@ -705,24 +936,78 @@ pub fn indent_for_newline(
705936 line_before_end_pos: usize,
706937 current_line: usize,
707938) -> String {
708− if let (Some(query), Some(syntax)) = (
939+ 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,
709946 language_config.and_then(|config| config.indent_query()),
710947 syntax,
711948 ) {
712949 if let Some(indent) = treesitter_indent_for_pos(
713950 query,
714951 syntax,
715− indent_style,
716952 tab_width,
953+ indent_width,
717954 text,
718955 line_before,
719956 line_before_end_pos,
720957 true,
721958 ) {
722− return indent;
959+ if *indent_heuristic == IndentationHeuristic::Hybrid {
960+ // We want to compute the indentation not only based on the
961+ // syntax tree but also on the actual indentation of a previous
962+ // line. This makes indentation computation more resilient to
963+ // incomplete queries, incomplete source code & differing indentation
964+ // styles for the same language.
965+ // However, using the indent of a previous line as a baseline may not
966+ // make sense, e.g. if it has a different alignment than the new line.
967+ // In order to prevent edge cases with long running times, we only try
968+ // a constant number of (non-empty) lines.
969+ const MAX_ATTEMPTS: usize = 4;
970+ let mut num_attempts = 0;
971+ for line_idx in (0..=line_before).rev() {
972+ let line = text.line(line_idx);
973+ let first_non_whitespace_char = match find_first_non_whitespace_char(line) {
974+ Some(i) => i,
975+ None => {
976+ continue;
977+ }
978+ };
979+ if let Some(indent) = (|| {
980+ let computed_indent = treesitter_indent_for_pos(
981+ query,
982+ syntax,
983+ tab_width,
984+ indent_width,
985+ text,
986+ line_idx,
987+ text.line_to_char(line_idx) + first_non_whitespace_char,
988+ false,
989+ )?;
990+ let leading_whitespace = line.slice(0..first_non_whitespace_char);
991+ indent.relative_indent(
992+ &computed_indent,
993+ leading_whitespace,
994+ indent_style,
995+ tab_width,
996+ )
997+ })() {
998+ return indent;
999+ }
1000+ num_attempts += 1;
1001+ if num_attempts == MAX_ATTEMPTS {
1002+ break;
1003+ }
1004+ }
1005+ }
1006+ return indent.to_string(indent_style, tab_width);
7231007 };
7241008 }
725− let indent_level = indent_level_for_line(text.line(current_line), tab_width);
1009+ // Fallback in case we either don't have indent queries or they failed for some reason
1010+ let indent_level = indent_level_for_line(text.line(current_line), tab_width, indent_width);
7261011 indent_style.as_str().repeat(indent_level)
7271012}
7281013
@@ -759,12 +1044,229 @@ mod test {
7591044 #[test]
7601045 fn test_indent_level() {
7611046 let tab_width = 4;
1047+ let indent_width = 4;
7621048 let line = Rope::from(" fn new"); // 8 spaces
763− assert_eq!(indent_level_for_line(line.slice(..), tab_width), 2);
1049+ assert_eq!(
1050+ indent_level_for_line(line.slice(..), tab_width, indent_width),
1051+ 2
1052+ );
7641053 let line = Rope::from("\t\t\tfn new"); // 3 tabs
765− assert_eq!(indent_level_for_line(line.slice(..), tab_width), 3);
1054+ assert_eq!(
1055+ indent_level_for_line(line.slice(..), tab_width, indent_width),
1056+ 3
1057+ );
7661058 // mixed indentation
7671059 let line = Rope::from("\t \tfn new"); // 1 tab, 4 spaces, tab
768− assert_eq!(indent_level_for_line(line.slice(..), tab_width), 3);
1060+ assert_eq!(
1061+ indent_level_for_line(line.slice(..), tab_width, indent_width),
1062+ 3
1063+ );
1064+ }
1065+
1066+ #[test]
1067+ fn test_large_indent_level() {
1068+ let tab_width = 16;
1069+ let indent_width = 16;
1070+ let line = Rope::from(" fn new"); // 16 spaces
1071+ assert_eq!(
1072+ indent_level_for_line(line.slice(..), tab_width, indent_width),
1073+ 1
1074+ );
1075+ let line = Rope::from(" fn new"); // 32 spaces
1076+ assert_eq!(
1077+ indent_level_for_line(line.slice(..), tab_width, indent_width),
1078+ 2
1079+ );
1080+ }
1081+
1082+ #[test]
1083+ fn add_capture() {
1084+ let indent = || Indentation {
1085+ indent: 1,
1086+ ..Default::default()
1087+ };
1088+ let indent_always = || Indentation {
1089+ indent_always: 1,
1090+ ..Default::default()
1091+ };
1092+ let outdent = || Indentation {
1093+ outdent: 1,
1094+ ..Default::default()
1095+ };
1096+ let outdent_always = || Indentation {
1097+ outdent_always: 1,
1098+ ..Default::default()
1099+ };
1100+
1101+ fn add_capture<'a>(
1102+ mut indent: Indentation<'a>,
1103+ capture: IndentCaptureType<'a>,
1104+ ) -> Indentation<'a> {
1105+ indent.add_capture(capture);
1106+ indent
1107+ }
1108+
1109+ // adding an indent to no indent makes an indent
1110+ assert_eq!(
1111+ indent(),
1112+ add_capture(Indentation::default(), IndentCaptureType::Indent)
1113+ );
1114+ assert_eq!(
1115+ indent_always(),
1116+ add_capture(Indentation::default(), IndentCaptureType::IndentAlways)
1117+ );
1118+ assert_eq!(
1119+ outdent(),
1120+ add_capture(Indentation::default(), IndentCaptureType::Outdent)
1121+ );
1122+ assert_eq!(
1123+ outdent_always(),
1124+ add_capture(Indentation::default(), IndentCaptureType::OutdentAlways)
1125+ );
1126+
1127+ // adding an indent to an already indented has no effect
1128+ assert_eq!(indent(), add_capture(indent(), IndentCaptureType::Indent));
1129+ assert_eq!(
1130+ outdent(),
1131+ add_capture(outdent(), IndentCaptureType::Outdent)
1132+ );
1133+
1134+ // adding an always to a regular makes it always
1135+ assert_eq!(
1136+ indent_always(),
1137+ add_capture(indent(), IndentCaptureType::IndentAlways)
1138+ );
1139+ assert_eq!(
1140+ outdent_always(),
1141+ add_capture(outdent(), IndentCaptureType::OutdentAlways)
1142+ );
1143+
1144+ // adding an always to an always is additive
1145+ assert_eq!(
1146+ Indentation {
1147+ indent_always: 2,
1148+ ..Default::default()
1149+ },
1150+ add_capture(indent_always(), IndentCaptureType::IndentAlways)
1151+ );
1152+ assert_eq!(
1153+ Indentation {
1154+ outdent_always: 2,
1155+ ..Default::default()
1156+ },
1157+ add_capture(outdent_always(), IndentCaptureType::OutdentAlways)
1158+ );
1159+
1160+ // adding regular to always should be associative
1161+ assert_eq!(
1162+ Indentation {
1163+ indent_always: 1,
1164+ ..Default::default()
1165+ },
1166+ add_capture(
1167+ add_capture(indent(), IndentCaptureType::Indent),
1168+ IndentCaptureType::IndentAlways
1169+ )
1170+ );
1171+ assert_eq!(
1172+ Indentation {
1173+ indent_always: 1,
1174+ ..Default::default()
1175+ },
1176+ add_capture(
1177+ add_capture(indent(), IndentCaptureType::IndentAlways),
1178+ IndentCaptureType::Indent
1179+ )
1180+ );
1181+ assert_eq!(
1182+ Indentation {
1183+ outdent_always: 1,
1184+ ..Default::default()
1185+ },
1186+ add_capture(
1187+ add_capture(outdent(), IndentCaptureType::Outdent),
1188+ IndentCaptureType::OutdentAlways
1189+ )
1190+ );
1191+ assert_eq!(
1192+ Indentation {
1193+ outdent_always: 1,
1194+ ..Default::default()
1195+ },
1196+ add_capture(
1197+ add_capture(outdent(), IndentCaptureType::OutdentAlways),
1198+ IndentCaptureType::Outdent
1199+ )
1200+ );
1201+ }
1202+
1203+ #[test]
1204+ fn test_relative_indent() {
1205+ let indent_style = IndentStyle::Spaces(4);
1206+ let tab_width: usize = 4;
1207+ let no_align = [
1208+ Indentation::default(),
1209+ Indentation {
1210+ indent: 1,
1211+ ..Default::default()
1212+ },
1213+ Indentation {
1214+ indent: 5,
1215+ outdent: 1,
1216+ ..Default::default()
1217+ },
1218+ ];
1219+ let align = no_align.clone().map(|indent| Indentation {
1220+ align: Some(RopeSlice::from("12345")),
1221+ ..indent
1222+ });
1223+ let different_align = Indentation {
1224+ align: Some(RopeSlice::from("123456")),
1225+ ..Default::default()
1226+ };
1227+
1228+ // Check that relative and absolute indentation computation are the same when the line we compare to is
1229+ // indented as we expect.
1230+ let check_consistency = |indent: &Indentation, other: &Indentation| {
1231+ assert_eq!(
1232+ indent.relative_indent(
1233+ other,
1234+ RopeSlice::from(other.to_string(&indent_style, tab_width).as_str()),
1235+ &indent_style,
1236+ tab_width
1237+ ),
1238+ Some(indent.to_string(&indent_style, tab_width))
1239+ );
1240+ };
1241+ for a in &no_align {
1242+ for b in &no_align {
1243+ check_consistency(a, b);
1244+ }
1245+ }
1246+ for a in &align {
1247+ for b in &align {
1248+ check_consistency(a, b);
1249+ }
1250+ }
1251+
1252+ // Relative indent computation makes no sense if the alignment differs
1253+ assert_eq!(
1254+ align[0].relative_indent(
1255+ &no_align[0],
1256+ RopeSlice::from(" "),
1257+ &indent_style,
1258+ tab_width
1259+ ),
1260+ None
1261+ );
1262+ assert_eq!(
1263+ align[0].relative_indent(
1264+ &different_align,
1265+ RopeSlice::from(" "),
1266+ &indent_style,
1267+ tab_width
1268+ ),
1269+ None
1270+ );
7691271 }
7701272}
helix-core/src/lib.rs+15−52
@@ -6,6 +6,8 @@ pub mod comment;
66pub mod config;
77pub mod diagnostic;
88pub mod diff;
9+pub mod doc_formatter;
10+pub mod fuzzy;
911pub mod graphemes;
1012pub mod history;
1113pub mod increment;
@@ -17,14 +19,13 @@ pub mod movement;
1719pub mod object;
1820pub mod path;
1921mod position;
20−pub mod register;
2122pub mod search;
2223pub mod selection;
2324pub mod shellwords;
24−mod state;
2525pub mod surround;
2626pub mod syntax;
2727pub mod test;
28+pub mod text_annotations;
2829pub mod textobject;
2930mod transaction;
3031pub mod wrap;
@@ -35,56 +36,15 @@ pub mod unicode {
3536 pub use unicode_width as width;
3637}
3738
39+pub use helix_loader::find_workspace;
40+
3841pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
3942 line.chars().position(|ch| !ch.is_whitespace())
4043}
44+mod rope_reader;
4145
42−/// Find project root.
43−///
44−/// Order of detection:
45−/// * Top-most folder containing a root marker in current git repository
46−/// * Git repository root if no marker detected
47−/// * Top-most folder containing a root marker if not git repository detected
48−/// * Current working directory as fallback
49−pub fn find_root(root: Option<&str>, root_markers: &[String]) -> std::path::PathBuf {
50− let current_dir = std::env::current_dir().expect("unable to determine current directory");
51−
52− let root = match root {
53− Some(root) => {
54− let root = std::path::Path::new(root);
55− if root.is_absolute() {
56− root.to_path_buf()
57− } else {
58− current_dir.join(root)
59− }
60− }
61− None => current_dir.clone(),
62− };
63−
64− let mut top_marker = None;
65− for ancestor in root.ancestors() {
66− if root_markers
67− .iter()
68− .any(|marker| ancestor.join(marker).exists())
69− {
70− top_marker = Some(ancestor);
71− }
72−
73− if ancestor.join(".git").is_dir() {
74− // Top marker is repo root if not root marker was detected yet
75− if top_marker.is_none() {
76− top_marker = Some(ancestor);
77− }
78− // Don't go higher than repo if we're in one
79− break;
80− }
81− }
82−
83− // Return the found top marker or the current_dir as fallback
84− top_marker.map_or(current_dir, |a| a.to_path_buf())
85−}
86−
87−pub use ropey::{str_utils, Rope, RopeBuilder, RopeSlice};
46+pub use rope_reader::RopeReader;
47+pub use ropey::{self, str_utils, Rope, RopeBuilder, RopeSlice};
8848
8949// pub use tendril::StrTendril as Tendril;
9050pub use smartstring::SmartString;
@@ -96,14 +56,17 @@ pub use {regex, tree_sitter};
9656
9757pub use graphemes::RopeGraphemes;
9858pub use position::{
99− coords_at_pos, pos_at_coords, pos_at_visual_coords, visual_coords_at_pos, Position,
59+ char_idx_at_visual_offset, coords_at_pos, pos_at_coords, visual_offset_from_anchor,
60+ visual_offset_from_block, Position, VisualOffsetError,
10061};
62+#[allow(deprecated)]
63+pub use position::{pos_at_visual_coords, visual_coords_at_pos};
64+
10165pub use selection::{Range, Selection};
10266pub use smallvec::{smallvec, SmallVec};
10367pub use syntax::Syntax;
10468
10569pub use diagnostic::Diagnostic;
106−pub use state::State;
10770
108−pub use line_ending::{LineEnding, DEFAULT_LINE_ENDING};
109−pub use transaction::{Assoc, Change, ChangeSet, Operation, Transaction};
71+pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
72+pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction};
helix-core/src/line_ending.rs+9−2
@@ -1,9 +1,9 @@
11use crate::{Rope, RopeSlice};
22
33#[cfg(target_os = "windows")]
4−pub const DEFAULT_LINE_ENDING: LineEnding = LineEnding::Crlf;
4+pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::Crlf;
55#[cfg(not(target_os = "windows"))]
6−pub const DEFAULT_LINE_ENDING: LineEnding = LineEnding::LF;
6+pub const NATIVE_LINE_ENDING: LineEnding = LineEnding::LF;
77
88/// Represents one of the valid Unicode line endings.
99#[derive(PartialEq, Eq, Copy, Clone, Debug)]
@@ -203,6 +203,13 @@ pub fn line_end_char_index(slice: &RopeSlice, line: usize) -> usize {
203203 .unwrap_or(0)
204204}
205205
206+pub fn line_end_byte_index(slice: &RopeSlice, line: usize) -> usize {
207+ slice.line_to_byte(line + 1)
208+ - get_line_ending(&slice.line(line))
209+ .map(|le| le.as_str().len())
210+ .unwrap_or(0)
211+}
212+
206213/// Fetches line `line_idx` from the passed rope slice, sans any line ending.
207214pub fn line_without_line_ending<'a>(slice: &'a RopeSlice, line_idx: usize) -> RopeSlice<'a> {
208215 let start = slice.line_to_char(line_idx);
helix-core/src/match_brackets.rs+232−29
@@ -1,7 +1,15 @@
1+use std::iter;
2+
3+use ropey::RopeSlice;
14use tree_sitter::Node;
25
3−use crate::{Rope, Syntax};
6+use crate::movement::Direction::{self, Backward, Forward};
7+use crate::Syntax;
8+
9+const MAX_PLAINTEXT_SCAN: usize = 10000;
10+const MATCH_LIMIT: usize = 16;
411
12+// Limit matching pairs to only ( ) { } [ ] < > ' ' " "
513const PAIRS: &[(char, char)] = &[
614 ('(', ')'),
715 ('{', '}'),
@@ -11,17 +19,17 @@ const PAIRS: &[(char, char)] = &[
1119 ('\"', '\"'),
1220];
1321
14−// limit matching pairs to only ( ) { } [ ] < > ' ' " "
15−
16−// Returns the position of the matching bracket under cursor.
17−//
18−// If the cursor is one the opening bracket, the position of
19−// the closing bracket is returned. If the cursor in the closing
20−// bracket, the position of the opening bracket is returned.
21−//
22−// If the cursor is not on a bracket, `None` is returned.
22+/// Returns the position of the matching bracket under cursor.
23+///
24+/// If the cursor is on the opening bracket, the position of
25+/// the closing bracket is returned. If the cursor on the closing
26+/// bracket, the position of the opening bracket is returned.
27+///
28+/// If the cursor is not on a bracket, `None` is returned.
29+///
30+/// If no matching bracket is found, `None` is returned.
2331#[must_use]
24−pub fn find_matching_bracket(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
32+pub fn find_matching_bracket(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
2533 if pos >= doc.len_chars() || !is_valid_bracket(doc.char(pos)) {
2634 return None;
2735 }
@@ -39,46 +47,159 @@ pub fn find_matching_bracket(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<
3947//
4048// If no surrounding scope is found, the function returns `None`.
4149#[must_use]
42−pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: &Rope, pos: usize) -> Option<usize> {
50+pub fn find_matching_bracket_fuzzy(syntax: &Syntax, doc: RopeSlice, pos: usize) -> Option<usize> {
4351 find_pair(syntax, doc, pos, true)
4452}
4553
46−fn find_pair(syntax: &Syntax, doc: &Rope, pos: usize, traverse_parents: bool) -> Option<usize> {
54+fn find_pair(
55+ syntax: &Syntax,
56+ doc: RopeSlice,
57+ pos_: usize,
58+ traverse_parents: bool,
59+) -> Option<usize> {
4760 let tree = syntax.tree();
48− let pos = doc.char_to_byte(pos);
61+ let pos = doc.char_to_byte(pos_);
4962
50− let mut node = tree.root_node().named_descendant_for_byte_range(pos, pos)?;
63+ let mut node = tree.root_node().descendant_for_byte_range(pos, pos)?;
5164
5265 loop {
53− let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
54− let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
66+ if node.is_named() {
67+ let (start_byte, end_byte) = surrounding_bytes(doc, &node)?;
68+ let (start_char, end_char) = (doc.byte_to_char(start_byte), doc.byte_to_char(end_byte));
69+
70+ if is_valid_pair(doc, start_char, end_char) {
71+ if end_byte == pos {
72+ return Some(start_char);
73+ }
5574
56− if is_valid_pair(doc, start_char, end_char) {
57− if end_byte == pos {
58− return Some(start_char);
75+ // We return the end char if the cursor is either on the start char
76+ // or at some arbitrary position between start and end char.
77+ if traverse_parents || start_byte == pos {
78+ return Some(end_char);
79+ }
5980 }
60− // We return the end char if the cursor is either on the start char
61− // or at some arbitrary position between start and end char.
62− return Some(end_char);
6381 }
82+ // this node itselt wasn't a pair but maybe its siblings are
6483
65− if traverse_parents {
66− node = node.parent()?;
67− } else {
68− return None;
84+ // check if we are *on* the pair (special cased so we don't look
85+ // at the current node twice and to jump to the start on that case)
86+ if let Some(open) = as_close_pair(doc, &node) {
87+ if let Some(pair_start) = find_pair_end(doc, node.prev_sibling(), open, Backward) {
88+ return Some(pair_start);
89+ }
6990 }
91+
92+ if !traverse_parents {
93+ // check if we are *on* the opening pair (special cased here as
94+ // an opptimization since we only care about bracket on the cursor
95+ // here)
96+ if let Some(close) = as_open_pair(doc, &node) {
97+ if let Some(pair_end) = find_pair_end(doc, node.next_sibling(), close, Forward) {
98+ return Some(pair_end);
99+ }
100+ }
101+ if node.is_named() {
102+ break;
103+ }
104+ }
105+
106+ for close in
107+ iter::successors(node.next_sibling(), |node| node.next_sibling()).take(MATCH_LIMIT)
108+ {
109+ let Some(open) = as_close_pair(doc, &close) else {
110+ continue;
111+ };
112+ if find_pair_end(doc, Some(node), open, Backward).is_some() {
113+ return doc.try_byte_to_char(close.start_byte()).ok();
114+ }
115+ }
116+ let Some(parent) = node.parent() else {
117+ break;
118+ };
119+ node = parent;
120+ }
121+ let node = tree.root_node().named_descendant_for_byte_range(pos, pos)?;
122+ if node.child_count() != 0 {
123+ return None;
70124 }
125+ let node_start = doc.byte_to_char(node.start_byte());
126+ find_matching_bracket_plaintext(doc.byte_slice(node.byte_range()), pos_ - node_start)
127+ .map(|pos| pos + node_start)
128+}
129+
130+/// Returns the position of the matching bracket under cursor.
131+/// This function works on plain text and ignores tree-sitter grammar.
132+/// The search is limited to `MAX_PLAINTEXT_SCAN` characters
133+///
134+/// If the cursor is on the opening bracket, the position of
135+/// the closing bracket is returned. If the cursor on the closing
136+/// bracket, the position of the opening bracket is returned.
137+///
138+/// If the cursor is not on a bracket, `None` is returned.
139+///
140+/// If no matching bracket is found, `None` is returned.
141+#[must_use]
142+pub fn find_matching_bracket_plaintext(doc: RopeSlice, cursor_pos: usize) -> Option<usize> {
143+ // Don't do anything when the cursor is not on top of a bracket.
144+ let bracket = doc.char(cursor_pos);
145+ if !is_valid_bracket(bracket) {
146+ return None;
147+ }
148+
149+ // Determine the direction of the matching.
150+ let is_fwd = is_forward_bracket(bracket);
151+ let chars_iter = if is_fwd {
152+ doc.chars_at(cursor_pos + 1)
153+ } else {
154+ doc.chars_at(cursor_pos).reversed()
155+ };
156+
157+ let mut open_cnt = 1;
158+
159+ for (i, candidate) in chars_iter.take(MAX_PLAINTEXT_SCAN).enumerate() {
160+ if candidate == bracket {
161+ open_cnt += 1;
162+ } else if is_valid_pair(
163+ doc,
164+ if is_fwd {
165+ cursor_pos
166+ } else {
167+ cursor_pos - i - 1
168+ },
169+ if is_fwd {
170+ cursor_pos + i + 1
171+ } else {
172+ cursor_pos
173+ },
174+ ) {
175+ // Return when all pending brackets have been closed.
176+ if open_cnt == 1 {
177+ return Some(if is_fwd {
178+ cursor_pos + i + 1
179+ } else {
180+ cursor_pos - i - 1
181+ });
182+ }
183+ open_cnt -= 1;
184+ }
185+ }
186+
187+ None
71188}
72189
73190fn is_valid_bracket(c: char) -> bool {
74191 PAIRS.iter().any(|(l, r)| *l == c || *r == c)
75192}
76193
77−fn is_valid_pair(doc: &Rope, start_char: usize, end_char: usize) -> bool {
194+fn is_forward_bracket(c: char) -> bool {
195+ PAIRS.iter().any(|(l, _)| *l == c)
196+}
197+
198+fn is_valid_pair(doc: RopeSlice, start_char: usize, end_char: usize) -> bool {
78199 PAIRS.contains(&(doc.char(start_char), doc.char(end_char)))
79200}
80201
81−fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> {
202+fn surrounding_bytes(doc: RopeSlice, node: &Node) -> Option<(usize, usize)> {
82203 let len = doc.len_bytes();
83204
84205 let start_byte = node.start_byte();
@@ -90,3 +211,85 @@ fn surrounding_bytes(doc: &Rope, node: &Node) -> Option<(usize, usize)> {
90211
91212 Some((start_byte, end_byte))
92213}
214+
215+/// Tests if this node is a pair close char and returns the expected open char
216+fn as_close_pair(doc: RopeSlice, node: &Node) -> Option<char> {
217+ let close = as_char(doc, node)?.1;
218+ PAIRS
219+ .iter()
220+ .find_map(|&(open, close_)| (close_ == close).then_some(open))
221+}
222+
223+/// Checks if `node` or its siblings (at most MATCH_LIMIT nodes) is the specified closing char
224+///
225+/// # Returns
226+///
227+/// The position of the found node or `None` otherwise
228+fn find_pair_end(
229+ doc: RopeSlice,
230+ node: Option<Node>,
231+ end_char: char,
232+ direction: Direction,
233+) -> Option<usize> {
234+ let advance = match direction {
235+ Forward => Node::next_sibling,
236+ Backward => Node::prev_sibling,
237+ };
238+ iter::successors(node, advance)
239+ .take(MATCH_LIMIT)
240+ .find_map(|node| {
241+ let (pos, c) = as_char(doc, &node)?;
242+ (end_char == c).then_some(pos)
243+ })
244+}
245+
246+/// Tests if this node is a pair close char and returns the expected open char
247+fn as_open_pair(doc: RopeSlice, node: &Node) -> Option<char> {
248+ let open = as_char(doc, node)?.1;
249+ PAIRS
250+ .iter()
251+ .find_map(|&(open_, close)| (open_ == open).then_some(close))
252+}
253+
254+/// If node is a single char return it (and its char position)
255+fn as_char(doc: RopeSlice, node: &Node) -> Option<(usize, char)> {
256+ // TODO: multi char/non ASCII pairs
257+ if node.byte_range().len() != 1 {
258+ return None;
259+ }
260+ let pos = doc.try_byte_to_char(node.start_byte()).ok()?;
261+ Some((pos, doc.char(pos)))
262+}
263+
264+#[cfg(test)]
265+mod tests {
266+ use super::*;
267+
268+ #[test]
269+ fn test_find_matching_bracket_current_line_plaintext() {
270+ let assert = |input: &str, pos, expected| {
271+ let input = RopeSlice::from(input);
272+ let actual = find_matching_bracket_plaintext(input, pos);
273+ assert_eq!(expected, actual.unwrap());
274+
275+ let actual = find_matching_bracket_plaintext(input, expected);
276+ assert_eq!(pos, actual.unwrap(), "expected symmetrical behaviour");
277+ };
278+
279+ assert("(hello)", 0, 6);
280+ assert("((hello))", 0, 8);
281+ assert("((hello))", 1, 7);
282+ assert("(((hello)))", 2, 8);
283+
284+ assert("key: ${value}", 6, 12);
285+ assert("key: ${value} # (some comment)", 16, 29);
286+
287+ assert("(paren (paren {bracket}))", 0, 24);
288+ assert("(paren (paren {bracket}))", 7, 23);
289+ assert("(paren (paren {bracket}))", 14, 22);
290+
291+ assert("(prev line\n ) (middle) ( \n next line)", 0, 12);
292+ assert("(prev line\n ) (middle) ( \n next line)", 14, 21);
293+ assert("(prev line\n ) (middle) ( \n next line)", 23, 36);
294+ }
295+}
helix-core/src/movement.rs+400−39
@@ -1,19 +1,22 @@
1−use std::iter;
1+use std::{cmp::Reverse, iter};
22
33use ropey::iter::Chars;
44use tree_sitter::{Node, QueryCursor};
55
66use crate::{
7+ char_idx_at_visual_offset,
78 chars::{categorize_char, char_is_line_ending, CharCategory},
9+ doc_formatter::TextFormat,
810 graphemes::{
911 next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary,
1012 prev_grapheme_boundary,
1113 },
1214 line_ending::rope_is_line_ending,
13− pos_at_visual_coords,
15+ position::char_idx_at_visual_block_offset,
1416 syntax::LanguageConfiguration,
17+ text_annotations::TextAnnotations,
1518 textobject::TextObject,
16− visual_coords_at_pos, Position, Range, RopeSlice,
19+ visual_offset_from_block, Range, RopeSlice, Selection, Syntax,
1720};
1821
1922#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -34,7 +37,8 @@ pub fn move_horizontally(
3437 dir: Direction,
3538 count: usize,
3639 behaviour: Movement,
37− _: usize,
40+ _: &TextFormat,
41+ _: &mut TextAnnotations,
3842) -> Range {
3943 let pos = range.cursor(slice);
4044
@@ -48,35 +52,116 @@ pub fn move_horizontally(
4852 range.put_cursor(slice, new_pos, behaviour == Movement::Extend)
4953}
5054
55+pub fn move_vertically_visual(
56+ slice: RopeSlice,
57+ range: Range,
58+ dir: Direction,
59+ count: usize,
60+ behaviour: Movement,
61+ text_fmt: &TextFormat,
62+ annotations: &mut TextAnnotations,
63+) -> Range {
64+ if !text_fmt.soft_wrap {
65+ return move_vertically(slice, range, dir, count, behaviour, text_fmt, annotations);
66+ }
67+ annotations.clear_line_annotations();
68+ let pos = range.cursor(slice);
69+
70+ // Compute the current position's 2d coordinates.
71+ let (visual_pos, block_off) = visual_offset_from_block(slice, pos, pos, text_fmt, annotations);
72+ let new_col = range
73+ .old_visual_position
74+ .map_or(visual_pos.col as u32, |(_, col)| col);
75+
76+ // Compute the new position.
77+ let mut row_off = match dir {
78+ Direction::Forward => count as isize,
79+ Direction::Backward => -(count as isize),
80+ };
81+
82+ // TODO how to handle inline annotations that span an entire visual line (very unlikely).
83+
84+ // Compute visual offset relative to block start to avoid trasversing the block twice
85+ row_off += visual_pos.row as isize;
86+ let new_pos = char_idx_at_visual_offset(
87+ slice,
88+ block_off,
89+ row_off,
90+ new_col as usize,
91+ text_fmt,
92+ annotations,
93+ )
94+ .0;
95+
96+ // Special-case to avoid moving to the end of the last non-empty line.
97+ if behaviour == Movement::Extend && slice.line(slice.char_to_line(new_pos)).len_chars() == 0 {
98+ return range;
99+ }
100+
101+ let mut new_range = range.put_cursor(slice, new_pos, behaviour == Movement::Extend);
102+ new_range.old_visual_position = Some((0, new_col));
103+ new_range
104+}
105+
51106pub fn move_vertically(
52107 slice: RopeSlice,
53108 range: Range,
54109 dir: Direction,
55110 count: usize,
56111 behaviour: Movement,
57− tab_width: usize,
112+ text_fmt: &TextFormat,
113+ annotations: &mut TextAnnotations,
58114) -> Range {
115+ annotations.clear_line_annotations();
59116 let pos = range.cursor(slice);
117+ let line_idx = slice.char_to_line(pos);
118+ let line_start = slice.line_to_char(line_idx);
60119
61120 // Compute the current position's 2d coordinates.
62− let Position { row, col } = visual_coords_at_pos(slice, pos, tab_width);
63− let horiz = range.horiz.unwrap_or(col as u32);
121+ let visual_pos = visual_offset_from_block(slice, line_start, pos, text_fmt, annotations).0;
122+ let (mut new_row, new_col) = range
123+ .old_visual_position
124+ .map_or((visual_pos.row as u32, visual_pos.col as u32), |pos| pos);
125+ new_row = new_row.max(visual_pos.row as u32);
126+ let line_idx = slice.char_to_line(pos);
64127
65128 // Compute the new position.
66− let new_row = match dir {
67− Direction::Forward => (row + count).min(slice.len_lines().saturating_sub(1)),
68− Direction::Backward => row.saturating_sub(count),
129+ let mut new_line_idx = match dir {
130+ Direction::Forward => line_idx.saturating_add(count),
131+ Direction::Backward => line_idx.saturating_sub(count),
132+ };
133+
134+ let line = if new_line_idx >= slice.len_lines() - 1 {
135+ // there is no line terminator for the last line
136+ // so the logic below is not necessary here
137+ new_line_idx = slice.len_lines() - 1;
138+ slice
139+ } else {
140+ // char_idx_at_visual_block_offset returns a one-past-the-end index
141+ // in case it reaches the end of the slice
142+ // to avoid moving to the nextline in that case the line terminator is removed from the line
143+ let new_line_end = prev_grapheme_boundary(slice, slice.line_to_char(new_line_idx + 1));
144+ slice.slice(..new_line_end)
69145 };
70− let new_col = col.max(horiz as usize);
71− let new_pos = pos_at_visual_coords(slice, Position::new(new_row, new_col), tab_width);
146+
147+ let new_line_start = line.line_to_char(new_line_idx);
148+
149+ let (new_pos, _) = char_idx_at_visual_block_offset(
150+ line,
151+ new_line_start,
152+ new_row as usize,
153+ new_col as usize,
154+ text_fmt,
155+ annotations,
156+ );
72157
73158 // Special-case to avoid moving to the end of the last non-empty line.
74− if behaviour == Movement::Extend && slice.line(new_row).len_chars() == 0 {
159+ if behaviour == Movement::Extend && slice.line(new_line_idx).len_chars() == 0 {
75160 return range;
76161 }
77162
78163 let mut new_range = range.put_cursor(slice, new_pos, behaviour == Movement::Extend);
79− new_range.horiz = Some(horiz);
164+ new_range.old_visual_position = Some((new_row, new_col));
80165 new_range
81166}
82167
@@ -92,6 +177,10 @@ pub fn move_prev_word_start(slice: RopeSlice, range: Range, count: usize) -> Ran
92177 word_move(slice, range, count, WordMotionTarget::PrevWordStart)
93178}
94179
180+pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
181+ word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
182+}
183+
95184pub fn move_next_long_word_start(slice: RopeSlice, range: Range, count: usize) -> Range {
96185 word_move(slice, range, count, WordMotionTarget::NextLongWordStart)
97186}
@@ -104,8 +193,8 @@ pub fn move_prev_long_word_start(slice: RopeSlice, range: Range, count: usize) -
104193 word_move(slice, range, count, WordMotionTarget::PrevLongWordStart)
105194}
106195
107−pub fn move_prev_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
108− word_move(slice, range, count, WordMotionTarget::PrevWordEnd)
196+pub fn move_prev_long_word_end(slice: RopeSlice, range: Range, count: usize) -> Range {
197+ word_move(slice, range, count, WordMotionTarget::PrevLongWordEnd)
109198}
110199
111200fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTarget) -> Range {
@@ -114,6 +203,7 @@ fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTar
114203 WordMotionTarget::PrevWordStart
115204 | WordMotionTarget::PrevLongWordStart
116205 | WordMotionTarget::PrevWordEnd
206+ | WordMotionTarget::PrevLongWordEnd
117207 );
118208
119209 // Special-case early-out.
@@ -142,9 +232,15 @@ fn word_move(slice: RopeSlice, range: Range, count: usize, target: WordMotionTar
142232 };
143233
144234 // Do the main work.
145− (0..count).fold(start_range, |r, _| {
146− slice.chars_at(r.head).range_to_target(target, r)
147− })
235+ let mut range = start_range;
236+ for _ in 0..count {
237+ let next_range = slice.chars_at(range.head).range_to_target(target, range);
238+ if range == next_range {
239+ break;
240+ }
241+ range = next_range;
242+ }
243+ range
148244}
149245
150246pub fn move_prev_paragraph(
@@ -166,6 +262,7 @@ pub fn move_prev_paragraph(
166262 let mut lines = slice.lines_at(line);
167263 lines.reverse();
168264 let mut lines = lines.map(rope_is_line_ending).peekable();
265+ let mut last_line = line;
169266 for _ in 0..count {
170267 while lines.next_if(|&e| e).is_some() {
171268 line -= 1;
@@ -173,6 +270,10 @@ pub fn move_prev_paragraph(
173270 while lines.next_if(|&e| !e).is_some() {
174271 line -= 1;
175272 }
273+ if line == last_line {
274+ break;
275+ }
276+ last_line = line;
176277 }
177278
178279 let head = slice.line_to_char(line);
@@ -208,6 +309,7 @@ pub fn move_next_paragraph(
208309 line += 1;
209310 }
210311 let mut lines = slice.lines_at(line).map(rope_is_line_ending).peekable();
312+ let mut last_line = line;
211313 for _ in 0..count {
212314 while lines.next_if(|&e| !e).is_some() {
213315 line += 1;
@@ -215,6 +317,10 @@ pub fn move_next_paragraph(
215317 while lines.next_if(|&e| e).is_some() {
216318 line += 1;
217319 }
320+ if line == last_line {
321+ break;
322+ }
323+ last_line = line;
218324 }
219325 let head = slice.line_to_char(line);
220326 let anchor = if behavior == Movement::Move {
@@ -276,6 +382,7 @@ pub enum WordMotionTarget {
276382 NextLongWordStart,
277383 NextLongWordEnd,
278384 PrevLongWordStart,
385+ PrevLongWordEnd,
279386}
280387
281388pub trait CharHelpers {
@@ -292,6 +399,7 @@ impl CharHelpers for Chars<'_> {
292399 WordMotionTarget::PrevWordStart
293400 | WordMotionTarget::PrevLongWordStart
294401 | WordMotionTarget::PrevWordEnd
402+ | WordMotionTarget::PrevLongWordEnd
295403 );
296404
297405 // Reverse the iterator if needed for the motion direction.
@@ -378,7 +486,7 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
378486 is_word_boundary(prev_ch, next_ch)
379487 && (!prev_ch.is_whitespace() || char_is_line_ending(next_ch))
380488 }
381− WordMotionTarget::NextLongWordStart => {
489+ WordMotionTarget::NextLongWordStart | WordMotionTarget::PrevLongWordEnd => {
382490 is_long_word_boundary(prev_ch, next_ch)
383491 && (char_is_line_ending(next_ch) || !next_ch.is_whitespace())
384492 }
@@ -419,10 +527,10 @@ pub fn goto_treesitter_object(
419527 let node = match dir {
420528 Direction::Forward => nodes
421529 .filter(|n| n.start_byte() > byte_pos)
422− .min_by_key(|n| n.start_byte())?,
530+ .min_by_key(|n| (n.start_byte(), Reverse(n.end_byte())))?,
423531 Direction::Backward => nodes
424532 .filter(|n| n.end_byte() < byte_pos)
425− .max_by_key(|n| n.end_byte())?,
533+ .max_by_key(|n| (n.end_byte(), Reverse(n.start_byte())))?,
426534 };
427535
428536 let len = slice.len_bytes();
@@ -438,7 +546,93 @@ pub fn goto_treesitter_object(
438546 // head of range should be at beginning
439547 Some(Range::new(start_char, end_char))
440548 };
441− (0..count).fold(range, |range, _| get_range(range).unwrap_or(range))
549+ let mut last_range = range;
550+ for _ in 0..count {
551+ match get_range(last_range) {
552+ Some(r) if r != last_range => last_range = r,
553+ _ => break,
554+ }
555+ }
556+ last_range
557+}
558+
559+fn find_parent_start(mut node: Node) -> Option<Node> {
560+ let start = node.start_byte();
561+
562+ while node.start_byte() >= start || !node.is_named() {
563+ node = node.parent()?;
564+ }
565+
566+ Some(node)
567+}
568+
569+pub fn move_parent_node_end(
570+ syntax: &Syntax,
571+ text: RopeSlice,
572+ selection: Selection,
573+ dir: Direction,
574+ movement: Movement,
575+) -> Selection {
576+ let tree = syntax.tree();
577+
578+ selection.transform(|range| {
579+ let start_from = text.char_to_byte(range.from());
580+ let start_to = text.char_to_byte(range.to());
581+
582+ let mut node = match tree
583+ .root_node()
584+ .named_descendant_for_byte_range(start_from, start_to)
585+ {
586+ Some(node) => node,
587+ None => {
588+ log::debug!(
589+ "no descendant found for byte range: {} - {}",
590+ start_from,
591+ start_to
592+ );
593+ return range;
594+ }
595+ };
596+
597+ let mut end_head = match dir {
598+ // moving forward, we always want to move one past the end of the
599+ // current node, so use the end byte of the current node, which is an exclusive
600+ // end of the range
601+ Direction::Forward => text.byte_to_char(node.end_byte()),
602+
603+ // moving backward, we want the cursor to land on the start char of
604+ // the current node, or if it is already at the start of a node, to traverse up to
605+ // the parent
606+ Direction::Backward => {
607+ let end_head = text.byte_to_char(node.start_byte());
608+
609+ // if we're already on the beginning, look up to the parent
610+ if end_head == range.cursor(text) {
611+ node = find_parent_start(node).unwrap_or(node);
612+ text.byte_to_char(node.start_byte())
613+ } else {
614+ end_head
615+ }
616+ }
617+ };
618+
619+ if movement == Movement::Move {
620+ // preserve direction of original range
621+ if range.direction() == Direction::Forward {
622+ Range::new(end_head, end_head + 1)
623+ } else {
624+ Range::new(end_head + 1, end_head)
625+ }
626+ } else {
627+ // if we end up with a forward range, then adjust it to be one past
628+ // where we want
629+ if end_head >= range.anchor {
630+ end_head += 1;
631+ }
632+
633+ Range::new(range.anchor, end_head)
634+ }
635+ })
442636}
443637
444638#[cfg(test)]
@@ -473,7 +667,16 @@ mod test {
473667 assert_eq!(
474668 coords_at_pos(
475669 slice,
476− move_vertically(slice, range, Direction::Forward, 1, Movement::Move, 4).head
670+ move_vertically_visual(
671+ slice,
672+ range,
673+ Direction::Forward,
674+ 1,
675+ Movement::Move,
676+ &TextFormat::default(),
677+ &mut TextAnnotations::default(),
678+ )
679+ .head
477680 ),
478681 (1, 3).into()
479682 );
@@ -497,7 +700,15 @@ mod test {
497700 ];
498701
499702 for ((direction, amount), coordinates) in moves_and_expected_coordinates {
500− range = move_horizontally(slice, range, direction, amount, Movement::Move, 0);
703+ range = move_horizontally(
704+ slice,
705+ range,
706+ direction,
707+ amount,
708+ Movement::Move,
709+ &TextFormat::default(),
710+ &mut TextAnnotations::default(),
711+ );
501712 assert_eq!(coords_at_pos(slice, range.head), coordinates.into())
502713 }
503714 }
@@ -523,7 +734,15 @@ mod test {
523734 ];
524735
525736 for ((direction, amount), coordinates) in moves_and_expected_coordinates {
526− range = move_horizontally(slice, range, direction, amount, Movement::Move, 0);
737+ range = move_horizontally(
738+ slice,
739+ range,
740+ direction,
741+ amount,
742+ Movement::Move,
743+ &TextFormat::default(),
744+ &mut TextAnnotations::default(),
745+ );
527746 assert_eq!(coords_at_pos(slice, range.head), coordinates.into());
528747 assert_eq!(range.head, range.anchor);
529748 }
@@ -545,7 +764,15 @@ mod test {
545764 ];
546765
547766 for (direction, amount) in moves {
548− range = move_horizontally(slice, range, direction, amount, Movement::Extend, 0);
767+ range = move_horizontally(
768+ slice,
769+ range,
770+ direction,
771+ amount,
772+ Movement::Extend,
773+ &TextFormat::default(),
774+ &mut TextAnnotations::default(),
775+ );
549776 assert_eq!(range.anchor, original_anchor);
550777 }
551778 }
@@ -569,7 +796,15 @@ mod test {
569796 ];
570797
571798 for ((direction, amount), coordinates) in moves_and_expected_coordinates {
572− range = move_vertically(slice, range, direction, amount, Movement::Move, 4);
799+ range = move_vertically_visual(
800+ slice,
801+ range,
802+ direction,
803+ amount,
804+ Movement::Move,
805+ &TextFormat::default(),
806+ &mut TextAnnotations::default(),
807+ );
573808 assert_eq!(coords_at_pos(slice, range.head), coordinates.into());
574809 assert_eq!(range.head, range.anchor);
575810 }
@@ -603,8 +838,24 @@ mod test {
603838
604839 for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
605840 range = match axis {
606− Axis::H => move_horizontally(slice, range, direction, amount, Movement::Move, 0),
607− Axis::V => move_vertically(slice, range, direction, amount, Movement::Move, 4),
841+ Axis::H => move_horizontally(
842+ slice,
843+ range,
844+ direction,
845+ amount,
846+ Movement::Move,
847+ &TextFormat::default(),
848+ &mut TextAnnotations::default(),
849+ ),
850+ Axis::V => move_vertically_visual(
851+ slice,
852+ range,
853+ direction,
854+ amount,
855+ Movement::Move,
856+ &TextFormat::default(),
857+ &mut TextAnnotations::default(),
858+ ),
608859 };
609860 assert_eq!(coords_at_pos(slice, range.head), coordinates.into());
610861 assert_eq!(range.head, range.anchor);
@@ -638,8 +889,24 @@ mod test {
638889
639890 for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates {
640891 range = match axis {
641− Axis::H => move_horizontally(slice, range, direction, amount, Movement::Move, 0),
642− Axis::V => move_vertically(slice, range, direction, amount, Movement::Move, 4),
892+ Axis::H => move_horizontally(
893+ slice,
894+ range,
895+ direction,
896+ amount,
897+ Movement::Move,
898+ &TextFormat::default(),
899+ &mut TextAnnotations::default(),
900+ ),
901+ Axis::V => move_vertically_visual(
902+ slice,
903+ range,
904+ direction,
905+ amount,
906+ Movement::Move,
907+ &TextFormat::default(),
908+ &mut TextAnnotations::default(),
909+ ),
643910 };
644911 assert_eq!(coords_at_pos(slice, range.head), coordinates.into());
645912 assert_eq!(range.head, range.anchor);
@@ -1264,6 +1531,100 @@ mod test {
12641531 }
12651532 }
12661533
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+
12671628 #[test]
12681629 fn test_behaviour_when_moving_to_prev_paragraph_single() {
12691630 let tests = [
@@ -1293,7 +1654,7 @@ mod test {
12931654 let text = Rope::from(s.as_str());
12941655 let selection =
12951656 selection.transform(|r| move_prev_paragraph(text.slice(..), r, 1, Movement::Move));
1296− let actual = crate::test::plain(&s, selection);
1657+ let actual = crate::test::plain(s.as_ref(), &selection);
12971658 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
12981659 }
12991660 }
@@ -1316,7 +1677,7 @@ mod test {
13161677 let text = Rope::from(s.as_str());
13171678 let selection =
13181679 selection.transform(|r| move_prev_paragraph(text.slice(..), r, 2, Movement::Move));
1319− let actual = crate::test::plain(&s, selection);
1680+ let actual = crate::test::plain(s.as_ref(), &selection);
13201681 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
13211682 }
13221683 }
@@ -1339,7 +1700,7 @@ mod test {
13391700 let text = Rope::from(s.as_str());
13401701 let selection = selection
13411702 .transform(|r| move_prev_paragraph(text.slice(..), r, 1, Movement::Extend));
1342− let actual = crate::test::plain(&s, selection);
1703+ let actual = crate::test::plain(s.as_ref(), &selection);
13431704 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
13441705 }
13451706 }
@@ -1359,7 +1720,7 @@ mod test {
13591720 "a\nb\n\n#[goto\nthird\n\n|]#paragraph",
13601721 ),
13611722 (
1362− "a\nb#[\n|]#\ngoto\nsecond\n\nparagraph",
1723+ "a\nb#[\n|]#\n\ngoto\nsecond\n\nparagraph",
13631724 "a\nb#[\n\n|]#goto\nsecond\n\nparagraph",
13641725 ),
13651726 (
@@ -1381,7 +1742,7 @@ mod test {
13811742 let text = Rope::from(s.as_str());
13821743 let selection =
13831744 selection.transform(|r| move_next_paragraph(text.slice(..), r, 1, Movement::Move));
1384− let actual = crate::test::plain(&s, selection);
1745+ let actual = crate::test::plain(s.as_ref(), &selection);
13851746 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
13861747 }
13871748 }
@@ -1404,7 +1765,7 @@ mod test {
14041765 let text = Rope::from(s.as_str());
14051766 let selection =
14061767 selection.transform(|r| move_next_paragraph(text.slice(..), r, 2, Movement::Move));
1407− let actual = crate::test::plain(&s, selection);
1768+ let actual = crate::test::plain(s.as_ref(), &selection);
14081769 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
14091770 }
14101771 }
@@ -1427,7 +1788,7 @@ mod test {
14271788 let text = Rope::from(s.as_str());
14281789 let selection = selection
14291790 .transform(|r| move_next_paragraph(text.slice(..), r, 1, Movement::Extend));
1430− let actual = crate::test::plain(&s, selection);
1791+ let actual = crate::test::plain(s.as_ref(), &selection);
14311792 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
14321793 }
14331794 }
helix-core/src/path.rs+27−8
@@ -40,6 +40,21 @@ pub fn expand_tilde(path: &Path) -> PathBuf {
4040/// needs to improve on.
4141/// Copied from cargo: <https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81>
4242pub fn get_normalized_path(path: &Path) -> PathBuf {
43+ // normalization strategy is to canonicalize first ancestor path that exists (i.e., canonicalize as much as possible),
44+ // then run handrolled normalization on the non-existent remainder
45+ let (base, path) = path
46+ .ancestors()
47+ .find_map(|base| {
48+ let canonicalized_base = dunce::canonicalize(base).ok()?;
49+ let remainder = path.strip_prefix(base).ok()?.into();
50+ Some((canonicalized_base, remainder))
51+ })
52+ .unwrap_or_else(|| (PathBuf::new(), PathBuf::from(path)));
53+
54+ if path.as_os_str().is_empty() {
55+ return base;
56+ }
57+
4358 let mut components = path.components().peekable();
4459 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
4560 components.next();
@@ -63,32 +78,36 @@ pub fn get_normalized_path(path: &Path) -> PathBuf {
6378 }
6479 }
6580 }
66− ret
81+ base.join(ret)
6782}
6883
6984/// Returns the canonical, absolute form of a path with all intermediate components normalized.
7085///
7186/// This function is used instead of `std::fs::canonicalize` because we don't want to verify
7287/// here if the path exists, just normalize it's components.
73−pub fn get_canonicalized_path(path: &Path) -> std::io::Result<PathBuf> {
88+pub fn get_canonicalized_path(path: &Path) -> PathBuf {
7489 let path = expand_tilde(path);
7590 let path = if path.is_relative() {
76− std::env::current_dir().map(|current_dir| current_dir.join(path))?
91+ helix_loader::current_working_dir().join(path)
7792 } else {
7893 path
7994 };
8095
81− Ok(get_normalized_path(path.as_path()))
96+ get_normalized_path(path.as_path())
8297}
8398
8499pub fn get_relative_path(path: &Path) -> PathBuf {
100+ let path = PathBuf::from(path);
85101 let path = if path.is_absolute() {
86− let cwdir = std::env::current_dir().expect("couldn't determine current directory");
87− path.strip_prefix(cwdir).unwrap_or(path)
102+ let cwdir = get_normalized_path(&helix_loader::current_working_dir());
103+ get_normalized_path(&path)
104+ .strip_prefix(cwdir)
105+ .map(PathBuf::from)
106+ .unwrap_or(path)
88107 } else {
89108 path
90109 };
91− fold_home_dir(path)
110+ fold_home_dir(&path)
92111}
93112
94113/// Returns a truncated filepath where the basepart of the path is reduced to the first
@@ -121,7 +140,7 @@ pub fn get_relative_path(path: &Path) -> PathBuf {
121140/// ```
122141///
123142pub fn get_truncated_path<P: AsRef<Path>>(path: P) -> PathBuf {
124− let cwd = std::env::current_dir().unwrap_or_default();
143+ let cwd = helix_loader::current_working_dir();
125144 let path = path
126145 .as_ref()
127146 .strip_prefix(cwd)
helix-core/src/position.rs+452−1
@@ -1,9 +1,11 @@
1−use std::borrow::Cow;
1+use std::{borrow::Cow, cmp::Ordering};
22
33use crate::{
44 chars::char_is_line_ending,
5+ doc_formatter::{DocumentFormatter, TextFormat},
56 graphemes::{ensure_grapheme_boundary_prev, grapheme_width, RopeGraphemes},
67 line_ending::line_end_char_index,
8+ text_annotations::TextAnnotations,
79 RopeSlice,
810};
911
@@ -73,6 +75,13 @@ pub fn coords_at_pos(text: RopeSlice, pos: usize) -> Position {
7375/// Takes \t, double-width characters (CJK) into account as well as text
7476/// not in the document in the future.
7577/// See [`coords_at_pos`] for an "objective" one.
78+///
79+/// This function should be used very rarely. Usually `visual_offset_from_anchor`
80+/// or `visual_offset_from_block` is preferable. However when you want to compute the
81+/// actual visual row/column in the text (not what is actually shown on screen)
82+/// then you should use this function. For example aligning text should ignore virtual
83+/// text and softwrap.
84+#[deprecated = "Doesn't account for softwrap or decorations, use visual_offset_from_anchor instead"]
7685pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Position {
7786 let line = text.char_to_line(pos);
7887
@@ -93,6 +102,106 @@ pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Po
93102 Position::new(line, col)
94103}
95104
105+/// Returns the visual offset from the start of the first visual line
106+/// in the block that contains anchor.
107+/// Text is always wrapped at blocks, they usually correspond to
108+/// actual line breaks but for very long lines
109+/// softwrapping positions are estimated with an O(1) algorithm
110+/// to ensure consistent performance for large lines (currently unimplemented)
111+///
112+/// Usually you want to use `visual_offset_from_anchor` instead but this function
113+/// can be useful (and faster) if
114+/// * You already know the visual position of the block
115+/// * You only care about the horizontal offset (column) and not the vertical offset (row)
116+pub fn visual_offset_from_block(
117+ text: RopeSlice,
118+ anchor: usize,
119+ pos: usize,
120+ text_fmt: &TextFormat,
121+ annotations: &TextAnnotations,
122+) -> (Position, usize) {
123+ let mut last_pos = Position::default();
124+ let (formatter, block_start) =
125+ DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
126+ let mut char_pos = block_start;
127+
128+ for (grapheme, vpos) in formatter {
129+ last_pos = vpos;
130+ char_pos += grapheme.doc_chars();
131+
132+ if char_pos > pos {
133+ return (last_pos, block_start);
134+ }
135+ }
136+
137+ (last_pos, block_start)
138+}
139+
140+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
141+pub enum VisualOffsetError {
142+ PosBeforeAnchorRow,
143+ PosAfterMaxRow,
144+}
145+
146+/// Returns the visual offset from the start of the visual line
147+/// that contains anchor.
148+pub fn visual_offset_from_anchor(
149+ text: RopeSlice,
150+ anchor: usize,
151+ pos: usize,
152+ text_fmt: &TextFormat,
153+ annotations: &TextAnnotations,
154+ max_rows: usize,
155+) -> Result<(Position, usize), VisualOffsetError> {
156+ let (formatter, block_start) =
157+ DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
158+ let mut char_pos = block_start;
159+ let mut anchor_line = None;
160+ let mut found_pos = None;
161+ let mut last_pos = Position::default();
162+
163+ if pos < block_start {
164+ return Err(VisualOffsetError::PosBeforeAnchorRow);
165+ }
166+
167+ for (grapheme, vpos) in formatter {
168+ last_pos = vpos;
169+ char_pos += grapheme.doc_chars();
170+
171+ if char_pos > pos {
172+ if let Some(anchor_line) = anchor_line {
173+ last_pos.row -= anchor_line;
174+ return Ok((last_pos, block_start));
175+ } else {
176+ found_pos = Some(last_pos);
177+ }
178+ }
179+ if char_pos > anchor && anchor_line.is_none() {
180+ if let Some(mut found_pos) = found_pos {
181+ return if found_pos.row == last_pos.row {
182+ found_pos.row = 0;
183+ Ok((found_pos, block_start))
184+ } else {
185+ Err(VisualOffsetError::PosBeforeAnchorRow)
186+ };
187+ } else {
188+ anchor_line = Some(last_pos.row);
189+ }
190+ }
191+
192+ if let Some(anchor_line) = anchor_line {
193+ if vpos.row >= anchor_line + max_rows {
194+ return Err(VisualOffsetError::PosAfterMaxRow);
195+ }
196+ }
197+ }
198+
199+ let anchor_line = anchor_line.unwrap_or(last_pos.row);
200+ last_pos.row -= anchor_line;
201+
202+ Ok((last_pos, block_start))
203+}
204+
96205/// Convert (line, column) coordinates to a character index.
97206///
98207/// If the `line` coordinate is beyond the end of the file, the EOF
@@ -140,6 +249,11 @@ pub fn pos_at_coords(text: RopeSlice, coords: Position, limit_before_line_ending
140249/// If the `column` coordinate is past the end of the given line, the
141250/// line-end position (in this case, just before the line ending
142251/// character) will be returned.
252+/// This function should be used very rarely. Usually `char_idx_at_visual_offset` is preferable.
253+/// However when you want to compute a char position from the visual row/column in the text
254+/// (not what is actually shown on screen) then you should use this function.
255+/// For example aligning text should ignore virtual text and softwrap.
256+#[deprecated = "Doesn't account for softwrap or decorations, use char_idx_at_visual_offset instead"]
143257pub fn pos_at_visual_coords(text: RopeSlice, coords: Position, tab_width: usize) -> usize {
144258 let Position { mut row, col } = coords;
145259 row = row.min(text.len_lines() - 1);
@@ -169,6 +283,121 @@ pub fn pos_at_visual_coords(text: RopeSlice, coords: Position, tab_width: usize)
169283 line_start + col_char_offset
170284}
171285
286+/// Returns the char index on the visual line `row_offset` below the visual line of
287+/// the provided char index `anchor` that is closest to the supplied visual `column`.
288+///
289+/// If the targeted visual line is entirely covered by virtual text the last
290+/// char position before the virtual text and a virtual offset is returned instead.
291+///
292+/// If no (text) grapheme starts at exactly at the specified column the
293+/// start of the grapheme to the left is returned. If there is no grapheme
294+/// to the left (for example if the line starts with virtual text) then the positioning
295+/// of the next grapheme to the right is returned.
296+///
297+/// If the `line` coordinate is beyond the end of the file, the EOF
298+/// position will be returned.
299+///
300+/// If the `column` coordinate is past the end of the given line, the
301+/// line-end position (in this case, just before the line ending
302+/// character) will be returned.
303+///
304+/// # Returns
305+///
306+/// `(real_char_idx, virtual_lines)`
307+///
308+/// The nearest character idx "closest" (see above) to the specified visual offset
309+/// on the visual line is returned if the visual line contains any text:
310+/// If the visual line at the specified offset is a virtual line generated by a `LineAnnotation`
311+/// the previous char_index is returned, together with the remaining vertical offset (`virtual_lines`)
312+pub fn char_idx_at_visual_offset(
313+ text: RopeSlice,
314+ mut anchor: usize,
315+ mut row_offset: isize,
316+ column: usize,
317+ text_fmt: &TextFormat,
318+ annotations: &TextAnnotations,
319+) -> (usize, usize) {
320+ let mut pos = anchor;
321+ // convert row relative to visual line containing anchor to row relative to a block containing anchor (anchor may change)
322+ loop {
323+ let (visual_pos_in_block, block_char_offset) =
324+ visual_offset_from_block(text, anchor, pos, text_fmt, annotations);
325+ row_offset += visual_pos_in_block.row as isize;
326+ anchor = block_char_offset;
327+ if row_offset >= 0 {
328+ break;
329+ }
330+
331+ if block_char_offset == 0 {
332+ row_offset = 0;
333+ break;
334+ }
335+ // the row_offset is negative so we need to look at the previous block
336+ // set the anchor to the last char before the current block so that we can compute
337+ // the distance of this block from the start of the previous block
338+ pos = anchor;
339+ anchor -= 1;
340+ }
341+
342+ char_idx_at_visual_block_offset(
343+ text,
344+ anchor,
345+ row_offset as usize,
346+ column,
347+ text_fmt,
348+ annotations,
349+ )
350+}
351+
352+/// This function behaves the same as `char_idx_at_visual_offset`, except that
353+/// the vertical offset `row` is always computed relative to the block that contains `anchor`
354+/// instead of the visual line that contains `anchor`.
355+/// Usually `char_idx_at_visual_offset` is more useful but this function can be
356+/// used in some situations as an optimization when `visual_offset_from_block` was used
357+///
358+/// # Returns
359+///
360+/// `(real_char_idx, virtual_lines)`
361+///
362+/// See `char_idx_at_visual_offset` for details
363+pub fn char_idx_at_visual_block_offset(
364+ text: RopeSlice,
365+ anchor: usize,
366+ row: usize,
367+ column: usize,
368+ text_fmt: &TextFormat,
369+ annotations: &TextAnnotations,
370+) -> (usize, usize) {
371+ let (formatter, mut char_idx) =
372+ DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
373+ let mut last_char_idx = char_idx;
374+ let mut last_char_idx_on_line = None;
375+ let mut last_row = 0;
376+ for (grapheme, grapheme_pos) in formatter {
377+ match grapheme_pos.row.cmp(&row) {
378+ Ordering::Equal => {
379+ if grapheme_pos.col + grapheme.width() > column {
380+ if !grapheme.is_virtual() {
381+ return (char_idx, 0);
382+ } else if let Some(char_idx) = last_char_idx_on_line {
383+ return (char_idx, 0);
384+ }
385+ } else if !grapheme.is_virtual() {
386+ last_char_idx_on_line = Some(char_idx)
387+ }
388+ }
389+ Ordering::Greater => return (last_char_idx, row - last_row),
390+ _ => (),
391+ }
392+
393+ last_char_idx = char_idx;
394+ last_row = grapheme_pos.row;
395+ char_idx += grapheme.doc_chars();
396+ }
397+
398+ (char_idx, 0)
399+}
400+
172401#[cfg(test)]
173402mod test {
174403 use super::*;
@@ -228,6 +457,7 @@ mod test {
228457 }
229458
230459 #[test]
460+ #[allow(deprecated)]
231461 fn test_visual_coords_at_pos() {
232462 let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
233463 let slice = text.slice(..);
@@ -275,6 +505,130 @@ mod test {
275505 assert_eq!(visual_coords_at_pos(slice, 2, 8), (0, 9).into());
276506 }
277507
508+ #[test]
509+ fn test_visual_off_from_block() {
510+ let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
511+ let slice = text.slice(..);
512+ let annot = TextAnnotations::default();
513+ let text_fmt = TextFormat::default();
514+ assert_eq!(
515+ visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0,
516+ (0, 0).into()
517+ );
518+ assert_eq!(
519+ visual_offset_from_block(slice, 0, 5, &text_fmt, &annot).0,
520+ (0, 5).into()
521+ ); // position on \n
522+ assert_eq!(
523+ visual_offset_from_block(slice, 0, 6, &text_fmt, &annot).0,
524+ (1, 0).into()
525+ ); // position on w
526+ assert_eq!(
527+ visual_offset_from_block(slice, 0, 7, &text_fmt, &annot).0,
528+ (1, 1).into()
529+ ); // position on o
530+ assert_eq!(
531+ visual_offset_from_block(slice, 0, 10, &text_fmt, &annot).0,
532+ (1, 4).into()
533+ ); // position on d
534+
535+ // Test with wide characters.
536+ let text = Rope::from("今日はいい\n");
537+ let slice = text.slice(..);
538+ assert_eq!(
539+ visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0,
540+ (0, 0).into()
541+ );
542+ assert_eq!(
543+ visual_offset_from_block(slice, 0, 1, &text_fmt, &annot).0,
544+ (0, 2).into()
545+ );
546+ assert_eq!(
547+ visual_offset_from_block(slice, 0, 2, &text_fmt, &annot).0,
548+ (0, 4).into()
549+ );
550+ assert_eq!(
551+ visual_offset_from_block(slice, 0, 3, &text_fmt, &annot).0,
552+ (0, 6).into()
553+ );
554+ assert_eq!(
555+ visual_offset_from_block(slice, 0, 4, &text_fmt, &annot).0,
556+ (0, 8).into()
557+ );
558+ assert_eq!(
559+ visual_offset_from_block(slice, 0, 5, &text_fmt, &annot).0,
560+ (0, 10).into()
561+ );
562+ assert_eq!(
563+ visual_offset_from_block(slice, 0, 6, &text_fmt, &annot).0,
564+ (1, 0).into()
565+ );
566+
567+ // Test with grapheme clusters.
568+ let text = Rope::from("a̐éö̲\r\n");
569+ let slice = text.slice(..);
570+ assert_eq!(
571+ visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0,
572+ (0, 0).into()
573+ );
574+ assert_eq!(
575+ visual_offset_from_block(slice, 0, 2, &text_fmt, &annot).0,
576+ (0, 1).into()
577+ );
578+ assert_eq!(
579+ visual_offset_from_block(slice, 0, 4, &text_fmt, &annot).0,
580+ (0, 2).into()
581+ );
582+ assert_eq!(
583+ visual_offset_from_block(slice, 0, 7, &text_fmt, &annot).0,
584+ (0, 3).into()
585+ );
586+ assert_eq!(
587+ visual_offset_from_block(slice, 0, 9, &text_fmt, &annot).0,
588+ (1, 0).into()
589+ );
590+
591+ // Test with wide-character grapheme clusters.
592+ // TODO: account for cluster.
593+ let text = Rope::from("किमपि\n");
594+ let slice = text.slice(..);
595+ assert_eq!(
596+ visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0,
597+ (0, 0).into()
598+ );
599+ assert_eq!(
600+ visual_offset_from_block(slice, 0, 2, &text_fmt, &annot).0,
601+ (0, 2).into()
602+ );
603+ assert_eq!(
604+ visual_offset_from_block(slice, 0, 3, &text_fmt, &annot).0,
605+ (0, 3).into()
606+ );
607+ assert_eq!(
608+ visual_offset_from_block(slice, 0, 5, &text_fmt, &annot).0,
609+ (0, 5).into()
610+ );
611+ assert_eq!(
612+ visual_offset_from_block(slice, 0, 6, &text_fmt, &annot).0,
613+ (1, 0).into()
614+ );
615+
616+ // Test with tabs.
617+ let text = Rope::from("\tHello\n");
618+ let slice = text.slice(..);
619+ assert_eq!(
620+ visual_offset_from_block(slice, 0, 0, &text_fmt, &annot).0,
621+ (0, 0).into()
622+ );
623+ assert_eq!(
624+ visual_offset_from_block(slice, 0, 1, &text_fmt, &annot).0,
625+ (0, 4).into()
626+ );
627+ assert_eq!(
628+ visual_offset_from_block(slice, 0, 2, &text_fmt, &annot).0,
629+ (0, 5).into()
630+ );
631+ }
278632 #[test]
279633 fn test_pos_at_coords() {
280634 let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
@@ -341,6 +695,7 @@ mod test {
341695 }
342696
343697 #[test]
698+ #[allow(deprecated)]
344699 fn test_pos_at_visual_coords() {
345700 let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ");
346701 let slice = text.slice(..);
@@ -405,4 +760,100 @@ mod test {
405760 assert_eq!(pos_at_visual_coords(slice, (0, 10).into(), 4), 0);
406761 assert_eq!(pos_at_visual_coords(slice, (10, 10).into(), 4), 0);
407762 }
763+
764+ #[test]
765+ fn test_char_idx_at_visual_row_offset() {
766+ let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ\nfoo");
767+ let slice = text.slice(..);
768+ let mut text_fmt = TextFormat::default();
769+ for i in 0isize..3isize {
770+ for j in -2isize..=2isize {
771+ if !(0..3).contains(&(i + j)) {
772+ continue;
773+ }
774+ println!("{i} {j}");
775+ assert_eq!(
776+ char_idx_at_visual_offset(
777+ slice,
778+ slice.line_to_char(i as usize),
779+ j,
780+ 3,
781+ &text_fmt,
782+ &TextAnnotations::default(),
783+ )
784+ .0,
785+ slice.line_to_char((i + j) as usize) + 3
786+ );
787+ }
788+ }
789+
790+ text_fmt.soft_wrap = true;
791+ let mut softwrapped_text = "foo ".repeat(10);
792+ softwrapped_text.push('\n');
793+ let last_char = softwrapped_text.len() - 1;
794+
795+ let text = Rope::from(softwrapped_text.repeat(3));
796+ let slice = text.slice(..);
797+ assert_eq!(
798+ char_idx_at_visual_offset(
799+ slice,
800+ last_char,
801+ 0,
802+ 0,
803+ &text_fmt,
804+ &TextAnnotations::default(),
805+ )
806+ .0,
807+ 32
808+ );
809+ assert_eq!(
810+ char_idx_at_visual_offset(
811+ slice,
812+ last_char,
813+ -1,
814+ 0,
815+ &text_fmt,
816+ &TextAnnotations::default(),
817+ )
818+ .0,
819+ 16
820+ );
821+ assert_eq!(
822+ char_idx_at_visual_offset(
823+ slice,
824+ last_char,
825+ -2,
826+ 0,
827+ &text_fmt,
828+ &TextAnnotations::default(),
829+ )
830+ .0,
831+ 0
832+ );
833+ assert_eq!(
834+ char_idx_at_visual_offset(
835+ slice,
836+ softwrapped_text.len() + last_char,
837+ -2,
838+ 0,
839+ &text_fmt,
840+ &TextAnnotations::default(),
841+ )
842+ .0,
843+ softwrapped_text.len()
844+ );
845+
846+ assert_eq!(
847+ char_idx_at_visual_offset(
848+ slice,
849+ softwrapped_text.len() + last_char,
850+ -5,
851+ 0,
852+ &text_fmt,
853+ &TextAnnotations::default(),
854+ )
855+ .0,
856+ 0
857+ );
858+ }
408859}
helix-core/src/register.rsdeleted−83
@@ -1,83 +0,0 @@
1−use std::collections::HashMap;
2−
3−#[derive(Debug)]
4−pub struct Register {
5− name: char,
6− values: Vec<String>,
7−}
8−
9−impl Register {
10− pub const fn new(name: char) -> Self {
11− Self {
12− name,
13− values: Vec::new(),
14− }
15− }
16−
17− pub fn new_with_values(name: char, values: Vec<String>) -> Self {
18− if name == '_' {
19− Self::new(name)
20− } else {
21− Self { name, values }
22− }
23− }
24−
25− pub const fn name(&self) -> char {
26− self.name
27− }
28−
29− pub fn read(&self) -> &[String] {
30− &self.values
31− }
32−
33− pub fn write(&mut self, values: Vec<String>) {
34− if self.name != '_' {
35− self.values = values;
36− }
37− }
38−
39− pub fn push(&mut self, value: String) {
40− if self.name != '_' {
41− self.values.push(value);
42− }
43− }
44−}
45−
46−/// Currently just wraps a `HashMap` of `Register`s
47−#[derive(Debug, Default)]
48−pub struct Registers {
49− inner: HashMap<char, Register>,
50−}
51−
52−impl Registers {
53− pub fn get(&self, name: char) -> Option<&Register> {
54− self.inner.get(&name)
55− }
56−
57− pub fn get_mut(&mut self, name: char) -> &mut Register {
58− self.inner
59− .entry(name)
60− .or_insert_with(|| Register::new(name))
61− }
62−
63− pub fn write(&mut self, name: char, values: Vec<String>) {
64− self.inner
65− .insert(name, Register::new_with_values(name, values));
66− }
67−
68− pub fn read(&self, name: char) -> Option<&[String]> {
69− self.get(name).map(|reg| reg.read())
70− }
71−
72− pub fn first(&self, name: char) -> Option<&String> {
73− self.read(name).and_then(|entries| entries.first())
74− }
75−
76− pub fn last(&self, name: char) -> Option<&String> {
77− self.read(name).and_then(|entries| entries.last())
78− }
79−
80− pub fn inner(&self) -> &HashMap<char, Register> {
81− &self.inner
82− }
83−}
helix-core/src/rope_reader.rsnew file+37
@@ -0,0 +1,37 @@
1+use std::io;
2+
3+use ropey::iter::Chunks;
4+use ropey::RopeSlice;
5+
6+pub struct RopeReader<'a> {
7+ current_chunk: &'a [u8],
8+ chunks: Chunks<'a>,
9+}
10+
11+impl<'a> RopeReader<'a> {
12+ pub fn new(rope: RopeSlice<'a>) -> RopeReader<'a> {
13+ RopeReader {
14+ current_chunk: &[],
15+ chunks: rope.chunks(),
16+ }
17+ }
18+}
19+
20+impl io::Read for RopeReader<'_> {
21+ fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
22+ let buf_len = buf.len();
23+ loop {
24+ let read_bytes = self.current_chunk.read(buf)?;
25+ buf = &mut buf[read_bytes..];
26+ if buf.is_empty() {
27+ return Ok(buf_len);
28+ }
29+
30+ if let Some(next_chunk) = self.chunks.next() {
31+ self.current_chunk = next_chunk.as_bytes();
32+ } else {
33+ return Ok(buf_len - buf.len());
34+ }
35+ }
36+ }
37+}
helix-core/src/selection.rs+205−79
@@ -21,14 +21,14 @@ use std::borrow::Cow;
2121/// can be in any order, or even share the same position.
2222///
2323/// The anchor and head positions use gap indexing, meaning
24−/// that their indices represent the the gaps *between* `char`s
24+/// that their indices represent the gaps *between* `char`s
2525/// rather than the `char`s themselves. For example, 1
2626/// represents the position between the first and second `char`.
2727///
28−/// Below are some example `Range` configurations to better
29−/// illustrate. The anchor and head indices are show as
30−/// "(anchor, head)", followed by example text with "[" and "]"
31−/// inserted to represent the anchor and head positions:
28+/// Below are some examples of `Range` configurations.
29+/// The anchor and head indices are shown as "(anchor, head)"
30+/// tuples, followed by example text with "[" and "]" symbols
31+/// representing the anchor and head positions:
3232///
3333/// - (0, 3): `[Som]e text`.
3434/// - (3, 0): `]Som[e text`.
@@ -38,7 +38,7 @@ use std::borrow::Cow;
3838/// Ranges are considered to be inclusive on the left and
3939/// exclusive on the right, regardless of anchor-head ordering.
4040/// This means, for example, that non-zero-width ranges that
41−/// are directly adjecent, sharing an edge, do not overlap.
41+/// are directly adjacent, sharing an edge, do not overlap.
4242/// However, a zero-width range will overlap with the shared
4343/// left-edge of another range.
4444///
@@ -53,7 +53,9 @@ pub struct Range {
5353 pub anchor: usize,
5454 /// The head of the range, moved when extending.
5555 pub head: usize,
56− pub horiz: Option<u32>,
56+ /// The previous visual offset (softwrapped lines and columns) from
57+ /// the start of the line
58+ pub old_visual_position: Option<(u32, u32)>,
5759}
5860
5961impl Range {
@@ -61,7 +63,7 @@ impl Range {
6163 Self {
6264 anchor,
6365 head,
64− horiz: None,
66+ old_visual_position: None,
6567 }
6668 }
6769
@@ -127,7 +129,7 @@ impl Range {
127129 Self {
128130 anchor: self.head,
129131 head: self.anchor,
130− horiz: self.horiz,
132+ old_visual_position: self.old_visual_position,
131133 }
132134 }
133135
@@ -159,34 +161,35 @@ impl Range {
159161 self.from() <= pos && pos < self.to()
160162 }
161163
162− /// Map a range through a set of changes. Returns a new range representing the same position
163− /// after the changes are applied.
164− pub fn map(self, changes: &ChangeSet) -> Self {
164+ /// Map a range through a set of changes. Returns a new range representing
165+ /// the same position after the changes are applied. Note that this
166+ /// function runs in O(N) (N is number of changes) and can therefore
167+ /// cause performance problems if run for a large number of ranges as the
168+ /// complexity is then O(MN) (for multicuror M=N usually). Instead use
169+ /// [Selection::map] or [ChangeSet::update_positions] instead
170+ pub fn map(mut self, changes: &ChangeSet) -> Self {
165171 use std::cmp::Ordering;
166− let (anchor, head) = match self.anchor.cmp(&self.head) {
167− Ordering::Equal => (
168− changes.map_pos(self.anchor, Assoc::After),
169− changes.map_pos(self.head, Assoc::After),
170− ),
171− Ordering::Less => (
172− changes.map_pos(self.anchor, Assoc::After),
173− changes.map_pos(self.head, Assoc::Before),
174− ),
175− Ordering::Greater => (
176− changes.map_pos(self.anchor, Assoc::Before),
177− changes.map_pos(self.head, Assoc::After),
178− ),
179− };
180−
181− // We want to return a new `Range` with `horiz == None` every time,
182− // even if the anchor and head haven't changed, because we don't
183− // know if the *visual* position hasn't changed due to
184− // character-width or grapheme changes earlier in the text.
185− Self {
186− anchor,
187− head,
188− horiz: None,
172+ if changes.is_empty() {
173+ return self;
189174 }
175+
176+ let positions_to_map = match self.anchor.cmp(&self.head) {
177+ Ordering::Equal => [
178+ (&mut self.anchor, Assoc::After),
179+ (&mut self.head, Assoc::After),
180+ ],
181+ Ordering::Less => [
182+ (&mut self.anchor, Assoc::After),
183+ (&mut self.head, Assoc::Before),
184+ ],
185+ Ordering::Greater => [
186+ (&mut self.head, Assoc::After),
187+ (&mut self.anchor, Assoc::Before),
188+ ],
189+ };
190+ changes.update_positions(positions_to_map.into_iter());
191+ self.old_visual_position = None;
192+ self
190193 }
191194
192195 /// Extend the range to cover at least `from` `to`.
@@ -198,13 +201,13 @@ impl Range {
198201 Self {
199202 anchor: self.anchor.min(from),
200203 head: self.head.max(to),
201− horiz: None,
204+ old_visual_position: None,
202205 }
203206 } else {
204207 Self {
205208 anchor: self.anchor.max(to),
206209 head: self.head.min(from),
207− horiz: None,
210+ old_visual_position: None,
208211 }
209212 }
210213 }
@@ -219,13 +222,13 @@ impl Range {
219222 Range {
220223 anchor: self.anchor.max(other.anchor),
221224 head: self.head.min(other.head),
222− horiz: None,
225+ old_visual_position: None,
223226 }
224227 } else {
225228 Range {
226229 anchor: self.from().min(other.from()),
227230 head: self.to().max(other.to()),
228− horiz: None,
231+ old_visual_position: None,
229232 }
230233 }
231234 }
@@ -279,8 +282,8 @@ impl Range {
279282 Range {
280283 anchor: new_anchor,
281284 head: new_head,
282− horiz: if new_anchor == self.anchor {
283− self.horiz
285+ old_visual_position: if new_anchor == self.anchor {
286+ self.old_visual_position
284287 } else {
285288 None
286289 },
@@ -306,7 +309,7 @@ impl Range {
306309 Range {
307310 anchor: self.anchor,
308311 head: next_grapheme_boundary(slice, self.head),
309− horiz: self.horiz,
312+ old_visual_position: self.old_visual_position,
310313 }
311314 } else {
312315 *self
@@ -378,7 +381,7 @@ impl From<(usize, usize)> for Range {
378381 Self {
379382 anchor,
380383 head,
381− horiz: None,
384+ old_visual_position: None,
382385 }
383386 }
384387}
@@ -449,17 +452,36 @@ impl Selection {
449452 /// Map selections over a set of changes. Useful for adjusting the selection position after
450453 /// applying changes to a document.
451454 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 {
452461 if changes.is_empty() {
453462 return self;
454463 }
455464
456− Self::new(
457− self.ranges
458− .into_iter()
459− .map(|range| range.map(changes))
460− .collect(),
461− self.primary_index,
462− )
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
463485 }
464486
465487 pub fn ranges(&self) -> &[Range] {
@@ -482,7 +504,7 @@ impl Selection {
482504 ranges: smallvec![Range {
483505 anchor,
484506 head,
485− horiz: None
507+ old_visual_position: None
486508 }],
487509 primary_index: 0,
488510 }
@@ -495,28 +517,63 @@ impl Selection {
495517
496518 /// Normalizes a `Selection`.
497519 fn normalize(mut self) -> Self {
498− let primary = self.ranges[self.primary_index];
520+ if self.len() < 2 {
521+ return self;
522+ }
523+ let mut primary = self.ranges[self.primary_index];
499524 self.ranges.sort_unstable_by_key(Range::from);
525+
526+ self.ranges.dedup_by(|curr_range, prev_range| {
527+ if prev_range.overlaps(curr_range) {
528+ let new_range = curr_range.merge(*prev_range);
529+ if prev_range == &primary || curr_range == &primary {
530+ primary = new_range;
531+ }
532+ *prev_range = new_range;
533+ true
534+ } else {
535+ false
536+ }
537+ });
538+
500539 self.primary_index = self
501540 .ranges
502541 .iter()
503542 .position(|&range| range == primary)
504543 .unwrap();
505544
506− let mut prev_i = 0;
507− for i in 1..self.ranges.len() {
508− if self.ranges[prev_i].overlaps(&self.ranges[i]) {
509− self.ranges[prev_i] = self.ranges[prev_i].merge(self.ranges[i]);
545+ self
546+ }
547+
548+ /// Replaces ranges with one spanning from first to last range.
549+ pub fn merge_ranges(self) -> Self {
550+ let first = self.ranges.first().unwrap();
551+ let last = self.ranges.last().unwrap();
552+ Selection::new(smallvec![first.merge(*last)], 0)
553+ }
554+
555+ /// Merges all ranges that are consecutive.
556+ pub fn merge_consecutive_ranges(mut self) -> Self {
557+ let mut primary = self.ranges[self.primary_index];
558+
559+ self.ranges.dedup_by(|curr_range, prev_range| {
560+ if prev_range.to() == curr_range.from() {
561+ let new_range = curr_range.merge(*prev_range);
562+ if prev_range == &primary || curr_range == &primary {
563+ primary = new_range;
564+ }
565+ *prev_range = new_range;
566+ true
510567 } else {
511− prev_i += 1;
512− self.ranges[prev_i] = self.ranges[i];
568+ false
513569 }
514− if i == self.primary_index {
515− self.primary_index = prev_i;
516− }
517− }
570+ });
518571
519− self.ranges.truncate(prev_i + 1);
572+ self.primary_index = self
573+ .ranges
574+ .iter()
575+ .position(|&range| range == primary)
576+ .unwrap();
520577
521578 self
522579 }
@@ -527,23 +584,18 @@ impl Selection {
527584 assert!(!ranges.is_empty());
528585 debug_assert!(primary_index < ranges.len());
529586
530− let mut selection = Self {
587+ let selection = Self {
531588 ranges,
532589 primary_index,
533590 };
534591
535− if selection.ranges.len() > 1 {
536− // TODO: only normalize if needed (any ranges out of order)
537− selection = selection.normalize();
538− }
539−
540− selection
592+ selection.normalize()
541593 }
542594
543595 /// Takes a closure and maps each `Range` over the closure.
544− pub fn transform<F>(mut self, f: F) -> Self
596+ pub fn transform<F>(mut self, mut f: F) -> Self
545597 where
546− F: Fn(Range) -> Range,
598+ F: FnMut(Range) -> Range,
547599 {
548600 for range in self.ranges.iter_mut() {
549601 *range = f(*range)
@@ -551,6 +603,16 @@ impl Selection {
551603 self.normalize()
552604 }
553605
606+ /// Takes a closure and maps each `Range` over the closure to multiple `Range`s.
607+ pub fn transform_iter<F, I>(mut self, f: F) -> Self
608+ where
609+ F: FnMut(Range) -> I,
610+ I: Iterator<Item = Range>,
611+ {
612+ self.ranges = self.ranges.into_iter().flat_map(f).collect();
613+ self.normalize()
614+ }
615+
554616 // Ensures the selection adheres to the following invariants:
555617 // 1. All ranges are grapheme aligned.
556618 // 2. All ranges are at least 1 character wide, unless at the
@@ -568,11 +630,19 @@ impl Selection {
568630 self.transform(|range| Range::point(range.cursor(text)))
569631 }
570632
571− pub fn fragments<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = Cow<str>> + 'a {
633+ pub fn fragments<'a>(
634+ &'a self,
635+ text: RopeSlice<'a>,
636+ ) -> impl DoubleEndedIterator<Item = Cow<'a, str>> + ExactSizeIterator<Item = Cow<str>> + 'a
637+ {
572638 self.ranges.iter().map(move |range| range.fragment(text))
573639 }
574640
575− pub fn slices<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = RopeSlice> + 'a {
641+ pub fn slices<'a>(
642+ &'a self,
643+ text: RopeSlice<'a>,
644+ ) -> impl DoubleEndedIterator<Item = RopeSlice<'a>> + ExactSizeIterator<Item = RopeSlice<'a>> + 'a
645+ {
576646 self.ranges.iter().map(move |range| range.slice(text))
577647 }
578648
@@ -588,11 +658,6 @@ impl Selection {
588658
589659 // returns true if self ⊇ other
590660 pub fn contains(&self, other: &Selection) -> bool {
591− // can't contain other if it is larger
592− if other.len() > self.len() {
593− return false;
594− }
595−
596661 let (mut iter_self, mut iter_other) = (self.iter(), other.iter());
597662 let (mut ele_self, mut ele_other) = (iter_self.next(), iter_other.next());
598663
@@ -629,6 +694,15 @@ impl<'a> IntoIterator for &'a Selection {
629694 }
630695}
631696
697+impl IntoIterator for Selection {
698+ type Item = Range;
699+ type IntoIter = smallvec::IntoIter<[Range; 1]>;
700+
701+ fn into_iter(self) -> smallvec::IntoIter<[Range; 1]> {
702+ self.ranges.into_iter()
703+ }
704+}
705+
632706// TODO: checkSelection -> check if valid for doc length && sorted
633707
634708pub fn keep_or_remove_matches(
@@ -1132,6 +1206,52 @@ mod test {
11321206 &["", "abcd", "efg", "rs", "xyz"]
11331207 );
11341208 }
1209+
1210+ #[test]
1211+ fn test_merge_consecutive_ranges() {
1212+ let selection = Selection::new(
1213+ smallvec![
1214+ Range::new(0, 1),
1215+ Range::new(1, 10),
1216+ Range::new(15, 20),
1217+ Range::new(25, 26),
1218+ Range::new(26, 30)
1219+ ],
1220+ 4,
1221+ );
1222+
1223+ let result = selection.merge_consecutive_ranges();
1224+
1225+ assert_eq!(
1226+ result.ranges(),
1227+ &[Range::new(0, 10), Range::new(15, 20), Range::new(25, 30)]
1228+ );
1229+ assert_eq!(result.primary_index, 2);
1230+
1231+ let selection = Selection::new(smallvec![Range::new(0, 1)], 0);
1232+ let result = selection.merge_consecutive_ranges();
1233+
1234+ assert_eq!(result.ranges(), &[Range::new(0, 1)]);
1235+ assert_eq!(result.primary_index, 0);
1236+
1237+ let selection = Selection::new(
1238+ smallvec![
1239+ Range::new(0, 1),
1240+ Range::new(1, 5),
1241+ Range::new(5, 8),
1242+ Range::new(8, 10),
1243+ Range::new(10, 15),
1244+ Range::new(18, 25)
1245+ ],
1246+ 3,
1247+ );
1248+
1249+ let result = selection.merge_consecutive_ranges();
1250+
1251+ assert_eq!(result.ranges(), &[Range::new(0, 15), Range::new(18, 25)]);
1252+ assert_eq!(result.primary_index, 0);
1253+ }
1254+
11351255 #[test]
11361256 fn test_selection_contains() {
11371257 fn contains(a: Vec<(usize, usize)>, b: Vec<(usize, usize)>) -> bool {
@@ -1157,5 +1277,11 @@ mod test {
11571277 vec!((3, 4), (7, 9))
11581278 ));
11591279 assert!(!contains(vec!((1, 1), (5, 6)), vec!((1, 6))));
1280+
1281+ // multiple ranges of other are all contained in some ranges of self,
1282+ assert!(contains(
1283+ vec!((1, 4), (7, 10)),
1284+ vec!((1, 2), (3, 4), (7, 9))
1285+ ));
11601286 }
11611287}
helix-core/src/shellwords.rs+187−162
@@ -27,181 +27,173 @@ enum State {
2727 DquoteEscaped,
2828}
2929
30−/// Get the vec of escaped / quoted / doublequoted filenames from the input str
31−pub fn shellwords(input: &str) -> Vec<Cow<'_, str>> {
32− use State::*;
30+pub struct Shellwords<'a> {
31+ state: State,
32+ /// Shellwords where whitespace and escapes has been resolved.
33+ words: Vec<Cow<'a, str>>,
34+ /// The parts of the input that are divided into shellwords. This can be
35+ /// used to retrieve the original text for a given word by looking up the
36+ /// same index in the Vec as the word in `words`.
37+ parts: Vec<&'a str>,
38+}
3339
34− let mut state = Unquoted;
35− let mut args: Vec<Cow<str>> = Vec::new();
36− let mut escaped = String::with_capacity(input.len());
40+impl<'a> From<&'a str> for Shellwords<'a> {
41+ fn from(input: &'a str) -> Self {
42+ use State::*;
3743
38− let mut start = 0;
39− let mut end = 0;
44+ let mut state = Unquoted;
45+ let mut words = Vec::new();
46+ let mut parts = Vec::new();
47+ let mut escaped = String::with_capacity(input.len());
4048
41− for (i, c) in input.char_indices() {
42− state = match state {
43− OnWhitespace => match c {
44− '"' => {
45− end = i;
46− Dquoted
47− }
48− '\'' => {
49− end = i;
50− Quoted
51− }
52− '\\' => {
53− if cfg!(unix) {
54− escaped.push_str(&input[start..i]);
55− start = i + 1;
56− UnquotedEscaped
57− } else {
49+ let mut part_start = 0;
50+ let mut unescaped_start = 0;
51+ let mut end = 0;
52+
53+ for (i, c) in input.char_indices() {
54+ state = match state {
55+ OnWhitespace => match c {
56+ '"' => {
57+ end = i;
58+ Dquoted
59+ }
60+ '\'' => {
61+ end = i;
62+ Quoted
63+ }
64+ '\\' => {
65+ if cfg!(unix) {
66+ escaped.push_str(&input[unescaped_start..i]);
67+ unescaped_start = i + 1;
68+ UnquotedEscaped
69+ } else {
70+ OnWhitespace
71+ }
72+ }
73+ c if c.is_ascii_whitespace() => {
74+ end = i;
5875 OnWhitespace
5976 }
60− }
61− c if c.is_ascii_whitespace() => {
62− end = i;
63− OnWhitespace
64− }
65− _ => Unquoted,
66− },
67− Unquoted => match c {
68− '\\' => {
69− if cfg!(unix) {
70− escaped.push_str(&input[start..i]);
71− start = i + 1;
72− UnquotedEscaped
73− } else {
74− Unquoted
77+ _ => Unquoted,
78+ },
79+ Unquoted => match c {
80+ '\\' => {
81+ if cfg!(unix) {
82+ escaped.push_str(&input[unescaped_start..i]);
83+ unescaped_start = i + 1;
84+ UnquotedEscaped
85+ } else {
86+ Unquoted
87+ }
7588 }
76− }
77− c if c.is_ascii_whitespace() => {
78− end = i;
79− OnWhitespace
80− }
81− _ => Unquoted,
82− },
83− UnquotedEscaped => Unquoted,
84− Quoted => match c {
85− '\\' => {
86− if cfg!(unix) {
87− escaped.push_str(&input[start..i]);
88− start = i + 1;
89− QuoteEscaped
90− } else {
91− Quoted
89+ c if c.is_ascii_whitespace() => {
90+ end = i;
91+ OnWhitespace
9292 }
93− }
94− '\'' => {
95− end = i;
96− OnWhitespace
97− }
98− _ => Quoted,
99− },
100− QuoteEscaped => Quoted,
101− Dquoted => match c {
102− '\\' => {
103− if cfg!(unix) {
104− escaped.push_str(&input[start..i]);
105− start = i + 1;
106− DquoteEscaped
107− } else {
108− Dquoted
93+ _ => Unquoted,
94+ },
95+ UnquotedEscaped => Unquoted,
96+ Quoted => match c {
97+ '\\' => {
98+ if cfg!(unix) {
99+ escaped.push_str(&input[unescaped_start..i]);
100+ unescaped_start = i + 1;
101+ QuoteEscaped
102+ } else {
103+ Quoted
104+ }
109105 }
110− }
111− '"' => {
112− end = i;
113− OnWhitespace
114− }
115− _ => Dquoted,
116− },
117− DquoteEscaped => Dquoted,
118− };
106+ '\'' => {
107+ end = i;
108+ OnWhitespace
109+ }
110+ _ => Quoted,
111+ },
112+ QuoteEscaped => Quoted,
113+ Dquoted => match c {
114+ '\\' => {
115+ if cfg!(unix) {
116+ escaped.push_str(&input[unescaped_start..i]);
117+ unescaped_start = i + 1;
118+ DquoteEscaped
119+ } else {
120+ Dquoted
121+ }
122+ }
123+ '"' => {
124+ end = i;
125+ OnWhitespace
126+ }
127+ _ => Dquoted,
128+ },
129+ DquoteEscaped => Dquoted,
130+ };
119131
120− if i >= input.len() - 1 && end == 0 {
121− end = i + 1;
122− }
132+ let c_len = c.len_utf8();
133+ if i == input.len() - c_len && end == 0 {
134+ end = i + c_len;
135+ }
123136
124− if end > 0 {
125− let esc_trim = escaped.trim();
126− let inp = &input[start..end];
137+ if end > 0 {
138+ let esc_trim = escaped.trim();
139+ let inp = &input[unescaped_start..end];
127140
128− if !(esc_trim.is_empty() && inp.trim().is_empty()) {
129− if esc_trim.is_empty() {
130− args.push(inp.into());
131− } else {
132− args.push([escaped, inp.into()].concat().into());
133− escaped = "".to_string();
141+ if !(esc_trim.is_empty() && inp.trim().is_empty()) {
142+ if esc_trim.is_empty() {
143+ words.push(inp.into());
144+ parts.push(inp);
145+ } else {
146+ words.push([escaped, inp.into()].concat().into());
147+ parts.push(&input[part_start..end]);
148+ escaped = "".to_string();
149+ }
134150 }
151+ unescaped_start = i + 1;
152+ part_start = i + 1;
153+ end = 0;
135154 }
136− start = i + 1;
137− end = 0;
138155 }
139− }
140− args
141−}
142156
143−/// Checks that the input ends with an ascii whitespace character which is
144−/// not escaped.
145−///
146−/// # Examples
147−///
148−/// ```rust
149−/// use helix_core::shellwords::ends_with_whitespace;
150−/// assert_eq!(ends_with_whitespace(" "), true);
151−/// assert_eq!(ends_with_whitespace(":open "), true);
152−/// assert_eq!(ends_with_whitespace(":open foo.txt "), true);
153−/// assert_eq!(ends_with_whitespace(":open"), false);
154−/// #[cfg(unix)]
155−/// assert_eq!(ends_with_whitespace(":open a\\ "), false);
156−/// #[cfg(unix)]
157−/// assert_eq!(ends_with_whitespace(":open a\\ b.txt"), false);
158−/// ```
159−pub fn ends_with_whitespace(input: &str) -> bool {
160− use State::*;
157+ debug_assert!(words.len() == parts.len());
161158
162− // Fast-lane: the input must end with a whitespace character
163− // regardless of quoting.
164− if !input.ends_with(|c: char| c.is_ascii_whitespace()) {
165− return false;
159+ Self {
160+ state,
161+ words,
162+ parts,
163+ }
166164 }
165+}
167166
168− let mut state = Unquoted;
167+impl<'a> Shellwords<'a> {
168+ /// Checks that the input ends with a whitespace character which is not escaped.
169+ ///
170+ /// # Examples
171+ ///
172+ /// ```rust
173+ /// use helix_core::shellwords::Shellwords;
174+ /// assert_eq!(Shellwords::from(" ").ends_with_whitespace(), true);
175+ /// assert_eq!(Shellwords::from(":open ").ends_with_whitespace(), true);
176+ /// assert_eq!(Shellwords::from(":open foo.txt ").ends_with_whitespace(), true);
177+ /// assert_eq!(Shellwords::from(":open").ends_with_whitespace(), false);
178+ /// #[cfg(unix)]
179+ /// assert_eq!(Shellwords::from(":open a\\ ").ends_with_whitespace(), false);
180+ /// #[cfg(unix)]
181+ /// assert_eq!(Shellwords::from(":open a\\ b.txt").ends_with_whitespace(), false);
182+ /// ```
183+ pub fn ends_with_whitespace(&self) -> bool {
184+ matches!(self.state, State::OnWhitespace)
185+ }
169186
170− for c in input.chars() {
171− state = match state {
172− OnWhitespace => match c {
173− '"' => Dquoted,
174− '\'' => Quoted,
175− '\\' if cfg!(unix) => UnquotedEscaped,
176− '\\' => OnWhitespace,
177− c if c.is_ascii_whitespace() => OnWhitespace,
178− _ => Unquoted,
179− },
180− Unquoted => match c {
181− '\\' if cfg!(unix) => UnquotedEscaped,
182− '\\' => Unquoted,
183− c if c.is_ascii_whitespace() => OnWhitespace,
184− _ => Unquoted,
185− },
186− UnquotedEscaped => Unquoted,
187− Quoted => match c {
188− '\\' if cfg!(unix) => QuoteEscaped,
189− '\\' => Quoted,
190− '\'' => OnWhitespace,
191− _ => Quoted,
192− },
193− QuoteEscaped => Quoted,
194− Dquoted => match c {
195− '\\' if cfg!(unix) => DquoteEscaped,
196− '\\' => Dquoted,
197− '"' => OnWhitespace,
198− _ => Dquoted,
199− },
200− DquoteEscaped => Dquoted,
201− }
187+ /// Returns the list of shellwords calculated from the input string.
188+ pub fn words(&self) -> &[Cow<'a, str>] {
189+ &self.words
202190 }
203191
204− matches!(state, OnWhitespace)
192+ /// Returns a list of strings which correspond to [`Self::words`] but represent the original
193+ /// text in the input string - including escape characters - without separating whitespace.
194+ pub fn parts(&self) -> &[&'a str] {
195+ &self.parts
196+ }
205197}
206198
207199#[cfg(test)]
@@ -212,7 +204,8 @@ mod test {
212204 #[cfg(windows)]
213205 fn test_normal() {
214206 let input = r#":o single_word twó wörds \three\ \"with\ escaping\\"#;
215− let result = shellwords(input);
207+ let shellwords = Shellwords::from(input);
208+ let result = shellwords.words().to_vec();
216209 let expected = vec![
217210 Cow::from(":o"),
218211 Cow::from("single_word"),
@@ -230,7 +223,8 @@ mod test {
230223 #[cfg(unix)]
231224 fn test_normal() {
232225 let input = r#":o single_word twó wörds \three\ \"with\ escaping\\"#;
233− let result = shellwords(input);
226+ let shellwords = Shellwords::from(input);
227+ let result = shellwords.words().to_vec();
234228 let expected = vec![
235229 Cow::from(":o"),
236230 Cow::from("single_word"),
@@ -247,7 +241,8 @@ mod test {
247241 fn test_quoted() {
248242 let quoted =
249243 r#":o 'single_word' 'twó wörds' '' ' ''\three\' \"with\ escaping\\' 'quote incomplete"#;
250− let result = shellwords(quoted);
244+ let shellwords = Shellwords::from(quoted);
245+ let result = shellwords.words().to_vec();
251246 let expected = vec![
252247 Cow::from(":o"),
253248 Cow::from("single_word"),
@@ -262,7 +257,8 @@ mod test {
262257 #[cfg(unix)]
263258 fn test_dquoted() {
264259 let dquoted = r#":o "single_word" "twó wörds" "" " ""\three\' \"with\ escaping\\" "dquote incomplete"#;
265− let result = shellwords(dquoted);
260+ let shellwords = Shellwords::from(dquoted);
261+ let result = shellwords.words().to_vec();
266262 let expected = vec![
267263 Cow::from(":o"),
268264 Cow::from("single_word"),
@@ -277,7 +273,8 @@ mod test {
277273 #[cfg(unix)]
278274 fn test_mixed() {
279275 let dquoted = r#":o single_word 'twó wörds' "\three\' \"with\ escaping\\""no space before"'and after' $#%^@ "%^&(%^" ')(*&^%''a\\\\\b' '"#;
280− let result = shellwords(dquoted);
276+ let shellwords = Shellwords::from(dquoted);
277+ let result = shellwords.words().to_vec();
281278 let expected = vec![
282279 Cow::from(":o"),
283280 Cow::from("single_word"),
@@ -297,13 +294,14 @@ mod test {
297294 #[test]
298295 fn test_lists() {
299296 let input =
300− r#":set statusline.center ["file-type","file-encoding"] '["list", "in", "qoutes"]'"#;
301− let result = shellwords(input);
297+ r#":set statusline.center ["file-type","file-encoding"] '["list", "in", "quotes"]'"#;
298+ let shellwords = Shellwords::from(input);
299+ let result = shellwords.words().to_vec();
302300 let expected = vec![
303301 Cow::from(":set"),
304302 Cow::from("statusline.center"),
305303 Cow::from(r#"["file-type","file-encoding"]"#),
306− Cow::from(r#"["list", "in", "qoutes"]"#),
304+ Cow::from(r#"["list", "in", "quotes"]"#),
307305 ];
308306 assert_eq!(expected, result);
309307 }
@@ -322,4 +320,31 @@ mod test {
322320 assert_eq!(escape("foobar".into()), Cow::Borrowed("foobar"));
323321 assert_eq!(escape("foo bar".into()), Cow::Borrowed("\"foo bar\""));
324322 }
323+
324+ #[test]
325+ #[cfg(unix)]
326+ fn test_parts() {
327+ assert_eq!(Shellwords::from(":o a").parts(), &[":o", "a"]);
328+ assert_eq!(Shellwords::from(":o a\\ ").parts(), &[":o", "a\\ "]);
329+ }
330+
331+ #[test]
332+ #[cfg(windows)]
333+ fn test_parts() {
334+ assert_eq!(Shellwords::from(":o a").parts(), &[":o", "a"]);
335+ assert_eq!(Shellwords::from(":o a\\ ").parts(), &[":o", "a\\"]);
336+ }
337+
338+ #[test]
339+ fn test_multibyte_at_end() {
340+ assert_eq!(Shellwords::from("𒀀").parts(), &["𒀀"]);
341+ assert_eq!(
342+ Shellwords::from(":sh echo 𒀀").parts(),
343+ &[":sh", "echo", "𒀀"]
344+ );
345+ assert_eq!(
346+ Shellwords::from(":sh echo 𒀀 hello world𒀀").parts(),
347+ &[":sh", "echo", "𒀀", "hello", "world𒀀"]
348+ );
349+ }
325350}
helix-core/src/state.rsdeleted−17
@@ -1,17 +0,0 @@
1−use crate::{Rope, Selection};
2−
3−#[derive(Debug, Clone)]
4−pub struct State {
5− pub doc: Rope,
6− pub selection: Selection,
7−}
8−
9−impl State {
10− #[must_use]
11− pub fn new(doc: Rope) -> Self {
12− Self {
13− doc,
14− selection: Selection::point(0),
15− }
16− }
17−}
helix-core/src/surround.rs+145−122
@@ -1,6 +1,6 @@
11use std::fmt::Display;
22
3−use crate::{search, Range, Selection};
3+use crate::{movement::Direction, search, Range, Selection};
44use ropey::RopeSlice;
55
66pub const PAIRS: &[(char, char)] = &[
@@ -55,15 +55,18 @@ pub fn get_pair(ch: char) -> (char, char) {
5555pub fn find_nth_closest_pairs_pos(
5656 text: RopeSlice,
5757 range: Range,
58− n: usize,
58+ mut skip: usize,
5959) -> Result<(usize, usize)> {
6060 let is_open_pair = |ch| PAIRS.iter().any(|(open, _)| *open == ch);
6161 let is_close_pair = |ch| PAIRS.iter().any(|(_, close)| *close == ch);
6262
6363 let mut stack = Vec::with_capacity(2);
64− let pos = range.cursor(text);
64+ let pos = range.from();
65+ let mut close_pos = pos.saturating_sub(1);
6566
6667 for ch in text.chars_at(pos) {
68+ close_pos += 1;
69+
6770 if is_open_pair(ch) {
6871 // Track open pairs encountered so that we can step over
6972 // the corresponding close pairs that will come up further
@@ -71,20 +74,46 @@ pub fn find_nth_closest_pairs_pos(
7174 // open pair is before the cursor position.
7275 stack.push(ch);
7376 continue;
74− } else if is_close_pair(ch) {
75− let (open, _) = get_pair(ch);
76− if stack.last() == Some(&open) {
77− stack.pop();
78− continue;
79− } else {
80− // In the ideal case the stack would be empty here and the
81− // current character would be the close pair that we are
82− // looking for. It could also be the case that the pairs
83− // are unbalanced and we encounter a close pair that doesn't
84− // close the last seen open pair. In either case use this
85− // char as the auto-detected closest pair.
86− return find_nth_pairs_pos(text, ch, range, n);
77+ }
78+
79+ if !is_close_pair(ch) {
80+ // We don't care if this character isn't a brace pair item,
81+ // so short circuit here.
82+ continue;
83+ }
84+
85+ let (open, close) = get_pair(ch);
86+
87+ if stack.last() == Some(&open) {
88+ // If we are encountering the closing pair for an opener
89+ // we just found while traversing, then its inside the
90+ // selection and should be skipped over.
91+ stack.pop();
92+ continue;
93+ }
94+
95+ match find_nth_open_pair(text, open, close, close_pos, 1) {
96+ // Before we accept this pair, we want to ensure that the
97+ // pair encloses the range rather than just the cursor.
98+ Some(open_pos)
99+ if open_pos <= pos.saturating_add(1)
100+ && close_pos >= range.to().saturating_sub(1) =>
101+ {
102+ // Since we have special conditions for when to
103+ // accept, we can't just pass the skip parameter on
104+ // through to the find_nth_*_pair methods, so we
105+ // track skips manually here.
106+ if skip > 1 {
107+ skip -= 1;
108+ continue;
109+ }
110+
111+ return match range.direction() {
112+ Direction::Forward => Ok((open_pos, close_pos)),
113+ Direction::Backward => Ok((close_pos, open_pos)),
114+ };
87115 }
116+ _ => continue,
88117 }
89118 }
90119
@@ -244,141 +273,135 @@ mod test {
244273 use ropey::Rope;
245274 use smallvec::SmallVec;
246275
247− #[allow(clippy::type_complexity)]
248− fn check_find_nth_pair_pos(
249− text: &str,
250− cases: Vec<(usize, char, usize, Result<(usize, usize)>)>,
251− ) {
252− let doc = Rope::from(text);
253− let slice = doc.slice(..);
254−
255− for (cursor_pos, ch, n, expected_range) in cases {
256− let range = find_nth_pairs_pos(slice, ch, (cursor_pos, cursor_pos + 1).into(), n);
257− assert_eq!(
258− range, expected_range,
259− "Expected {:?}, got {:?}",
260− expected_range, range
276+ #[test]
277+ fn test_get_surround_pos() {
278+ #[rustfmt::skip]
279+ let (doc, selection, expectations) =
280+ rope_with_selections_and_expectations(
281+ "(some) (chars)\n(newline)",
282+ "_ ^ _ _ ^ _\n_ ^ _"
261283 );
262− }
284+
285+ assert_eq!(
286+ get_surround_pos(doc.slice(..), &selection, Some('('), 1).unwrap(),
287+ expectations
288+ );
263289 }
264290
265291 #[test]
266− fn test_find_nth_pairs_pos() {
267− check_find_nth_pair_pos(
268− "some (text) here",
269− vec![
270− // cursor on [t]ext
271− (6, '(', 1, Ok((5, 10))),
272− (6, ')', 1, Ok((5, 10))),
273− // cursor on so[m]e
274− (2, '(', 1, Err(Error::PairNotFound)),
275− // cursor on bracket itself
276− (5, '(', 1, Ok((5, 10))),
277− (10, '(', 1, Ok((5, 10))),
278− ],
292+ fn test_get_surround_pos_bail_different_surround_chars() {
293+ #[rustfmt::skip]
294+ let (doc, selection, _) =
295+ rope_with_selections_and_expectations(
296+ "[some]\n(chars)xx\n(newline)",
297+ " ^ \n ^ \n "
298+ );
299+
300+ assert_eq!(
301+ get_surround_pos(doc.slice(..), &selection, Some('('), 1),
302+ Err(Error::PairNotFound)
279303 );
280304 }
281305
282306 #[test]
283− fn test_find_nth_pairs_pos_skip() {
284− check_find_nth_pair_pos(
285− "(so (many (good) text) here)",
286− vec![
287− // cursor on go[o]d
288− (13, '(', 1, Ok((10, 15))),
289− (13, '(', 2, Ok((4, 21))),
290− (13, '(', 3, Ok((0, 27))),
291− ],
307+ fn test_get_surround_pos_bail_overlapping_surround_chars() {
308+ #[rustfmt::skip]
309+ let (doc, selection, _) =
310+ rope_with_selections_and_expectations(
311+ "[some]\n(chars)xx\n(newline)",
312+ " \n ^ \n ^ "
313+ );
314+
315+ assert_eq!(
316+ get_surround_pos(doc.slice(..), &selection, Some('('), 1),
317+ Err(Error::PairNotFound) // overlapping surround chars
292318 );
293319 }
294320
295321 #[test]
296− fn test_find_nth_pairs_pos_same() {
297− check_find_nth_pair_pos(
298− "'so 'many 'good' text' here'",
299− vec![
300− // cursor on go[o]d
301− (13, '\'', 1, Ok((10, 15))),
302− (13, '\'', 2, Ok((4, 21))),
303− (13, '\'', 3, Ok((0, 27))),
304− // cursor on the quotes
305− (10, '\'', 1, Err(Error::CursorOnAmbiguousPair)),
306− ],
307− )
322+ fn test_get_surround_pos_bail_cursor_overlap() {
323+ #[rustfmt::skip]
324+ let (doc, selection, _) =
325+ rope_with_selections_and_expectations(
326+ "[some]\n(chars)xx\n(newline)",
327+ " ^^ \n \n "
328+ );
329+
330+ assert_eq!(
331+ get_surround_pos(doc.slice(..), &selection, Some('['), 1),
332+ Err(Error::CursorOverlap)
333+ );
308334 }
309335
310336 #[test]
311− fn test_find_nth_pairs_pos_step() {
312− check_find_nth_pair_pos(
313− "((so)((many) good (text))(here))",
314− vec![
315− // cursor on go[o]d
316− (15, '(', 1, Ok((5, 24))),
317− (15, '(', 2, Ok((0, 31))),
318− ],
337+ fn test_find_nth_pairs_pos_quote_success() {
338+ #[rustfmt::skip]
339+ let (doc, selection, expectations) =
340+ rope_with_selections_and_expectations(
341+ "some 'quoted text' on this 'line'\n'and this one'",
342+ " _ ^ _ \n "
343+ );
344+
345+ assert_eq!(2, expectations.len());
346+ assert_eq!(
347+ find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 1)
348+ .expect("find should succeed"),
349+ (expectations[0], expectations[1])
319350 )
320351 }
321352
322353 #[test]
323− fn test_find_nth_pairs_pos_mixed() {
324− check_find_nth_pair_pos(
325− "(so [many {good} text] here)",
326− vec![
327− // cursor on go[o]d
328− (13, '{', 1, Ok((10, 15))),
329− (13, '[', 1, Ok((4, 21))),
330− (13, '(', 1, Ok((0, 27))),
331− ],
354+ fn test_find_nth_pairs_pos_nested_quote_success() {
355+ #[rustfmt::skip]
356+ let (doc, selection, expectations) =
357+ rope_with_selections_and_expectations(
358+ "some 'nested 'quoted' text' on this 'line'\n'and this one'",
359+ " _ ^ _ \n "
360+ );
361+
362+ assert_eq!(2, expectations.len());
363+ assert_eq!(
364+ find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 2)
365+ .expect("find should succeed"),
366+ (expectations[0], expectations[1])
332367 )
333368 }
334369
335370 #[test]
336− fn test_get_surround_pos() {
337− let doc = Rope::from("(some) (chars)\n(newline)");
338− let slice = doc.slice(..);
339− let selection = Selection::new(
340− SmallVec::from_slice(&[Range::point(2), Range::point(9), Range::point(20)]),
341− 0,
342− );
371+ fn test_find_nth_pairs_pos_inside_quote_ambiguous() {
372+ #[rustfmt::skip]
373+ let (doc, selection, _) =
374+ rope_with_selections_and_expectations(
375+ "some 'nested 'quoted' text' on this 'line'\n'and this one'",
376+ " ^ \n "
377+ );
343378
344− // cursor on s[o]me, c[h]ars, newl[i]ne
345379 assert_eq!(
346− get_surround_pos(slice, &selection, Some('('), 1)
347− .unwrap()
348− .as_slice(),
349− &[0, 5, 7, 13, 15, 23]
350− );
380+ find_nth_pairs_pos(doc.slice(..), '\'', selection.primary(), 1),
381+ Err(Error::CursorOnAmbiguousPair)
382+ )
351383 }
352384
353− #[test]
354− fn test_get_surround_pos_bail() {
355− let doc = Rope::from("[some]\n(chars)xx\n(newline)");
356− let slice = doc.slice(..);
385+ // Create a Rope and a matching Selection using a specification language.
386+ // ^ is a single-point selection.
387+ // _ is an expected index. These are returned as a Vec<usize> for use in assertions.
388+ fn rope_with_selections_and_expectations(
389+ text: &str,
390+ spec: &str,
391+ ) -> (Rope, Selection, Vec<usize>) {
392+ if text.len() != spec.len() {
393+ panic!("specification must match text length -- are newlines aligned?");
394+ }
357395
358− let selection =
359− Selection::new(SmallVec::from_slice(&[Range::point(2), Range::point(9)]), 0);
360− // cursor on s[o]me, c[h]ars
361− assert_eq!(
362− get_surround_pos(slice, &selection, Some('('), 1),
363− Err(Error::PairNotFound) // different surround chars
364− );
396+ let rope = Rope::from(text);
365397
366− let selection = Selection::new(
367− SmallVec::from_slice(&[Range::point(14), Range::point(24)]),
368− 0,
369− );
370− // cursor on [x]x, newli[n]e
371− assert_eq!(
372− get_surround_pos(slice, &selection, Some('('), 1),
373− Err(Error::PairNotFound) // overlapping surround chars
374− );
398+ let selections: SmallVec<[Range; 1]> = spec
399+ .match_indices('^')
400+ .map(|(i, _)| Range::point(i))
401+ .collect();
375402
376− let selection =
377− Selection::new(SmallVec::from_slice(&[Range::point(2), Range::point(3)]), 0);
378− // cursor on s[o][m]e
379− assert_eq!(
380− get_surround_pos(slice, &selection, Some('['), 1),
381− Err(Error::CursorOverlap)
382− );
403+ let expectations: Vec<usize> = spec.match_indices('_').map(|(i, _)| i).collect();
404+
405+ (rope, Selection::new(selections, 0), expectations)
383406 }
384407}
helix-core/src/syntax.rs+634−258
@@ -4,26 +4,29 @@ use crate::{
44 diagnostic::Severity,
55 regex::Regex,
66 transaction::{ChangeSet, Operation},
7− Rope, RopeSlice, Tendril,
7+ RopeSlice, Tendril,
88};
99
10+use ahash::RandomState;
1011use arc_swap::{ArcSwap, Guard};
1112use bitflags::bitflags;
13+use hashbrown::raw::RawTable;
1214use slotmap::{DefaultKey as LayerId, HopSlotMap};
1315
1416use std::{
1517 borrow::Cow,
1618 cell::RefCell,
17− collections::{HashMap, VecDeque},
18− fmt,
19− mem::replace,
20− path::Path,
19+ collections::{HashMap, HashSet, VecDeque},
20+ fmt::{self, Display},
21+ hash::{Hash, Hasher},
22+ mem::{replace, transmute},
23+ path::{Path, PathBuf},
2124 str::FromStr,
2225 sync::Arc,
2326};
2427
2528use once_cell::sync::{Lazy, OnceCell};
26−use serde::{Deserialize, Serialize};
29+use serde::{ser::SerializeSeq, Deserialize, Serialize};
2730
2831use helix_loader::grammar::{get_language, load_runtime_file};
2932
@@ -45,6 +48,21 @@ where
4548 .transpose()
4649}
4750
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+
4866pub fn deserialize_auto_pairs<'de, D>(deserializer: D) -> Result<Option<AutoPairs>, D::Error>
4967where
5068 D: serde::Deserializer<'de>,
@@ -57,8 +75,11 @@ fn default_timeout() -> u64 {
5775}
5876
5977#[derive(Debug, Serialize, Deserialize)]
78+#[serde(rename_all = "kebab-case")]
6079pub struct Configuration {
6180 pub language: Vec<LanguageConfiguration>,
81+ #[serde(default)]
82+ pub language_server: HashMap<String, LanguageServerConfiguration>,
6283}
6384
6485impl Default for Configuration {
@@ -72,17 +93,19 @@ impl Default for Configuration {
7293#[serde(rename_all = "kebab-case", deny_unknown_fields)]
7394pub struct LanguageConfiguration {
7495 #[serde(rename = "name")]
75− pub language_id: String, // c-sharp, rust
96+ pub language_id: String, // c-sharp, rust, tsx
97+ #[serde(rename = "language-id")]
98+ // see the table under https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem
99+ pub language_server_language_id: Option<String>, // csharp, rust, typescriptreact, for the language-server
76100 pub scope: String, // source.rust
77101 pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc>
78102 #[serde(default)]
79103 pub shebangs: Vec<String>, // interpreter(s) associated with language
80− pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
104+ #[serde(default)]
105+ pub roots: Vec<String>, // these indicate project roots <.git, Cargo.toml>
81106 pub comment_token: Option<String>,
82− pub max_line_length: Option<usize>,
83−
84− #[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
85− pub config: Option<serde_json::Value>,
107+ pub text_width: Option<usize>,
108+ pub soft_wrap: Option<SoftWrap>,
86109
87110 #[serde(default)]
88111 pub auto_format: bool,
@@ -103,8 +126,13 @@ pub struct LanguageConfiguration {
103126 #[serde(skip)]
104127 pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
105128 // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
106− #[serde(skip_serializing_if = "Option::is_none")]
107− pub language_server: Option<LanguageServerConfiguration>,
129+ #[serde(
130+ default,
131+ skip_serializing_if = "Vec::is_empty",
132+ serialize_with = "serialize_lang_features",
133+ deserialize_with = "deserialize_lang_features"
134+ )]
135+ pub language_servers: Vec<LanguageServerFeatures>,
108136 #[serde(skip_serializing_if = "Option::is_none")]
109137 pub indent: Option<IndentationConfiguration>,
110138
@@ -123,6 +151,12 @@ pub struct LanguageConfiguration {
123151 pub auto_pairs: Option<AutoPairs>,
124152
125153 pub rulers: Option<Vec<u16>>, // if set, override editor's rulers
154+
155+ /// Hardcoded LSP root directories relative to the workspace root, like `examples` or `tools/fuzz`.
156+ /// Falling back to the current working directory if none are configured.
157+ pub workspace_lsp_roots: Option<Vec<PathBuf>>,
158+ #[serde(default)]
159+ pub persistent_diagnostic_sources: Vec<String>,
126160}
127161
128162#[derive(Debug, PartialEq, Eq, Hash)]
@@ -179,9 +213,9 @@ impl<'de> Deserialize<'de> for FileType {
179213 M: serde::de::MapAccess<'de>,
180214 {
181215 match map.next_entry::<String, String>()? {
182− Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix(
183− suffix.replace('/', &std::path::MAIN_SEPARATOR.to_string()),
184− )),
216+ Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
217+ suffix.replace('/', std::path::MAIN_SEPARATOR_STR)
218+ })),
185219 Some((key, _value)) => Err(serde::de::Error::custom(format!(
186220 "unknown key in `file-types` list: {}",
187221 key
@@ -197,6 +231,133 @@ impl<'de> Deserialize<'de> for FileType {
197231 }
198232}
199233
234+#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
235+#[serde(rename_all = "kebab-case")]
236+pub enum LanguageServerFeature {
237+ Format,
238+ GotoDeclaration,
239+ GotoDefinition,
240+ GotoTypeDefinition,
241+ GotoReference,
242+ GotoImplementation,
243+ // Goto, use bitflags, combining previous Goto members?
244+ SignatureHelp,
245+ Hover,
246+ DocumentHighlight,
247+ Completion,
248+ CodeAction,
249+ WorkspaceCommand,
250+ DocumentSymbols,
251+ WorkspaceSymbols,
252+ // Symbols, use bitflags, see above?
253+ Diagnostics,
254+ RenameSymbol,
255+ InlayHints,
256+}
257+
258+impl Display for LanguageServerFeature {
259+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260+ use LanguageServerFeature::*;
261+ let feature = match self {
262+ Format => "format",
263+ GotoDeclaration => "goto-declaration",
264+ GotoDefinition => "goto-definition",
265+ GotoTypeDefinition => "goto-type-definition",
266+ GotoReference => "goto-type-definition",
267+ GotoImplementation => "goto-implementation",
268+ SignatureHelp => "signature-help",
269+ Hover => "hover",
270+ DocumentHighlight => "document-highlight",
271+ Completion => "completion",
272+ CodeAction => "code-action",
273+ WorkspaceCommand => "workspace-command",
274+ DocumentSymbols => "document-symbols",
275+ WorkspaceSymbols => "workspace-symbols",
276+ Diagnostics => "diagnostics",
277+ RenameSymbol => "rename-symbol",
278+ InlayHints => "inlay-hints",
279+ };
280+ write!(f, "{feature}",)
281+ }
282+}
283+
284+#[derive(Debug, Serialize, Deserialize)]
285+#[serde(untagged, rename_all = "kebab-case", deny_unknown_fields)]
286+enum LanguageServerFeatureConfiguration {
287+ #[serde(rename_all = "kebab-case")]
288+ Features {
289+ #[serde(default, skip_serializing_if = "HashSet::is_empty")]
290+ only_features: HashSet<LanguageServerFeature>,
291+ #[serde(default, skip_serializing_if = "HashSet::is_empty")]
292+ except_features: HashSet<LanguageServerFeature>,
293+ name: String,
294+ },
295+ Simple(String),
296+}
297+
298+#[derive(Debug, Default)]
299+pub struct LanguageServerFeatures {
300+ pub name: String,
301+ pub only: HashSet<LanguageServerFeature>,
302+ pub excluded: HashSet<LanguageServerFeature>,
303+}
304+
305+impl LanguageServerFeatures {
306+ pub fn has_feature(&self, feature: LanguageServerFeature) -> bool {
307+ (self.only.is_empty() || self.only.contains(&feature)) && !self.excluded.contains(&feature)
308+ }
309+}
310+
311+fn deserialize_lang_features<'de, D>(
312+ deserializer: D,
313+) -> Result<Vec<LanguageServerFeatures>, D::Error>
314+where
315+ D: serde::Deserializer<'de>,
316+{
317+ let raw: Vec<LanguageServerFeatureConfiguration> = Deserialize::deserialize(deserializer)?;
318+ let res = raw
319+ .into_iter()
320+ .map(|config| match config {
321+ LanguageServerFeatureConfiguration::Simple(name) => LanguageServerFeatures {
322+ name,
323+ ..Default::default()
324+ },
325+ LanguageServerFeatureConfiguration::Features {
326+ only_features,
327+ except_features,
328+ name,
329+ } => LanguageServerFeatures {
330+ name,
331+ only: only_features,
332+ excluded: except_features,
333+ },
334+ })
335+ .collect();
336+ Ok(res)
337+}
338+fn serialize_lang_features<S>(
339+ map: &Vec<LanguageServerFeatures>,
340+ serializer: S,
341+) -> Result<S::Ok, S::Error>
342+where
343+ S: serde::Serializer,
344+{
345+ let mut serializer = serializer.serialize_seq(Some(map.len()))?;
346+ for features in map {
347+ let features = if features.only.is_empty() && features.excluded.is_empty() {
348+ LanguageServerFeatureConfiguration::Simple(features.name.to_owned())
349+ } else {
350+ LanguageServerFeatureConfiguration::Features {
351+ only_features: features.only.clone(),
352+ except_features: features.excluded.clone(),
353+ name: features.name.to_owned(),
354+ }
355+ };
356+ serializer.serialize_element(&features)?;
357+ }
358+ serializer.end()
359+}
360+
200361#[derive(Debug, Serialize, Deserialize)]
201362#[serde(rename_all = "kebab-case")]
202363pub struct LanguageServerConfiguration {
@@ -204,9 +365,12 @@ pub struct LanguageServerConfiguration {
204365 #[serde(default)]
205366 #[serde(skip_serializing_if = "Vec::is_empty")]
206367 pub args: Vec<String>,
368+ #[serde(default, skip_serializing_if = "HashMap::is_empty")]
369+ pub environment: HashMap<String, String>,
370+ #[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")]
371+ pub config: Option<serde_json::Value>,
207372 #[serde(default = "default_timeout")]
208373 pub timeout: u64,
209− pub language_id: Option<String>,
210374}
211375
212376#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -275,10 +439,27 @@ pub struct DebuggerQuirks {
275439#[derive(Debug, Serialize, Deserialize)]
276440#[serde(rename_all = "kebab-case")]
277441pub struct IndentationConfiguration {
442+ #[serde(deserialize_with = "deserialize_tab_width")]
278443 pub tab_width: usize,
279444 pub unit: String,
280445}
281446
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+
282463/// Configuration for auto pairs
283464#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
284465#[serde(rename_all = "kebab-case", deny_unknown_fields, untagged)]
@@ -354,6 +535,26 @@ impl<'a> CapturedNode<'a> {
354535 }
355536}
356537
538+/// The maximum number of in-progress matches a TS cursor can consider at once.
539+/// This is set to a constant in order to avoid performance problems for medium to large files. Set with `set_match_limit`.
540+/// Using such a limit means that we lose valid captures, so there is fundamentally a tradeoff here.
541+///
542+///
543+/// Old tree sitter versions used a limit of 32 by default until this limit was removed in version `0.19.5` (must now be set manually).
544+/// However, this causes performance issues for medium to large files.
545+/// In helix, this problem caused treesitter motions to take multiple seconds to complete in medium-sized rust files (3k loc).
546+///
547+///
548+/// Neovim also encountered this problem and reintroduced this limit after it was removed upstream
549+/// (see <https://github.com/neovim/neovim/issues/14897> and <https://github.com/neovim/neovim/pull/14915>).
550+/// The number used here is fundamentally a tradeoff between breaking some obscure edge cases and performance.
551+///
552+///
553+/// Neovim chose 64 for this value somewhat arbitrarily (<https://github.com/neovim/neovim/pull/18397>).
554+/// 64 is too low for some languages though. In particular, it breaks some highlighting for record fields in Erlang record definitions.
555+/// This number can be increased if new syntax highlight breakages are found, as long as the performance penalty is not too high.
556+const TREE_SITTER_MATCH_LIMIT: u32 = 256;
557+
357558impl TextObjectQuery {
358559 /// Run the query on the given node and return sub nodes which match given
359560 /// capture ("function.inside", "class.around", etc).
@@ -394,13 +595,15 @@ impl TextObjectQuery {
394595 .iter()
395596 .find_map(|cap| self.query.capture_index_for_name(cap))?;
396597
598+ cursor.set_match_limit(TREE_SITTER_MATCH_LIMIT);
599+
397600 let nodes = cursor
398601 .captures(&self.query, node, RopeProvider(slice))
399602 .filter_map(move |(mat, _)| {
400603 let nodes: Vec<_> = mat
401604 .captures
402605 .iter()
403− .filter_map(|cap| (cap.index == capture_idx).then(|| cap.node))
606+ .filter_map(|cap| (cap.index == capture_idx).then_some(cap.node))
404607 .collect();
405608
406609 if nodes.len() > 1 {
@@ -519,6 +722,35 @@ impl LanguageConfiguration {
519722 .ok()
520723 }
521724}
725+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
726+#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
727+pub struct SoftWrap {
728+ /// Soft wrap lines that exceed viewport width. Default to off
729+ // NOTE: Option on purpose because the struct is shared between language config and global config.
730+ // By default the option is None so that the language config falls back to the global config unless explicitly set.
731+ pub enable: Option<bool>,
732+ /// Maximum space left free at the end of the line.
733+ /// This space is used to wrap text at word boundaries. If that is not possible within this limit
734+ /// the word is simply split at the end of the line.
735+ ///
736+ /// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views.
737+ ///
738+ /// Default to 20
739+ pub max_wrap: Option<u16>,
740+ /// Maximum number of indentation that can be carried over from the previous line when softwrapping.
741+ /// If a line is indented further then this limit it is rendered at the start of the viewport instead.
742+ ///
743+ /// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views.
744+ ///
745+ /// Default to 40
746+ pub max_indent_retain: Option<u16>,
747+ /// Indicator placed at the beginning of softwrapped lines
748+ ///
749+ /// Defaults to ↪
750+ pub wrap_indicator: Option<String>,
751+ /// Softwrap at `text_width` instead of viewport width if it is shorter
752+ pub wrap_at_text_width: Option<bool>,
753+}
522754
523755// Expose loader as Lazy<> global since it's always static?
524756
@@ -530,6 +762,8 @@ pub struct Loader {
530762 language_config_ids_by_suffix: HashMap<String, usize>,
531763 language_config_ids_by_shebang: HashMap<String, usize>,
532764
765+ language_server_configs: HashMap<String, LanguageServerConfiguration>,
766+
533767 scopes: ArcSwap<Vec<String>>,
534768}
535769
@@ -537,6 +771,7 @@ impl Loader {
537771 pub fn new(config: Configuration) -> Self {
538772 let mut loader = Self {
539773 language_configs: Vec::new(),
774+ language_server_configs: config.language_server,
540775 language_config_ids_by_extension: HashMap::new(),
541776 language_config_ids_by_suffix: HashMap::new(),
542777 language_config_ids_by_shebang: HashMap::new(),
@@ -599,11 +834,13 @@ impl Loader {
599834 // TODO: content_regex handling conflict resolution
600835 }
601836
602− pub fn language_config_for_shebang(&self, source: &Rope) -> Option<Arc<LanguageConfiguration>> {
837+ pub fn language_config_for_shebang(
838+ &self,
839+ source: RopeSlice,
840+ ) -> Option<Arc<LanguageConfiguration>> {
603841 let line = Cow::from(source.line(0));
604− static SHEBANG_REGEX: Lazy<Regex> = Lazy::new(|| {
605− Regex::new(r"^#!\s*(?:\S*[/\\](?:env\s+(?:\-\S+\s+)*)?)?([^\s\.\d]+)").unwrap()
606− });
842+ static SHEBANG_REGEX: Lazy<Regex> =
843+ Lazy::new(|| Regex::new(&["^", SHEBANG].concat()).unwrap());
607844 let configuration_id = SHEBANG_REGEX
608845 .captures(&line)
609846 .and_then(|cap| self.language_config_ids_by_shebang.get(&cap[1]));
@@ -625,15 +862,14 @@ impl Loader {
625862 .cloned()
626863 }
627864
628− pub fn language_configuration_for_injection_string(
629− &self,
630− string: &str,
631− ) -> Option<Arc<LanguageConfiguration>> {
865+ /// Unlike language_config_for_language_id, which only returns Some for an exact id, this
866+ /// function will perform a regex match on the given string to find the closest language match.
867+ pub fn language_config_for_name(&self, name: &str) -> Option<Arc<LanguageConfiguration>> {
632868 let mut best_match_length = 0;
633869 let mut best_match_position = None;
634870 for (i, configuration) in self.language_configs.iter().enumerate() {
635871 if let Some(injection_regex) = &configuration.injection_regex {
636− if let Some(mat) = injection_regex.find(string) {
872+ if let Some(mat) = injection_regex.find(name) {
637873 let length = mat.end() - mat.start();
638874 if length > best_match_length {
639875 best_match_position = Some(i);
@@ -643,17 +879,30 @@ impl Loader {
643879 }
644880 }
645881
646− if let Some(i) = best_match_position {
647− let configuration = &self.language_configs[i];
648− return Some(configuration.clone());
882+ best_match_position.map(|i| self.language_configs[i].clone())
883+ }
884+
885+ pub fn language_configuration_for_injection_string(
886+ &self,
887+ capture: &InjectionLanguageMarker,
888+ ) -> Option<Arc<LanguageConfiguration>> {
889+ match capture {
890+ InjectionLanguageMarker::Name(string) => self.language_config_for_name(string),
891+ InjectionLanguageMarker::Filename(file) => self.language_config_for_file_name(file),
892+ InjectionLanguageMarker::Shebang(shebang) => {
893+ self.language_config_for_language_id(shebang)
894+ }
649895 }
650− None
651896 }
652897
653898 pub fn language_configs(&self) -> impl Iterator<Item = &Arc<LanguageConfiguration>> {
654899 self.language_configs.iter()
655900 }
656901
902+ pub fn language_server_configs(&self) -> &HashMap<String, LanguageServerConfiguration> {
903+ &self.language_server_configs
904+ }
905+
657906 pub fn set_scopes(&self, scopes: Vec<String>) {
658907 self.scopes.store(Arc::new(scopes));
659908
@@ -697,7 +946,11 @@ fn byte_range_to_str(range: std::ops::Range<usize>, source: RopeSlice) -> Cow<st
697946}
698947
699948impl Syntax {
700− pub fn new(source: &Rope, config: Arc<HighlightConfiguration>, loader: Arc<Loader>) -> Self {
949+ pub fn new(
950+ source: RopeSlice,
951+ config: Arc<HighlightConfiguration>,
952+ loader: Arc<Loader>,
953+ ) -> Option<Self> {
701954 let root_layer = LanguageLayer {
702955 tree: None,
703956 config,
@@ -722,24 +975,26 @@ impl Syntax {
722975 loader,
723976 };
724977
725− syntax
726− .update(source, source, &ChangeSet::new(source))
727− .unwrap();
978+ let res = syntax.update(source, source, &ChangeSet::new(source));
728979
729− syntax
980+ if res.is_err() {
981+ log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
982+ return None;
983+ }
984+ Some(syntax)
730985 }
731986
732987 pub fn update(
733988 &mut self,
734− old_source: &Rope,
735− source: &Rope,
989+ old_source: RopeSlice,
990+ source: RopeSlice,
736991 changeset: &ChangeSet,
737992 ) -> Result<(), Error> {
738993 let mut queue = VecDeque::new();
739994 queue.push_back(self.root);
740995
741996 let scopes = self.loader.scopes.load();
742− let injection_callback = |language: &str| {
997+ let injection_callback = |language: &InjectionLanguageMarker| {
743998 self.loader
744999 .language_configuration_for_injection_string(language)
7451000 .and_then(|language_config| language_config.highlight_config(&scopes))
@@ -748,30 +1003,38 @@ impl Syntax {
7481003 // Convert the changeset into tree sitter edits.
7491004 let edits = generate_edits(old_source, changeset);
7501005
1006+ // This table allows inverse indexing of `layers`.
1007+ // That is by hashing a `Layer` you can find
1008+ // the `LayerId` of an existing equivalent `Layer` in `layers`.
1009+ //
1010+ // It is used to determine if a new layer exists for an injection
1011+ // or if an existing layer needs to be updated.
1012+ let mut layers_table = RawTable::with_capacity(self.layers.len());
1013+ let layers_hasher = RandomState::new();
7511014 // Use the edits to update all layers markers
752− if !edits.is_empty() {
753− fn point_add(a: Point, b: Point) -> Point {
754− if b.row > 0 {
755− Point::new(a.row.saturating_add(b.row), b.column)
756− } else {
757− Point::new(0, a.column.saturating_add(b.column))
758− }
1015+ fn point_add(a: Point, b: Point) -> Point {
1016+ if b.row > 0 {
1017+ Point::new(a.row.saturating_add(b.row), b.column)
1018+ } else {
1019+ Point::new(0, a.column.saturating_add(b.column))
7591020 }
760− fn point_sub(a: Point, b: Point) -> Point {
761− if a.row > b.row {
762− Point::new(a.row.saturating_sub(b.row), a.column)
763− } else {
764− Point::new(0, a.column.saturating_sub(b.column))
765− }
1021+ }
1022+ fn point_sub(a: Point, b: Point) -> Point {
1023+ if a.row > b.row {
1024+ Point::new(a.row.saturating_sub(b.row), a.column)
1025+ } else {
1026+ Point::new(0, a.column.saturating_sub(b.column))
7661027 }
1028+ }
7671029
768− for layer in self.layers.values_mut() {
769− // The root layer always covers the whole range (0..usize::MAX)
770− if layer.depth == 0 {
771− layer.flags = LayerUpdateFlags::MODIFIED;
772− continue;
773− }
1030+ for (layer_id, layer) in self.layers.iter_mut() {
1031+ // The root layer always covers the whole range (0..usize::MAX)
1032+ if layer.depth == 0 {
1033+ layer.flags = LayerUpdateFlags::MODIFIED;
1034+ continue;
1035+ }
7741036
1037+ if !edits.is_empty() {
7751038 for range in &mut layer.ranges {
7761039 // Roughly based on https://github.com/tree-sitter/tree-sitter/blob/ddeaa0c7f534268b35b4f6cb39b52df082754413/lib/src/subtree.c#L691-L720
7771040 for edit in edits.iter().rev() {
@@ -836,13 +1099,21 @@ impl Syntax {
8361099 }
8371100 }
8381101 }
1102+
1103+ let hash = layers_hasher.hash_one(layer);
1104+ // Safety: insert_no_grow is unsafe because it assumes that the table
1105+ // has enough capacity to hold additional elements.
1106+ // This is always the case as we reserved enough capacity above.
1107+ unsafe { layers_table.insert_no_grow(hash, layer_id) };
8391108 }
8401109
8411110 PARSER.with(|ts_parser| {
8421111 let ts_parser = &mut ts_parser.borrow_mut();
1112+ ts_parser.parser.set_timeout_micros(1000 * 500); // half a second is pretty generours
8431113 let mut cursor = ts_parser.cursors.pop().unwrap_or_else(QueryCursor::new);
8441114 // TODO: might need to set cursor range
8451115 cursor.set_byte_range(0..usize::MAX);
1116+ cursor.set_match_limit(TREE_SITTER_MATCH_LIMIT);
8461117
8471118 let source_slice = source.slice(..);
8481119
@@ -883,14 +1154,37 @@ impl Syntax {
8831154 layer.tree().root_node(),
8841155 RopeProvider(source_slice),
8851156 );
1157+ let mut combined_injections = vec![
1158+ (None, Vec::new(), IncludedChildren::default());
1159+ layer.config.combined_injections_patterns.len()
1160+ ];
8861161 let mut injections = Vec::new();
1162+ let mut last_injection_end = 0;
8871163 for mat in matches {
888− let (language_name, content_node, included_children) = injection_for_match(
889− &layer.config,
890− &layer.config.injections_query,
891− &mat,
892− source_slice,
893− );
1164+ let (injection_capture, content_node, included_children) = layer
1165+ .config
1166+ .injection_for_match(&layer.config.injections_query, &mat, source_slice);
1167+
1168+ // in case this is a combined injection save it for more processing later
1169+ if let Some(combined_injection_idx) = layer
1170+ .config
1171+ .combined_injections_patterns
1172+ .iter()
1173+ .position(|&pattern| pattern == mat.pattern_index)
1174+ {
1175+ let entry = &mut combined_injections[combined_injection_idx];
1176+ if injection_capture.is_some() {
1177+ entry.0 = injection_capture;
1178+ }
1179+ if let Some(content_node) = content_node {
1180+ if content_node.start_byte() >= last_injection_end {
1181+ entry.1.push(content_node);
1182+ last_injection_end = content_node.end_byte();
1183+ }
1184+ }
1185+ entry.2 = included_children;
1186+ continue;
1187+ }
8941188
8951189 // Explicitly remove this match so that none of its other captures will remain
8961190 // in the stream of captures.
@@ -898,59 +1192,31 @@ impl Syntax {
8981192
8991193 // If a language is found with the given name, then add a new language layer
9001194 // to the highlighted document.
901− if let (Some(language_name), Some(content_node)) = (language_name, content_node)
1195+ if let (Some(injection_capture), Some(content_node)) =
1196+ (injection_capture, content_node)
9021197 {
903− if let Some(config) = (injection_callback)(&language_name) {
1198+ if let Some(config) = (injection_callback)(&injection_capture) {
9041199 let ranges =
9051200 intersect_ranges(&layer.ranges, &[content_node], included_children);
9061201
9071202 if !ranges.is_empty() {
1203+ if content_node.start_byte() < last_injection_end {
1204+ continue;
1205+ }
1206+ last_injection_end = content_node.end_byte();
9081207 injections.push((config, ranges));
9091208 }
9101209 }
9111210 }
9121211 }
9131212
914− // Process combined injections.
915− if let Some(combined_injections_query) = &layer.config.combined_injections_query {
916− let mut injections_by_pattern_index =
917− vec![
918− (None, Vec::new(), IncludedChildren::default());
919− combined_injections_query.pattern_count()
920− ];
921− let matches = cursor.matches(
922− combined_injections_query,
923− layer.tree().root_node(),
924− RopeProvider(source_slice),
925− );
926− for mat in matches {
927− let entry = &mut injections_by_pattern_index[mat.pattern_index];
928− let (language_name, content_node, included_children) = injection_for_match(
929− &layer.config,
930− combined_injections_query,
931− &mat,
932− source_slice,
933− );
934− if language_name.is_some() {
935− entry.0 = language_name;
936− }
937− if let Some(content_node) = content_node {
938− entry.1.push(content_node);
939− }
940− entry.2 = included_children;
941− }
942− for (lang_name, content_nodes, included_children) in injections_by_pattern_index
943− {
944− if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) {
945− if let Some(config) = (injection_callback)(&lang_name) {
946− let ranges = intersect_ranges(
947− &layer.ranges,
948− &content_nodes,
949− included_children,
950− );
951− if !ranges.is_empty() {
952− injections.push((config, ranges));
953− }
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));
9541220 }
9551221 }
9561222 }
@@ -959,27 +1225,23 @@ impl Syntax {
9591225 let depth = layer.depth + 1;
9601226 // TODO: can't inline this since matches borrows self.layers
9611227 for (config, ranges) in injections {
962− // Find an existing layer
963− let layer = self
964− .layers
965− .iter_mut()
966− .find(|(_, layer)| {
967− layer.depth == depth && // TODO: track parent id instead
968− layer.config.language == config.language && layer.ranges == ranges
1228+ let new_layer = LanguageLayer {
1229+ tree: None,
1230+ config,
1231+ depth,
1232+ ranges,
1233+ flags: LayerUpdateFlags::empty(),
1234+ };
1235+
1236+ // Find an identical existing layer
1237+ let layer = layers_table
1238+ .get(layers_hasher.hash_one(&new_layer), |&it| {
1239+ self.layers[it] == new_layer
9691240 })
970− .map(|(id, _layer)| id);
1241+ .copied();
9711242
9721243 // ...or insert a new one.
973− let layer_id = layer.unwrap_or_else(|| {
974− self.layers.insert(LanguageLayer {
975− tree: None,
976− config,
977− depth,
978− ranges,
979− // set the modified flag to ensure the layer is parsed
980− flags: LayerUpdateFlags::empty(),
981− })
982− });
1244+ let layer_id = layer.unwrap_or_else(|| self.layers.insert(new_layer));
9831245
9841246 queue.push_back(layer_id);
9851247 }
@@ -1032,6 +1294,7 @@ impl Syntax {
10321294
10331295 // if reusing cursors & no range this resets to whole range
10341296 cursor_ref.set_byte_range(range.clone().unwrap_or(0..usize::MAX));
1297+ cursor_ref.set_match_limit(TREE_SITTER_MATCH_LIMIT);
10351298
10361299 let mut captures = cursor_ref
10371300 .captures(
@@ -1053,21 +1316,14 @@ impl Syntax {
10531316 }],
10541317 cursor,
10551318 _tree: None,
1056− captures,
1319+ captures: RefCell::new(captures),
10571320 config: layer.config.as_ref(), // TODO: just reuse `layer`
10581321 depth: layer.depth, // TODO: just reuse `layer`
1059− ranges: &layer.ranges, // TODO: temp
10601322 })
10611323 })
10621324 .collect::<Vec<_>>();
10631325
1064− // HAXX: arrange layers by byte range, with deeper layers positioned first
1065− layers.sort_by_key(|layer| {
1066− (
1067− layer.ranges.first().cloned(),
1068− std::cmp::Reverse(layer.depth),
1069− )
1070− });
1326+ layers.sort_unstable_by_key(|layer| layer.sort_key());
10711327
10721328 let mut result = HighlightIter {
10731329 source,
@@ -1097,6 +1353,7 @@ impl Syntax {
10971353bitflags! {
10981354 /// Flags that track the status of a layer
10991355 /// in the `Sytaxn::update` function
1356+ #[derive(Debug)]
11001357 struct LayerUpdateFlags : u32{
11011358 const MODIFIED = 0b001;
11021359 const MOVED = 0b010;
@@ -1115,13 +1372,41 @@ pub struct LanguageLayer {
11151372 flags: LayerUpdateFlags,
11161373}
11171374
1375+/// This PartialEq implementation only checks if that
1376+/// two layers are theoretically identical (meaning they highlight the same text range with the same language).
1377+/// It does not check whether the layers have the same internal treesitter
1378+/// state.
1379+impl PartialEq for LanguageLayer {
1380+ fn eq(&self, other: &Self) -> bool {
1381+ self.depth == other.depth
1382+ && self.config.language == other.config.language
1383+ && self.ranges == other.ranges
1384+ }
1385+}
1386+
1387+/// Hash implementation belongs to PartialEq implementation above.
1388+/// See its documentation for details.
1389+impl Hash for LanguageLayer {
1390+ fn hash<H: Hasher>(&self, state: &mut H) {
1391+ self.depth.hash(state);
1392+ // The transmute is necessary here because tree_sitter::Language does not derive Hash at the moment.
1393+ // However it does use #[repr] transparent so the transmute here is safe
1394+ // as `Language` (which `Grammar` is an alias for) is just a newtype wrapper around a (thin) pointer.
1395+ // This is also compatible with the PartialEq implementation of language
1396+ // as that is just a pointer comparison.
1397+ let language: *const () = unsafe { transmute(self.config.language) };
1398+ language.hash(state);
1399+ self.ranges.hash(state);
1400+ }
1401+}
1402+
11181403impl LanguageLayer {
11191404 pub fn tree(&self) -> &Tree {
11201405 // TODO: no unwrap
11211406 self.tree.as_ref().unwrap()
11221407 }
11231408
1124− fn parse(&mut self, parser: &mut Parser, source: &Rope) -> Result<(), Error> {
1409+ fn parse(&mut self, parser: &mut Parser, source: RopeSlice) -> Result<(), Error> {
11251410 parser
11261411 .set_included_ranges(&self.ranges)
11271412 .map_err(|_| Error::InvalidRanges)?;
@@ -1136,7 +1421,7 @@ impl LanguageLayer {
11361421 &mut |byte, _| {
11371422 if byte <= source.len_bytes() {
11381423 let (chunk, start_byte, _, _) = source.chunk_at_byte(byte);
1139− chunk[byte - start_byte..].as_bytes()
1424+ &chunk.as_bytes()[byte - start_byte..]
11401425 } else {
11411426 // out of range
11421427 &[]
@@ -1152,7 +1437,7 @@ impl LanguageLayer {
11521437}
11531438
11541439pub(crate) fn generate_edits(
1155− old_text: &Rope,
1440+ old_text: RopeSlice,
11561441 changeset: &ChangeSet,
11571442) -> Vec<tree_sitter::InputEdit> {
11581443 use Operation::*;
@@ -1168,7 +1453,7 @@ pub(crate) fn generate_edits(
11681453
11691454 // TODO; this is a lot easier with Change instead of Operation.
11701455
1171− fn point_at_pos(text: &Rope, pos: usize) -> (usize, Point) {
1456+ fn point_at_pos(text: RopeSlice, pos: usize) -> (usize, Point) {
11721457 let byte = text.char_to_byte(pos); // <- attempted to index past end
11731458 let line = text.char_to_line(pos);
11741459 let line_start_byte = text.line_to_byte(line);
@@ -1260,7 +1545,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
12601545use std::{iter, mem, ops, str, usize};
12611546use tree_sitter::{
12621547 Language as Grammar, Node, Parser, Point, Query, QueryCaptures, QueryCursor, QueryError,
1263− QueryMatch, Range, TextProvider, Tree,
1548+ QueryMatch, Range, TextProvider, Tree, TreeCursor,
12641549};
12651550
12661551const CANCELLATION_CHECK_INTERVAL: usize = 100;
@@ -1294,12 +1579,14 @@ pub struct HighlightConfiguration {
12941579 pub language: Grammar,
12951580 pub query: Query,
12961581 injections_query: Query,
1297− combined_injections_query: Option<Query>,
1582+ combined_injections_patterns: Vec<usize>,
12981583 highlights_pattern_index: usize,
12991584 highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
13001585 non_local_variable_patterns: Vec<bool>,
13011586 injection_content_capture_index: Option<u32>,
13021587 injection_language_capture_index: Option<u32>,
1588+ injection_filename_capture_index: Option<u32>,
1589+ injection_shebang_capture_index: Option<u32>,
13031590 local_scope_capture_index: Option<u32>,
13041591 local_def_capture_index: Option<u32>,
13051592 local_def_value_capture_index: Option<u32>,
@@ -1343,7 +1630,7 @@ impl<'a> Iterator for ChunksBytes<'a> {
13431630}
13441631
13451632pub struct RopeProvider<'a>(pub RopeSlice<'a>);
1346−impl<'a> TextProvider<'a> for RopeProvider<'a> {
1633+impl<'a> TextProvider<&'a [u8]> for RopeProvider<'a> {
13471634 type I = ChunksBytes<'a>;
13481635
13491636 fn text(&mut self, node: Node) -> Self::I {
@@ -1357,12 +1644,11 @@ impl<'a> TextProvider<'a> for RopeProvider<'a> {
13571644struct HighlightIterLayer<'a> {
13581645 _tree: Option<Tree>,
13591646 cursor: QueryCursor,
1360− captures: iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>>>,
1647+ captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>, &'a [u8]>>>,
13611648 config: &'a HighlightConfiguration,
13621649 highlight_end_stack: Vec<usize>,
13631650 scope_stack: Vec<LocalScope<'a>>,
13641651 depth: u32,
1365− ranges: &'a [Range],
13661652}
13671653
13681654impl<'a> fmt::Debug for HighlightIterLayer<'a> {
@@ -1409,26 +1695,15 @@ impl HighlightConfiguration {
14091695 }
14101696 }
14111697
1412− let mut injections_query = Query::new(language, injection_query)?;
1413−
1414− // Construct a separate query just for dealing with the 'combined injections'.
1415− // Disable the combined injection patterns in the main query.
1416− let mut combined_injections_query = Query::new(language, injection_query)?;
1417− let mut has_combined_queries = false;
1418− for pattern_index in 0..injections_query.pattern_count() {
1419− let settings = injections_query.property_settings(pattern_index);
1420− if settings.iter().any(|s| &*s.key == "injection.combined") {
1421− has_combined_queries = true;
1422− injections_query.disable_pattern(pattern_index);
1423− } else {
1424− combined_injections_query.disable_pattern(pattern_index);
1425− }
1426− }
1427− let combined_injections_query = if has_combined_queries {
1428− Some(combined_injections_query)
1429− } else {
1430− None
1431− };
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();
14321707
14331708 // Find all of the highlighting patterns that are disabled for nodes that
14341709 // have been identified as local variables.
@@ -1444,6 +1719,8 @@ impl HighlightConfiguration {
14441719 // Store the numeric ids for all of the special captures.
14451720 let mut injection_content_capture_index = None;
14461721 let mut injection_language_capture_index = None;
1722+ let mut injection_filename_capture_index = None;
1723+ let mut injection_shebang_capture_index = None;
14471724 let mut local_def_capture_index = None;
14481725 let mut local_def_value_capture_index = None;
14491726 let mut local_ref_capture_index = None;
@@ -1464,6 +1741,8 @@ impl HighlightConfiguration {
14641741 match name.as_str() {
14651742 "injection.content" => injection_content_capture_index = i,
14661743 "injection.language" => injection_language_capture_index = i,
1744+ "injection.filename" => injection_filename_capture_index = i,
1745+ "injection.shebang" => injection_shebang_capture_index = i,
14671746 _ => {}
14681747 }
14691748 }
@@ -1473,12 +1752,14 @@ impl HighlightConfiguration {
14731752 language,
14741753 query,
14751754 injections_query,
1476− combined_injections_query,
1755+ combined_injections_patterns,
14771756 highlights_pattern_index,
14781757 highlight_indices,
14791758 non_local_variable_patterns,
14801759 injection_content_capture_index,
14811760 injection_language_capture_index,
1761+ injection_filename_capture_index,
1762+ injection_shebang_capture_index,
14821763 local_scope_capture_index,
14831764 local_def_capture_index,
14841765 local_def_value_capture_index,
@@ -1537,16 +1818,101 @@ impl HighlightConfiguration {
15371818
15381819 self.highlight_indices.store(Arc::new(indices));
15391820 }
1821+
1822+ fn injection_pair<'a>(
1823+ &self,
1824+ query_match: &QueryMatch<'a, 'a>,
1825+ source: RopeSlice<'a>,
1826+ ) -> (Option<InjectionLanguageMarker<'a>>, Option<Node<'a>>) {
1827+ let mut injection_capture = None;
1828+ let mut content_node = None;
1829+
1830+ for capture in query_match.captures {
1831+ let index = Some(capture.index);
1832+ if index == self.injection_language_capture_index {
1833+ let name = byte_range_to_str(capture.node.byte_range(), source);
1834+ injection_capture = Some(InjectionLanguageMarker::Name(name));
1835+ } else if index == self.injection_filename_capture_index {
1836+ let name = byte_range_to_str(capture.node.byte_range(), source);
1837+ let path = Path::new(name.as_ref()).to_path_buf();
1838+ injection_capture = Some(InjectionLanguageMarker::Filename(path.into()));
1839+ } else if index == self.injection_shebang_capture_index {
1840+ let node_slice = source.byte_slice(capture.node.byte_range());
1841+
1842+ // some languages allow space and newlines before the actual string content
1843+ // so a shebang could be on either the first or second line
1844+ let lines = if let Ok(end) = node_slice.try_line_to_byte(2) {
1845+ node_slice.byte_slice(..end)
1846+ } else {
1847+ node_slice
1848+ };
1849+
1850+ static SHEBANG_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(SHEBANG).unwrap());
1851+
1852+ injection_capture = SHEBANG_REGEX
1853+ .captures(&Cow::from(lines))
1854+ .map(|cap| InjectionLanguageMarker::Shebang(cap[1].to_owned()))
1855+ } else if index == self.injection_content_capture_index {
1856+ content_node = Some(capture.node);
1857+ }
1858+ }
1859+ (injection_capture, content_node)
1860+ }
1861+
1862+ fn injection_for_match<'a>(
1863+ &self,
1864+ query: &'a Query,
1865+ query_match: &QueryMatch<'a, 'a>,
1866+ source: RopeSlice<'a>,
1867+ ) -> (
1868+ Option<InjectionLanguageMarker<'a>>,
1869+ Option<Node<'a>>,
1870+ IncludedChildren,
1871+ ) {
1872+ let (mut injection_capture, content_node) = self.injection_pair(query_match, source);
1873+
1874+ let mut included_children = IncludedChildren::default();
1875+ for prop in query.property_settings(query_match.pattern_index) {
1876+ match prop.key.as_ref() {
1877+ // In addition to specifying the language name via the text of a
1878+ // captured node, it can also be hard-coded via a `#set!` predicate
1879+ // that sets the injection.language key.
1880+ "injection.language" if injection_capture.is_none() => {
1881+ injection_capture = prop
1882+ .value
1883+ .as_ref()
1884+ .map(|s| InjectionLanguageMarker::Name(s.as_ref().into()));
1885+ }
1886+
1887+ // By default, injections do not include the *children* of an
1888+ // `injection.content` node - only the ranges that belong to the
1889+ // node itself. This can be changed using a `#set!` predicate that
1890+ // sets the `injection.include-children` key.
1891+ "injection.include-children" => included_children = IncludedChildren::All,
1892+
1893+ // Some queries might only exclude named children but include unnamed
1894+ // children in their `injection.content` node. This can be enabled using
1895+ // a `#set!` predicate that sets the `injection.include-unnamed-children` key.
1896+ "injection.include-unnamed-children" => {
1897+ included_children = IncludedChildren::Unnamed
1898+ }
1899+ _ => {}
1900+ }
1901+ }
1902+
1903+ (injection_capture, content_node, included_children)
1904+ }
15401905}
15411906
15421907impl<'a> HighlightIterLayer<'a> {
15431908 // First, sort scope boundaries by their byte offset in the document. At a
15441909 // given position, emit scope endings before scope beginnings. Finally, emit
15451910 // scope boundaries from deeper layers first.
1546− fn sort_key(&mut self) -> Option<(usize, bool, isize)> {
1911+ fn sort_key(&self) -> Option<(usize, bool, isize)> {
15471912 let depth = -(self.depth as isize);
15481913 let next_start = self
15491914 .captures
1915+ .borrow_mut()
15501916 .peek()
15511917 .map(|(m, i)| m.captures[*i].node.start_byte());
15521918 let next_end = self.highlight_end_stack.last().cloned();
@@ -1771,7 +2137,8 @@ impl<'a> Iterator for HighlightIter<'a> {
17712137 // Get the next capture from whichever layer has the earliest highlight boundary.
17722138 let range;
17732139 let layer = &mut self.layers[0];
1774− if let Some((next_match, capture_index)) = layer.captures.peek() {
2140+ let captures = layer.captures.get_mut();
2141+ if let Some((next_match, capture_index)) = captures.peek() {
17752142 let next_capture = next_match.captures[*capture_index];
17762143 range = next_capture.node.byte_range();
17772144
@@ -1794,7 +2161,7 @@ impl<'a> Iterator for HighlightIter<'a> {
17942161 return self.emit_event(self.source.len_bytes(), None);
17952162 };
17962163
1797− let (mut match_, capture_index) = layer.captures.next().unwrap();
2164+ let (mut match_, capture_index) = captures.next().unwrap();
17982165 let mut capture = match_.captures[capture_index];
17992166
18002167 // Remove from the local scope stack any local scopes that have already ended.
@@ -1870,11 +2237,11 @@ impl<'a> Iterator for HighlightIter<'a> {
18702237 }
18712238
18722239 // Continue processing any additional matches for the same node.
1873− if let Some((next_match, next_capture_index)) = layer.captures.peek() {
2240+ if let Some((next_match, next_capture_index)) = captures.peek() {
18742241 let next_capture = next_match.captures[*next_capture_index];
18752242 if next_capture.node == capture.node {
18762243 capture = next_capture;
1877− match_ = layer.captures.next().unwrap().0;
2244+ match_ = captures.next().unwrap().0;
18782245 continue;
18792246 }
18802247 }
@@ -1897,11 +2264,11 @@ impl<'a> Iterator for HighlightIter<'a> {
18972264 // highlighting patterns that are disabled for local variables.
18982265 if definition_highlight.is_some() || reference_highlight.is_some() {
18992266 while layer.config.non_local_variable_patterns[match_.pattern_index] {
1900− if let Some((next_match, next_capture_index)) = layer.captures.peek() {
2267+ if let Some((next_match, next_capture_index)) = captures.peek() {
19012268 let next_capture = next_match.captures[*next_capture_index];
19022269 if next_capture.node == capture.node {
19032270 capture = next_capture;
1904− match_ = layer.captures.next().unwrap().0;
2271+ match_ = captures.next().unwrap().0;
19052272 continue;
19062273 }
19072274 }
@@ -1916,10 +2283,10 @@ impl<'a> Iterator for HighlightIter<'a> {
19162283 // for a given node are ordered by pattern index, so these subsequent
19172284 // captures are guaranteed to be for highlighting, not injections or
19182285 // local variables.
1919− while let Some((next_match, next_capture_index)) = layer.captures.peek() {
2286+ while let Some((next_match, next_capture_index)) = captures.peek() {
19202287 let next_capture = next_match.captures[*next_capture_index];
19212288 if next_capture.node == capture.node {
1922− layer.captures.next();
2289+ captures.next();
19232290 } else {
19242291 break;
19252292 }
@@ -1946,56 +2313,15 @@ impl<'a> Iterator for HighlightIter<'a> {
19462313 }
19472314}
19482315
1949−fn injection_for_match<'a>(
1950− config: &HighlightConfiguration,
1951− query: &'a Query,
1952− query_match: &QueryMatch<'a, 'a>,
1953− source: RopeSlice<'a>,
1954−) -> (Option<Cow<'a, str>>, Option<Node<'a>>, IncludedChildren) {
1955− let content_capture_index = config.injection_content_capture_index;
1956− let language_capture_index = config.injection_language_capture_index;
1957−
1958− let mut language_name = None;
1959− let mut content_node = None;
1960− for capture in query_match.captures {
1961− let index = Some(capture.index);
1962− if index == language_capture_index {
1963− let name = byte_range_to_str(capture.node.byte_range(), source);
1964− language_name = Some(name);
1965− } else if index == content_capture_index {
1966− content_node = Some(capture.node);
1967− }
1968− }
1969−
1970− let mut included_children = IncludedChildren::default();
1971− for prop in query.property_settings(query_match.pattern_index) {
1972− match prop.key.as_ref() {
1973− // In addition to specifying the language name via the text of a
1974− // captured node, it can also be hard-coded via a `#set!` predicate
1975− // that sets the injection.language key.
1976− "injection.language" => {
1977− if language_name.is_none() {
1978− language_name = prop.value.as_ref().map(|s| s.as_ref().into())
1979− }
1980− }
1981−
1982− // By default, injections do not include the *children* of an
1983− // `injection.content` node - only the ranges that belong to the
1984− // node itself. This can be changed using a `#set!` predicate that
1985− // sets the `injection.include-children` key.
1986− "injection.include-children" => included_children = IncludedChildren::All,
1987−
1988− // Some queries might only exclude named children but include unnamed
1989− // children in their `injection.content` node. This can be enabled using
1990− // a `#set!` predicate that sets the `injection.include-unnamed-children` key.
1991− "injection.include-unnamed-children" => included_children = IncludedChildren::Unnamed,
1992− _ => {}
1993− }
1994− }
1995−
1996− (language_name, content_node, included_children)
2316+#[derive(Debug, Clone)]
2317+pub enum InjectionLanguageMarker<'a> {
2318+ Name(Cow<'a, str>),
2319+ Filename(Cow<'a, Path>),
2320+ Shebang(String),
19972321}
19982322
2323+const SHEBANG: &str = r"#!\s*(?:\S*[/\\](?:env\s+(?:\-\S+\s+)*)?)?([^\s\.\d]+)";
2324+
19992325pub struct Merge<I> {
20002326 iter: I,
20012327 spans: Box<dyn Iterator<Item = (usize, std::ops::Range<usize>)>>,
@@ -2130,57 +2456,68 @@ impl<I: Iterator<Item = HighlightEvent>> Iterator for Merge<I> {
21302456 }
21312457}
21322458
2459+fn node_is_visible(node: &Node) -> bool {
2460+ node.is_missing() || (node.is_named() && node.language().node_kind_is_visible(node.kind_id()))
2461+}
2462+
21332463pub fn pretty_print_tree<W: fmt::Write>(fmt: &mut W, node: Node) -> fmt::Result {
2134− pretty_print_tree_impl(fmt, node, true, None, 0)
2464+ if node.child_count() == 0 {
2465+ if node_is_visible(&node) {
2466+ write!(fmt, "({})", node.kind())
2467+ } else {
2468+ write!(fmt, "\"{}\"", node.kind())
2469+ }
2470+ } else {
2471+ pretty_print_tree_impl(fmt, &mut node.walk(), 0)
2472+ }
21352473}
21362474
21372475fn pretty_print_tree_impl<W: fmt::Write>(
21382476 fmt: &mut W,
2139− node: Node,
2140− is_root: bool,
2141− field_name: Option<&str>,
2477+ cursor: &mut TreeCursor,
21422478 depth: usize,
21432479) -> fmt::Result {
2144− fn is_visible(node: Node) -> bool {
2145− node.is_missing()
2146− || (node.is_named() && node.language().node_kind_is_visible(node.kind_id()))
2147− }
2480+ let node = cursor.node();
2481+ let visible = node_is_visible(&node);
21482482
2149− if is_visible(node) {
2483+ if visible {
21502484 let indentation_columns = depth * 2;
21512485 write!(fmt, "{:indentation_columns$}", "")?;
21522486
2153− if let Some(field_name) = field_name {
2487+ if let Some(field_name) = cursor.field_name() {
21542488 write!(fmt, "{}: ", field_name)?;
21552489 }
21562490
21572491 write!(fmt, "({}", node.kind())?;
2158− } else if is_root {
2159− write!(fmt, "(\"{}\")", node.kind())?;
21602492 }
21612493
2162− for child_idx in 0..node.child_count() {
2163− if let Some(child) = node.child(child_idx) {
2164− if is_visible(child) {
2494+ // Handle children.
2495+ if cursor.goto_first_child() {
2496+ loop {
2497+ if node_is_visible(&cursor.node()) {
21652498 fmt.write_char('\n')?;
21662499 }
21672500
2168− pretty_print_tree_impl(
2169− fmt,
2170− child,
2171− false,
2172− node.field_name_for_child(child_idx as u32),
2173− depth + 1,
2174− )?;
2501+ pretty_print_tree_impl(fmt, cursor, depth + 1)?;
2502+
2503+ if !cursor.goto_next_sibling() {
2504+ break;
2505+ }
21752506 }
2507+
2508+ let moved = cursor.goto_parent();
2509+ // The parent of the first child must exist, and must be `node`.
2510+ debug_assert!(moved);
2511+ debug_assert!(cursor.node() == node);
21762512 }
21772513
2178− if is_visible(node) {
2179− write!(fmt, ")")?;
2514+ if visible {
2515+ fmt.write_char(')')?;
21802516 }
21812517
21822518 Ok(())
21832519}
2520+
21842521#[cfg(test)]
21852522mod test {
21862523 use super::*;
@@ -2200,7 +2537,10 @@ mod test {
22002537 "#,
22012538 );
22022539
2203− let loader = Loader::new(Configuration { language: vec![] });
2540+ let loader = Loader::new(Configuration {
2541+ language: vec![],
2542+ language_server: HashMap::new(),
2543+ });
22042544 let language = get_language("rust").unwrap();
22052545
22062546 let query = Query::new(language, query_str).unwrap();
@@ -2208,7 +2548,7 @@ mod test {
22082548 let mut cursor = QueryCursor::new();
22092549
22102550 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2211− let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2551+ let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
22122552
22132553 let root = syntax.tree().root_node();
22142554 let mut test = |capture, range| {
@@ -2259,7 +2599,10 @@ mod test {
22592599 .map(String::from)
22602600 .collect();
22612601
2262− let loader = Loader::new(Configuration { language: vec![] });
2602+ let loader = Loader::new(Configuration {
2603+ language: vec![],
2604+ language_server: HashMap::new(),
2605+ });
22632606
22642607 let language = get_language("rust").unwrap();
22652608 let config = HighlightConfiguration::new(
@@ -2279,7 +2622,7 @@ mod test {
22792622 fn main() {}
22802623 ",
22812624 );
2282− let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2625+ let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
22832626 let tree = syntax.tree();
22842627 let root = tree.root_node();
22852628 assert_eq!(root.kind(), "source_file");
@@ -2306,7 +2649,7 @@ mod test {
23062649 &doc,
23072650 vec![(6, 11, Some("test".into())), (12, 17, None)].into_iter(),
23082651 );
2309− let edits = generate_edits(&doc, transaction.changes());
2652+ let edits = generate_edits(doc.slice(..), transaction.changes());
23102653 // transaction.apply(&mut state);
23112654
23122655 assert_eq!(
@@ -2335,7 +2678,7 @@ mod test {
23352678 let mut doc = Rope::from("fn test() {}");
23362679 let transaction =
23372680 Transaction::change(&doc, vec![(8, 8, Some("a: u32".into()))].into_iter());
2338− let edits = generate_edits(&doc, transaction.changes());
2681+ let edits = generate_edits(doc.slice(..), transaction.changes());
23392682 transaction.apply(&mut doc);
23402683
23412684 assert_eq!(doc, "fn test(a: u32) {}");
@@ -2353,14 +2696,23 @@ mod test {
23532696 }
23542697
23552698 #[track_caller]
2356− fn assert_pretty_print(source: &str, expected: &str, start: usize, end: usize) {
2699+ fn assert_pretty_print(
2700+ language_name: &str,
2701+ source: &str,
2702+ expected: &str,
2703+ start: usize,
2704+ end: usize,
2705+ ) {
23572706 let source = Rope::from_str(source);
23582707
2359− let loader = Loader::new(Configuration { language: vec![] });
2360− let language = get_language("rust").unwrap();
2708+ let loader = Loader::new(Configuration {
2709+ language: vec![],
2710+ language_server: HashMap::new(),
2711+ });
2712+ let language = get_language(language_name).unwrap();
23612713
23622714 let config = HighlightConfiguration::new(language, "", "", "").unwrap();
2363− let syntax = Syntax::new(&source, Arc::new(config), Arc::new(loader));
2715+ let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
23642716
23652717 let root = syntax
23662718 .tree()
@@ -2377,13 +2729,14 @@ mod test {
23772729 #[test]
23782730 fn test_pretty_print() {
23792731 let source = r#"/// Hello"#;
2380− assert_pretty_print(source, "(line_comment)", 0, source.len());
2732+ assert_pretty_print("rust", source, "(line_comment)", 0, source.len());
23812733
23822734 // A large tree should be indented with fields:
23832735 let source = r#"fn main() {
23842736 println!("Hello, World!");
23852737 }"#;
23862738 assert_pretty_print(
2739+ "rust",
23872740 source,
23882741 concat!(
23892742 "(function_item\n",
@@ -2402,11 +2755,34 @@ mod test {
24022755
24032756 // Selecting a token should print just that token:
24042757 let source = r#"fn main() {}"#;
2405− assert_pretty_print(source, r#"("fn")"#, 0, 1);
2758+ assert_pretty_print("rust", source, r#""fn""#, 0, 1);
24062759
24072760 // Error nodes are printed as errors:
24082761 let source = r#"}{"#;
2409− assert_pretty_print(source, "(ERROR)", 0, source.len());
2762+ assert_pretty_print("rust", source, "(ERROR)", 0, source.len());
2763+
2764+ // Fields broken under unnamed nodes are determined correctly.
2765+ // In the following source, `object` belongs to the `singleton_method`
2766+ // rule but `name` and `body` belong to an unnamed helper `_method_rest`.
2767+ // This can cause a bug with a pretty-printing implementation that
2768+ // uses `Node::field_name_for_child` to determine field names but is
2769+ // fixed when using `TreeCursor::field_name`.
2770+ let source = "def self.method_name
2771+ true
2772+ end";
2773+ assert_pretty_print(
2774+ "ruby",
2775+ source,
2776+ concat!(
2777+ "(singleton_method\n",
2778+ " object: (self)\n",
2779+ " name: (identifier)\n",
2780+ " body: (body_statement\n",
2781+ " (true)))"
2782+ ),
2783+ 0,
2784+ source.len(),
2785+ );
24102786 }
24112787
24122788 #[test]
helix-core/src/test.rs+139−18
@@ -1,7 +1,9 @@
11//! Test helpers.
22use crate::{Range, Selection};
3+use ropey::Rope;
34use smallvec::SmallVec;
45use std::cmp::Reverse;
6+use unicode_segmentation::UnicodeSegmentation;
57
68/// Convert annotated test string to test string and selection.
79///
@@ -10,6 +12,10 @@ use std::cmp::Reverse;
1012/// `#[` for primary selection with head after anchor followed by `|]#`.
1113/// `#(` for secondary selection with head after anchor followed by `|)#`.
1214///
15+/// If the selection contains any LF or CRLF sequences, which are immediately
16+/// followed by the same grapheme, then the subsequent one is removed. This is
17+/// to allow representing having the cursor over the end of the line.
18+///
1319/// # Examples
1420///
1521/// ```
@@ -30,23 +36,23 @@ use std::cmp::Reverse;
3036pub fn print(s: &str) -> (String, Selection) {
3137 let mut primary_idx = None;
3238 let mut ranges = SmallVec::new();
33− let mut iter = s.chars().peekable();
39+ let mut iter = UnicodeSegmentation::graphemes(s, true).peekable();
3440 let mut left = String::with_capacity(s.len());
3541
3642 'outer: while let Some(c) = iter.next() {
3743 let start = left.chars().count();
3844
39− if c != '#' {
40− left.push(c);
45+ if c != "#" {
46+ left.push_str(c);
4147 continue;
4248 }
4349
4450 let (is_primary, close_pair) = match iter.next() {
45− Some('[') => (true, ']'),
46− Some('(') => (false, ')'),
51+ Some("[") => (true, "]"),
52+ Some("(") => (false, ")"),
4753 Some(ch) => {
4854 left.push('#');
49− left.push(ch);
55+ left.push_str(ch);
5056 continue;
5157 }
5258 None => break,
@@ -56,24 +62,45 @@ pub fn print(s: &str) -> (String, Selection) {
5662 panic!("primary `#[` already appeared {:?} {:?}", left, s);
5763 }
5864
59− let head_at_beg = iter.next_if_eq(&'|').is_some();
65+ let head_at_beg = iter.next_if_eq(&"|").is_some();
66+ let last_grapheme = |s: &str| {
67+ UnicodeSegmentation::graphemes(s, true)
68+ .last()
69+ .map(String::from)
70+ };
6071
6172 while let Some(c) = iter.next() {
62− if !(c == close_pair && iter.peek() == Some(&'#')) {
63− left.push(c);
73+ let next = iter.peek();
74+ let mut prev = last_grapheme(left.as_str());
75+
76+ if !(c == close_pair && next == Some(&"#")) {
77+ left.push_str(c);
6478 continue;
6579 }
6680
6781 if !head_at_beg {
68− let prev = left.pop().unwrap();
69− if prev != '|' {
70− left.push(prev);
71− left.push(c);
72− continue;
82+ match &prev {
83+ Some(p) if p != "|" => {
84+ left.push_str(c);
85+ continue;
86+ }
87+ Some(p) if p == "|" => {
88+ left.pop().unwrap(); // pop the |
89+ prev = last_grapheme(left.as_str());
90+ }
91+ _ => (),
7392 }
7493 }
7594
7695 iter.next(); // skip "#"
96+ let next = iter.peek();
97+
98+ // skip explicit line end inside selection
99+ if (prev == Some(String::from("\r\n")) || prev == Some(String::from("\n")))
100+ && next.map(|n| String::from(*n)) == prev
101+ {
102+ iter.next();
103+ }
77104
78105 if is_primary {
79106 primary_idx = Some(ranges.len());
@@ -118,14 +145,16 @@ pub fn print(s: &str) -> (String, Selection) {
118145/// use smallvec::smallvec;
119146///
120147/// assert_eq!(
121−/// plain("abc", Selection::new(smallvec![Range::new(0, 1), Range::new(3, 2)], 0)),
148+/// plain("abc", &Selection::new(smallvec![Range::new(0, 1), Range::new(3, 2)], 0)),
122149/// "#[a|]#b#(|c)#".to_owned()
123150/// );
124151/// ```
125−pub fn plain(s: &str, selection: Selection) -> String {
152+pub fn plain<R: Into<Rope>>(s: R, selection: &Selection) -> String {
153+ let s = s.into();
126154 let primary = selection.primary_index();
127− let mut out = String::with_capacity(s.len() + 5 * selection.len());
128− out.push_str(s);
155+ let mut out = String::with_capacity(s.len_bytes() + 5 * selection.len());
156+ out.push_str(&s.to_string());
157+
129158 let mut insertion: Vec<_> = selection
130159 .iter()
131160 .enumerate()
@@ -138,7 +167,9 @@ pub fn plain(s: &str, selection: Selection) -> String {
138167 (false, false) => [(range.anchor, ")#"), (range.head, "#(|")],
139168 }
140169 })
170+ .map(|(char_idx, marker)| (s.char_to_byte(char_idx), marker))
141171 .collect();
172+
142173 // insert in reverse order
143174 insertion.sort_unstable_by_key(|k| Reverse(k.0));
144175 for (i, s) in insertion {
@@ -262,4 +293,94 @@ mod test {
262293 print("hello #[|👨‍👩‍👧‍👦]# goodbye")
263294 );
264295 }
296+
297+ #[test]
298+ fn plain_single() {
299+ assert_eq!("#[|h]#ello", plain("hello", &Selection::single(1, 0)));
300+ assert_eq!("#[h|]#ello", plain("hello", &Selection::single(0, 1)));
301+ assert_eq!("#[|hell]#o", plain("hello", &Selection::single(4, 0)));
302+ assert_eq!("#[hell|]#o", plain("hello", &Selection::single(0, 4)));
303+ assert_eq!("#[|hello]#", plain("hello", &Selection::single(5, 0)));
304+ assert_eq!("#[hello|]#", plain("hello", &Selection::single(0, 5)));
305+ }
306+
307+ #[test]
308+ fn plain_multi() {
309+ assert_eq!(
310+ plain(
311+ "hello",
312+ &Selection::new(
313+ SmallVec::from_slice(&[Range::new(1, 0), Range::new(5, 4)]),
314+ 0
315+ )
316+ ),
317+ String::from("#[|h]#ell#(|o)#")
318+ );
319+ assert_eq!(
320+ plain(
321+ "hello",
322+ &Selection::new(
323+ SmallVec::from_slice(&[Range::new(0, 1), Range::new(4, 5)]),
324+ 0
325+ )
326+ ),
327+ String::from("#[h|]#ell#(o|)#")
328+ );
329+ assert_eq!(
330+ plain(
331+ "hello",
332+ &Selection::new(
333+ SmallVec::from_slice(&[Range::new(2, 0), Range::new(5, 3)]),
334+ 0
335+ )
336+ ),
337+ String::from("#[|he]#l#(|lo)#")
338+ );
339+ assert_eq!(
340+ plain(
341+ "hello\r\nhello\r\nhello\r\n",
342+ &Selection::new(
343+ SmallVec::from_slice(&[
344+ Range::new(7, 5),
345+ Range::new(21, 19),
346+ Range::new(14, 12)
347+ ]),
348+ 0
349+ )
350+ ),
351+ String::from("hello#[|\r\n]#hello#(|\r\n)#hello#(|\r\n)#")
352+ );
353+ }
354+
355+ #[test]
356+ fn plain_multi_byte_code_point() {
357+ assert_eq!(
358+ plain("„“", &Selection::single(1, 0)),
359+ String::from("#[|„]#“")
360+ );
361+ assert_eq!(
362+ plain("„“", &Selection::single(2, 1)),
363+ String::from("„#[|“]#")
364+ );
365+ assert_eq!(
366+ plain("„“", &Selection::single(0, 1)),
367+ String::from("#[„|]#“")
368+ );
369+ assert_eq!(
370+ plain("„“", &Selection::single(1, 2)),
371+ String::from("„#[“|]#")
372+ );
373+ assert_eq!(
374+ plain("they said „hello“", &Selection::single(11, 10)),
375+ String::from("they said #[|„]#hello“")
376+ );
377+ }
378+
379+ #[test]
380+ fn plain_multi_code_point_grapheme() {
381+ assert_eq!(
382+ plain("hello 👨‍👩‍👧‍👦 goodbye", &Selection::single(13, 6)),
383+ String::from("hello #[|👨‍👩‍👧‍👦]# goodbye")
384+ );
385+ }
265386}
helix-core/src/text_annotations.rsnew file+271
@@ -0,0 +1,271 @@
1+use std::cell::Cell;
2+use std::ops::Range;
3+use std::rc::Rc;
4+
5+use crate::syntax::Highlight;
6+use crate::Tendril;
7+
8+/// An inline annotation is continuous text shown
9+/// on the screen before the grapheme that starts at
10+/// `char_idx`
11+#[derive(Debug, Clone)]
12+pub struct InlineAnnotation {
13+ pub text: Tendril,
14+ pub char_idx: usize,
15+}
16+
17+impl InlineAnnotation {
18+ pub fn new(char_idx: usize, text: impl Into<Tendril>) -> Self {
19+ Self {
20+ char_idx,
21+ text: text.into(),
22+ }
23+ }
24+}
25+
26+/// Represents a **single Grapheme** that is part of the document
27+/// that start at `char_idx` that will be replaced with
28+/// a different `grapheme`.
29+/// If `grapheme` contains multiple graphemes the text
30+/// will render incorrectly.
31+/// If you want to overlay multiple graphemes simply
32+/// use multiple `Overlays`.
33+///
34+/// # Examples
35+///
36+/// The following examples are valid overlays for the following text:
37+///
38+/// `aX͎̊͢͜͝͡bc`
39+///
40+/// ```
41+/// use helix_core::text_annotations::Overlay;
42+///
43+/// // replaces a
44+/// Overlay::new(0, "X");
45+///
46+/// // replaces X͎̊͢͜͝͡
47+/// Overlay::new(1, "\t");
48+///
49+/// // replaces b
50+/// Overlay::new(6, "X̢̢̟͖̲͌̋̇͑͝");
51+/// ```
52+///
53+/// The following examples are invalid uses
54+///
55+/// ```
56+/// use helix_core::text_annotations::Overlay;
57+///
58+/// // overlay is not aligned at grapheme boundary
59+/// Overlay::new(3, "x");
60+///
61+/// // overlay contains multiple graphemes
62+/// Overlay::new(0, "xy");
63+/// ```
64+#[derive(Debug, Clone)]
65+pub struct Overlay {
66+ pub char_idx: usize,
67+ pub grapheme: Tendril,
68+}
69+
70+impl Overlay {
71+ pub fn new(char_idx: usize, grapheme: impl Into<Tendril>) -> Self {
72+ Self {
73+ char_idx,
74+ grapheme: grapheme.into(),
75+ }
76+ }
77+}
78+
79+/// Line annotations allow for virtual text between normal
80+/// text lines. They cause `height` empty lines to be inserted
81+/// below the document line that contains `anchor_char_idx`.
82+///
83+/// These lines can be filled with text in the rendering code
84+/// as their contents have no effect beyond visual appearance.
85+///
86+/// To insert a line after a document line simply set
87+/// `anchor_char_idx` to `doc.line_to_char(line_idx)`
88+#[derive(Debug, Clone)]
89+pub struct LineAnnotation {
90+ pub anchor_char_idx: usize,
91+ pub height: usize,
92+}
93+
94+#[derive(Debug)]
95+struct Layer<A, M> {
96+ annotations: Rc<[A]>,
97+ current_index: Cell<usize>,
98+ metadata: M,
99+}
100+
101+impl<A, M: Clone> Clone for Layer<A, M> {
102+ fn clone(&self) -> Self {
103+ Layer {
104+ annotations: self.annotations.clone(),
105+ current_index: self.current_index.clone(),
106+ metadata: self.metadata.clone(),
107+ }
108+ }
109+}
110+
111+impl<A, M> Layer<A, M> {
112+ pub fn reset_pos(&self, char_idx: usize, get_char_idx: impl Fn(&A) -> usize) {
113+ let new_index = self
114+ .annotations
115+ .partition_point(|annot| get_char_idx(annot) < char_idx);
116+ self.current_index.set(new_index);
117+ }
118+
119+ pub fn consume(&self, char_idx: usize, get_char_idx: impl Fn(&A) -> usize) -> Option<&A> {
120+ let annot = self.annotations.get(self.current_index.get())?;
121+ debug_assert!(get_char_idx(annot) >= char_idx);
122+ if get_char_idx(annot) == char_idx {
123+ self.current_index.set(self.current_index.get() + 1);
124+ Some(annot)
125+ } else {
126+ None
127+ }
128+ }
129+}
130+
131+impl<A, M> From<(Rc<[A]>, M)> for Layer<A, M> {
132+ fn from((annotations, metadata): (Rc<[A]>, M)) -> Layer<A, M> {
133+ Layer {
134+ annotations,
135+ current_index: Cell::new(0),
136+ metadata,
137+ }
138+ }
139+}
140+
141+fn reset_pos<A, M>(layers: &[Layer<A, M>], pos: usize, get_pos: impl Fn(&A) -> usize) {
142+ for layer in layers {
143+ layer.reset_pos(pos, &get_pos)
144+ }
145+}
146+
147+/// Annotations that change that is displayed when the document is render.
148+/// Also commonly called virtual text.
149+#[derive(Default, Debug, Clone)]
150+pub struct TextAnnotations {
151+ inline_annotations: Vec<Layer<InlineAnnotation, Option<Highlight>>>,
152+ overlays: Vec<Layer<Overlay, Option<Highlight>>>,
153+ line_annotations: Vec<Layer<LineAnnotation, ()>>,
154+}
155+
156+impl TextAnnotations {
157+ /// Prepare the TextAnnotations for iteration starting at char_idx
158+ pub fn reset_pos(&self, char_idx: usize) {
159+ reset_pos(&self.inline_annotations, char_idx, |annot| annot.char_idx);
160+ reset_pos(&self.overlays, char_idx, |annot| annot.char_idx);
161+ reset_pos(&self.line_annotations, char_idx, |annot| {
162+ annot.anchor_char_idx
163+ });
164+ }
165+
166+ pub fn collect_overlay_highlights(
167+ &self,
168+ char_range: Range<usize>,
169+ ) -> Vec<(usize, Range<usize>)> {
170+ let mut highlights = Vec::new();
171+ self.reset_pos(char_range.start);
172+ for char_idx in char_range {
173+ if let Some((_, Some(highlight))) = self.overlay_at(char_idx) {
174+ // we don't know the number of chars the original grapheme takes
175+ // however it doesn't matter as highlight boundaries are automatically
176+ // aligned to grapheme boundaries in the rendering code
177+ highlights.push((highlight.0, char_idx..char_idx + 1))
178+ }
179+ }
180+
181+ highlights
182+ }
183+
184+ /// Add new inline annotations.
185+ ///
186+ /// The annotations grapheme will be rendered with `highlight`
187+ /// patched on top of `ui.text`.
188+ ///
189+ /// The annotations **must be sorted** by their `char_idx`.
190+ /// Multiple annotations with the same `char_idx` are allowed,
191+ /// they will be display in the order that they are present in the layer.
192+ ///
193+ /// If multiple layers contain annotations at the same position
194+ /// the annotations that belong to the layers added first will be shown first.
195+ pub fn add_inline_annotations(
196+ &mut self,
197+ layer: Rc<[InlineAnnotation]>,
198+ highlight: Option<Highlight>,
199+ ) -> &mut Self {
200+ self.inline_annotations.push((layer, highlight).into());
201+ self
202+ }
203+
204+ /// Add new grapheme overlays.
205+ ///
206+ /// The overlaid grapheme will be rendered with `highlight`
207+ /// patched on top of `ui.text`.
208+ ///
209+ /// The overlays **must be sorted** by their `char_idx`.
210+ /// Multiple overlays with the same `char_idx` **are allowed**.
211+ ///
212+ /// If multiple layers contain overlay at the same position
213+ /// the overlay from the layer added last will be show.
214+ pub fn add_overlay(&mut self, layer: Rc<[Overlay]>, highlight: Option<Highlight>) -> &mut Self {
215+ self.overlays.push((layer, highlight).into());
216+ self
217+ }
218+
219+ /// Add new annotation lines.
220+ ///
221+ /// The line annotations **must be sorted** by their `char_idx`.
222+ /// Multiple line annotations with the same `char_idx` **are not allowed**.
223+ pub fn add_line_annotation(&mut self, layer: Rc<[LineAnnotation]>) -> &mut Self {
224+ self.line_annotations.push((layer, ()).into());
225+ self
226+ }
227+
228+ /// Removes all line annotations, useful for vertical motions
229+ /// so that virtual text lines are automatically skipped.
230+ pub fn clear_line_annotations(&mut self) {
231+ self.line_annotations.clear();
232+ }
233+
234+ pub(crate) fn next_inline_annotation_at(
235+ &self,
236+ char_idx: usize,
237+ ) -> Option<(&InlineAnnotation, Option<Highlight>)> {
238+ self.inline_annotations.iter().find_map(|layer| {
239+ let annotation = layer.consume(char_idx, |annot| annot.char_idx)?;
240+ Some((annotation, layer.metadata))
241+ })
242+ }
243+
244+ pub(crate) fn overlay_at(&self, char_idx: usize) -> Option<(&Overlay, Option<Highlight>)> {
245+ let mut overlay = None;
246+ for layer in &self.overlays {
247+ while let Some(new_overlay) = layer.consume(char_idx, |annot| annot.char_idx) {
248+ overlay = Some((new_overlay, layer.metadata));
249+ }
250+ }
251+ overlay
252+ }
253+
254+ pub(crate) fn annotation_lines_at(&self, char_idx: usize) -> usize {
255+ self.line_annotations
256+ .iter()
257+ .map(|layer| {
258+ let mut lines = 0;
259+ while let Some(annot) = layer.annotations.get(layer.current_index.get()) {
260+ if annot.anchor_char_idx == char_idx {
261+ layer.current_index.set(layer.current_index.get() + 1);
262+ lines += annot.height
263+ } else {
264+ break;
265+ }
266+ }
267+ lines
268+ })
269+ .sum()
270+ }
271+}
helix-core/src/textobject.rs+17−5
@@ -231,8 +231,20 @@ fn textobject_pair_surround_impl(
231231 };
232232 pair_pos
233233 .map(|(anchor, head)| match textobject {
234− TextObject::Inside => Range::new(next_grapheme_boundary(slice, anchor), head),
235− TextObject::Around => Range::new(anchor, next_grapheme_boundary(slice, head)),
234+ TextObject::Inside => {
235+ if anchor < head {
236+ Range::new(next_grapheme_boundary(slice, anchor), head)
237+ } else {
238+ Range::new(anchor, next_grapheme_boundary(slice, head))
239+ }
240+ }
241+ TextObject::Around => {
242+ if anchor < head {
243+ Range::new(anchor, next_grapheme_boundary(slice, head))
244+ } else {
245+ Range::new(next_grapheme_boundary(slice, anchor), head)
246+ }
247+ }
236248 TextObject::Movement => unreachable!(),
237249 })
238250 .unwrap_or(range)
@@ -425,7 +437,7 @@ mod test {
425437 let text = Rope::from(s.as_str());
426438 let selection = selection
427439 .transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Inside, 1));
428− let actual = crate::test::plain(&s, selection);
440+ let actual = crate::test::plain(s.as_ref(), &selection);
429441 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
430442 }
431443 }
@@ -448,7 +460,7 @@ mod test {
448460 let text = Rope::from(s.as_str());
449461 let selection = selection
450462 .transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Inside, 2));
451− let actual = crate::test::plain(&s, selection);
463+ let actual = crate::test::plain(s.as_ref(), &selection);
452464 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
453465 }
454466 }
@@ -479,7 +491,7 @@ mod test {
479491 let text = Rope::from(s.as_str());
480492 let selection = selection
481493 .transform(|r| textobject_paragraph(text.slice(..), r, TextObject::Around, 1));
482− let actual = crate::test::plain(&s, selection);
494+ let actual = crate::test::plain(s.as_ref(), &selection);
483495 assert_eq!(actual, expected, "\nbefore: `{:?}`", before);
484496 }
485497 }
helix-core/src/transaction.rs+322−56
@@ -1,8 +1,12 @@
1−use crate::{Range, Rope, Selection, Tendril};
2−use std::borrow::Cow;
1+use ropey::RopeSlice;
2+use smallvec::SmallVec;
3+
4+use crate::{chars::char_is_word, Range, Rope, Selection, Tendril};
5+use std::{borrow::Cow, iter::once};
36
47/// (from, to, replacement)
58pub type Change = (usize, usize, Option<Tendril>);
9+pub type Deletion = (usize, usize);
610
711// TODO: pub(crate)
812#[derive(Debug, Clone, PartialEq, Eq)]
@@ -19,6 +23,30 @@ pub enum Operation {
1923pub enum Assoc {
2024 Before,
2125 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+ }
2250}
2351
2452#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -39,7 +67,7 @@ impl ChangeSet {
3967 }
4068
4169 #[must_use]
42− pub fn new(doc: &Rope) -> Self {
70+ pub fn new(doc: RopeSlice) -> Self {
4371 let len = doc.len_chars();
4472 Self {
4573 changes: Vec::new(),
@@ -56,7 +84,7 @@ impl ChangeSet {
5684 }
5785
5886 // Changeset builder operations: delete/insert/retain
59− fn delete(&mut self, n: usize) {
87+ pub(crate) fn delete(&mut self, n: usize) {
6088 use Operation::*;
6189 if n == 0 {
6290 return;
@@ -71,7 +99,7 @@ impl ChangeSet {
7199 }
72100 }
73101
74− fn insert(&mut self, fragment: Tendril) {
102+ pub(crate) fn insert(&mut self, fragment: Tendril) {
75103 use Operation::*;
76104
77105 if fragment.is_empty() {
@@ -93,7 +121,7 @@ impl ChangeSet {
93121 self.changes.push(new_last);
94122 }
95123
96− fn retain(&mut self, n: usize) {
124+ pub(crate) fn retain(&mut self, n: usize) {
97125 use Operation::*;
98126 if n == 0 {
99127 return;
@@ -323,20 +351,75 @@ impl ChangeSet {
323351 self.changes.is_empty() || self.changes == [Operation::Retain(self.len)]
324352 }
325353
326− /// Map a position through the changes.
354+ /// Map a (mostly) *sorted* list of positions through the changes.
327355 ///
328− /// `assoc` indicates which size to associate the position with. `Before` will keep the
329− /// position close to the character before, and will place it before insertions over that
330− /// range, or at that point. `After` will move it forward, placing it at the end of such
331− /// insertions.
332− pub fn map_pos(&self, pos: usize, assoc: Assoc) -> usize {
356+ /// This is equivalent to updating each position with `map_pos`:
357+ ///
358+ /// ``` no-compile
359+ /// for (pos, assoc) in positions {
360+ /// *pos = changes.map_pos(*pos, assoc);
361+ /// }
362+ /// ```
363+ /// However this function is significantly faster for sorted lists running
364+ /// in `O(N+M)` instead of `O(NM)`. This function also handles unsorted/
365+ /// partially sorted lists. However, in that case worst case complexity is
366+ /// again `O(MN)`. For lists that are often/mostly sorted (like the end of diagnostic ranges)
367+ /// performance is usally close to `O(N + M)`
368+ pub fn update_positions<'a>(&self, positions: impl Iterator<Item = (&'a mut usize, Assoc)>) {
333369 use Operation::*;
370+
371+ let mut positions = positions.peekable();
372+
334373 let mut old_pos = 0;
335374 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+ }
336414
337− let mut iter = self.changes.iter().peekable();
415+ let Some((i, change)) = iter.next() else {
416+ map!(
417+ |pos, _| (old_pos == pos).then_some(new_pos),
418+ self.changes.len()
419+ );
420+ break;
421+ };
338422
339− while let Some(change) = iter.next() {
340423 let len = match change {
341424 Delete(i) | Retain(i) => *i,
342425 Insert(_) => 0,
@@ -345,61 +428,65 @@ impl ChangeSet {
345428
346429 match change {
347430 Retain(_) => {
348− if old_end > pos {
349− return new_pos + (pos - old_pos);
350− }
431+ map!(
432+ |pos, _| (old_end > pos).then_some(new_pos + (pos - old_pos)),
433+ i
434+ );
351435 new_pos += len;
352436 }
353437 Delete(_) => {
354438 // in range
355− if old_end > pos {
356− return new_pos;
357− }
439+ map!(|pos, _| (old_end > pos).then_some(new_pos), i);
358440 }
359441 Insert(s) => {
360− let ins = s.chars().count();
361−
362442 // a subsequent delete means a replace, consume it
363− if let Some(Delete(len)) = iter.peek() {
443+ if let Some((_, Delete(len))) = iter.peek() {
364444 iter.next();
365445
366446 old_end = old_pos + len;
367447 // in range of replaced text
368− if old_end > pos {
369− // at point or tracking before
370− if pos == old_pos || assoc == Assoc::Before {
371− return new_pos;
372− } else {
373− // place to end of insert
374− return new_pos + ins;
375− }
376− }
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+ );
377460 } else {
378461 // at insert point
379− if old_pos == pos {
380− // return position before inserted text
381− if assoc == Assoc::Before {
382− return new_pos;
383− } else {
384− // after text
385− return new_pos + ins;
386− }
387− }
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+ );
388469 }
389470
390− new_pos += ins;
471+ new_pos += s.chars().count();
391472 }
392473 }
393474 old_pos = old_end;
394475 }
476+ let out_of_bounds: Vec<_> = positions.collect();
395477
396− if pos > old_pos {
397− panic!(
398− "Position {} is out of range for changeset len {}!",
399− pos, old_pos
400− )
401− }
402− new_pos
478+ panic!("Positions {out_of_bounds:?} are out of range for changeset len {old_pos}!",)
479+ }
480+
481+ /// Map a position through the changes.
482+ ///
483+ /// `assoc` indicates which side to associate the position with. `Before` will keep the
484+ /// position close to the character before, and will place it before insertions over that
485+ /// range, or at that point. `After` will move it forward, placing it at the end of such
486+ /// insertions.
487+ pub fn map_pos(&self, mut pos: usize, assoc: Assoc) -> usize {
488+ self.update_positions(once((&mut pos, assoc)));
489+ pos
403490 }
404491
405492 pub fn changes_iter(&self) -> ChangeIterator {
@@ -419,7 +506,7 @@ impl Transaction {
419506 /// Create a new, empty transaction.
420507 pub fn new(doc: &Rope) -> Self {
421508 Self {
422− changes: ChangeSet::new(doc),
509+ changes: ChangeSet::new(doc.slice(..)),
423510 selection: None,
424511 }
425512 }
@@ -466,6 +553,33 @@ impl Transaction {
466553 self
467554 }
468555
556+ /// Generate a transaction from a set of potentially overlapping changes. The `change_ranges`
557+ /// iterator yield the range (of removed text) in the old document for each edit. If any change
558+ /// overlaps with a range overlaps with a previous range then that range is ignored.
559+ ///
560+ /// The `process_change` callback is called for each edit that is not ignored (in the order
561+ /// yielded by `changes`) and should return the new text that the associated range will be
562+ /// replaced with.
563+ ///
564+ /// To make this function more flexible the iterator can yield additional data for each change
565+ /// that is passed to `process_change`
566+ pub fn change_ignore_overlapping<T>(
567+ doc: &Rope,
568+ change_ranges: impl Iterator<Item = (usize, usize, T)>,
569+ mut process_change: impl FnMut(usize, usize, T) -> Option<Tendril>,
570+ ) -> Self {
571+ let mut last = 0;
572+ let changes = change_ranges.filter_map(|(from, to, data)| {
573+ if from < last {
574+ return None;
575+ }
576+ let tendril = process_change(from, to, data);
577+ last = to;
578+ Some((from, to, tendril))
579+ });
580+ Self::change(doc, changes)
581+ }
582+
469583 /// Generate a transaction from a set of changes.
470584 pub fn change<I>(doc: &Rope, changes: I) -> Self
471585 where
@@ -481,6 +595,11 @@ impl Transaction {
481595 for (from, to, tendril) in changes {
482596 // Verify ranges are ordered and not overlapping
483597 debug_assert!(last <= from);
598+ // Verify ranges are correct
599+ debug_assert!(
600+ from <= to,
601+ "Edit end must end before it starts (should {from} <= {to})"
602+ );
484603
485604 // Retain from last "to" to current "from"
486605 changeset.retain(from - last);
@@ -500,6 +619,46 @@ impl Transaction {
500619 Self::from(changeset)
501620 }
502621
622+ /// Generate a transaction from a set of potentially overlapping deletions
623+ /// by merging overlapping deletions together.
624+ pub fn delete<I>(doc: &Rope, deletions: I) -> Self
625+ where
626+ I: Iterator<Item = Deletion>,
627+ {
628+ let len = doc.len_chars();
629+
630+ let (lower, upper) = deletions.size_hint();
631+ let size = upper.unwrap_or(lower);
632+ let mut changeset = ChangeSet::with_capacity(2 * size + 1); // rough estimate
633+
634+ let mut last = 0;
635+ for (mut from, to) in deletions {
636+ if last > to {
637+ continue;
638+ }
639+ if last > from {
640+ from = last
641+ }
642+ debug_assert!(
643+ from <= to,
644+ "Edit end must end before it starts (should {from} <= {to})"
645+ );
646+ // Retain from last "to" to current "from"
647+ changeset.retain(from - last);
648+ changeset.delete(to - from);
649+ last = to;
650+ }
651+
652+ changeset.retain(len - last);
653+
654+ Self::from(changeset)
655+ }
656+
657+ pub fn insert_at_eof(mut self, text: Tendril) -> Transaction {
658+ self.changes.insert(text);
659+ self
660+ }
661+
503662 /// Generate a transaction with a change per selection range.
504663 pub fn change_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
505664 where
@@ -508,6 +667,54 @@ impl Transaction {
508667 Self::change(doc, selection.iter().map(f))
509668 }
510669
670+ pub fn change_by_selection_ignore_overlapping(
671+ doc: &Rope,
672+ selection: &Selection,
673+ mut change_range: impl FnMut(&Range) -> (usize, usize),
674+ mut create_tendril: impl FnMut(usize, usize) -> Option<Tendril>,
675+ ) -> (Transaction, Selection) {
676+ let mut last_selection_idx = None;
677+ let mut new_primary_idx = None;
678+ let mut ranges: SmallVec<[Range; 1]> = SmallVec::new();
679+ let process_change = |change_start, change_end, (idx, range): (usize, &Range)| {
680+ // update the primary idx
681+ if idx == selection.primary_index() {
682+ new_primary_idx = Some(idx);
683+ } else if new_primary_idx.is_none() {
684+ if idx > selection.primary_index() {
685+ new_primary_idx = last_selection_idx;
686+ } else {
687+ last_selection_idx = Some(idx);
688+ }
689+ }
690+ ranges.push(*range);
691+ create_tendril(change_start, change_end)
692+ };
693+ let transaction = Self::change_ignore_overlapping(
694+ doc,
695+ selection.iter().enumerate().map(|range| {
696+ let (change_start, change_end) = change_range(range.1);
697+ (change_start, change_end, range)
698+ }),
699+ process_change,
700+ );
701+
702+ (
703+ transaction,
704+ Selection::new(ranges, new_primary_idx.unwrap_or(0)),
705+ )
706+ }
707+
708+ /// Generate a transaction with a deletion per selection range.
709+ /// Compared to using `change_by_selection` directly these ranges may overlap.
710+ /// In that case they are merged
711+ pub fn delete_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self
712+ where
713+ F: FnMut(&Range) -> Deletion,
714+ {
715+ Self::delete(doc, selection.iter().map(f))
716+ }
717+
511718 /// Insert text at each selection head.
512719 pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self {
513720 Self::change_by_selection(doc, selection, |range| {
@@ -577,7 +784,7 @@ impl<'a> Iterator for ChangeIterator<'a> {
577784#[cfg(test)]
578785mod test {
579786 use super::*;
580− use crate::State;
787+ use crate::history::State;
581788
582789 #[test]
583790 fn composition() {
@@ -680,6 +887,62 @@ mod test {
680887 };
681888 assert_eq!(cs.map_pos(2, Assoc::Before), 2);
682889 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);
683946 }
684947
685948 #[test]
@@ -704,7 +967,10 @@ mod test {
704967
705968 #[test]
706969 fn optimized_composition() {
707− let mut state = State::new("".into());
970+ let mut state = State {
971+ doc: "".into(),
972+ selection: Selection::point(0),
973+ };
708974 let t1 = Transaction::insert(&state.doc, &state.selection, Tendril::from("h"));
709975 t1.apply(&mut state.doc);
710976 state.selection = state.selection.clone().map(t1.changes());
@@ -743,9 +1009,9 @@ mod test {
7431009 #[test]
7441010 fn combine_with_empty() {
7451011 let empty = Rope::from("");
746− let a = ChangeSet::new(&empty);
1012+ let a = ChangeSet::new(empty.slice(..));
7471013
748− let mut b = ChangeSet::new(&empty);
1014+ let mut b = ChangeSet::new(empty.slice(..));
7491015 b.insert("a".into());
7501016
7511017 let changes = a.compose(b);
@@ -759,9 +1025,9 @@ mod test {
7591025 const TEST_CASE: &str = "Hello, これはヘリックスエディターです!";
7601026
7611027 let empty = Rope::from("");
762− let a = ChangeSet::new(&empty);
1028+ let a = ChangeSet::new(empty.slice(..));
7631029
764− let mut b = ChangeSet::new(&empty);
1030+ let mut b = ChangeSet::new(empty.slice(..));
7651031 b.insert(TEST_CASE.into());
7661032
7671033 let changes = a.compose(b);
helix-core/src/wrap.rs+4−2
@@ -1,7 +1,9 @@
11use smartstring::{LazyCompact, SmartString};
2+use textwrap::{Options, WordSplitter::NoHyphenation};
23
34/// Given a slice of text, return the text re-wrapped to fit it
45/// within the given width.
5−pub fn reflow_hard_wrap(text: &str, max_line_len: usize) -> SmartString<LazyCompact> {
6− textwrap::refill(text, max_line_len).into()
6+pub 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()
79}
helix-core/tests/data/indent/cpp.cppnew file+48
@@ -0,0 +1,48 @@
1+std::vector<std::string>
2+fn_with_many_parameters(int parm1, long parm2, float parm3, double parm4,
3+ char* parm5, bool parm6);
4+
5+std::vector<std::string>
6+fn_with_many_parameters(int parm1, long parm2, float parm3, double parm4,
7+ char* parm5, bool parm6) {
8+ auto lambda = []() {
9+ return 0;
10+ };
11+ auto lambda_with_a_really_long_name_that_uses_a_whole_line
12+ = [](int some_more_aligned_parameters,
13+ std::string parm2) {
14+ do_smth();
15+ };
16+ if (brace_on_same_line) {
17+ do_smth();
18+ } else if (brace_on_next_line)
19+ {
20+ do_smth();
21+ } else if (another_condition) {
22+ do_smth();
23+ }
24+ else {
25+ do_smth();
26+ }
27+ if (inline_if_statement)
28+ do_smth();
29+ if (another_inline_if_statement)
30+ return [](int parm1, char* parm2) {
31+ this_is_a_really_pointless_lambda();
32+ };
33+
34+ switch (var) {
35+ case true:
36+ return -1;
37+ case false:
38+ return 42;
39+ }
40+}
41+
42+class MyClass : public MyBaseClass {
43+public:
44+ MyClass();
45+ void public_fn();
46+private:
47+ super_secret_private_fn();
48+}
helix-core/tests/data/indent/indent.rsdeleted−1
@@ -1 +0,0 @@
1−../../../src/indent.rs
helix-core/tests/data/indent/languages.toml+14−1
@@ -10,4 +10,17 @@ indent = { tab-width = 4, unit = " " }
1010
1111[[grammar]]
1212name = "rust"
13−source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "a360da0a29a19c281d08295a35ecd0544d2da211" }
13+source = { git = "https://github.com/tree-sitter/tree-sitter-rust", rev = "0431a2c60828731f27491ee9fdefe25e250ce9c9" }
14+
15+[[language]]
16+name = "cpp"
17+scope = "source.cpp"
18+injection-regex = "cpp"
19+file-types = ["cc", "hh", "c++", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino", "C", "H"]
20+roots = []
21+comment-token = "//"
22+indent = { tab-width = 2, unit = " " }
23+
24+[[grammar]]
25+name = "cpp"
26+source = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "2d2c4aee8672af4c7c8edff68e7dd4c07e88d2b1" }
helix-core/tests/indent.rs+187−24

This diff is too big to show here.

helix-dap/Cargo.toml+11−9

This diff is too big to show here.

helix-dap/src/client.rs+54−15

This diff is too big to show here.

helix-dap/src/lib.rs+3−3

This diff is too big to show here.

helix-dap/src/transport.rs+28−18

This diff is too big to show here.

helix-dap/src/types.rs+23−3

This diff is too big to show here.

helix-event/Cargo.tomlnew file+16

This diff is too big to show here.

helix-event/src/lib.rsnew file+8

This diff is too big to show here.

helix-event/src/redraw.rsnew file+49

This diff is too big to show here.

helix-loader/Cargo.toml+17−13

This diff is too big to show here.

helix-loader/build.rs+71

This diff is too big to show here.

helix-loader/src/config.rs+36−32

This diff is too big to show here.

helix-loader/src/grammar.rs+137−57

This diff is too big to show here.

helix-loader/src/lib.rs+175−58

This diff is too big to show here.

helix-lsp/Cargo.toml+17−12

This diff is too big to show here.

helix-lsp/src/client.rs+693−120

This diff is too big to show here.

helix-lsp/src/file_event.rsnew file+193

This diff is too big to show here.

helix-lsp/src/jsonrpc.rs+43−2

This diff is too big to show here.

helix-lsp/src/lib.rs+589−109

This diff is too big to show here.

helix-lsp/src/snippet.rsnew file+1010

This diff is too big to show here.

helix-lsp/src/transport.rs+81−20

This diff is too big to show here.

helix-parsec/Cargo.tomlnew file+14

This diff is too big to show here.

helix-parsec/src/lib.rsnew file+574

This diff is too big to show here.

helix-term/Cargo.toml+37−29

This diff is too big to show here.

helix-term/build.rs−21

This diff is too big to show here.

helix-term/src/application.rs+530−269

This diff is too big to show here.

helix-term/src/args.rs+29

This diff is too big to show here.

helix-term/src/commands.rs+1889−1074

This diff is too big to show here.

helix-term/src/commands/dap.rs+115−65

This diff is too big to show here.

helix-term/src/commands/lsp.rs+873−372

This diff is too big to show here.

helix-term/src/commands/typed.rs+1645−701

This diff is too big to show here.

helix-term/src/compositor.rs+26−67

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/src/health.rs+75−25

This diff is too big to show here.

helix-term/src/job.rs+5−2

This diff is too big to show here.

helix-term/src/keymap.rs+159−167

This diff is too big to show here.

helix-term/src/keymap/default.rs+41−21

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/src/lib.rs+25

This diff is too big to show here.

helix-term/src/main.rs+39−33

This diff is too big to show here.

helix-term/src/ui/completion.rs+361−215

This diff is too big to show here.

helix-term/src/ui/document.rsnew file+520

This diff is too big to show here.

helix-term/src/ui/editor.rs+419−436

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/src/ui/lsp.rs+9−4

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/src/ui/menu.rs+115−71

This diff is too big to show here.

helix-term/src/ui/mod.rs+141−163

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/src/ui/picker.rs+769−444

This diff is too big to show here.

helix-term/src/ui/popup.rs+93−13

This diff is too big to show here.

helix-term/src/ui/prompt.rs+84−59

This diff is too big to show here.

helix-term/src/ui/statusline.rs+146−16

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/tests/test/commands.rs+415−104

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/tests/test/commands/write.rsnew file+566

This diff is too big to show here.

helix-term/tests/test/helpers.rs+70−40

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-term/tests/test/write.rsdeleted−287

This diff is too big to show here.

helix-tui/Cargo.toml+17−15

This diff is too big to show here.

helix-tui/src/backend/crossterm.rs+175−46

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-tui/src/buffer.rs+52−10

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-tui/src/text.rs+28

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

helix-tui/src/widgets/table.rs+49−69

This diff is too big to show here.

helix-vcs/Cargo.tomlnew file+32

This diff is too big to show here.

helix-vcs/src/diff.rsnew file+292

This diff is too big to show here.

helix-vcs/src/diff/line_cache.rsnew file+138

This diff is too big to show here.

helix-vcs/src/diff/worker.rsnew file+204

This diff is too big to show here.

helix-vcs/src/diff/worker/test.rsnew file+145

This diff is too big to show here.

helix-vcs/src/git.rsnew file+139

This diff is too big to show here.

helix-vcs/src/git/test.rsnew file+121

This diff is too big to show here.

helix-vcs/src/lib.rsnew file+78

This diff is too big to show here.

helix-view/Cargo.toml+29−19

This diff is too big to show here.

helix-view/src/base64.rs+1−1

This diff is too big to show here.

helix-view/src/clipboard.rs+30−10

This diff is too big to show here.

helix-view/src/document.rs+895−180

This diff is too big to show here.

helix-view/src/editor.rs+640−132

This diff is too big to show here.

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

This diff is too big to show here.

helix-view/src/gutter.rs+333−90

This diff is too big to show here.

helix-view/src/handlers/dap.rs+64−4

This diff is too big to show here.

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

This diff is too big to show here.

helix-view/src/input.rs+209−5

This diff is too big to show here.

helix-view/src/keyboard.rs+195−12

This diff is too big to show here.

helix-view/src/lib.rs+16−21

This diff is too big to show here.

helix-view/src/macros.rs+1−1

This diff is too big to show here.

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

This diff is too big to show here.

helix-view/src/theme.rs+156−97

This diff is too big to show here.

helix-view/src/tree.rs+58−47

This diff is too big to show here.

helix-view/src/view.rs+676−173

This diff is too big to show here.

languages.toml+1342−300

This diff is too big to show here.

logo.svgnew file+1

This diff is too big to show here.

logo_dark.svgnew file+115

This diff is too big to show here.

logo_light.svgnew file+115

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/bash/indents.scmnew file+12

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/bibtex/highlights.scmnew file+47

This diff is too big to show here.

runtime/queries/bibtex/locals.scmnew file

No changes to the content.

runtime/queries/bibtex/tags.scmnew file

No changes to the content.

runtime/queries/bicep/highlights.scmnew file+73

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/c/highlights.scm+1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/cpp/injections.scm+3

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/css/highlights.scm+68−47

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/dhall/highlights.scmnew file+110

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/dhall/textobjects.scmnew file+23

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/ecma/indents.scm+6

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/edoc/highlights.scm+2

This diff is too big to show here.

runtime/queries/edoc/injections.scm+2−2

This diff is too big to show here.

runtime/queries/elixir/indents.scmnew file+13

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/env/highlights.scm+1−19

This diff is too big to show here.

runtime/queries/erlang/highlights.scm+45−15

This diff is too big to show here.

runtime/queries/erlang/injections.scm+7−2

This diff is too big to show here.

runtime/queries/erlang/locals.scmnew file+30

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/git-rebase/highlights.scm+33−8

This diff is too big to show here.

runtime/queries/git-rebase/injections.scm+5−4

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/go/highlights.scm+71−23

This diff is too big to show here.

runtime/queries/go/indents.scm+19−5

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/godot-resource/highlights.scm+2−1

This diff is too big to show here.

runtime/queries/godot-resource/injections.scmnew file+18

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/haskell/injections.scm+12

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/heex/injections.scm+5−1

This diff is too big to show here.

runtime/queries/hosts/highlights.scmnew file+3

This diff is too big to show here.

runtime/queries/html/highlights.scm+1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/java/highlights.scm+98−4

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/julia/injections.scm+6

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/latex/injections.scm+6−2

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/make/injections.scm+3

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/markdown/injections.scm+8−1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/matlab/highlights.scmnew file+128

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/mermaid/highlights.scmnew file+187

This diff is too big to show here.

runtime/queries/meson/highlights.scm+46−46

This diff is too big to show here.

runtime/queries/meson/indents.scm+4−3

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/nix/injections.scm+140−1

This diff is too big to show here.

runtime/queries/nu/folds.scmdeleted−4

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/nu/locals.scmdeleted−13

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/ocaml/highlights.scm+2

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/php/injections.scm+19

This diff is too big to show here.

runtime/queries/po/highlights.scmnew file+15

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/ponylang/highlights.scmnew file+176

This diff is too big to show here.

runtime/queries/ponylang/indents.scmnew file+32

This diff is too big to show here.

runtime/queries/ponylang/locals.scmnew file+37

This diff is too big to show here.

runtime/queries/ponylang/textobjects.scmnew file+64

This diff is too big to show here.

runtime/queries/prisma/highlights.scm+2

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/python/indents.scm+22

This diff is too big to show here.

runtime/queries/qml/highlights.scmnew file+90

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/rego/highlights.scmnew file+68

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/ruby/injections.scm+6

This diff is too big to show here.

runtime/queries/rust/highlights.scm+29−27

This diff is too big to show here.

runtime/queries/rust/indents.scm+71−3

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/rust/textobjects.scm+5−2

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/scala/highlights.scm+102−30

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/scheme/highlights.scm+32−28

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/sql/highlights.scm+345−119

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/svelte/injections.scm+10

This diff is too big to show here.

runtime/queries/sway/highlights.scmnew file+336

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/tfvars/folds.scmnew file+1

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/uxntal/highlights.scmnew file+15

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/vue/injections.scm+27−3

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/yaml/injections.scm+52

This diff is too big to show here.

runtime/queries/yuck/highlights.scmnew file+107

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/queries/zig/indents.scm−1

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/README.md+8

This diff is too big to show here.

runtime/themes/acme.toml+13−4

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/amberwood.tomlnew file+138

This diff is too big to show here.

runtime/themes/autumn.toml+74−64

This diff is too big to show here.

runtime/themes/autumn_night.toml+14−72

This diff is too big to show here.

runtime/themes/ayu_dark.toml+52−43

This diff is too big to show here.

runtime/themes/ayu_evolve.tomlnew file+37

This diff is too big to show here.

runtime/themes/ayu_light.toml+62−60

This diff is too big to show here.

runtime/themes/ayu_mirage.toml+60−52

This diff is too big to show here.

runtime/themes/base16_default_dark.toml+1

This diff is too big to show here.

runtime/themes/base16_default_light.toml+1

This diff is too big to show here.

runtime/themes/base16_terminal.toml+1

This diff is too big to show here.

runtime/themes/base16_transparent.toml+31−23

This diff is too big to show here.

runtime/themes/bogster.toml+5−1

This diff is too big to show here.

runtime/themes/bogster_light.toml+5−1

This diff is too big to show here.

runtime/themes/boo_berry.toml+7−1

This diff is too big to show here.

runtime/themes/catppuccin_frappe.toml+3−105

This diff is too big to show here.

runtime/themes/catppuccin_latte.toml+3−106

This diff is too big to show here.

runtime/themes/catppuccin_macchiato.toml+3−105

This diff is too big to show here.

runtime/themes/catppuccin_mocha.toml+36−17

This diff is too big to show here.

runtime/themes/cyan_light.tomlnew file+154

This diff is too big to show here.

runtime/themes/darcula-solid.tomlnew file+16

This diff is too big to show here.

runtime/themes/darcula.toml+17−5

This diff is too big to show here.

runtime/themes/dark_high_contrast.toml+31−23

This diff is too big to show here.

runtime/themes/dark_plus.toml+11−3

This diff is too big to show here.

runtime/themes/doom_acario_dark.toml+6−5

This diff is too big to show here.

runtime/themes/dracula.toml+128−64

This diff is too big to show here.

runtime/themes/dracula_at_night.toml+3

This diff is too big to show here.

runtime/themes/emacs.toml+12−6

This diff is too big to show here.

runtime/themes/everblush.tomlnew file+115

This diff is too big to show here.

runtime/themes/everforest_dark.toml+79−52

This diff is too big to show here.

runtime/themes/everforest_light.toml+79−53

This diff is too big to show here.

runtime/themes/ferra.tomlnew file+83

This diff is too big to show here.

runtime/themes/flatwhite.toml+13−4

This diff is too big to show here.

runtime/themes/fleet_dark.tomlnew file+166

This diff is too big to show here.

runtime/themes/fleetish.tomldeleted−114

This diff is too big to show here.

runtime/themes/github_dark.tomlnew file+229

This diff is too big to show here.

runtime/themes/github_dark_colorblind.tomlnew file+151

This diff is too big to show here.

runtime/themes/github_dark_dimmed.tomlnew file+151

This diff is too big to show here.

runtime/themes/github_dark_high_contrast.tomlnew file+151

This diff is too big to show here.

runtime/themes/github_dark_tritanopia.tomlnew file+151

This diff is too big to show here.

runtime/themes/github_light.tomlnew file+229

This diff is too big to show here.

runtime/themes/github_light_colorblind.tomlnew file+151

This diff is too big to show here.

runtime/themes/github_light_high_contrast.tomlnew file+151

This diff is too big to show here.

runtime/themes/github_light_tritanopia.tomlnew file+151

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/gruvbox.toml+12−5

This diff is too big to show here.

runtime/themes/gruvbox_dark_hard.toml+2−91

This diff is too big to show here.

runtime/themes/gruvbox_dark_soft.tomlnew file+7

This diff is too big to show here.

runtime/themes/gruvbox_light.toml+1−72

This diff is too big to show here.

runtime/themes/heisenberg.toml+4−4

This diff is too big to show here.

runtime/themes/hex_lavender.tomlnew file+31

This diff is too big to show here.

runtime/themes/hex_steel.toml+25−15

This diff is too big to show here.

runtime/themes/hex_toxic.toml+5−5

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/ingrid.toml+6

This diff is too big to show here.

runtime/themes/jellybeans.tomlnew file+138

This diff is too big to show here.

runtime/themes/kanagawa.toml+56−29

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/material_darker.tomlnew file+20

This diff is too big to show here.

runtime/themes/material_deep_ocean.tomlnew file+123

This diff is too big to show here.

runtime/themes/material_oceanic.tomlnew file+21

This diff is too big to show here.

runtime/themes/material_palenight.tomlnew file+19

This diff is too big to show here.

runtime/themes/meliora.toml+8−3

This diff is too big to show here.

runtime/themes/mellow.tomlnew file+143

This diff is too big to show here.

runtime/themes/merionette.tomlnew file+98

This diff is too big to show here.

runtime/themes/modus_operandi.tomlnew file+194

This diff is too big to show here.

runtime/themes/modus_operandi_deuteranopia.tomlnew file+46

This diff is too big to show here.

runtime/themes/modus_operandi_tinted.tomlnew file+45

This diff is too big to show here.

runtime/themes/modus_operandi_tritanopia.tomlnew file+73

This diff is too big to show here.

runtime/themes/modus_vivendi.tomlnew file+242

This diff is too big to show here.

runtime/themes/modus_vivendi_deuteranopia.tomlnew file+158

This diff is too big to show here.

runtime/themes/modus_vivendi_tinted.tomlnew file+144

This diff is too big to show here.

runtime/themes/modus_vivendi_tritanopia.tomlnew file+186

This diff is too big to show here.

runtime/themes/molokai.tomlnew file+48

This diff is too big to show here.

runtime/themes/monokai.toml+12−3

This diff is too big to show here.

runtime/themes/monokai_aqua.tomlnew file+17

This diff is too big to show here.

runtime/themes/monokai_pro.toml+8−1

This diff is too big to show here.

runtime/themes/monokai_pro_machine.toml+5−1

This diff is too big to show here.

runtime/themes/monokai_pro_octagon.toml+5−1

This diff is too big to show here.

runtime/themes/monokai_pro_ristretto.toml+5−1

This diff is too big to show here.

runtime/themes/monokai_pro_spectrum.toml+7−3

This diff is too big to show here.

runtime/themes/naysayer.tomlnew file+91

This diff is too big to show here.

runtime/themes/new_moon.tomlnew file+124

This diff is too big to show here.

runtime/themes/night_owl.toml+6−1

This diff is too big to show here.

runtime/themes/nightfox.tomlnew file+188

This diff is too big to show here.

runtime/themes/noctis.toml+41−33

This diff is too big to show here.

runtime/themes/noctis_bordo.toml+6−1

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/nord.toml+163−96

This diff is too big to show here.

runtime/themes/nord_light.toml+8−3

This diff is too big to show here.

runtime/themes/onedark.toml+9−1

This diff is too big to show here.

runtime/themes/onedarker.toml+9−1

This diff is too big to show here.

runtime/themes/onelight.toml+150−84

This diff is too big to show here.

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

This diff is too big to show here.

runtime/themes/papercolor-light.toml+195−98

This diff is too big to show here.

runtime/themes/penumbra+.toml+5−1

This diff is too big to show here.

runtime/themes/poimandres.tomlnew file+113

This diff is too big to show here.

runtime/themes/poimandres_storm.tomlnew file+19

This diff is too big to show here.

runtime/themes/pop-dark.toml+25−17

This diff is too big to show here.

runtime/themes/rasmus.toml+6−1

This diff is too big to show here.

runtime/themes/rose_pine.toml+182−74

This diff is too big to show here.

runtime/themes/rose_pine_dawn.toml+27−78

This diff is too big to show here.

runtime/themes/rose_pine_moon.toml+27−85

This diff is too big to show here.

runtime/themes/serika-dark.toml+9−3

This diff is too big to show here.

runtime/themes/serika-light.toml+10−5

This diff is too big to show here.

runtime/themes/snazzy.toml+65−19

This diff is too big to show here.

runtime/themes/solarized_dark.toml+8−2

This diff is too big to show here.

runtime/themes/solarized_light.toml+8−1

This diff is too big to show here.

runtime/themes/sonokai.toml+10−3

This diff is too big to show here.

runtime/themes/spacebones_light.toml+6−1

This diff is too big to show here.

runtime/themes/starlight.tomlnew file+113

This diff is too big to show here.

runtime/themes/tokyonight.toml+6−1

This diff is too big to show here.

runtime/themes/varua.toml+10−2

This diff is too big to show here.

runtime/themes/vim_dark_high_contrast.tomlnew file+72

This diff is too big to show here.

runtime/themes/yellowed.tomlnew file+135

This diff is too big to show here.

runtime/themes/zed_onedark.tomlnew file+110

This diff is too big to show here.

runtime/themes/zed_onelight.tomlnew file+55

This diff is too big to show here.

runtime/themes/zenburn.tomlnew file+63

This diff is too big to show here.

runtime/tutor+336−68

This diff is too big to show here.

rust-toolchain.toml+2−2

This diff is too big to show here.

theme.toml+5−1

This diff is too big to show here.

xtask/Cargo.toml+13−7

This diff is too big to show here.

xtask/src/docgen.rs+6−5

This diff is too big to show here.

xtask/src/helpers.rs+2−2

This diff is too big to show here.

xtask/src/themelint.rs+4−2

This diff is too big to show here.